本文介绍如何对多个按月采集的客户数据框(含 id_number、company、type 和 income)进行两阶段整合:先基于 id_number 去重构建主表结构,再逐月将 income 映射为 income_1/income_2/income_3 等列,缺失值补 0。
本文介绍如何对多个按月采集的客户数据框(含 id_number、company、type 和 income)进行两阶段整合:先基于 id_number 去重构建主表结构,再逐月将 income 映射为 income_1/income_2/income_3 等列,缺失值补 0。
在实际业务场景中(如保险客户月度收入追踪),我们常需将多个时间切片的同构数据框(如 df_22_3、df_22_4、df_22_5)融合为一张宽表,其中:
- 行维度以 id_number 为唯一标识,覆盖所有出现过的客户;
- 列维度保留首张表的非 Income 字段(company、type),并为每月 Income 新增独立列(如 Income_1、Income_2、Income_3);
- 若某客户在某月无记录,则对应 Income_X 填 0;若字段(如 company)在不同月份存在差异,以首次出现的值为准(即按 id_number 首次出现顺序保留)。
该需求无法通过单一 merge 或 concat 完成,需分步处理:
✅ 第一步:构建统一主键骨架(去重保序)
使用 pd.concat() 合并所有 DataFrame,但提前剔除 Income 列,再按 id_number 去重。此操作确保最终行顺序由 id_number 首次出现位置决定,且 company/type 取最早有效值:
dfs = [df_22_3, df_22_4, df_22_5]
result = (
pd.concat([df.drop(columns='Income') for df in dfs])
.drop_duplicates('id_number')
.reset_index(drop=True)
)
⚠️ 注意:必须用列表推导式 df.drop(columns='Income') 预先移除每张表的 Income,否则 concat 会保留所有 Income 列导致结构混乱。
✅ 第二步:逐月映射 Income 到新列(精准对齐 + 缺失填充)
对每个原始 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 array,避免索引对齐副作用
)
? 关键点:.reindex(..., fill_value=0) 是核心——它不依赖索引交集,而是严格按目标索引序列(result['id_number'])查找值,找不到则填 0,完美满足“新客户 Income_X=值,老客户 Income_X=0”的逻辑。
✅ 第三步:列名标准化(可选)
若需将 Income_1 还原为 Income(保持首月语义),执行重命名:
result = result.rename(columns={'Income_1': 'Income'})
最终结果与预期完全一致:
| id_number | company | type | Income | Income_2 | Income_3 |
|---|---|---|---|---|---|
| A123 | Insurance1 | A | 100 | 150 | 180 |
| B456 | Insurance2 | A | 200 | 250 | 0 |
| C789 | Insurance3 | C | 300 | 0 | 320 |
| D012 | Insurance1 | B | 0 | 400 | 0 |
| E034 | Insurance5 | B | 0 | 0 | 500 |
✅ 总结:该方案兼顾正确性与可扩展性——新增月份只需追加到 dfs 列表,循环自动处理;逻辑清晰分离“结构构建”与“数值填充”,避免复杂嵌套 merge,是处理时序宽表聚合的推荐范式。










