news 2026/10/1 10:35:35

Python用户画像实战:基于Jupyter Notebook的RFM聚类分群源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python用户画像实战:基于Jupyter Notebook的RFM聚类分群源码解析

简介:这份资源是一套基于Jupyter Notebook的Python用户画像构建源码,面向数据分析初学者与从事用户研究的从业者,帮助其掌握从原始数据到画像输出的完整流程。压缩包共20个文件,约118.46MB,以13个CSV数据文件为核心载体,配合4个ipynb笔记本承载数据处理与建模代码,另有1个py脚本封装可复用函数、1个png图像展示关键结果、1个txt说明文档,结构清晰便于按模块学习。内容覆盖用户行为分析、特征提取与可视化呈现,涉及Pandas数据处理及Matplotlib、Seaborn图表绘制等常用技能,并包含泰坦尼克号数据挖掘等实践案例。目前已有125人学习下载。读者可借此获得一套可直接运行的画像构建范例,理解数据清洗、整合与特征工程的组织方式,并参考其分析思路迁移到市场分析、产品设计或公共服务评估等场景。

1. 从一份 Jupyter Notebook 源码说起:用户画像到底在算什么

电商运营丢过来一份订单表,问“能不能把用户分个层,我想知道哪些人值得发券”。这个场景里,用户画像不是画一个人长什么样,而是把散落在订单、行为、属性表里的数据,压缩成几个能驱动决策的标签:高价值、流失预警、价格敏感、复购潜力。基于 Jupyter Notebook 的 Python 用户画像构建源码分享,核心价值就在于把“数据清洗 → 特征计算 → 聚类分群 → 标签落库”这条链路,用可交互、可复现的 Notebook 串起来,而不是丢一个跑不通的脚本。它适合两类人:一类是刚学完 pandas 想找个完整项目练手的 Python 入门者,另一类是手里有业务数据、需要快速验证分群逻辑的数据分析从业者。Notebook 的好处是每一步都能看到中间结果,调参不用反复重启进程,这对画像这种需要反复试阈值的工作特别友好。

2. 环境与数据准备:把 Jupyter Notebook 跑起来并喂进第一份数据

2.1 用 Miniconda 装出一套不打架的 Python 环境

很多人卡在第一步不是代码写错,而是环境里包版本互相冲突。我一般不会在系统 Python 上直接装,而是用 Miniconda 建独立环境。Miniconda 安装后如何使用 Jupyter Notebook,顺序是这样的:先装 Miniconda,再建环境,再装包,最后启动。下面这套命令在 Windows、macOS、Linux 上基本一致,只是路径写法略有差别。

# 创建名为 user_profile 的独立环境,指定 Python 3.10 conda create -n user_profile python=3.10 -y # 激活环境 conda activate user_profile # 安装画像常用四件套:数据处理、数值计算、绘图、Notebook conda install pandas numpy matplotlib scikit-learn jupyter -y # 启动 Notebook,浏览器会自动打开 jupyter notebook

逻辑说明:conda create -n的-n后面跟环境名,python=3.10是版本约束,写死版本是为了避免不同机器上 pandas 行为不一致。jupyter notebook启动后默认监听本地 8888 端口,如果提示端口占用,加--port 8889换一个。参数上,-y表示自动确认,适合脚本化操作;手动操作时可以去掉,看清楚它要装哪些包。

提示:如果启动时显示找不到指定的程序,八成是 conda 环境没激活就敲了 jupyter,先conda activate user_profile再启动。

2.2 画像数据的最小字段集与读入方式

用户画像不需要一开始就上大数据平台,一张订单明细表加一张用户属性表就能跑通最小闭环。订单表至少要有用户 ID、订单时间、订单金额、商品类目;属性表要有用户 ID、注册时间、城市、渠道。下面用 pandas 读入并做第一次体检。

import pandas as pd import numpy as np # 读入订单表和用户属性表,注意编码,中文 CSV 常见 gbk orders = pd.read_csv("orders.csv", encoding="gbk", parse_dates=["order_time"]) users = pd.read_csv("users.csv", encoding="gbk", parse_dates=["register_time"]) # 第一次体检:看形状、看缺失、看时间范围 print("订单表形状:", orders.shape) print("属性表形状:", users.shape) print("缺失值:\n", orders.isnull().sum()) print("订单时间范围:", orders["order_time"].min(), "到", orders["order_time"].max()) # 把订单金额转成数值,异常字符变 NaN 再填 0 orders["amount"] = pd.to_numeric(orders["amount"], errors="coerce").fillna(0)

逻辑说明:parse_dates让 pandas 直接把时间列解析成 datetime,后面算最近一次购买间隔才不用再转。errors="coerce"是关键参数,遇到“¥100”这种带符号的脏数据会变成 NaN,而不是直接报错中断。fillna(0)是保守处理,金额缺失按 0 算,避免影响求和,但如果缺失比例超过 30%,就要回头找数据源,不能硬填。

2.3 用 RFM 把原始表压成用户级特征

RFM 是画像里最稳的起点:R 是最近一次购买距今天数,F 是购买频次,M 是累计金额。这三个指标几乎不依赖业务理解,先算出来,后面聚类才有输入。

# 设定一个观察基准日,通常取数据里最大日期加一天 snapshot_date = orders["order_time"].max() + pd.Timedelta(days=1) # 按用户聚合,算出 R、F、M rfm = orders.groupby("user_id").agg( last_order=("order_time", "max"), frequency=("order_id", "nunique"), monetary=("amount", "sum") ).reset_index() # R 值:基准日减去最近一次购买日期 rfm["recency"] = (snapshot_date - rfm["last_order"]).dt.days # 只保留建模需要的三列 rfm = rfm[["user_id", "recency", "frequency", "monetary"]] print(rfm.describe())

逻辑说明:groupby("user_id")把订单粒度压成用户粒度,nunique统计订单号去重数量,避免同一订单多行被重复计数。snapshot_date用最大日期加一天,是为了让最近购买的用户的 R 值为 1 而不是 0,方便后续做对数变换。describe()看分布,如果 monetary 的最大值和均值差两个数量级,说明有极端值,后面要处理。

3. 特征工程与聚类:把 RFM 变成可解释的用户分群

3.1 标准化之前先处理偏态,否则聚类会被大额用户带偏

RFM 三个指标量纲不同,R 是几十到几百天,M 可能是几块到几万块。直接丢进 KMeans,距离会被 M 主导。常见做法是先做对数变换压偏态,再标准化。

from sklearn.preprocessing import StandardScaler # 对 frequency 和 monetary 做 log1p 变换,recency 本身偏态不严重可不动 rfm["frequency_log"] = np.log1p(rfm["frequency"]) rfm["monetary_log"] = np.log1p(rfm["monetary"]) # 选取三列做标准化 features = rfm[["recency", "frequency_log", "monetary_log"]].copy() scaler = StandardScaler() features_scaled = scaler.fit_transform(features) print("标准化后均值:", features_scaled.mean(axis=0).round(3)) print("标准化后标准差:", features_scaled.std(axis=0).round(3))

逻辑说明:log1p是log(1+x),专门处理有零值的情况,frequency 可能为 0 的用户不会报错。StandardScaler做的是减均值除标准差,让三列在同一尺度。参数上,fit_transform只在训练集上用,如果后面有新用户要预测,必须用同一个 scaler 的transform,不能重新 fit,否则新旧用户不在同一空间。

3.2 用肘部法则和轮廓系数定 K,别拍脑袋选 4 或 5

分几群是画像里最容易被问的问题。我的习惯是跑 K 从 2 到 8,同时看惯性和轮廓系数,两个指标交叉验证。

from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score inertias = [] silhouettes = [] K_range = range(2, 9) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) labels = kmeans.fit_predict(features_scaled) inertias.append(kmeans.inertia_) silhouettes.append(silhouette_score(features_scaled, labels)) # 打印结果,人工看拐点 for k, inertia, sil in zip(K_range, inertias, silhouettes): print(f"K={k}, 惯性={inertia:.1f}, 轮廓系数={sil:.3f}")

逻辑说明:random_state=42固定随机种子,保证每次跑结果一致,方便复现。n_init=10表示用 10 组不同初始中心跑,取最优,避免陷入局部最优。惯性下降变缓的拐点就是候选 K,轮廓系数越接近 1 越好,但实际业务里经常选轮廓系数不是最高、但分群可解释性最好的那个 K。

3.3 给每个簇打上业务标签,画像才算落地

聚类出来的 0、1、2 没有意义,必须回头算每个簇的 RFM 均值,再翻译成人话。

# 用选定的 K 重新训练,这里假设选 K=4 kmeans = KMeans(n_clusters=4, random_state=42, n_init=10) rfm["cluster"] = kmeans.fit_predict(features_scaled) # 看每个簇的 RFM 均值 cluster_summary = rfm.groupby("cluster").agg( recency_mean=("recency", "mean"), frequency_mean=("frequency", "mean"), monetary_mean=("monetary", "mean"), user_count=("user_id", "count") ).round(1) print(cluster_summary) # 按业务规则打标签:R 低 F 高 M 高 = 高价值;R 高 F 低 = 流失预警 def label_cluster(row): if row["recency_mean"] < 30 and row["monetary_mean"] > 1000: return "高价值活跃" elif row["recency_mean"] > 90 and row["frequency_mean"] < 2: return "流失预警" elif row["monetary_mean"] < 200: return "低价值长尾" else: return "潜力复购" cluster_summary["label"] = cluster_summary.apply(label_cluster, axis=1) print(cluster_summary[["label", "user_count"]])

逻辑说明:groupby("cluster").agg把每个簇的均值算出来,user_count看各群人数是否均衡,如果某一群只有几个人,说明 K 选大了或者有离群点。label_cluster里的阈值 30、90、1000、200 是示例,实际要按业务客单价和复购周期调整,不能照搬。打完标签后,把user_id和label两列导出成 CSV,就能给运营用了。

4. 避坑与排查:画像源码跑不通时先看这五处

4.1 中文列名或编码导致读文件直接报错

现象:pd.read_csv报UnicodeDecodeError,或者列名变成乱码。原因:CSV 是 GBK 编码,pandas 默认 UTF-8。解决:加encoding="gbk",如果还不行试encoding="gb18030",它兼容范围更广。列名乱码的话,读入后用df.columns = df.columns.str.encode("latin1").str.decode("gbk")修一次。

4.2 时间列没解析,算 R 值时直接抛类型错误

现象:snapshot_date - rfm["last_order"]报TypeError。原因:order_time还是字符串,不是 datetime。解决:读文件时加parse_dates=["order_time"],或者读入后pd.to_datetime(df["order_time"], errors="coerce")。注意errors="coerce"会把解析失败的行变成 NaT,之后要检查有多少 NaT,比例高说明时间格式不统一。

4.3 聚类结果每次跑都不一样,没法复现

现象:同样的数据,两次运行分群结果不同。原因:KMeans 初始中心随机,没固定种子。解决:KMeans(n_clusters=k, random_state=42, n_init=10),random_state固定随机性,n_init提高找到更优解的概率。如果换了 scikit-learn 版本结果仍有差异,把版本号记在 Notebook 第一格,方便回溯。

4.4 极端值把标准化拉偏,小群被吞掉

现象:某个用户一年买了 50 万,聚类后他自己一群,其他人挤在一起。原因:monetary 极端值在标准化后仍然很大。解决:先做log1p,再标准化;如果还有极端值,用分位数截断,比如把 monetary 超过 99% 分位的值压到 99% 分位。截断前先确认这些极端值是不是真实业务用户,别把大客户误伤。

4.5 新用户预测时重新 fit 标准化器,标签全乱

现象:训练时用fit_transform,预测新用户时又fit_transform一次,结果新用户分群和训练时对不上。原因:两次 fit 的均值和标准差不同。解决:把训练时的 scaler 用joblib.dump存下来,预测时joblib.load再transform。Notebook 里可以把 scaler 和 kmeans 一起存,下次直接加载。

5. 把 Notebook 变成可交付的画像工具:导出、调度与增量更新

Notebook 适合探索,但运营不会每天打开 Notebook 点运行。我的做法是探索阶段在 Notebook 里调通,稳定后把核心逻辑抽成profile.py,用jupyter nbconvert --to script导出再整理。增量更新时,不要每次全量重跑,而是把历史用户的标签存成一张表,新数据只算新增用户和需要更新的用户。下面这段是导出标签并做增量合并的最小写法。

import joblib # 保存模型和标准化器,供下次增量预测使用 joblib.dump(scaler, "scaler.pkl") joblib.dump(kmeans, "kmeans.pkl") # 导出用户标签 rfm[["user_id", "cluster"]].to_csv("user_cluster.csv", index=False, encoding="utf-8-sig") # 增量:读入新订单,用同一套 scaler 和 kmeans 预测 new_orders = pd.read_csv("new_orders.csv", encoding="gbk", parse_dates=["order_time"]) new_rfm = new_orders.groupby("user_id").agg( recency=("order_time", lambda x: (snapshot_date - x.max()).days), frequency=("order_id", "nunique"), monetary=("amount", "sum") ).reset_index() new_rfm["frequency_log"] = np.log1p(new_rfm["frequency"]) new_rfm["monetary_log"] = np.log1p(new_rfm["monetary"]) new_scaled = scaler.transform(new_rfm[["recency", "frequency_log", "monetary_log"]]) new_rfm["cluster"] = kmeans.predict(new_scaled)

逻辑说明:joblib.dump把 scaler 和 kmeans 序列化,utf-8-sig带 BOM,Excel 打开中文不乱码。增量预测时transform和predict分开调用,保证新用户落在训练时的同一空间。lambda x里算 recency 用的是同一个snapshot_date,如果跨月更新,snapshot_date 要跟着数据窗口滚动,否则 R 值会整体偏大。

验证方法上,我习惯抽 20 个用户手工核对:看他的订单时间、金额、频次,和模型给的标签是否一致。如果高价值群里混进了只买过一次的用户,回头检查 monetary 的 log 变换是不是没做,或者标准化器加载错了。这套源码分享的价值不在代码多长,而在每一步都能在 Notebook 里看到输入输出,改一个参数立刻知道影响哪群人。我自己踩过最深的坑是拿训练集重新 fit 标准化器去预测,标签全乱,排查了一下午才发现是这行。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 10:35:11

面阵二维测向的关键算法:Unitary-ESPRIT原理与MATLAB实现

简介&#xff1a;面向阵列信号处理研究者与学习者&#xff0c;围绕面阵二维酉矩阵参数估计问题提供一套仿真实现&#xff0c;重点解决多径传播、多个信号源共存时的方位与频率参数估计&#xff0c;适合具备矩阵论和信号处理基础、希望通过代码理解算法原理的读者。压缩包共2个文…

作者头像 李华
网站建设 2026/10/1 10:35:06

MATLAB KMeans聚类实战:异常加热数据定K与可视化

简介&#xff1a;这份资源围绕KMeans聚类算法展开&#xff0c;面向机器学习入门者与需要做故障类型分析的数据从业者&#xff0c;帮助解决无监督场景下自动分组、聚类数目难以确定的问题。包内共2个文件&#xff0c;含1个m脚本与1个xlsx数据表&#xff0c;压缩包约57KB&#xf…

作者头像 李华
网站建设 2026/10/1 10:34:59

Java微信小程序跑腿平台从课设到实战:核心逻辑与避坑指南

简介&#xff1a;这是一份结合Java后端与微信小程序技术开发的跑腿平台项目资源&#xff0c;面向Java开发者、小程序学习者&#xff0c;也适合课程设计与毕业设计参考。项目覆盖用户下单、跑腿接单、订单支付和任务调度等核心流程&#xff0c;同时包含数据库设计、地理定位、消…

作者头像 李华
网站建设 2026/10/1 10:34:39

乳腺癌图像分类数据集实操:从数据预处理到ResNet18迁移学习

简介&#xff1a;这是面向深度学习和医学图像分类场景的乳腺癌症图像二分类数据集&#xff0c;适用于需要训练卷积神经网络、进行迁移学习或验证分类算法的研究者和开发者&#xff0c;目前已有286人学习使用。类别由JSON类别文件定义&#xff0c;图像按训练集、验证集、测试集目…

作者头像 李华
网站建设 2026/10/1 10:34:29

KNN股市预测源码全解析:从特征工程到回测避坑指南

简介&#xff1a;利用k近邻算法实现股市预测的Python源代码包&#xff0c;面向已有基础Python语法、正接触机器学习的开发者和量化分析爱好者&#xff0c;通过历史行情数据进行走势预测&#xff0c;直观展示kNN分类与回归思路在金融场景中的应用。压缩包共2个文件&#xff0c;含…

作者头像 李华
网站建设 2026/10/1 10:31:56

Java内存泄漏原理与实战防控:从GC Roots到MAT分析

1. 什么是内存泄漏&#xff1f;它不是“程序变慢”那么简单刚入行那会儿&#xff0c;我带过几个实习生&#xff0c;他们一遇到应用卡顿、响应延迟&#xff0c;第一反应就是“服务器CPU太高了”“是不是网络抖动&#xff1f;”——直到某次线上服务凌晨OOM崩溃&#xff0c;堆内存…

作者头像 李华