0

0

标题:Pandas对比两个客户数据表并按区域层级汇总变动明细(含人员名单)

霞舞

霞舞

发布时间:2026-01-13 17:18:18

|

666人浏览过

|

来源于php中文网

原创

标题:Pandas对比两个客户数据表并按区域层级汇总变动明细(含人员名单)

本文介绍如何使用pandas对两个时间点的客户数据表(df1、df2)进行精细化比对,按zone/region/district三级分组统计客户数量变化,并完整列出“转入、转出、新增、流失”四类客户的姓名清单。

在客户运营分析中,常需追踪客户在地理层级(如 Zone → Region → District)上的动态迁移与结构变化。仅统计数量增减远远不够——业务方更关注“谁来了”“谁走了”“谁转到了哪里”。以下教程将带你从零构建一个完整的客户变动分析流水线,输出包含13列的结构化结果表(含所有人员姓名列表),兼顾准确性与可读性。

核心逻辑拆解

整个流程围绕四类客户行为展开:

  • Transfer In(转入):客户在 df2 中出现在某区域,但其在 df1 中属于其他区域(同名客户跨区迁移);
  • Transfer Out(转出):客户在 df1 中属于某区域,但在 df2 中迁至其他区域
  • New Customer(新增):客户仅存在于 df2,不在 df1 中(全新客户);
  • Leaver(流失):客户仅存在于 df1,不在 df2 中(自然流失或离网)。

⚠️ 关键注意:cust_name 作为唯一标识符(假设无重名),若实际场景存在重名风险,应改用 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']
})

# Step 1: 合并两表,保留双方地理信息(用于识别迁移)
merged = df1.merge(df2, on='cust_name', suffixes=('_old', '_new'), how='outer', indicator=True)
# _merge == 'both' → 存在于两期;'left_only' → 仅 df1(潜在leaver);'right_only' → 仅 df2(潜在new)

# Step 2: 分类处理四类客户
# ✅ Transfer In & Out(仅针对共同客户,且地理变更)
common_customers = merged[merged['_merge'] == 'both'].copy()
common_customers['moved'] = common_customers['District_old'] != common_customers['District_new']

# 转入:新归属地(df2 的 Zone/Region/District)
transfer_in = common_customers[common_customers['moved']].copy()
transfer_in = transfer_in[['Zone_new', 'Region_new', 'District_new', 'cust_name']].rename(
    columns={'Zone_new': 'Zone', 'Region_new': 'Region', 'District_new': 'District'}
)

# 转出:旧归属地(df1 的 Zone/Region/District)
transfer_out = common_customers[common_customers['moved']].copy()
transfer_out = transfer_out[['Zone_old', 'Region_old', 'District_old', 'cust_name']].rename(
    columns={'Zone_old': 'Zone', 'Region_old': 'Region', 'District_old': 'District'}
)

# ✅ New Customers(仅 df2)
new_customers = merged[merged['_merge'] == 'right_only'][['cust_name', 'Zone', 'Region', 'District']]

# ✅ Leavers(仅 df1)
leavers = merged[merged['_merge'] == 'left_only'][['cust_name', 'Zone', 'Region', 'District']]

# Step 3: 按三级地理维度分组聚合姓名列表
def agg_names(series):
    return list(series) if not series.empty else []

result = pd.concat([
    transfer_in.groupby(['Zone', 'Region', 'District'])['cust_name'].apply(agg_names).rename('NamesTransferIn'),
    transfer_out.groupby(['Zone', 'Region', 'District'])['cust_name'].apply(agg_names).rename('NamTransferOut'),
    new_customers.groupby(['Zone', 'Region', 'District'])['cust_name'].apply(agg_names).rename('NamNewCustomer'),
    leavers.groupby(['Zone', 'Region', 'District'])['cust_name'].apply(agg_names).rename('NamLeaver')
], axis=1).fillna('').reset_index()

# Step 4: 补全初始/最终计数(可选,增强业务可读性)
initial_cnt = df1.groupby(['Zone', 'Region', 'District']).size().rename('Initial Count')
final_cnt = df2.groupby(['Zone', 'Region', 'District']).size().rename('Final Count')

result = result.merge(initial_cnt, on=['Zone', 'Region', 'District'], how='left').fillna(0).astype({'Initial Count': 'int'})
result = result.merge(final_cnt, on=['Zone', 'Region', 'District'], how='left').fillna(0).astype({'Final Count': 'int'})

# 计算衍生指标(按需添加)
result['Transfer Out Count'] = result['NamTransferOut'].str.len()
result['Transfer In Count'] = result['NamesTransferIn'].str.len()
result['New Customer Count'] = result['NamNewCustomer'].str.len()
result['Leaver Count'] = result['NamLeaver'].str.len()

# 重排列并输出(符合问题要求的13列顺序)
output_cols = [
    'Zone', 'Region', 'District',
    'Initial Count', 'Final Count',
    'Transfer Out Count', 'Transfer In Count',
    'New Customer Count', 'Leaver Count',
    'NamesTransferIn', 'NamTransferOut', 'NamLeaver', 'NamNewCustomer'
]
result = result.reindex(columns=output_cols)

print(result.to_string(index=False))

输出说明与业务提示

运行上述代码后,你将获得结构清晰、字段完备的结果表。例如:

Elser AI Comics
Elser AI Comics

一个免费且强大的AI漫画生成工具,助力你三步创作自己的一出好戏

下载
Zone Region District Initial Count Final Count Transfer Out Count Transfer In Count New Customer Count Leaver Count NamesTransferIn NamTransferOut NamLeaver NamNewCustomer
A A1 A1a 2 2 0 1 0 1 ['cxd'] [] ['cxa'] []
C C1 C1a 0 1 0 0 1 0 [] [] [] ['cxf']

优势亮点

  • 使用 merge(..., how='outer', indicator=True) 统一识别三类客户状态,逻辑更鲁棒;
  • 所有聚合均基于 groupby + apply(list),天然支持空组填充(fillna('') 或 []);
  • 列名严格对齐需求,后续可直接导出 Excel 或对接 BI 工具
  • 易扩展:如需添加“转入来源区域”“流失原因标签”,只需在对应子集增加字段即可。

? 最后建议

  • 生产环境中务必校验 cust_name 唯一性,或优先使用 cust_id 作为 join key;
  • 若数据量大(>100万行),建议用 pd.concat([df1, df2]).drop_duplicates(...) 配合 map 替代多次 merge 提升性能;
  • 名单列可进一步处理为字符串(', '.join(x))便于阅读,或保留 list 类型供下游程序解析。

至此,你已掌握一套工业级客户地理变动分析方案——不止于数字,更见人名。

相关专题

更多
Python 时间序列分析与预测
Python 时间序列分析与预测

本专题专注讲解 Python 在时间序列数据处理与预测建模中的实战技巧,涵盖时间索引处理、周期性与趋势分解、平稳性检测、ARIMA/SARIMA 模型构建、预测误差评估,以及基于实际业务场景的时间序列项目实操,帮助学习者掌握从数据预处理到模型预测的完整时序分析能力。

51

2025.12.04

mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

179

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

277

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

252

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

121

2025.08.07

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

254

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

206

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1463

2023.10.24

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

1

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Excel 教程
Excel 教程

共162课时 | 11.6万人学习

成为PHP架构师-自制PHP框架
成为PHP架构师-自制PHP框架

共28课时 | 2.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号