
简介这份源码包面向数据分析初学者与用户研究从业者提供一套基于Jupyter Notebook的Python用户画像构建完整流程帮助读者理解如何从原始用户行为数据出发完成数据清洗、特征提取与画像建模。资源共20个文件以13个CSV数据文件为主要数据载体配合4个IPython Notebook文件承载核心分析代码另含1个Python源文件、1个PNG图像与1个Markdown说明文档压缩包约118.46MB目录结构清晰便于按模块查阅与复现。已有125人学习下载。读者可从中获得一套可直接运行的Notebook分析模板涵盖Pandas数据处理、Matplotlib或Seaborn可视化呈现以及泰坦尼克号等经典数据挖掘案例的实操参考适合作为课程设计、项目练手或用户画像入门学习的工具集。1. 从一份 20 文件的压缩包说起这套用户画像源码到底能跑出什么很多人第一次接触用户画像是被业务方一句“给我做个用户分层”逼上梁山的。手上只有几张 CSV字段名还是拼音缩写Jupyter Notebook 打开一片空白不知道从哪下手。这份 upload.zip 里的 02用户画像 目录就是冲着这个场景来的它把一套完整的用户画像构建流程拆成了可运行的 Notebook外加 13 个 CSV 数据文件、1 个 Python 源文件、1 张 PNG 结果图和 1 份 readme.txt总共 20 个文件。核心不是某个高深算法而是把“数据加载 → 清洗 → 特征提取 → 标签化 → 可视化”这条链路用 Jupyter Notebook 串起来让你能对着改、对着跑。适合两类人一是刚学完 Pandas 想找个完整案例练手的 Python 入门者二是需要快速搭出用户画像原型、再往生产环境迁移的数据分析从业者。泰坦尼克号那个子目录是经典的数据挖掘流程演示stormzudi 目录则更贴近真实用户行为数据两者互补。2. 拆包先看目录Jupyter Notebook 工程结构与数据文件怎么对应2.1 目录树还原与文件角色划分拿到压缩包先别急着双击 .ipynb先把目录结构看清楚。根据项目正文解压后大致是这样02用户画像/ ├── 用户画像/ │ ├── data/ │ │ ├── train.csv │ │ ├── test.csv │ │ └── ...其余 CSV 数据文件 │ ├── stormzudi/ │ │ └── ...用户行为相关数据与 Notebook │ ├── img/ │ │ └── ...PNG 图像文件 │ ├── 01泰坦尼克号/ │ │ └── 泰坦尼克-数据挖掘流程.ipynb │ ├── readme.txt │ └── ...其余 IPython Notebook 文件 └── .ipynb_checkpoints/这里有几个关键点。第一.ipynb_checkpoints是 Jupyter 自动生成的检查点目录不是源码的一部分迁移或打包时可以删掉否则会多出一堆无用的 -checkpoint.ipynb 文件。第二13 个 CSV 是数据载体4 个 IPython Notebook 是代码主体1 个 Python 源文件大概率是抽出来的工具函数1 个 Markdown 文档负责说明设计思路。第三01泰坦尼克号和stormzudi是两个独立的数据挖掘场景前者用于验证流程后者用于真实用户画像构建。2.2 用 Python 脚本快速盘点文件与数据规模在启动 Jupyter 之前我习惯先用一段独立脚本把文件清单和 CSV 的基本信息扫一遍避免在 Notebook 里反复试错。这段代码可以直接在终端里用python check_files.py跑import os import pandas as pd # 把这里换成你解压后的实际路径 base_dir ./02用户画像/用户画像 # 遍历目录统计文件类型 for root, dirs, files in os.walk(base_dir): # 跳过 Jupyter 检查点目录 if .ipynb_checkpoints in root: continue for f in files: path os.path.join(root, f) size_kb os.path.getsize(path) / 1024 print(f{path} | {size_kb:.1f} KB) # 单独看 data 目录下的 CSV 结构 data_dir os.path.join(base_dir, data) if os.path.exists(data_dir): for csv_file in os.listdir(data_dir): if csv_file.endswith(.csv): df pd.read_csv(os.path.join(data_dir, csv_file), nrows5) print(f\n {csv_file} ) print(f列名: {list(df.columns)}) print(f前5行预览:\n{df.head()})逻辑说明os.walk递归遍历时主动跳过.ipynb_checkpoints避免把检查点文件当成源码统计。nrows5只读前 5 行防止大 CSV 一次性吃满内存。参数上base_dir必须指向解压后的用户画像目录而不是压缩包根目录否则会找不到 data 子目录。跑完这段你就能知道每个 CSV 有哪些字段、大概是什么业务含义再决定用哪个 Notebook 去处理它。2.3 Notebook 与数据文件的绑定关系4 个 IPython Notebook 不是随便放的。常见做法是泰坦尼克-数据挖掘流程.ipynb对应data/train.csv和data/test.csv用来演示标准的监督学习流程stormzudi目录下的 Notebook 则读取同目录或 data 目录下的用户行为 CSV做无监督的标签聚类和画像生成。打开 Notebook 后第一件事是检查pd.read_csv()里的路径是相对路径还是绝对路径。如果是作者本机的绝对路径比如/Users/xxx/Desktop/...你必须改成自己的相对路径否则一运行就报FileNotFoundError。这是新手翻车率最高的一步没有之一。3. 跑通第一个 NotebookPandas 数据清洗与用户特征提取3.1 环境准备与 Jupyter Notebook 启动这套源码基于 Python 和 Jupyter Notebook环境配置不复杂但版本兼容性有讲究。推荐用 Miniconda 建独立环境避免和系统 Python 打架# 创建独立环境指定 Python 3.9 或 3.10 都比较稳 conda create -n user_profile python3.10 -y conda activate user_profile # 安装核心依赖 pip install pandas numpy matplotlib seaborn scikit-learn jupyter # 启动 Notebook注意在解压后的项目根目录执行 jupyter notebook参数说明python3.10是我实测下来对 Pandas 和 Scikit-learn 兼容性较好的版本太新的 Python 有时会让某些老版本依赖编译失败。jupyter notebook启动后默认监听 8888 端口浏览器会自动打开文件浏览界面。如果你遇到“jupyter notebook 启动时显示找不到指定的程序”大概率是 conda 环境里的 Scripts 目录没加进 PATH用conda activate后直接运行python -m jupyter notebook可以绕过这个问题。3.2 数据加载与缺失值处理打开泰坦尼克-数据挖掘流程.ipynb前几个 cell 通常是导入库和加载数据。我一般会在作者代码基础上补一段缺失值审计因为用户画像对缺失值特别敏感——一个字段缺失率超过 60%硬填反而会引入噪声import pandas as pd import numpy as np # 加载训练数据注意路径改成你自己的 train pd.read_csv(./data/train.csv) # 缺失值审计 missing train.isnull().sum().sort_values(ascendingFalse) missing_pct (missing / len(train) * 100).round(2) missing_df pd.DataFrame({缺失数: missing, 缺失率%: missing_pct}) print(missing_df[missing_df[缺失数] 0]) # 按缺失率分策略处理 # 缺失率 5%数值列用中位数填充类别列用众数填充 # 缺失率 5% ~ 60%考虑用模型预测填充或保留缺失标志 # 缺失率 60%直接丢弃该列 threshold_high 0.6 cols_to_drop missing_pct[missing_pct threshold_high * 100].index.tolist() train.drop(columnscols_to_drop, inplaceTrue) print(f已丢弃高缺失列: {cols_to_drop}) # 对 Age 这类中等缺失率的数值列用中位数填充并加缺失标志 if Age in train.columns: train[Age_missing] train[Age].isnull().astype(int) train[Age].fillna(train[Age].median(), inplaceTrue)逻辑说明isnull().sum()统计每列缺失数量除以总行数得到缺失率。threshold_high 0.6是我常用的经验阈值超过 60% 的列在用户画像场景下基本没有区分度。给Age加Age_missing标志位是个小技巧——有时候“是否缺失”本身就是一个有信息量的特征比如某些渠道的用户就是不愿意填年龄。参数上中位数比均值更抗极端值用户行为数据里长尾分布很常见这一点要特别注意。3.3 类别特征编码与用户标签生成用户画像的核心是把原始字段转成可解释的标签。泰坦尼克号数据集里Sex、Embarked是典型类别特征stormzudi里的用户行为数据则可能有city、device_type等。常见做法是先用 Pandas 的get_dummies做独热编码再用分箱把连续变量变成离散标签# 类别特征独热编码 cat_cols [c for c in [Sex, Embarked, Pclass] if c in train.columns] train_encoded pd.get_dummies(train, columnscat_cols, drop_firstTrue) # 连续变量分箱生成用户标签 # 以 Fare 为例分成低、中、高消费三档 if Fare in train_encoded.columns: train_encoded[Fare_Level] pd.cut( train_encoded[Fare], bins[-1, 10, 50, np.inf], labels[低消费, 中消费, 高消费] ) # 组合标签性别 消费等级形成粗粒度用户画像 if Sex_male in train_encoded.columns and Fare_Level in train_encoded.columns: train_encoded[User_Persona] ( train_encoded[Sex_male].map({0: 女性, 1: 男性}) _ train_encoded[Fare_Level].astype(str) ) print(train_encoded[User_Persona].value_counts())逻辑说明drop_firstTrue在独热编码时丢弃第一个类别避免多重共线性这对后续如果要用逻辑回归建模很重要。pd.cut的分箱边界[-1, 10, 50, np.inf]是根据 Fare 的实际分布调的你的数据如果不一样先用train[Fare].describe()看分位数再定边界。组合标签那一步是用户画像的雏形——把两个维度交叉得到“男性_高消费”“女性_低消费”这样的可读标签业务方一看就懂。参数上astype(str)不能省否则pd.cut返回的 Categorical 类型和字符串拼接会报错。4. 避坑与排查Jupyter Notebook 跑用户画像源码的五个血泪经验4.1 路径错误导致 FileNotFoundError现象Notebook 第一个 cell 就报FileNotFoundError: [Errno 2] No such file or directory: train.csv。原因作者写代码时用的是本机绝对路径或者相对路径的基准目录和你当前启动 Jupyter 的目录不一致。解决在 Notebook 开头加一段import os; print(os.getcwd())确认当前工作目录然后把所有pd.read_csv()的路径改成相对于该目录的正确路径。我一般会统一改成./data/xxx.csv这种形式并且把 Notebook 和 data 目录放在同一级。4.2 中文列名或中文路径引发编码报错现象读取 CSV 时报UnicodeDecodeError: utf-8 codec cant decode byte...。原因Windows 下用 Excel 保存的 CSV 默认是 GBK 编码而 Pandas 默认按 UTF-8 读。解决pd.read_csv(./data/xxx.csv, encodinggbk)如果还不行就试encodinggb18030。另外项目路径里如果有中文某些老版本 Jupyter 也会出问题尽量把项目放在纯英文路径下比如D:/projects/user_profile/。4.3 Notebook 内核与依赖版本不匹配现象import sklearn成功但调用某个函数时报AttributeError: module sklearn has no attribute xxx。原因Notebook 使用的内核不是你刚创建的那个 conda 环境或者 Scikit-learn 版本太老。解决在 Notebook 里运行import sys; print(sys.executable)确认内核路径如果不是你 conda 环境下的 python.exe通过 Kernel → Change Kernel 切换。依赖版本用pip install -U scikit-learn pandas统一升级到较新版本。4.4 大数据 CSV 导致内存溢出现象读取一个几百 MB 的 CSV 时Notebook 内核直接挂掉报MemoryError。原因pd.read_csv()默认把所有列按最占内存的类型读进来字符串列尤其吃内存。解决用dtype参数指定列类型或者用usecols只读需要的列再或者用chunksize分块读取。示例pd.read_csv(big.csv, usecols[user_id, action, timestamp], dtype{user_id: int32})。用户画像不需要全量字段先想清楚要哪些维度再读。4.5 可视化图表中文显示为方块现象Matplotlib 或 Seaborn 画的图里中文标签全变成方框。原因Matplotlib 默认字体不支持中文。解决在 Notebook 开头加两行配置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 用黑体 plt.rcParams[axes.unicode_minus] False # 解决负号显示问题Mac 系统把SimHei换成Arial Unicode MS或PingFang SC。这个坑不解决后面所有画像分布图都白画。5. 从泰坦尼克到 stormzudi把画像流程迁移到真实用户行为数据5.1 两个数据集的字段差异与迁移策略泰坦尼克号数据集是结构化表格字段少、含义明确适合验证流程。stormzudi目录下的数据更接近真实用户行为日志可能有用户 ID、时间戳、页面停留、点击事件等字段。迁移时不能直接套用泰坦尼克的代码核心差异在于泰坦尼克是“一个用户一行”行为日志是“一个用户多行”。你需要先做聚合把多行行为压缩成一行用户特征。常见做法是用groupby(user_id).agg()计算每个用户的点击次数、平均停留时长、活跃天数等统计量再把这些统计量作为画像的输入维度。# 假设行为日志有 user_id, event_type, duration 三列 logs pd.read_csv(./stormzudi/user_logs.csv) # 聚合到用户粒度 user_features logs.groupby(user_id).agg( 点击次数(event_type, count), 平均停留(duration, mean), 最大停留(duration, max), 活跃天数(event_type, lambda x: x.nunique()) # 这里按实际字段调整 ).reset_index() print(user_features.describe())逻辑说明groupby(user_id).agg()是行为数据转用户画像的标准入口。lambda x: x.nunique()这里只是示意实际要看你的字段含义——如果 event_type 是事件类型nunique 统计的是事件种类数不是活跃天数。活跃天数应该用时间戳字段的dt.date.nunique()。参数上聚合后的列名建议用中文方便后续和业务方沟通但要注意编码问题。5.2 用 Scikit-learn 做用户聚类与画像命名聚合完特征下一步是无监督聚类。常见做法是用 KMeans 把用户分成几群再对每群的特征均值做排序人工命名画像标签from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans # 选取用于聚类的特征列 feature_cols [点击次数, 平均停留, 最大停留] X user_features[feature_cols].fillna(0) # 标准化消除量纲影响 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 聚成 4 类具体几类看业务需要 kmeans KMeans(n_clusters4, random_state42, n_init10) user_features[cluster] kmeans.fit_predict(X_scaled) # 看每类的特征均值用于命名 print(user_features.groupby(cluster)[feature_cols].mean().round(2))逻辑说明StandardScaler必须做否则“点击次数”几千的量级会完全压制“平均停留”几十的量级。n_init10让 KMeans 用不同初始点跑 10 次取最优避免陷入局部最优。random_state42保证结果可复现。聚类完看均值表比如某一类点击次数高、停留短可以命名为“高频浅层用户”另一类点击少但停留长叫“低频深度用户”。这就是用户画像从数据到标签的落地过程。5.3 画像结果的可视化与导出最后一步是把画像结果可视化并导出方便写报告或对接下游。用 Seaborn 画个简单的散点图或箱线图import seaborn as sns import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 箱线图看各聚类在平均停留上的分布 plt.figure(figsize(8, 5)) sns.boxplot(xcluster, y平均停留, datauser_features) plt.title(各用户群体的平均停留时长分布) plt.xlabel(用户群体编号) plt.ylabel(平均停留时长) plt.tight_layout() plt.savefig(./img/user_cluster_boxplot.png, dpi150) plt.show() # 导出带画像标签的用户表 user_features.to_csv(./stormzudi/user_persona_output.csv, indexFalse, encodingutf-8-sig)逻辑说明encodingutf-8-sig导出 CSV 时带 BOM 头Excel 打开不会乱码这是给业务方发文件的必备操作。dpi150保证图片清晰度够放进 PPT。tight_layout()防止中文标签被截断。导出的user_persona_output.csv就是最终交付物包含 user_id、各特征列、cluster 编号下游可以直接用来做分层运营。5.4 从 Notebook 到脚本把画像流程固化下来Notebook 适合探索但每次手动跑 cell 效率低。我一般会在流程稳定后把核心步骤抽成 Python 脚本用python build_persona.py一键跑完。项目里那个 Python 源文件大概率就是干这个的。你可以照着 Notebook 的逻辑把数据加载、清洗、聚合、聚类、导出串成一个main()函数加上if __name__ __main__:入口。这样下次数据更新只需要替换 CSV 文件重新跑脚本就行不用再打开 Notebook 逐个 cell 执行。参数方面把n_clusters、feature_cols、文件路径这些提取成脚本开头的常量或命令行参数改起来更方便。从那以后我每次拿到新的用户行为数据都强制先跑一遍缺失值审计和字段类型检查再动手写画像逻辑。这套源码最大的价值不是代码本身多精妙而是它把一条完整的分析链路摊开给你看让你知道每一步该看什么、该改什么。希望帮到你。本文还有配套的精品资源点击获取