Python 实战:基于 K-Means 聚类与时间维度特征的学生贫困生精准识别
1. 项目背景 (Background)
痛点:传统贫困生认定往往依赖学生主动申请或老师主观判断,存在“漏报”或“面子问题”。
目标:利用校园卡消费流水,通过算法自动发现生活困难学生,实现“精准资助”和“隐形关怀”。
技术栈:Python, Pandas, Scikit-learn, Matplotlib/Seaborn。
2. 数据预处理 (Data Cleaning)
数据来源:http://D:\商业数据分析\数智教育数据集
异常值处理:如何处理退款、大额转账等非消费行为(提到 is_outlier_global 等字段)。
编码适配:在读取 consumption_cleaned.csv 时,我遇到了典型的编码冲突问题。为了增强脚本的鲁棒性,我没有硬编码指定某一种格式,而是编写了一个自动探测函数,依次尝试多种常见编码,直到成功读取为止。
import pandas as pd
def read_csv_safe(path):
"""
智能读取 CSV 文件,自动适配 utf-8, gbk, gb2312 等编码
"""
encodings = ['utf-8', 'gbk', 'gb2312', 'latin1']
for encoding in encodings:
try:
# low_memory=False 避免 DtypeWarning
df = pd.read_csv(path, low_memory=False, encoding=encoding)
print(f"✅ 成功使用 [{encoding}] 编码读取文件")
return df
except UnicodeDecodeError:
continue
raise ValueError(f"❌ 无法识别文件编码,请检查文件: {path}")
# 使用示例
df = read_csv_safe('cleaned_data/consumption_cleaned.csv')
列名标准化:原始流水表中的字段命名较为随意,例如 MonDeal 代表交易金额,DealTime_parsed 代表处理后的时间。为了方便后续的 Pandas 聚合操作和 Sklearn 建模,我建立了一套列名映射规则,并对关键数值列进行了类型强制转换。
def standardize_columns(df):
"""
标准化列名并处理关键字段类型
"""
# 1. 定义映射关系 (原始名 -> 标准名)
rename_map = {
'bf_studentID': 'student_id',
'AccName': 'student_name',
'PerSex': 'gender',
'MonDeal_clipped': 'amount', # 使用清洗后的金额
'DealTime_parsed': 'txn_time' # 使用解析后的时间
}
# 只重命名存在的列,防止报错
existing_map = {k: v for k, v in rename_map.items() if k in df.columns}
df = df.rename(columns=existing_map)
# 2. 确保数值列的类型正确
if 'amount' in df.columns:
df['amount'] = pd.to_numeric(df['amount'], errors='coerce').fillna(0)
# 3. 确保时间列是 datetime 对象
if 'txn_time' in df.columns:
df['txn_time'] = pd.to_datetime(df['txn_time'], errors='coerce')
return df
# 使用示例
df_clean = standardize_columns(df)
print(df_clean[['student_id', 'amount', 'txn_time']].head())
3. 核心创新:引入“时间维度”特征 (Feature Engineering)
特征定义:
🍚 食堂依赖度 (canteen_ratio):早中晚三餐消费占比。逻辑:贫困生通常在食堂解决三餐,极少点外卖。
🌙 夜间消费比 (night_ratio):19:00-23:00 的消费占比。逻辑:贫困生较少有夜间零食或娱乐开销。
📅 周末活跃度 (weekend_ratio):周末消费占比。逻辑:贫困生周末外出逛街、聚餐的频率较低。
代码片段:展示 extract_time_features 函数的核心逻辑。
代码如下:
def extract_time_features(df):
df['hour'] = df['time'].dt.hour
df['dow'] = df['time'].dt.dayofweek
# 定义时间段:早餐、午餐、晚餐、夜宵
def get_period(h):
if 6 <= h < 9: return 'breakfast'
if 11 <= h < 13: return 'lunch'
if 17 <= h < 19: return 'dinner'
if 19 <= h < 23: return 'night'
return 'other'
df['period'] = df['hour'].apply(get_period)
# 计算食堂依赖度、夜间消费比等...
4. 模型构建:K-Means 聚类 (Modeling)
为什么选 K-Means?:
1. 业务场景:无监督学习的天然选择 🎯
缺乏标签(No Labels):在贫困生认定场景中,我们并没有一个现成的“谁是贫困生”的标准答案(Label)。如果使用 SVM 或决策树等有监督学习,我们需要大量已标注的数据来训练模型,而这在实际操作中很难获取且涉及隐私。
“物以类聚”的直觉:贫困生的消费行为具有高度的相似性(如:低金额、高食堂依赖、规律作息)。K-Means 的核心思想就是让相似的数据点聚集在一起,这与我们“寻找特定消费群体”的目标完美契合。
2. 算法优势:高效处理多维特征 ⚡
多维度融合:我们的模型不仅看“花了多少钱”(avg_spent),还看了“什么时候花”(canteen_ratio, night_ratio)。K-Means 能够轻松地在 6维甚至更高维的空间中计算欧氏距离,将那些在多个维度上都表现出“节俭”特征的学生自动归为一类。
对大规模数据的友好性:全校学生的流水数据可能高达数十万条。相比于 DBSCAN(密度聚类)或层次聚类,K-Means 的时间复杂度仅为 $O(n)$,在处理这种量级的数据时速度极快,非常适合工程化部署。
3. 结果的可解释性与稳定性 📊
明确的群体划分:K-Means 会将每个学生明确地分配到一个簇(Cluster)中。我们可以简单地通过比较各簇的**中心点(Centroid)**来定义群体。例如:
簇 A:高消费、高夜间活跃度 -> “享乐型”
簇 B:中等消费、规律作息 -> “普通型”
簇 C:低消费、极高食堂依赖 -> “贫困生候选”
稳定性强:通过设置 random_state 和多次初始化(n_init=10),K-Means 能给出非常稳定的聚类结果,这对于需要长期执行的资助系统来说至关重要。
| 算法 | 是否需要标签 | 处理大数据效率 | 对异常值的敏感度 | 适用场景 |
| K-Means (本项目) | ❌ 不需要 | ⭐⭐⭐ 高 | 中等 (已通过清洗处理) | 群体画像、客户分群 |
| DBSCAN | ❌ 不需要 | ⭐ 低 | 低 (能识别噪声) | 发现任意形状的簇 |
| 决策树/随机森林 | ✅ 需要 | ⭐⭐ 中 | 低 | 已有历史认定记录的场景 |
标准化 (StandardScaler):确保“时间行为特征”能和“金额特征”一样,对聚类结果产生同等重要的影响。
聚类逻辑:
将学生映射到 6 维空间(金额+频次+3个时间特征)。
设定 $K=3$,自动分为高、中、低三组。
锁定策略:选取平均消费最低且食堂依赖度最高的簇作为贫困生候选。
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
# 选取关键特征:金额 + 时间行为
features = ['avg_spent_per_month', 'txn_count', 'canteen_ratio', 'night_ratio']
# 标准化:消除量纲影响
scaler = StandardScaler()
X_scaled = scaler.fit_transform(valid_df[features])
# K-Means 聚类
kmeans = KMeans(n_clusters=3, random_state=42)
valid_df['cluster'] = kmeans.fit_predict(X_scaled)
5. 结果可视化 (Visualization)
生成热力图代码:
heatmap_data = poor_txns.pivot_table(index='dow', columns='hour', values='amount', aggfunc='count')
sns.heatmap(heatmap_data, cmap='YlOrRd')
解读图表:
热力图:展示贫困生消费高度集中在工作日饭点。
散点图:展示贫困生在“夜间/周末”坐标轴的左下角聚集。
箱线图:直观对比两类学生的消费差距。

6. 总结与展望 (Conclusion)
结论:该方法比单纯看“月均消费低于XXX元”更科学,能排除那些“虽然花钱少但经常吃泡面/零食”的假性贫困。
不足:目前未结合家庭收入等外部数据,未来可以尝试半监督学习。
更多推荐



所有评论(0)