简介:这是一份面向数据分析初学者与高校学生的Python实战项目资源,围绕校园消费行为分析展开,可用于课程设计、技能练习或研究参考。压缩包共20个文件,约20.37MB,包含4个py源码脚本、4个csv数据文件、9张png与3张jpg结果图,分别对应分析代码、原始消费数据与可视化输出,结构清晰便于按任务模块对照学习。项目覆盖数据采集、清洗处理、分析与可视化、消费行为模型构建及结果展示等完整流程,读者可借助源码与数据复现聚类分析、关联规则和预测模型等典型方法,理解从原始数据到结论报告的落地路径。目前已有794人学习下载,适合希望掌握Python数据处理与分析技术、积累消费行为建模经验的人群参考使用。
1. 从一份校园消费数据包说起:Python 分析链路到底能跑出什么
很多做校园信息化或者零售运营的朋友,手里其实不缺数据,缺的是一条能跑通、能复现、能拿给别人看的分析链路。这份「基于 Python 的学生校园消费行为分析」资源包,恰好就是一条完整链路的实物切片:data目录下放着data1.csv、data2.csv两份原始消费流水,code目录里是task1_X1.py到task3_X4.py这一组任务脚本,result目录则沉淀了task1_X.csv、task1_1_X.csv两个中间结果表和十来张task2_X*.png、task3_X*.png可视化图。它解决的不是「教你怎么装 Python」这种入门问题,而是把采集、清洗、聚合、可视化、建模这条链路上每一步的输入输出都摆出来,让你能对着结果反推代码逻辑。适合谁?适合已经会写基础 Python、想拿一份真实校园消费数据练手的数据分析初学者,也适合需要给学校后勤或校园商家做消费洞察报告、但不想从零造轮子的从业者。下面我按「资源是什么 → 怎么跑 → 坑在哪 → 怎么进阶」的顺序拆一遍。
2. 拆包与数据摸底:两份 CSV 和一组任务脚本怎么对上
2.1 目录结构与文件职责
拿到压缩包先别急着跑代码,先把目录摊开看一遍。这份资源的文件组织是典型的「数据 / 代码 / 结果」三分法,职责边界很清楚:
| 目录 | 文件 | 作用 |
|---|---|---|
data | data1.csv、data2.csv | 原始消费流水,两份文件大概率是不同维度或不同时间段的记录 |
code | task1_X1.py、task1_X2.py、task2_X1.py、task3_X1.py等 | 按任务编号组织的分析脚本,task1 偏清洗、task2 偏统计可视化、task3 偏建模 |
result | task1_X.csv、task1_1_X.csv | 清洗或聚合后的中间结果表,可直接用 Excel 打开核对 |
result | task2_X1.png~task2_X5.png、task3_X1.png~task3_X4.png | 各任务输出的图表,是验证代码是否跑对的直接依据 |
demo.jpg在根目录和子目录都出现,通常是运行效果截图或流程图,用来对照「跑完应该长什么样」。这里有个细节值得注意:脚本命名里的X1、X2、X4不是随便编的,它对应的是同一任务下的不同子问题或不同数据集分支,比如task3_X1.py和task3_X2.py可能分别对data1和data2做聚类。跑之前先确认每个脚本读的是哪份 CSV,否则很容易出现「代码没报错但结果对不上」的玄学问题。
2.2 用 pandas 做第一轮数据摸底
在跑任何分析脚本之前,我习惯先用一段独立的摸底代码把两份 CSV 的字段、类型、缺失情况看清楚。这一步能帮你判断后面脚本里的列名引用是否和实际数据一致:
import pandas as pd # 分别读取两份原始数据,注意编码,校园系统导出的 CSV 常见 gbk 或 utf-8-sig for name in ["data1.csv", "data2.csv"]: df = pd.read_csv(f"data/{name}", encoding="utf-8-sig") print(f"===== {name} =====") print("形状:", df.shape) print("字段:", list(df.columns)) print("前 3 行:\n", df.head(3)) print("缺失值:\n", df.isnull().sum()) print("类型:\n", df.dtypes)这段代码的逻辑很直接:循环读两份文件,先看形状判断数据量级,再看字段名确认后面脚本引用的列是否存在,最后看缺失值和类型。参数上唯一需要留意的是encoding,如果报UnicodeDecodeError,把utf-8-sig换成gbk再试,这是国内校园系统导出 CSV 最常见的两种编码。跑完这一步你会得到两份数据的字段清单,拿它去对照task1_X1.py里的列名,能提前发现「脚本里写的是amount但数据里叫money」这类低级但致命的错位。
2.3 任务脚本的执行顺序
这份资源的脚本是按 task 编号递进的,不要跳着跑。合理的执行顺序是:先跑task1_X1.py和task1_X2.py完成清洗,产出result/task1_X.csv和task1_1_X.csv;再跑task2_X1.py到task2_X5.py做统计和可视化,产出那批task2_*.png;最后跑task3_X1.py到task3_X4.py做建模,产出task3_*.png。每个脚本跑完,先去result目录确认对应的输出文件是否生成、时间戳是否更新,再跑下一个。这种「跑一个验一个」的节奏,比一口气全跑完再排查要省事得多。
3. 清洗与聚合:task1 脚本里的字段处理和中间结果核对
3.1 消费流水的典型清洗动作
校园消费数据常见的脏法就那么几种:金额字段带货币符号、时间字段格式不统一、有退款导致的负数、有重复刷卡记录。task1_X1.py和task1_X2.py干的就是把这些收拾干净。我一般会在读懂原脚本后,按下面这个模式补一段可复用的清洗逻辑,方便你对照理解每一步在干什么:
import pandas as pd df = pd.read_csv("data/data1.csv", encoding="utf-8-sig") # 1. 金额列去掉可能的货币符号并转 float df["amount"] = df["amount"].astype(str).str.replace("¥", "", regex=False) df["amount"] = pd.to_numeric(df["amount"], errors="coerce") # 2. 时间列统一转 datetime,无法解析的置为 NaT df["trade_time"] = pd.to_datetime(df["trade_time"], errors="coerce") # 3. 剔除金额为空或为负的异常记录(退款单独处理) df = df[df["amount"].notna() & (df["amount"] > 0)] # 4. 按「学号 + 时间 + 金额」去重,防止重复刷卡 df = df.drop_duplicates(subset=["stu_id", "trade_time", "amount"]) # 5. 导出中间结果,供 task2 使用 df.to_csv("result/task1_X.csv", index=False, encoding="utf-8-sig") print("清洗后行数:", len(df))逻辑说明:第 1 步处理金额字段的类型污染,errors="coerce"保证无法转换的值变成NaN而不是直接抛异常;第 2 步统一时间格式,这是后面做「按小时/按天聚合」的前提;第 3 步把退款和异常金额剔掉,注意如果你的分析目标包含退款行为,这一步要改成单独标记而不是删除;第 4 步的去重键是经验值,校园一卡通重复刷卡的判定通常就是这三要素相同。参数上,encoding和前面摸底保持一致,导出时用utf-8-sig是为了 Excel 打开不乱码。
3.2 中间结果表的核对方法
result/task1_X.csv和task1_1_X.csv是清洗后的产物,也是 task2 的输入。核对这两个文件,重点看三件事:行数是否比原始数据少(少了说明清洗生效)、字段是否比原始数据多(多了说明做了衍生,比如拆出了「小时」「星期几」)、金额列的分布是否合理(有没有离谱的极大值)。我一般会写一段快速核对代码:
import pandas as pd raw = pd.read_csv("data/data1.csv", encoding="utf-8-sig") clean = pd.read_csv("result/task1_X.csv", encoding="utf-8-sig") print("原始行数:", len(raw), "清洗后行数:", len(clean)) print("清洗后字段:", list(clean.columns)) print("金额描述统计:\n", clean["amount"].describe())如果清洗后行数只少了个位数,说明数据本身比较干净;如果少了一半以上,就要回头检查去重键是不是设得太宽,把正常记录也误删了。金额的describe()里重点看max,如果出现几万甚至几十万的单笔消费,基本可以判定是异常值,需要在清洗阶段加一个分位数截断。
3.3 衍生字段的构造
消费行为分析里,光有原始时间和金额是不够的,task2 的很多图表依赖衍生字段。常见的衍生包括:从trade_time拆出hour(几点消费)、weekday(周几消费)、is_weekend(是否周末),以及按金额分档的amount_level(低/中/高消费)。这些字段一般在 task1 阶段就构造好,写进中间结果表,task2 直接拿来用。构造时注意hour要用dt.hour,weekday用dt.dayofweek(周一是 0),别用dt.weekday搞混。这一步没有太多坑,但字段名要和 task2 脚本里的引用严格一致,否则又是一轮「跑得通但图是空的」。
4. 可视化与建模:task2、task3 脚本的图表逻辑和模型选择
4.1 task2 的五张图分别在回答什么问题
task2_X1.png到task2_X5.png这五张图,基本覆盖了消费行为分析的标准问题集:消费金额分布、消费时间分布、不同人群消费对比、消费频次统计、消费趋势变化。对应的脚本task2_X1.py到task2_X5.py通常用 matplotlib 或 seaborn 出图。我拿「按小时统计消费笔数」这个最常见的图举例,说明脚本里的逻辑:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("result/task1_X.csv", encoding="utf-8-sig") df["trade_time"] = pd.to_datetime(df["trade_time"]) df["hour"] = df["trade_time"].dt.hour hourly = df.groupby("hour")["amount"].agg(["count", "sum"]).reset_index() plt.figure(figsize=(10, 5)) plt.bar(hourly["hour"], hourly["count"], color="#4C72B0") plt.xlabel("小时") plt.ylabel("消费笔数") plt.title("校园消费时段分布") plt.xticks(range(0, 24)) plt.tight_layout() plt.savefig("result/task2_X1.png", dpi=150)逻辑说明:先按小时分组,同时统计笔数和金额,agg(["count", "sum"])一次拿到两个指标;画图时用柱状图看分布形态,xticks固定 0 到 23 保证横轴完整。参数上dpi=150是出图清晰度和文件大小的平衡点,tight_layout防止标签被裁。跑完对照result/task2_X1.png,如果横轴只有部分小时、或者柱子高度明显异常,回去检查hour字段是否构造正确、有没有把NaT混进去。
4.2 task3 的建模脚本在做什么
task3_X1.py到task3_X4.py对应的是消费行为建模,从摘要描述看,涉及聚类分析、关联规则和预测模型。聚类(比如 KMeans 对学生按消费金额和频次分群)是最常见的入口,关联规则(比如「买了 A 的人也会买 B」)适合食堂窗口或超市商品分析,预测模型(比如预测下月消费额)则依赖时间序列或回归。以聚类为例,脚本的核心逻辑通常是:选特征 → 标准化 → 定 K 值 → 聚类 → 可视化。这里有个选型理由要讲清楚:消费金额和消费频次量纲差很多,不标准化直接聚类,结果会被金额主导,频次几乎不起作用。所以StandardScaler这一步不能省。
import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans df = pd.read_csv("result/task1_X.csv", encoding="utf-8-sig") # 按学生聚合出消费总额和消费笔数两个特征 stu = df.groupby("stu_id")["amount"].agg(["sum", "count"]).reset_index() stu.columns = ["stu_id", "total_amount", "trade_count"] # 标准化,消除量纲差异 X = StandardScaler().fit_transform(stu[["total_amount", "trade_count"]]) # 聚成 3 类,随机种子固定保证可复现 km = KMeans(n_clusters=3, random_state=42, n_init=10) stu["cluster"] = km.fit_predict(X) stu.to_csv("result/task3_X1.csv", index=False, encoding="utf-8-sig") print(stu.groupby("cluster")[["total_amount", "trade_count"]].mean())逻辑说明:先按学号聚合出两个核心特征,再标准化,再聚类。n_clusters=3是常见起点,实际 K 值要靠肘部法或轮廓系数确定,脚本里如果写死了 3,你可以改成循环试 2 到 6 看效果。random_state=42和n_init=10是为了结果可复现,这两个参数不设的话每次跑出来的分群可能不一样,做报告时会很尴尬。跑完看每个簇的均值,如果某一簇的total_amount和trade_count都明显偏高,那就是「高消费高频」人群,对应的是校园里消费能力最强的那批学生。
4.3 结果集与图表的交叉验证
result目录里的 PNG 和 CSV 是互相印证的:CSV 是数值结果,PNG 是数值的视觉呈现。验证时不要只看图好不好看,要拿图去反推数值是否合理。比如聚类图里如果三个簇的点完全重叠,说明特征区分度不够或者没标准化;时间分布图里如果凌晨时段也有大量消费,要么数据有问题,要么就是把NaT错误地归到了 0 点。这种交叉验证的习惯,能帮你在写报告之前就把数据问题拦下来。
5. 避坑与排查:跑这份资源时最容易翻车的五个地方
5.1 编码不对导致读取直接报错
现象:pd.read_csv抛UnicodeDecodeError,脚本第一步就挂。原因:国内校园系统导出的 CSV 常用gbk或gb2312,而脚本里可能写的是默认utf-8。解决:先试utf-8-sig,不行换gbk,再不行用chardet探测。我一般会在读取处加一个 try 链,把两种编码都兜住。
5.2 列名对不上导致 KeyError
现象:脚本跑到df["amount"]时报KeyError: 'amount'。原因:实际 CSV 的列名可能是中文「消费金额」或者英文money,和脚本里的引用不一致。解决:先跑第 2 章的摸底代码打印df.columns,拿实际列名去改脚本,或者统一在读取后做一次df.rename(columns={...})映射。这个坑血泪经验最多,因为报错信息只告诉你缺哪个键,不告诉你实际有哪些键。
5.3 时间字段解析失败被静默丢弃
现象:清洗后行数骤减,但没有任何报错。原因:pd.to_datetime(..., errors="coerce")会把解析失败的值变成NaT,如果后面又做了dropna,这些行就被无声无息地删了。解决:在to_datetime之后先统计NaT数量,如果占比超过 5%,说明时间格式比预想的复杂,需要先用字符串切片或正则把格式统一再转。别让coerce成为黑匣子。
5.4 聚类不标准化导致分群失真
现象:聚类结果里某一簇几乎包含所有样本,另外两簇只有零星几个点。原因:特征量纲差异大,金额的方差远大于频次,KMeans 的距离计算被金额主导。解决:聚类前必须StandardScaler,或者改用对量纲不敏感的方法。判断是否标准化到位,可以看标准化后各特征的均值是否接近 0、标准差是否接近 1。
5.5 图表中文显示成方块
现象:PNG 里中文标题和标签全是方框。原因:matplotlib 默认字体不含中文。解决:在绘图脚本开头设置plt.rcParams["font.sans-serif"] = ["SimHei"]和plt.rcParams["axes.unicode_minus"] = False。如果系统没有 SimHei,换成Microsoft YaHei或WenQuanYi Micro Hei。这个坑不影响数值结果,但会让你的结果图没法直接放进报告,属于「不致命但很烦」的那类。
6. 进阶玩法:把这份资源改成可复用的分析模板
跑通原脚本只是第一步,真正让这份资源产生长期价值的方式,是把它改造成一个参数化的分析模板。我的做法是抽出一个config.py,把文件路径、编码、聚类 K 值、图表输出目录这些易变项集中管理,脚本里只引用配置,不再写死。这样换一份新的校园消费数据,只需要改配置里的路径和字段映射,整条链路就能复用。
具体来说,我会在config.py里定义:
# config.py DATA_DIR = "data" RESULT_DIR = "result" ENCODING = "utf-8-sig" COL_MAP = {"消费金额": "amount", "交易时间": "trade_time", "学号": "stu_id"} K_CLUSTERS = 3 RANDOM_STATE = 42然后在每个 task 脚本开头from config import *,读取时统一走pd.read_csv(f"{DATA_DIR}/data1.csv", encoding=ENCODING).rename(columns=COL_MAP)。这样列名映射只维护一处,换数据时改COL_MAP就行,不用去每个脚本里搜替换。K 值和随机种子也集中管理,做对比实验时改一个地方就能重跑全链路。
再进一步,可以把 task1 到 task3 串成一个run_all.py,用subprocess或直接函数调用的方式按顺序执行,每步之间加一个输出文件存在性检查,任何一步失败就中止并打印是哪一步、缺哪个文件。这样你交付给别人的就不是一堆散脚本,而是一条能一键跑通、失败能定位的流水线。验证模板是否改造成功,标准很简单:拿一份字段名不同的新 CSV,只改config.py里的COL_MAP和路径,全链路能跑出结果图,就算成了。
从那以后我每次拿到这种「源码 + 数据 + 结果集」的资源包,都强制先跑一遍摸底、再核对中间结果、最后才动建模脚本,绝不跳步。这套顺序帮我省下了大量「跑得通但结果不对」的排查时间。希望帮到你。
本文还有配套的精品资源,点击获取