Python数据分析实战:用Pandas处理校园消费数据(附完整代码)
Python数据分析实战:用Pandas处理校园消费数据(附完整代码)
如果你刚开始接触Python数据分析,面对一堆看似杂乱无章的校园消费记录,是不是感觉无从下手?别担心,这篇文章就是为你准备的。我们将从一个真实的校园消费场景出发,手把手带你走完一个完整的数据分析流程。你不需要任何高深的统计学背景,只需要对Python和Pandas有最基础的了解,就能跟着我一起,把原始的CSV文件变成有价值的洞察。我们会从最基础的数据加载和清洗开始,一步步教你如何处理缺失值、合并多张数据表,再到如何从消费时间中挖掘出食堂的就餐规律,最后甚至会用聚类模型给学生们“分个类”,看看不同群体的消费习惯有何不同。整个过程,我都会提供可以直接复制运行的代码片段,让你在理解原理的同时,也能立刻动手实践。
1. 数据准备与初步探索:打开数据世界的大门
拿到数据后的第一步,永远不是急着写分析代码,而是先“认识”你的数据。这就像认识一个新朋友,你得先知道他的名字、年龄和背景。我们手头通常会有多个数据文件,比如学生基本信息表、消费流水表和门禁记录表。用Pandas加载它们非常简单。
import pandas as pd
# 假设数据文件放在当前目录下的‘data’文件夹中
df_student = pd.read_csv('./data/student_info.csv', encoding='utf-8')
df_consumption = pd.read_csv('./data/consumption_records.csv', encoding='utf-8')
df_access = pd.read_csv('./data/access_records.csv', encoding='utf-8')
加载之后,我习惯立刻用.info()和.head()方法快速瞥一眼数据的“长相”。.info()会告诉我每列有多少非空值,以及数据类型是什么;.head()则展示前几行数据,让我对字段内容有个直观感受。
print("学生信息表概览:")
print(df_student.info())
print("\n消费记录表前5行:")
print(df_consumption.head())
提示:读取CSV文件时,最常遇到的坑就是编码问题。如果遇到
UnicodeDecodeError,可以尝试将encoding参数改为'gbk'或'gb2312',特别是在处理中文系统导出的数据时。
初步查看后,我们往往会发现数据并不“干净”。常见的问题包括:
- 缺失值:某些学生的专业信息为空,或者消费记录里的终端编号缺失。
- 异常值:消费金额出现负数或极大值(比如一顿饭消费了9999元)。
- 格式不一致:日期时间列可能是字符串格式,需要转换;院系名称可能有全称和简称混用的情况。
处理缺失值是数据清洗的核心环节之一。我的策略是“分而治之”:
- 探查缺失比例:计算每列缺失值的百分比。
- 决定处理方式:根据缺失比例和业务意义,选择删除、填充或保留。
# 计算消费记录表中各列的缺失值比例
missing_ratio = df_consumption.isnull().sum() / len(df_consumption) * 100
print("消费记录缺失值比例:")
print(missing_ratio)
# 假设‘Terminal_Serial’列缺失率超过90%,且对后续分析不重要,可以考虑整列删除
if missing_ratio['Terminal_Serial'] > 90:
df_consumption.drop(columns=['Terminal_Serial'], inplace=True)
print("已删除‘Terminal_Serial’列。")
# 对于‘Department’列少量缺失,可以用‘未知’填充
df_consumption['Department'].fillna('未知', inplace=True)
2. 数据关联与整合:让数据“对话”
单一表格的信息是有限的,真正的价值往往隐藏在数据的关联之中。例如,我们只有把消费记录(df_consumption)和学生信息(df_student)通过“校园卡号”关联起来,才能知道每次消费是哪个学院、哪个性别的学生产生的。在Pandas中,这主要靠pd.merge()函数实现,它相当于SQL中的JOIN操作。
理解几种主要的合并方式至关重要:
合并方式 (how参数) | 类比SQL | 结果说明 |
|---|---|---|
inner | INNER JOIN | 只保留两个表都有的键值行。最常用,确保合并后的数据是完整的。 |
left | LEFT JOIN | 以左表(第一个表)的键值为准,保留所有行,右表没有匹配的则填充NaN。 |
right | RIGHT JOIN | 以右表(第二个表)的键值为准。 |
outer | FULL OUTER JOIN | 保留两个表所有的键值行,缺失部分填充NaN。 |
在我们的场景中,通常以学生信息表为基准,去关联消费和门禁记录,确保每个分析对象都有完整的背景信息。
# 将消费记录关联到学生信息(左连接,保留所有学生,即使他没有消费记录)
df_merged_consume = pd.merge(df_student, df_consumption,
how='left',
left_on='Card_No',
right_on='CardNo') # 如果两个表键名列名不同,需分别指定
# 将门禁记录关联到学生信息
df_merged_access = pd.merge(df_student, df_access,
how='left',
left_on='Card_No',
right_on='AccessCardNo')
print(f"合并后的消费数据形状:{df_merged_consume.shape}")
print(f"合并后的门禁数据形状:{df_merged_access.shape}")
合并后,数据量可能会膨胀。这时需要检查是否有重复的关联(比如一个学生对应多条消费记录),并确保关键字段的数据类型正确,特别是时间字段,需要转换为Pandas的datetime类型,以便后续进行时间序列分析。
# 将字符串时间转换为datetime类型
df_merged_consume['Transaction_Time'] = pd.to_datetime(df_merged_consume['Date'])
# 提取小时、星期几等特征,便于后续按时间分析
df_merged_consume['Hour'] = df_merged_consume['Transaction_Time'].dt.hour
df_merged_consume['DayOfWeek'] = df_merged_consume['Transaction_Time'].dt.dayofweek # 周一=0,周日=6
3. 深入消费行为分析:从时间与地点中洞察规律
数据清洗整合完毕,好戏才刚刚开始。校园消费数据是一座金矿,尤其是时间和地点维度,能告诉我们很多有趣的故事。
3.1 食堂就餐模式分析
首先,我们聚焦食堂消费。一个常见需求是分析各食堂的人流量分布。这里有个细节需要注意:一个学生在短时间内连续刷卡(比如打菜分开刷卡),可能只是一次就餐行为。我们需要先对这些记录进行去重处理。
我的处理逻辑是:针对同一个学生(CardNo),在同一天(Date),如果两次刷卡时间间隔小于5分钟,则视为同一次就餐,只保留第一条记录。
# 确保按时间和学生排序
df_canteen = df_merged_consume[df_merged_consume['Dept'].str.contains('食堂')].copy()
df_canteen.sort_values(by=['CardNo', 'Transaction_Time'], inplace=True)
# 计算时间差
df_canteen['Time_Diff'] = df_canteen.groupby(['CardNo', df_canteen['Transaction_Time'].dt.date])['Transaction_Time'].diff().dt.total_seconds() / 60
# 标记需要删除的重复就餐记录(时间差小于5分钟且非第一条记录)
df_canteen['Is_Duplicate'] = (df_canteen['Time_Diff'] < 5) & (df_canteen['Time_Diff'].notna())
# 删除标记为重复的记录
df_canteen_unique = df_canteen[~df_canteen['Is_Duplicate']].copy()
处理完后,我们就可以用groupby轻松统计各食堂的就餐人次了。为了更直观,用matplotlib或seaborn画个饼图或柱状图是很好的选择。
import matplotlib.pyplot as plt
canteen_traffic = df_canteen_unique['Dept'].value_counts()
plt.figure(figsize=(8, 8))
plt.pie(canteen_traffic.values, labels=canteen_traffic.index, autopct='%1.1f%%', startangle=90)
plt.title('各食堂就餐人次占比')
plt.show()
3.2 就餐高峰时段挖掘
食堂经理肯定想知道什么时候最忙。我们可以绘制一天24小时的就餐曲线。更进一步,区分工作日和周末(非工作日)会更有价值。
# 判断是否为工作日(这里使用一个简单逻辑:周一到周五为工作日)
df_canteen_unique['Is_Weekday'] = df_canteen_unique['DayOfWeek'].apply(lambda x: 0 <= x <= 4)
# 按小时和工作日/非工作日分组统计
traffic_by_hour = df_canteen_unique.groupby(['Is_Weekday', 'Hour']).size().unstack(level=0, fill_value=0)
# 绘制折线图
plt.figure(figsize=(12, 6))
plt.plot(traffic_by_hour.index, traffic_by_hour[1], label='工作日', marker='o')
plt.plot(traffic_by_hour.index, traffic_by_hour[0], label='非工作日', marker='s')
plt.xlabel('小时')
plt.ylabel('就餐人次')
plt.title('工作日 vs. 非工作日就餐时间曲线')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.5)
plt.xticks(range(0, 24))
plt.show()
通过这样的图表,你可以清晰地看到,工作日的早餐高峰可能在7:30-8:30,午餐在11:30-12:30,而周末的早餐高峰则可能推迟并拉长。这些结论可以直接用于指导食堂的人力安排和备餐计划。
4. 构建学生消费画像:从统计到聚类
除了分析整体规律,我们还可以从个体层面刻画学生。最基本的,是计算人均消费指标。
# 计算整体人均月消费额和人均刷卡频次
total_students = df_student['Card_No'].nunique() # 学生总人数
total_consumption = df_merged_consume['Money'].sum() # 总消费额
total_transactions = df_merged_consume['CardNo'].count() # 总交易次数
avg_expense_per_student = total_consumption / total_students
avg_freq_per_student = total_transactions / total_students
print(f"本月人均消费额:{avg_expense_per_student:.2f}元")
print(f"本月人均刷卡频次:{avg_freq_per_student:.2f}次")
更进一步,我们可以按专业和性别进行细分对比。比如,比较“计算机科学”、“金融学”和“外国语文学”三个专业男女生的人均消费水平。
# 选取三个专业
selected_majors = ['计算机科学', '金融学', '外国语文学']
df_selected = df_merged_consume[df_merged_consume['Major'].isin(selected_majors)]
# 按专业和性别分组计算
group_stats = df_selected.groupby(['Major', 'Sex']).agg(
avg_expense=('Money', 'mean'),
median_expense=('Money', 'median'),
transaction_count=('CardNo', 'size')
).round(2).reset_index()
print(group_stats)
4.1 使用聚类模型进行学生分群
统计对比能回答“哪个群体消费更高”,但无法自动发现数据中潜在的不同“消费模式”。这时,无监督学习中的聚类算法就派上用场了。K-Means是其中最常用的一种。我们的目标是找到消费行为相似的学生群体。
首先,需要为每个学生构建特征向量。可以包含以下特征:
- 消费特征:月均消费额、月均消费次数、单次平均消费额、消费最高频的场所。
- 门禁特征:最常出入的楼宇。
- 基础特征:性别(需编码)、专业(需编码)。
from sklearn.preprocessing import LabelEncoder
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
# 1. 构建特征DataFrame
# 假设我们已经有了一个包含每个学生上述特征的DataFrame: df_features
# 例如:
# df_features 包含列: ['CardNo', 'Avg_Expense', 'Freq', 'Fav_Canteen', 'Fav_Access', 'Sex_Encoded', 'Major_Encoded']
# 2. 数据标准化 (非常重要!)
scaler = StandardScaler()
features_scaled = scaler.fit_transform(df_features[['Avg_Expense', 'Freq', 'Sex_Encoded', 'Major_Encoded']])
# 3. 选择K值并训练模型(这里假设K=4)
kmeans = KMeans(n_clusters=4, random_state=42, n_init='auto')
df_features['Cluster'] = kmeans.fit_predict(features_scaled)
# 4. 查看聚类结果
cluster_summary = df_features.groupby('Cluster')[['Avg_Expense', 'Freq']].mean()
print(cluster_summary)
运行后,你可能会得到类似这样的分群:
- 群体0(高频高额):消费次数多,且平均每次消费金额高,可能是经常在食堂请客或消费习惯较高的学生。
- 群体1(低频低额):消费次数少,金额也低,可能是比较节俭或经常在校外就餐的学生。
- 群体2(高频低额):消费次数很多,但每次金额小,可能是习惯分次购买或主要在平价窗口消费。
- 群体3(低频高额):消费次数不多,但每次消费额高,可能是不常在食堂吃,但一吃就会选择较好的菜品。
这些分群结果可以为学校的精准服务(如助学金评定参考、个性化餐饮推荐)提供数据支持。当然,K-Means的结果需要结合业务知识来解读,并且可以通过“肘部法则”或“轮廓系数”来辅助确定最佳的K值。
整个分析流程走下来,你会发现,数据分析的核心不在于使用多复杂的模型,而在于清晰的问题定义、扎实的数据预处理和严谨的逻辑推理。Pandas提供了完成这些工作的强大工具,而你的思考和业务理解,才是让数据产生价值的灵魂。下次当你再面对一堆数据时,不妨先问自己:我想从里面知道什么?然后,就像我们今天做的一样,一步步拆解、清洗、分析和可视化,答案自然会浮现出来。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)