
本文介绍一种结合拼接(concat)与逐轮索引对齐的策略,用于将多个含相同id但不同月份收入的数据框合并为宽格式,确保主id表结构稳定、历史收入按列分月填充、缺失值补零。
本文介绍一种结合拼接(concat)与逐轮索引对齐的策略,用于将多个含相同id但不同月份收入的数据框合并为宽格式,确保主id表结构稳定、历史收入按列分月填充、缺失值补零。
在处理按月更新的客户或实体指标(如收入、活跃度)时,常遇到多个结构一致但时间点不同的DataFrame——每个代表当月快照,需统一为以id_number为主键、各月收入为独立列的宽表。关键约束在于:保留首次出现的非收入字段(如company、type),同时为每期Income创建带序号的新列(如Income_1、Income_2),未覆盖的ID对应位置填0。
直接使用pd.merge()链式连接易导致字段冲突或重复行;而pd.concat(..., join='outer')虽能保留所有ID,却无法自动对齐非收入列的“首次优先”逻辑。因此,推荐采用两阶段法:
第一阶段:构建唯一ID主干表
先拼接所有DataFrame(忽略Income列),再基于id_number去重,确保每个ID仅保留首次出现的company和type值:
dfs = [df_22_3, df_22_4, df_22_5]
result = (
pd.concat(dfs, ignore_index=True)
.drop(columns='Income')
.drop_duplicates(subset='id_number', keep='first')
.sort_values('id_number') # 可选:按ID排序提升可读性
.reset_index(drop=True)
)第二阶段:逐轮注入月度收入
对每个原始DataFrame,利用set_index('id_number')['Income']构造Series,再通过.reindex()严格按result['id_number']顺序对齐,并用fill_value=0补缺:
for i, df in enumerate(dfs, start=1):
result[f'Income_{i}'] = (
df.set_index('id_number')['Income']
.reindex(result['id_number'], fill_value=0)
.values # .values 确保返回numpy数组,避免索引对齐警告
)✅ 关键细节说明:
- reindex(..., fill_value=0) 比 map() 或 merge() 更简洁,且天然支持缺失ID补零;
- 使用 .values 而非 .reset_index(drop=True),避免因索引类型不一致引发的赋值警告;
- keep='first' 在drop_duplicates中明确指定保留首条记录,符合“ID首次出现字段优先”要求。
最终结果即为标准宽表结构,可直接用于时序分析或可视化:
# 可选:将 Income_1 重命名为 Income 以符合习惯
result = result.rename(columns={'Income_1': 'Income'})
print(result)输出:
id_number company type Income Income_2 Income_3 0 A123 Insurance1 A 100 150 180 1 B456 Insurance2 A 200 250 0 2 C789 Insurance3 C 300 0 320 3 D012 Insurance1 B 0 400 0 4 E034 Insurance5 B 0 0 500
该方法时间复杂度为O(n),扩展性强——无论新增多少月度DataFrame,只需追加至dfs列表并复用循环逻辑,无需修改主干逻辑。

















