Pandas按分组补全DataFrame缺失年份行及asfreq丢行问题解决
问题原因
你之前编写的代码丢失近一半行的核心原因是:将字符串格式的年份转为datetime类型时,默认生成的是对应年份的1月1日,而调用asfreq(freq='Y')时,Y年频的时间锚点是每年的12月31日,索引日期和频率锚点完全不匹配,导致原有数据行无法被识别匹配,最终大量数据被丢弃。
推荐解决方案
优先用MultiIndex重索引的方法实现,不需要做日期类型转换,没有时间锚点匹配的坑,在数千条数据的场景下性能表现也更好,代码逻辑更易维护。
import pandas as pd # 读取数据并去重 df = pd.read_csv(r'filepath') df = df.drop_duplicates(subset=['donor_id','year']) # 年份转为整数类型,避免字符串排序、匹配异常 df['year'] = df['year'].astype(int) # -------------------------- # 二选一即可:全局年份范围/单捐赠者自有年份范围 # 选项1:按全局所有捐赠者的最小-最大年份统一补全 full_index = pd.MultiIndex.from_product( [df['donor_id'].unique(), range(df['year'].min(), df['year'].max()+1)], names=['donor_id', 'year'] ) # 选项2:按每个捐赠者自己的首捐-末捐年份范围补全 # full_index_pairs = [] # for donor_id, sub_df in df.groupby('donor_id'): # min_year, max_year = sub_df['year'].min(), sub_df['year'].max() # full_index_pairs.extend([(donor_id, year) for year in range(min_year, max_year+1)]) # full_index = pd.MultiIndex.from_tuples(full_index_pairs, names=['donor_id', 'year']) # -------------------------- # 重索引补全缺失行,缺失捐赠额填充为0 df = df.set_index(['donor_id', 'year']).reindex(full_index, fill_value=0).reset_index() # 如果需要年份转回字符串格式,取消注释下一行即可 # df['year'] = df['year'].astype(str)
执行上述代码后,输出结果和你预期完全一致:
donor_id year donation 0 A 2011 10 1 A 2012 10 2 A 2013 10 3 B 2011 20 4 B 2012 0 5 B 2013 20
修正asfreq方案的方法
如果你一定要沿用之前的时间序列补全思路,只需要修正频率参数和日期锚点的匹配关系即可,注意要把年频从Y(年末)改成*YS(年初)*,和转datetime后生成的1月1日索引匹配:
import pandas as pd df = pd.read_csv(r'filepath') df = df.drop_duplicates(subset=['donor_id','year']) # 转年初日期 df['year_DT'] = pd.to_datetime(df['year'], format='%Y') df = (df.set_index('year_DT') .groupby('donor_id') # 用YS年初频率匹配索引 .apply(lambda x: x.asfreq(freq='YS')) .drop('donor_id', axis=1) # 缺失捐赠额填充0 .fillna({'donation': 0}) .reset_index(drop=True)) # 从日期字段提取年份,替换原year字段 df['year'] = df['year_DT'].dt.year.astype(str) df = df.drop('year_DT', axis=1)
内容的提问来源于stack exchange,提问作者Jacob Evers
相关产品推荐
相关产品推荐

