本文介绍一种结合拼接(concat)与逐月左连接(merge)的策略,将多个同结构月度数据框合并为宽表,确保id对齐、历史字段保留、新增id自动补零。
本文介绍一种结合拼接(concat)与逐月左连接(merge)的策略,将多个同结构月度数据框合并为宽表,确保id对齐、历史字段保留、新增id自动补零。
在处理多期业务数据(如每月客户收入)时,常需将多个时间切片的数据框整合为统一宽表:以 id_number 为唯一主键,保留首次出现的非数值字段(如 company、type),并将各期 Income 值分别映射至 Income_1、Income_2、Income_3 等列,缺失则填 0。直接链式 merge 易导致字段覆盖或逻辑混乱,而先拼接后索引对齐是更稳健的方案。
核心思路分两步:
构建主键骨架
:拼接所有数据框(忽略 Income 列),按 id_number 去重,保留首次出现的 company 和 type;
逐期注入收入值
:对每个原始数据框,以 id_number 为索引提取 Income,再用 reindex() 对齐主键骨架,缺失位置自动填充 0。
以下是完整可执行代码:
输出结果:
关键注意事项:
drop_duplicates(keep='first') 是保证 company/type 取最早期值的核心,若需最新期值可改为 'last';
reindex(..., fill_value=0) 比 map() 或 merge() 更简洁高效,避免产生 NaN 后再 fillna(0);
使用 .values 获取 NumPy 数组赋值,可规避 Pandas 版本升级中可能发生的索引对齐警告;
若数据量极大,建议将 dfs 中各 DataFrame 的 id_number 列设为 category 类型以提升 reindex 性能。
该方法逻辑清晰、扩展性强——只需将新月份数据追加到 dfs 列表,即可一键生成带 Income_4、Income_5 的宽表,适用于自动化月报流水线。
import pandas as pd
# 示例数据(3个不同月份)
data_22_3 = {'id_number': ['A123', 'B456', 'C789'], 'company': ['Insurance1', 'Insurance2', 'Insurance3'], 'type': ['A', 'A', 'C'], 'Income': [100, 200, 300]}
data_22_4 = {'id_number': ['A123', 'B456', 'D012'], 'company': ['Insurance1', 'Insurance2', 'Insurance1'], 'type': ['A', 'B', 'B'], 'Income': [150, 250, 400]}
data_22_5 = {'id_number': ['A123', 'C789', 'E034'], 'company': ['Insurance1', 'Insurance3', 'Insurance5'], 'type': ['A', 'C', 'B'], 'Income': [180, 320, 500]}
df_22_3 = pd.DataFrame(data_22_3)
df_22_4 = pd.DataFrame(data_22_4)
df_22_5 = pd.DataFrame(data_22_5)
dfs = [df_22_3, df_22_4, df_22_5]
# 步骤1:构建唯一ID骨架(保留首次出现的非Income字段)
skeleton = (
pd.concat(dfs, ignore_index=True)
.drop(columns='Income')
.drop_duplicates(subset='id_number', keep='first') # 关键:keep='first' 确保取最早期的company/type
.reset_index(drop=True)
)
# 步骤2:逐期添加Income列(列名按顺序编号)
for i, df in enumerate(dfs, start=1):
skeleton[f'Income_{i}'] = (
df.set_index('id_number')['Income']
.reindex(skeleton['id_number'], fill_value=0) # 自动对齐+补零
.values # .values 避免索引对齐警告,更安全
)
# 可选:将Income_1重命名为Income以符合习惯(保持首列为Income)
skeleton = skeleton.rename(columns={'Income_1': 'Income'})
print(skeleton) 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