
本文介绍如何在pandas中为每个国家自动检索“最近5年内(含)最晚年份的非空数值”,跳过空值并回溯查找,避免循环,兼顾性能与可读性。
本文介绍如何在pandas中为每个国家自动检索“最近5年内(含)最晚年份的非空数值”,跳过空值并回溯查找,避免循环,兼顾性能与可读性。
在实际数据分析中,常需从时间序列型宽表中为每个分组(如国家)提取“有效最新值”——但该值不能简单取最大年份对应行,而必须满足两个关键条件:
- 年份需在当前数据中最大年份减去5年以内(即最多回溯5年);
- 对应的value字段非空(非空字符串);
若在该时间窗口内无有效值,则结果为空。
以下以原始示例数据为例,展示两种专业、向量化、无需显式循环的解决方案:
import pandas as pd
df1 = pd.DataFrame(
data=[['Afghanistan','2015','5.1'],
['Afghanistan','2016','6.1'],
['Djibouti','2021',''],
['Djibouti','2020',''],
['Djibouti','2019','30'],
['Egypt','2019',''],
['Egypt','2018',''],
['Egypt','2015','37'],
['Bahrain','2020','32'],
['Bahrain','2021','']],
columns=['Country', 'Reference Year', 'value']
)✅ 方法一:布尔掩码 + 排序 + groupby.last()(推荐)
该方法完全向量化,逻辑清晰,执行效率高:
# 步骤1:将年份转为数值型(无法转换的设为NaN)
df = df1.copy()
df['Reference Year'] = pd.to_numeric(df['Reference Year'], errors='coerce')
# 步骤2:构造布尔掩码 `m`
# 表示:年份 ≥ (全局最大年份 - 5) 且 value 非空字符串
threshold = df['Reference Year'].max() - 5
mask = (df['Reference Year'] >= threshold) & (df['value'] != '')
# 步骤3:按 Country → mask(True优先)→ Reference Year(升序)排序
# 注意:True > False,因此 m=True 的行排在同组后部;再按年份升序 → 最新年份自然在最后
sorted_df = df.assign(m=mask).sort_values(['Country', 'm', 'Reference Year'])
# 步骤4:按 Country 分组,取每组最后一行(即满足条件的最晚年份非空值;若无则取任意行,但其 value 为空)
result = sorted_df.groupby('Country', as_index=False).last()[['Country', 'value']]
print(result)输出:
Country value 0 Afghanistan 6.1 1 Bahrain 32 2 Djibouti 30 3 Egypt
? 关键点说明:
- sort_values(['Country', 'm', 'Reference Year']) 中,m=True 的行被排在 m=False 之后,确保“有效候选行”位于每组末尾;
- groupby(...).last() 自动选取每组排序后的最后一行,天然实现“优先取有效值、无则退至任意值(此处为空)”逻辑;
- 整个流程无 for 循环、无 apply 函数调用,适合百万级数据。
✅ 方法二:自定义函数 + groupby.apply()(灵活可扩展)
当业务逻辑更复杂(如需记录所选年份、支持多字段判断等),可封装逻辑为函数:
def get_latest_valid_value(group, threshold):
# 筛选:年份达标 + value 非空
valid_mask = (group['Reference Year'] >= threshold) & (group['value'] != '')
valid_years = group.loc[valid_mask, 'Reference Year']
if valid_years.empty:
return '' # 无有效年份,返回空字符串
else:
# 取最晚年份对应的 value
latest_idx = valid_years.idxmax()
return group.loc[latest_idx, 'value']
df['Reference Year'] = pd.to_numeric(df['Reference Year'], errors='coerce')
threshold = df['Reference Year'].max() - 5
result_series = df.groupby('Country').apply(get_latest_valid_value, threshold)
result_df = result_series.reset_index(name='value')
print(result_df)输出同上,结构一致。
⚠️ 注意事项与最佳实践
- 空值处理:原始 value 列为字符串类型,空值为 '' 而非 NaN,务必用 != '' 判断,而非 notna();
- 年份健壮性:使用 pd.to_numeric(..., errors='coerce') 将异常年份(如 'N/A')转为 NaN,避免后续比较出错;
- 阈值动态性:threshold = max_year - 5 是硬编码逻辑,生产环境中建议抽离为参数(如 lookback_years=5);
- 性能权衡:方法一更快;方法二更易调试和扩展(例如增加日志、多条件分支、返回字典等);
- 缺失国家处理:若某国无任何数据,groupby 会自动忽略,如需保留全量国家,应在 groupby 前做 reindex 或左连接基准国家列表。
通过以上任一方案,你都能高效、准确地实现“带时间窗与非空约束的最新值提取”,彻底告别低效的 for 循环或嵌套 if 判断。

















