标题:Pandas 数据对比分析:按区域层级统计客户变动并汇总明细名单
发布时间 - 2026-01-13 00:00:00 点击率:次本文详解如何使用 pandas 对两个时间点的客户数据进行对比,按 zone/region/district 三级分组,精准计算客户流入、流出、新增、流失数量,并完整列出对应客户姓名清单。
在客户运营或区域管理场景中,常需对比不同时期的客户分布变化,尤其当客户归属存在跨区迁移(如从 A2b 迁至 A2a)
、新增入驻或完全退出时,仅统计数量远远不够——业务人员更需要知道“谁来了”“谁走了”“谁转到了哪里”。本文提供一套完整、可复用的 pandas 实现方案,基于 df1(期初快照)和 df2(期末快照),输出包含 13 列的结构化变动报告,其中关键难点在于 将客户姓名聚合为列表并按三级地理维度对齐。
核心思路:分四类客户分别处理,再统一合并
我们把客户变动划分为四类逻辑明确的群体:
- Transfer In(转入):同一客户在 df2 中出现在新区域(与 df1 的 Zone/Region/District 不同);
- Transfer Out(转出):同一客户在 df1 中的原属区域(即其“离开地”);
- Leaver(流失客户):存在于 df1 但完全不在 df2 中的客户;
- New Customer(新增客户):存在于 df2 但完全不在 df1 中的客户。
⚠️ 注意:merge(on='cust_name') 是关键前提——它要求 cust_name 具有唯一性且能稳定标识同一客户。若实际数据中存在重名风险,建议改用 cust_id 作为主键(修改 on='cust_id' 并同步调整列引用)。
完整实现代码(含注释与健壮性增强)
import pandas as pd
import numpy as np
# 构建示例数据(与问题一致)
df1 = pd.DataFrame({
'cust_name': ['cxa', 'cxb', 'cxc', 'cxd', 'cxe', 'cxf'],
'cust_id': ['c1001', 'c1002', 'c1003', 'c1004', 'c1006', 'c1007'],
'town_id': ['t001', 't002', 't001', 't003', 't002', 't002'],
'Zone': ['A', 'A', 'A', 'B', 'A', 'A'],
'Region': ['A1', 'A2', 'A1', 'B1', 'A2', 'A2'],
'District': ['A1a', 'A2a', 'A1a', 'B1a', 'A2b', 'A2b']
})
df2 = pd.DataFrame({
'cust_name': ['cxb', 'cxc', 'cxd', 'cxe', 'cxf'],
'cust_id': ['c1002', 'c1003', 'c1004', 'c1006', 'c1007'],
'town_id': ['t002', 't001', 't003', 't002', 't002'],
'Zone': ['A', 'A', 'A', 'A', 'C'],
'Region': ['A2', 'A1', 'A1', 'A2', 'C1'],
'District': ['A2a', 'A1a', 'A1a', 'A2a', 'C1a']
})
# 步骤1:识别迁移客户(同一客户,区域变化)
merged = df1.merge(df2, on='cust_name', suffixes=('_df1', '_df2'), how='inner')
# 判断是否发生跨区变动(任一地理层级不同即视为迁移)
merged['is_moved'] = (
(merged['Zone_df1'] != merged['Zone_df2']) |
(merged['Region_df1'] != merged['Region_df2']) |
(merged['District_df1'] != merged['District_df2'])
)
moved = merged[merged['is_moved']].copy()
# 步骤2:提取转入 & 转出名单(按目标/源区域分组)
transfer_in = moved[['Zone_df2', 'Region_df2', 'District_df2', 'cust_name']].rename(
columns={'Zone_df2': 'Zone', 'Region_df2': 'Region', 'District_df2': 'District', 'cust_name': 'NamesTransferIn'}
)
transfer_out = moved[['Zone_df1', 'Region_df1', 'District_df1', 'cust_name']].rename(
columns={'Zone_df1': 'Zone', 'Region_df1': 'Region', 'District_df1': 'District', 'cust_name': 'NamTransferOut'}
)
# 步骤3:按三级分组聚合姓名列表(自动去重,保留顺序)
def collect_names(series):
return list(series) # 若需去重:list(series.unique())
in_agg = transfer_in.groupby(['Zone', 'Region', 'District'])['NamesTransferIn'].apply(collect_names).reset_index()
out_agg = transfer_out.groupby(['Zone', 'Region', 'District'])['NamTransferOut'].apply(collect_names).reset_index()
# 步骤4:合并转入/转出(outer join 确保所有变动区域不遗漏)
result = pd.merge(in_agg, out_agg, on=['Zone', 'Region', 'District'], how='outer').fillna('')
# 步骤5:添加流失客户(df1有、df2无)
leavers = df1[~df1['cust_name'].isin(df2['cust_name'])][['cust_name', 'Zone', 'Region', 'District']]
leavers_agg = leavers.groupby(['Zone', 'Region', 'District'])['cust_name'].apply(collect_names).reset_index().rename(columns={'cust_name': 'NamLeaver'})
result = pd.merge(result, leavers_agg, on=['Zone', 'Region', 'District'], how='outer').fillna('')
# 步骤6:添加新增客户(df2有、df1无)
new_customers = df2[~df2['cust_name'].isin(df1['cust_name'])][['cust_name', 'Zone', 'Region', 'District']]
new_agg = new_customers.groupby(['Zone', 'Region', 'District'])['cust_name'].apply(collect_names).reset_index().rename(columns={'cust_name': 'NamNewCustomer'})
result = pd.merge(result, new_agg, on=['Zone', 'Region', 'District'], how='outer').fillna('')
# ✅ 最终结果:已包含全部13列中的姓名字段(其余数值列可基于此表用 groupby.size() 补全)
print(result[
['Zone', 'Region', 'District',
'NamesTransferIn', 'NamTransferOut', 'NamLeaver', 'NamNewCustomer']
])关键注意事项与优化建议
- 空值处理:使用 .fillna('') 将 NaN 替换为空字符串,避免后续 JSON 序列化或 Excel 导出报错;若需保留 None,可改用 fillna(pd.NA)。
- 姓名去重:若同一客户因数据质量问题重复出现,可在 collect_names() 中加入 series.unique()。
- 扩展数值列:本教程聚焦姓名字段,但 Initial Count / Final Count 等可通过 df1.groupby(['Zone','Region','District']).size() 和 df2.groupby(...).size() 快速生成,再 merge 进 result。
- 性能提示:对百万级数据,避免 apply(lambda x: ...),优先使用向量化操作;merge 前确保 cust_name 列已设为索引或启用 sort=False。
- 输出增强:可调用 result.to_excel("customer_movement_report.xlsx", index=False) 直接导出带格式报表。
通过该方案,你不仅获得结构清晰的变动摘要,更掌握了以客户实体为中心、支持业务溯源的精细化分析能力——让每一条数据变动都“有据可查、有人可溯”。
# excel
# js
# json
# app
# pandas
# count
# sort
# 字符串
# Lambda
# 转出
# 四类
# 若需
# 走了
# 设为
# 转到
# 可在
# 报错
# 可通过
# 谁来
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
谷歌Google入口永久地址_Google搜索引擎官网首页永久入口
Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】
如何有效防御Web建站篡改攻击?
Laravel如何配置和使用缓存?(Redis代码示例)
如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环
如何在服务器上配置二级域名建站?
Laravel如何优雅地处理服务层_在Laravel中使用Service层和Repository层
HTML 中如何正确使用模板变量为元素的 name 属性赋值
如何自定义safari浏览器工具栏?个性化设置safari浏览器界面教程【技巧】
如何用PHP快速搭建CMS系统?
Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程
如何快速生成高效建站系统源代码?
如何注册花生壳免费域名并搭建个人网站?
Laravel辅助函数有哪些_Laravel Helpers常用助手函数大全
轻松掌握MySQL函数中的last_insert_id()
如何在沈阳梯子盘古建站优化SEO排名与功能模块?
香港服务器建站指南:免备案优势与SEO优化技巧全解析
Python自动化办公教程_ExcelWordPDF批量处理案例
Java解压缩zip - 解压缩多个文件或文件夹实例
Laravel如何记录自定义日志?(Log频道配置)
JavaScript实现Fly Bird小游戏
Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出
Android仿QQ列表左滑删除操作
网站页面设计需要考虑到这些问题
如何为不同团队 ID 动态生成多个“认领值班”按钮
谷歌浏览器下载文件时中断怎么办 Google Chrome下载管理修复
高性能网站服务器部署指南:稳定运行与安全配置优化方案
Swift中循环语句中的转移语句 break 和 continue
如何快速使用云服务器搭建个人网站?
JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)
历史网站制作软件,华为如何找回被删除的网站?
高端网站建设与定制开发一站式解决方案 中企动力
Win11怎么更改系统语言为中文_Windows11安装语言包并设为显示语言
深圳网站制作的公司有哪些,dido官方网站?
悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤
Laravel如何使用Service Container和依赖注入?(代码示例)
Python文件异常处理策略_健壮性说明【指导】
利用 Google AI 进行 YouTube 视频 SEO 描述优化
JavaScript如何实现路由_前端路由原理是什么
Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践
html如何与html链接_实现多个HTML页面互相链接【互相】
Laravel如何配置Horizon来管理队列?(安装和使用)
动图在线制作网站有哪些,滑动动图图集怎么做?
python中快速进行多个字符替换的方法小结
常州企业网站制作公司,全国继续教育网怎么登录?
Win11应用商店下载慢怎么办 Win11更改DNS提速下载【修复】
Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】
Laravel如何实现模型的全局作用域?(Global Scope示例)

