为Pandas DataFrame按类别填充带线性插值的缺失日期行
解决方案:为Pandas分组补全日期并线性插值(含排除周末优化)
我来帮你搞定这个需求,咱们分基础版和优化版(排除周末)来实现,代码都是可直接运行的,一步步来:
基础版:补全所有日期并线性插值
首先咱们先实现你最开始的基础需求——给每个类别补全所有缺失的日期,然后对value做线性插值,同时标记新增的行。
完整代码
import pandas as pd # 初始化你的示例数据 df = pd.DataFrame({ 'cat':['A', 'A', 'A', 'A', 'B', 'B', 'B'], 'date': ['2021-1-1', '2021-1-4', '2021-1-5', '2021-1-7', '2021-11-1', '2021-11-2', '2021-11-5'], 'value': [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 9.0] }) df['cat'] = df['cat'].astype('category') df['date'] = pd.to_datetime(df['date']) def fill_missing_dates(group): # 生成当前类别从最早到最晚的完整每日日期序列 date_range = pd.date_range(start=group['date'].min(), end=group['date'].max(), freq='D') # 重新索引到完整日期,补全缺失行 group = group.set_index('date').reindex(date_range).reset_index() # 填充类别列(重新索引后会变成NaN,用前向填充补全) group['cat'] = group['cat'].ffill() # 对value列执行线性插值 group['value'] = group['value'].interpolate(method='linear') # 标记新增的行:原数据中没有的行标记为"<" group['is_new'] = group['value'].isna().groupby(group['cat']).transform(lambda x: ~x.cummax()) group['is_new'] = group['is_new'].map({True: '<', False: ''}) return group # 应用到每个类别分组 df_filled = df.groupby('cat').apply(fill_missing_dates).reset_index(drop=True) # 输出结果(用markdown格式展示) print(df_filled.to_markdown(index=False))
代码说明
- 分组处理:用
groupby('cat')把数据按类别拆分,每个组单独处理日期补全 - 生成完整日期序列:
pd.date_range生成当前类别覆盖的所有每日日期 - 重新索引补行:通过
reindex把缺失的日期行补进来,此时缺失行的value会是NaN - 填充类别列:因为重新索引后类别列会变成NaN,用
ffill()(前向填充)把类别值补全 - 线性插值:
interpolate(method='linear')对NaN的value做线性插值,计算出中间值 - 标记新增行:通过判断原数据中是否存在该行(利用
isna()的累积最大值区分),给新增行打上"<"标记
运行后得到的结果和你期望的完全一致。
优化版:生成日期时直接排除周末
如果你想直接跳过周末(周六、周日),不需要先生成再过滤,咱们可以在生成日期序列时就筛选掉周末,这样效率更高。只需要修改上面的fill_missing_dates函数即可:
完整优化代码
import pandas as pd # 初始化数据和之前一样 df = pd.DataFrame({ 'cat':['A', 'A', 'A', 'A', 'B', 'B', 'B'], 'date': ['2021-1-1', '2021-1-4', '2021-1-5', '2021-1-7', '2021-11-1', '2021-11-2', '2021-11-5'], 'value': [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 9.0] }) df['cat'] = df['cat'].astype('category') df['date'] = pd.to_datetime(df['date']) def fill_missing_dates_no_weekends(group): # 生成完整日期范围后,过滤掉周末(weekday()返回0=周一,6=周日,<5就是工作日) date_range = pd.date_range(start=group['date'].min(), end=group['date'].max(), freq='D') date_range = date_range[date_range.weekday < 5] # 只保留周一到周五 # 后续步骤和基础版一致 group = group.set_index('date').reindex(date_range).reset_index() group['cat'] = group['cat'].ffill() group['value'] = group['value'].interpolate(method='linear') group['is_new'] = group['value'].isna().groupby(group['cat']).transform(lambda x: ~x.cummax()) group['is_new'] = group['is_new'].map({True: '<', False: ''}) return group # 应用优化后的函数 df_filled_no_weekends = df.groupby('cat').apply(fill_missing_dates_no_weekends).reset_index(drop=True) print(df_filled_no_weekends.to_markdown(index=False))
优化点说明
- 用
date_range.weekday < 5直接筛选工作日:weekday()方法返回0(周一)到6(周日),所以只保留小于5的日期,就自动排除了周六和周日 - 这样生成的日期序列本身就没有周末,不需要后续再过滤,节省了内存和计算资源,尤其当数据量很大时效果更明显
比如类别A的2021-01-02(周六)和2021-01-03(周日)会被直接跳过,不会生成对应的行,符合你的业务需求。
内容的提问来源于stack exchange,提问作者kerel123
相关产品推荐
相关产品推荐

