简介:这份资源是一套基于Jupyter Notebook的Python用户画像构建源码,面向数据分析初学者与从事用户研究的从业者,帮助其掌握从原始数据到画像输出的完整流程。包内共20个文件,以13个CSV数据文件为主要数据载体,配合4个ipynb笔记本文件承载数据处理与画像构建代码,另有Python源文件、Markdown说明文档及PNG图像文件,压缩包约118.46MB,目录结构清晰,便于按模块查阅。目前已有125人学习下载。读者可借助Pandas完成数据清洗与特征提取,利用Matplotlib或Seaborn生成可视化图表,并通过Markdown文档理解整体设计思路与实现步骤,从而快速搭建可复用的用户画像分析框架,为产品设计、市场策略与个性化服务提供数据支撑。
1. 从一份 Jupyter Notebook 源码说起:用户画像到底在算什么
很多人第一次搜「基于 Jupyter Notebook 的 Python 用户画像构建源码分享」,心里想的其实是:有没有一份能直接跑起来的 notebook,把用户数据丢进去,就能吐出「高价值用户」「流失预警」「兴趣标签」这些结果。这个诉求很实在,也很容易翻车——因为用户画像从来不是某个算法,而是一条从原始行为日志到标签体系的加工流水线,notebook 只是这条流水线的可视化容器。
我在实际项目里用 Jupyter Notebook 做用户画像,最大的体会是:它适合探索、验证和交付「可解释的中间结果」,但不适合直接当生产调度。你要在 notebook 里完成的是 RFM 分层、行为埋点聚合、标签规则验证、聚类效果对比这几件事,然后把稳定下来的逻辑抽成 Python 脚本或调度任务。这篇文章就按这个思路,把一份可复现的画像构建源码拆开讲清楚:数据怎么进、特征怎么算、标签怎么打、坑在哪、怎么验证结果不是自嗨。适合有 Python 基础、手里有用户行为表、想快速搭出第一版画像体系的人。
2. 环境与数据准备:让 notebook 能稳定跑起来
2.1 Jupyter Notebook 安装与启动的常见翻车点
热词里「jupyter notebook 安装」「jupyter notebook 启动时显示找不到指定的程序」出现频率很高,这不是偶然。Windows 上最常见的场景是:用 python 官网下载安装包时没勾选 Add Python to PATH,之后 pip install notebook 看似成功,但命令行敲 jupyter 直接报「找不到指定的程序」。解决办法不是重装系统,而是先把 Python 和 Scripts 目录加进环境变量,或者干脆用 Miniconda 管理环境。
我一般会这么做,避免污染全局环境:
# 创建独立环境,Python 版本按你数据栈依赖选,3.9~3.11 都比较稳 conda create -n persona python=3.10 -y conda activate persona # 安装 notebook 和常用数据分析库 conda install -c conda-forge jupyter notebook pandas numpy scikit-learn matplotlib seaborn -y # 启动,指定端口和工作目录,避免默认目录太乱 jupyter notebook --port 8888 --notebook-dir=./persona_project这段命令的逻辑是:先隔离环境,再一次性装齐画像常用的 pandas、numpy、scikit-learn 和可视化库。参数上,--port固定端口方便你记住访问地址,--notebook-dir把 notebook 文件限制在项目目录内,避免散落到用户主目录。如果你用 VS Code,配置 python 环境时选这个 persona 解释器即可,jupyter 插件会自动识别内核。
提示:如果启动后浏览器没自动打开,看终端里输出的 token 和 URL,手动复制访问。不要用网上来路不明的「jupyter 网页版」,数据安全没法保证。
2.2 用户画像的数据表结构长什么样
画像构建的第一步不是写代码,是确认你手里有什么数据。典型的最小数据集包含三张表:用户基础表、行为事件表、订单交易表。没有订单表也能做,但 RFM 里的 M(金额)就得换成活跃度或互动次数。
| 表名 | 关键字段 | 用途 |
|---|---|---|
| dim_user | user_id, register_time, channel, city | 用户属性维度 |
| dwd_event | user_id, event_time, event_type, page_id | 行为频次与偏好 |
| dwd_order | user_id, order_time, amount, category | 消费能力与品类偏好 |
在 notebook 里读数据时,我习惯先做一次 schema 检查和空值统计,而不是直接 groupby。很多「画像结果不对」的问题,根源是 user_id 类型不一致(字符串和数字混用)或者时间字段带时区。
import pandas as pd import numpy as np # 读取时显式指定 user_id 为字符串,避免前导零丢失或类型不匹配 users = pd.read_csv('dim_user.csv', dtype={'user_id': str}, parse_dates=['register_time']) events = pd.read_csv('dwd_event.csv', dtype={'user_id': str}, parse_dates=['event_time']) orders = pd.read_csv('dwd_order.csv', dtype={'user_id': str}, parse_dates=['order_time']) # 基础体检:行数、空值、时间范围 for name, df in [('users', users), ('events', events), ('orders', orders)]: print(f"{name}: rows={len(df)}, nulls=\n{df.isnull().sum()[df.isnull().sum()>0]}") time_col = [c for c in df.columns if 'time' in c][0] print(f"{name} time range: {df[time_col].min()} ~ {df[time_col].max()}\n")这段代码的关键在dtype={'user_id': str}和parse_dates。参数说明:dtype 强制主键类型统一,parse_dates 让时间列直接变成 datetime,后续做时间窗口聚合不用再转换。输出里如果看到某张表时间范围明显偏窄,比如 events 只到上个月,那你的画像就是滞后的,需要先确认数据同步链路。
3. 特征工程:把行为日志变成可计算的用户标签
3.1 RFM 三要素的 Python 实现与参数选择
RFM 是用户画像里最稳的起点:Recency(最近一次消费/活跃)、Frequency(频次)、Monetary(金额)。它不依赖复杂模型,业务方一看就懂,而且能直接切出高价值、流失预警等分层。在 notebook 里算 RFM,核心是确定「观察时间点」和「统计窗口」。
# 设定观察时间点:通常取数据中最新时间 + 1 天,避免边界用户被误判 snapshot_date = events['event_time'].max().normalize() + pd.Timedelta(days=1) # 以订单表算 R 和 M,以行为表算 F(没有订单时 F 用活跃天数替代) rfm = orders.groupby('user_id').agg( last_order=('order_time', 'max'), frequency=('order_time', 'count'), monetary=('amount', 'sum') ).reset_index() rfm['recency'] = (snapshot_date - rfm['last_order']).dt.days # 行为频次:统计窗口取近 30 天,避免全量历史把早期活跃用户拉高 recent_events = events[events['event_time'] >= snapshot_date - pd.Timedelta(days=30)] freq_behavior = recent_events.groupby('user_id').size().rename('active_days').reset_index() rfm = rfm.merge(freq_behavior, on='user_id', how='left').fillna({'active_days': 0})逻辑说明:snapshot_date是整个画像的时间锚点,所有 R 值都相对它计算。参数上,30 天窗口是常见做法,但你要根据业务周期调整——低频高客单的品类可能要用 90 天。fillna处理的是「有订单但近 30 天无行为」的用户,这类人恰恰是流失预警的重点,不能直接丢掉。
3.2 用分位数打分替代拍脑袋阈值
RFM 算出来是连续值,要变成标签就得离散化。新手最容易犯的错是手动定阈值,比如「金额大于 1000 算高」。更稳的做法是用分位数(quantile)切分,让每个分层的人数大致均衡,也方便跨周期对比。
# 对 R 反向打分:recency 越小越好,所以用 ascending=True 后取反 rfm['R_score'] = pd.qcut(rfm['recency'], 5, labels=[5,4,3,2,1]).astype(int) rfm['F_score'] = pd.qcut(rfm['frequency'].rank(method='first'), 5, labels=[1,2,3,4,5]).astype(int) rfm['M_score'] = pd.qcut(rfm['monetary'], 5, labels=[1,2,3,4,5]).astype(int) # 组合成 RFM 总分与分层标签 rfm['rfm_sum'] = rfm[['R_score','F_score','M_score']].sum(axis=1) def segment(row): if row['R_score'] >= 4 and row['F_score'] >= 4 and row['M_score'] >= 4: return '高价值' if row['R_score'] <= 2 and row['F_score'] >= 3: return '流失预警' if row['R_score'] >= 4 and row['F_score'] <= 2: return '新客潜力' return '一般用户' rfm['segment'] = rfm.apply(segment, axis=1)参数说明:pd.qcut的 5 表示五等分,rank(method='first')是为了解决频次大量重复值导致 qcut 边界报错的问题,这是血泪经验——真实数据里 frequency 经常一堆 1,不 rank 直接 qcut 会抛「Bin edges must be unique」。分层规则里的阈值 4 和 2 不是固定的,你要看每层人数占比,如果「高价值」占了 40%,那说明阈值太松,画像失去了区分度。
3.3 行为偏好标签:从 page_id 到兴趣类目
除了 RFM,画像还需要兴趣标签。常见做法是把 page_id 或 item_id 映射到类目,再统计每个用户近 30 天点击占比最高的类目。
# 假设有 page 到类目的映射表 page_category = pd.read_csv('page_category.csv', dtype={'page_id': str}) event_cat = recent_events.merge(page_category, on='page_id', how='left') # 统计用户-类目点击次数 user_cat = event_cat.groupby(['user_id','category']).size().reset_index(name='cnt') # 取每个用户 top1 类目作为主兴趣 user_cat['rank'] = user_cat.groupby('user_id')['cnt'].rank(method='first', ascending=False) top_interest = user_cat[user_cat['rank'] == 1][['user_id','category']].rename(columns={'category':'main_interest'}) rfm = rfm.merge(top_interest, on='user_id', how='left')这里的关键是rank取 top1,而不是简单取 max,因为同一用户可能有多个类目并列。参数上,how='left'保证没有行为映射的用户不丢,他们的 main_interest 为空,后续可以单独打「未知兴趣」标签。如果你的类目体系很粗,top1 区分度不够,可以保留 top3 做多兴趣标签。
4. 避坑与排查:画像构建里最容易翻车的五件事
4.1 现象:notebook 重启后结果对不上
原因:notebook 的单元格执行顺序是人为的,你可能先跑了聚合,又改了上面的读取逻辑没重跑,内存里是旧数据。解决:养成「Restart & Run All」的习惯,或者把稳定逻辑抽成函数,每次从原始数据重新计算。我一般会在 notebook 开头加一个%autoreload和版本检查,但最可靠的还是全量重跑。
4.2 现象:qcut 报 Bin edges must be unique
原因:Frequency 或 Monetary 有大量重复值,分位数边界重合。解决:对频次列先rank(method='first')再 qcut,或者改用pd.cut手动指定边界。这个坑在用户行为数据里几乎必现,提前处理能省半小时排查。
4.3 现象:画像分层人数严重倾斜
原因:阈值拍脑袋,或者数据本身有极端值。解决:先看rfm['monetary'].describe(),如果 max 是均值的几百倍,考虑对金额做 log 变换或截断,再分位数。分层后打印每类占比,健康的分层应该是长尾分布,头部不超过 20%。
4.4 现象:user_id 对不上,merge 后大量空值
原因:不同表 user_id 类型不一致,或者有前后空格。解决:读取时统一dtype={'user_id': str},merge 前做str.strip(),并检查merge后的空值率。如果空值超过 5%,先别往下做,回去查数据源。
4.5 现象:notebook 跑得越来越慢,内存爆掉
原因:全量 events 表几十万上百万行,反复 groupby 不释放。解决:在 notebook 里用del删除中间大表,或者只读取需要的列usecols,时间窗口过滤尽量在读取阶段完成。参数上,pd.read_csv加usecols和nrows做采样调试,确认逻辑后再跑全量。
5. 验证与交付:让画像结果经得起业务追问
5.1 用交叉验证和业务指标检验分层质量
画像做完不能只看代码跑通,要验证分层是否真的区分了用户价值。我一般会做两件事:一是看各分层的平均订单金额和复购率是否单调,二是留出最近一个月的订单做验证,看「高价值」层的实际贡献占比。
# 分层质量检查:各层人数、平均金额、近30天活跃率 quality = rfm.groupby('segment').agg( users=('user_id','count'), avg_monetary=('monetary','mean'), avg_recency=('recency','mean'), avg_frequency=('frequency','mean') ).sort_values('avg_monetary', ascending=False) print(quality) # 验证:高价值层是否贡献了主要金额 total_m = rfm['monetary'].sum() high_m = rfm[rfm['segment']=='高价值']['monetary'].sum() print(f"高价值用户占比 {len(rfm[rfm['segment']=='高价值'])/len(rfm):.1%}, 金额贡献 {high_m/total_m:.1%}")如果「高价值」层人数占比 10% 但金额贡献 60% 以上,说明分层有效;如果各层金额差不多,那你的 RFM 权重或阈值需要重新调。这一步是给业务方看的,比任何算法解释都管用。
5.2 从 notebook 到可复用脚本的抽离技巧
notebook 适合探索,但交付时我习惯把稳定逻辑抽成persona_pipeline.py,notebook 只保留调用和可视化。抽离时注意三点:把 snapshot_date、窗口天数、分位数这些做成函数参数;把读写路径配置化;加一层简单的日志,记录每步输入输出行数。
# persona_pipeline.py 片段 def build_rfm(orders, events, snapshot_date, window_days=30, n_bins=5): """输入订单和行为表,返回带 RFM 分层和兴趣标签的用户画像""" # ... 聚合、打分、分层逻辑 return rfm # notebook 里调用 from persona_pipeline import build_rfm rfm = build_rfm(orders, events, snapshot_date, window_days=30)这样下次换一批数据,改参数即可,不用在几十个单元格里找哪一行改了阈值。参数说明:window_days控制行为统计窗口,n_bins控制分位数层数,业务周期短的可以设 3 层,长周期设 5 层。
5.3 一个具体技巧:用透视表快速定位异常分层
最后一招是我常用的排查习惯:用pd.pivot_table把分层和渠道、城市交叉,看有没有某个渠道全被打成「流失预警」。如果有,大概率是渠道数据同步延迟,不是用户真的流失。
pivot = pd.pivot_table(rfm, index='segment', columns='channel', values='user_id', aggfunc='count', fill_value=0) print(pivot)这个透视表能帮你在五分钟内判断画像结果是否符合业务常识。如果某个渠道的「高价值」占比异常高,先查这个渠道的订单是否重复计入。画像这件事,代码只占三成,剩下七成是反复和业务口径对齐。我踩过最深的坑就是闷头调模型,结果业务方说「这个高价值用户上个月就注销了」——所以每次交付前,我都会随机抽 20 个用户,人工看一眼他们的行为轨迹,确认标签没打反。希望帮到你。
本文还有配套的精品资源,点击获取