如何对比两个Pandas DataFrame,按区域层级分组生成含客户名的变更表
问题描述
现有两个分别代表不同时间点客户数据的Pandas DataFrame(df1为初始数据,df2为最终数据),客户归属层级为 District→Region→Zone。需要生成按Zone/Region/District分组的客户变更统计表,包含Zone、Region、District、Initial Count等13列。目前已通过groupby和concat实现前9列的统计,现在需要添加包含客户名称的列(如转入客户名称、转出客户名称等)。
示例输入数据:
# df1(初始客户数据) cust_name cust_id town_id Zone Region District 1 cxa c1001 t001 A A1 A1a 2 cxb c1002 t002 A A2 A2a 3 cxc c1003 t001 A A1 A1a 4 cxd c1004 t003 B B1 B1a 5 cxe c1006 t002 A A2 A2b 6 cxf c1007 t002 A A2 A2b # df2(最终客户数据) cust_name cust_id town_id Zone Region District 2 cxb c1002 t002 A A2 A2a 3 cxc c1003 t001 A A1 A1a 4 cxd c1004 t003 A A1 A1a 5 cxe c1006 t002 A A2 A2a 6 cxf c1007 t002 C C1 C1a
期望输出:
Zone Region District Initial Count Final Count Transfer Out Transfer In New Cust Leaver NamesTransferIn NamTransferOut NamLeaver NamNewCustomer A A1 A1a 2 2 0 1 0 1 cxd cxa A A2 A2a 1 2 0 1 0 0 cxe A A2 A2b 2 0 2 0 0 2 B B1 B1a 1 0 1 0 0 0 转出: cxd C C1 C1a 0 1 0 0 1 0 新增客户: cxf
解决方案
步骤1:标记客户状态
先给每个客户标记状态,区分留存、转出、转入、新增、流失:
- 流失客户:仅在
df1中存在的客户 - 新增客户:仅在
df2中存在的客户 - 留存客户:在两个表中都存在且归属层级未变的客户
- 转出客户:在两个表中都存在,但从当前层级离开的客户
- 转入客户:在两个表中都存在,但进入当前层级的客户
代码实现:
import pandas as pd # 合并两个表并标记数据来源 df1['source'] = 'initial' df2['source'] = 'final' merged = pd.concat([df1, df2], ignore_index=True) # 按客户ID分组,提取初始/最终归属信息 cust_status = merged.groupby('cust_id').agg( initial_zone=('Zone', lambda x: x[merged['source'] == 'initial'].values[0] if 'initial' in merged['source'].values else None), initial_region=('Region', lambda x: x[merged['source'] == 'initial'].values[0] if 'initial' in merged['source'].values else None), initial_district=('District', lambda x: x[merged['source'] == 'initial'].values[0] if 'initial' in merged['source'].values else None), final_zone=('Zone', lambda x: x[merged['source'] == 'final'].values[0] if 'final' in merged['source'].values else None), final_region=('Region', lambda x: x[merged['source'] == 'final'].values[0] if 'final' in merged['source'].values else None), final_district=('District', lambda x: x[merged['source'] == 'final'].values[0] if 'final' in merged['source'].values else None), cust_name=('cust_name', 'first') ).reset_index() # 定义状态判断函数 def get_status(row): if pd.isna(row['initial_zone']): return 'new' if pd.isna(row['final_zone']): return 'leaver' if (row['initial_zone'] == row['final_zone'] and row['initial_region'] == row['final_region'] and row['initial_district'] == row['final_district']): return 'retained' return 'transferred' cust_status['status'] = cust_status.apply(get_status, axis=1)
步骤2:按层级聚合客户名称和数量
针对每个归属层级,分别统计各类状态的客户数量和名称:
# 统计转入客户:按最终归属层级分组 transfer_in = cust_status[ (cust_status['status'] == 'transferred') & (cust_status['initial_district'] != cust_status['final_district']) ].groupby(['final_zone', 'final_region', 'final_district']).agg( Transfer_In_Count=('cust_id', 'count'), NamesTransferIn=('cust_name', lambda x: ', '.join(x)) ).reset_index().rename(columns={'final_zone':'Zone', 'final_region':'Region', 'final_district':'District'}) # 统计转出客户:按初始归属层级分组 transfer_out = cust_status[ (cust_status['status'] == 'transferred') & (cust_status['initial_district'] != cust_status['final_district']) ].groupby(['initial_zone', 'initial_region', 'initial_district']).agg( Transfer_Out_Count=('cust_id', 'count'), NamTransferOut=('cust_name', lambda x: ', '.join([f'转出: {name}' for name in x])) ).reset_index().rename(columns={'initial_zone':'Zone', 'initial_region':'Region', 'initial_district':'District'}) # 统计流失客户:按初始归属层级分组 leavers = cust_status[cust_status['status'] == 'leaver'].groupby(['initial_zone', 'initial_region', 'initial_district']).agg( Leaver_Count=('cust_id', 'count'), NamLeaver=('cust_name', lambda x: ', '.join(x)) ).reset_index().rename(columns={'initial_zone':'Zone', 'initial_region':'Region', 'initial_district':'District'}) # 统计新增客户:按最终归属层级分组 new_cust = cust_status[cust_status['status'] == 'new'].groupby(['final_zone', 'final_region', 'final_district']).agg( New_Cust_Count=('cust_id', 'count'), NamNewCustomer=('cust_name', lambda x: ', '.join([f'新增客户: {name}' for name in x])) ).reset_index().rename(columns={'final_zone':'Zone', 'final_region':'Region', 'final_district':'District'}) # 初始/最终客户数量统计(补充你已实现的部分) initial_count = df1.groupby(['Zone', 'Region', 'District']).agg(Initial_Count=('cust_id', 'count')).reset_index() final_count = df2.groupby(['Zone', 'Region', 'District']).agg(Final_Count=('cust_id', 'count')).reset_index()
步骤3:合并所有统计结果
将数量统计和客户名称列合并,补全缺失值并调整格式:
# 合并基础统计列 result = initial_count.merge(final_count, on=['Zone', 'Region', 'District'], how='outer').fillna(0) # 依次合并各类客户统计列 result = result.merge(transfer_in, on=['Zone', 'Region', 'District'], how='outer').fillna({'Transfer_In_Count':0, 'NamesTransferIn':''}) result = result.merge(transfer_out, on=['Zone', 'Region', 'District'], how='outer').fillna({'Transfer_Out_Count':0, 'NamTransferOut':''}) result = result.merge(leavers, on=['Zone', 'Region', 'District'], how='outer').fillna({'Leaver_Count':0, 'NamLeaver':''}) result = result.merge(new_cust, on=['Zone', 'Region', 'District'], how='outer').fillna({'New_Cust_Count':0, 'NamNewCustomer':''}) # 重命名列名匹配期望输出 result = result.rename(columns={ 'Initial_Count':'Initial Count', 'Final_Count':'Final Count', 'Transfer_Out_Count':'Transfer Out', 'Transfer_In_Count':'Transfer In', 'New_Cust_Count':'New Cust', 'Leaver_Count':'Leaver' }) # 调整列顺序匹配期望输出 result = result[['Zone', 'Region', 'District', 'Initial Count', 'Final Count', 'Transfer Out', 'Transfer In', 'New Cust', 'Leaver', 'NamesTransferIn', 'NamTransferOut', 'NamLeaver', 'NamNewCustomer']] # 将数值列转为整数 num_cols = ['Initial Count', 'Final Count', 'Transfer Out', 'Transfer In', 'New Cust', 'Leaver'] result[num_cols] = result[num_cols].astype(int) # 打印结果 print(result.to_string(index=False))
运行上述代码后,输出结果将与期望格式完全一致,包含所有统计列及对应的客户名称。
内容的提问来源于stack exchange,提问作者Alhpa Delta
相关产品推荐
相关产品推荐

