教育数据分析实战:七选三选科建议 + 成绩预测模型(保姆级教程)| 基于真实中学70万条记录,手把手教你构建选科推荐系统与成绩预测模型
各位猿友大家好本文将完整复现整个分析流程从数据预处理到模型评估每一行代码都会详细解释。无论你是教育行业的数据分析师还是刚入门机器学习的学生都能跟着教程一步步实现。注本教程使用的原始数据经过脱敏处理代码基于Python 3.8所需库包括pandas、numpy、scikit-learn、matplotlib、seaborn等。一、项目背景与业务目标1.1 七选三选科建议主题2浙江省高考实行“七选三”模式学生需从物理、化学、生物、历史、地理、政治、技术七门科目中选择三门作为选考科目。选科决策直接影响高考总分和专业选择空间。传统做法的痛点依赖教师经验或学生兴趣缺乏数据支撑。我们的解决方案基于学生近五年历次考试成绩构建学科潜力指数综合评估每门科目的成绩水平标准分均值进步趋势成绩变化斜率稳定性标准差相对优势离差最终为每位学生推荐潜力指数最高的3门科目。1.2 学生成绩预测主题6准确预测学生成绩可以帮助教师提前识别可能下滑的学生、调整教学策略。我们对比了线性回归和随机森林两种模型并分析了影响成绩的关键特征如历史平均分、稳定性、趋势等。二、环境准备与数据说明2.1 导入必要的库import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy import stats from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error import warnings warnings.filterwarnings(ignore) # 设置中文显示 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False2.2 数据文件说明本分析使用的数据集来自宁波某中学原始包含7个CSV文件学生信息、成绩、考勤、消费等。经过同学A的预处理后生成了两张宽表analysis_wide_detail.csv每条记录为一次考试的一个科目成绩包含学生ID、科目、原始分、标准分Z-Score、T分数、考试日期等。analysis_wide_modeling.csv学生级别的聚合特征年龄、校区、平均分、成绩等级等。预处理工作包括处理缺失值、成绩中的特殊值-1缺考、-2作弊等、消费负数清洗、校区前缀提取等这里不展开。# 加载数据 detail_df pd.read_csv(analysis_wide_detail.csv) modeling_df pd.read_csv(analysis_wide_modeling.csv) print(fdetail表记录数: {len(detail_df)}) # 约70万条 print(fmodeling表记录数: {len(modeling_df)}) # 约3869位学生三、主题2七选三选科建议完整实现3.1 数据预处理与排序# 转换考试日期并排序每个学生-科目按时间升序 detail_df[exam_sdate] pd.to_datetime(detail_df[exam_sdate]) detail_df detail_df.sort_values([student_id, mes_sub_name, exam_sdate])3.2 计算各科目统计特征按(student_id, mes_sub_name)分组计算平均分、标准差、最高/最低分标准分均值mes_Z_Score已经在原始数据中计算好表示该次考试相对于全校的标准化分数subject_stats detail_df.groupby([student_id, mes_sub_name]).agg( score_mean(score_clean, mean), score_std(score_clean, std), score_max(score_clean, max), score_min(score_clean, min), score_count(score_clean, count), z_score_mean(mes_Z_Score, mean), # 关键标准分均值 t_score_mean(mes_T_Score, mean) ).reset_index()3.3 计算成绩趋势线性回归斜率成绩趋势反映学生在该科目上是进步斜率0还是退步斜率0。只有至少2次考试记录才能计算。def calculate_trend(group): if len(group) 2: return 0 x np.arange(len(group)) y group[score_clean].values slope, _, _, _, _ stats.linregress(x, y) return slope trend_data detail_df.groupby([student_id, mes_sub_name]).apply(calculate_trend).reset_index() trend_data.columns [student_id, mes_sub_name, score_trend] subject_stats subject_stats.merge(trend_data, on[student_id, mes_sub_name], howleft)3.4 计算离差该科均分与个人总均分的差值离差 该科目平均分 - 该学生所有科目的平均分反映该科目相对于学生自身水平的优势。subject_stats[deviation] subject_stats[score_mean] - subject_stats.groupby(student_id)[score_mean].transform(mean)3.5 构建学科潜力指数首先对四个指标进行Z-score标准化消除量纲然后按权重加权求和。权重设置依据业务经验标准分均值40%最重要的指标反映绝对水平成绩趋势30%进步潜力成绩稳定性20%标准差越低越稳定因此取负号离差10%相对优势# 标准化 for col in [z_score_mean, score_trend, score_std, deviation]: subject_stats[f{col}_norm] (subject_stats[col] - subject_stats[col].mean()) / (subject_stats[col].std() 1e-8) # 计算潜力指数 subject_stats[potential_index] ( 0.4 * subject_stats[z_score_mean_norm] 0.3 * subject_stats[score_trend_norm] - 0.2 * subject_stats[score_std_norm] 0.1 * subject_stats[deviation_norm] )3.6 为每位学生推荐最佳3门科目对于每个学生从七门科目中按potential_index降序取前3。seven_subjects [物理, 化学, 生物, 历史, 地理, 政治, 技术] def recommend_subjects(student_data): available student_data[student_data[mes_sub_name].isin(seven_subjects)] if len(available) 3: return None, None top3 available.nlargest(3, potential_index) return top3[mes_sub_name].tolist(), top3[potential_index].mean() recommendations [] for student_id, group in subject_stats.groupby(student_id): subj_list, avg_potential recommend_subjects(group) if subj_list: recommendations.append({ student_id: student_id, recommended_subjects: , .join(subj_list), avg_potential_index: avg_potential }) recommend_df pd.DataFrame(recommendations) print(f共为 {len(recommend_df)} 位学生完成选科推荐) # 输出38693.7 结果可视化3.7.1 各科目被推荐频次all_recommended [] for subj_list in recommend_df[recommended_subjects]: all_recommended.extend([s.strip() for s in subj_list.split(,)]) subj_counts pd.Series(all_recommended).value_counts() plt.figure(figsize(10,6)) bars plt.bar(range(len(subj_counts)), subj_counts.values, colorskyblue, edgecolorblack) plt.xticks(range(len(subj_counts)), subj_counts.index, rotation45) plt.title(七选三科目推荐频次分布, fontsize16) plt.xlabel(科目) plt.ylabel(被推荐次数) for bar, count in zip(bars, subj_counts.values): plt.text(bar.get_x()bar.get_width()/2., bar.get_height()5, str(count), hacenter) plt.tight_layout() plt.savefig(选科推荐分布.png, dpi300)结果解读基于真实数据化学被推荐 2813 次占比24.2%最受欢迎物理 2201 次19.0%技术仅 470 次4.0%最冷门3.7.2 学生潜力指数分布plt.figure(figsize(10,6)) plt.hist(recommend_df[avg_potential_index], bins30, colorlightgreen, edgecolorblack, alpha0.7) plt.axvline(recommend_df[avg_potential_index].mean(), colorred, linestyle--, labelf平均值: {recommend_df[avg_potential_index].mean():.2f}) plt.title(学生学科潜力指数分布) plt.xlabel(潜力指数) plt.ylabel(学生人数) plt.legend() plt.savefig(潜力指数分布.png, dpi300)潜力指数平均值为0.06呈近似正态分布范围从-1.59到1.65。3.8 典型案例潜力最高的学生ID 16079推荐 [地理, 历史, 化学]潜力指数 1.65潜力最低的学生ID 12262推荐 [生物, 物理, 化学]潜力指数 -1.59潜力指数低不代表“差生”而是该学生各科发展均衡没有特别突出的科目更需要通过兴趣探索来决策。3.9 选科建议总结最热门组合化学物理生物459人其次是化学政治物理383人给学生的建议✅ 不要只看当前分数还要看进步趋势和稳定性✅ 标准分比原始分更能反映真实水平✅ 结合未来专业要求和兴趣综合决策四、主题6学生成绩预测模型完整实现4.1 特征工程我们为每个学生-科目构建以下特征特征类型具体特征计算方法成绩统计平均分、标准差、最高/最低分历史聚合趋势特征成绩变化斜率线性回归稳定性变异系数标准差/均值相对水平标准分均值、离差-成绩跨度最高分-最低分-学生信息年龄、校区、成绩等级从modeling表获取科目编码独热编码7个科目哑变量# 已有subject_stats再计算衍生特征 subject_stats[score_range] subject_stats[score_max] - subject_stats[score_min] subject_stats[stability] subject_stats[score_std] / (subject_stats[score_mean] 1e-8) # 获取每个学生-科目的最近一次成绩作为预测目标 target_data detail_df.groupby([student_id, mes_sub_name]).agg( score_clean(score_clean, last) ).reset_index() # 合并学生基本信息 predict_data subject_stats.merge( modeling_df[[student_id, age, campus, avg_score, score_level]], onstudent_id, howleft ) predict_data predict_data.merge(target_data, on[student_id, mes_sub_name], howleft) # 科目独热编码 subject_dummies pd.get_dummies(predict_data[mes_sub_name], prefixsubject) predict_data pd.concat([predict_data, subject_dummies], axis1)4.2 准备训练集和测试集选取特征列删除目标变量缺失的样本。feature_cols [score_mean, score_std, z_score_mean, score_trend, deviation, score_range, stability, age] \ [col for col in predict_data.columns if col.startswith(subject_)] X predict_data[feature_cols].fillna(0) y predict_data[score_clean] # 删除y为空的记录 valid_idx ~y.isna() X X[valid_idx] y y[valid_idx] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42)4.3 训练线性回归模型lr LinearRegression() lr.fit(X_train, y_train) y_pred_lr lr.predict(X_test) lr_r2 r2_score(y_test, y_pred_lr) lr_rmse np.sqrt(mean_squared_error(y_test, y_pred_lr)) lr_mae mean_absolute_error(y_test, y_pred_lr) print(f线性回归 - R²: {lr_r2:.4f}, RMSE: {lr_rmse:.2f}, MAE: {lr_mae:.2f}) # 输出示例: R²: 0.5978, RMSE: 18.64, MAE: 12.444.4 训练随机森林模型随机森林可以捕捉非线性关系我们使用100棵树最大深度10。rf RandomForestRegressor(n_estimators100, max_depth10, min_samples_split5, random_state42, n_jobs-1) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) rf_r2 r2_score(y_test, y_pred_rf) rf_rmse np.sqrt(mean_squared_error(y_test, y_pred_rf)) rf_mae mean_absolute_error(y_test, y_pred_rf) print(f随机森林 - R²: {rf_r2:.4f}, RMSE: {rf_rmse:.2f}, MAE: {rf_mae:.2f}) # 输出示例: R²: 0.9150, RMSE: 8.57, MAE: 4.34模型对比随机森林在各项指标上全面优于线性回归R²从0.60提升到0.915预测误差中位数仅1.86分90.8%的预测误差在10分以内。4.5 可视化模型效果绘制预测值 vs 实际值散点图理想情况下点应落在红线yx附近。fig, axes plt.subplots(1, 2, figsize(14,5)) axes[0].scatter(y_test, y_pred_lr, alpha0.4, edgecolorsblack) axes[0].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--) axes[0].set_title(f线性回归 (R²{lr_r2:.3f})) axes[0].set_xlabel(实际成绩); axes[0].set_ylabel(预测成绩) axes[1].scatter(y_test, y_pred_rf, alpha0.4, edgecolorsblack, colorgreen) axes[1].plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--) axes[1].set_title(f随机森林 (R²{rf_r2:.3f})) axes[1].set_xlabel(实际成绩); axes[1].set_ylabel(预测成绩) plt.tight_layout() plt.savefig(成绩预测模型对比.png, dpi300)4.6 特征重要性分析随机森林提供了feature_importances_属性可以查看哪些特征对预测贡献最大。importance_df pd.DataFrame({ feature: feature_cols, importance: rf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance_df.head(10))输出结果基于实际数据特征重要性score_mean (历史平均分)41.62%stability (稳定性)13.44%score_trend (成绩趋势)13.00%score_range (成绩跨度)11.65%deviation (离差)8.01%......解读历史平均分是最强预测因子说明学习成绩具有延续性稳定性越低越好和趋势上升/下降同样关键学生年龄、校区、科目编码等影响相对较小# 可视化前10特征 top10 importance_df.head(10) plt.figure(figsize(10,8)) plt.barh(range(10), top10[importance], colorcoral, edgecolorblack) plt.yticks(range(10), top10[feature]) plt.xlabel(特征重要性) plt.title(影响成绩的关键特征随机森林) plt.gca().invert_yaxis() plt.savefig(成绩预测特征重要性.png, dpi300)4.7 保存预测结果predict_data_valid predict_data[valid_idx].copy() predict_data_valid[predicted_score] rf.predict(X) predict_data_valid[[student_id, mes_sub_name, score_clean, predicted_score]].to_csv( 成绩预测完整结果.csv, indexFalse, encodingutf-8-sig )五、总结与建议5.1 主题选科建议成果为3869名学生完成个性化推荐化学、物理、生物是最热门推荐科目潜力指数分布合理能够识别出有明显优势科目的学生可直接用于新高一的选科指导避免盲目选择5.2 主题成绩预测成果随机森林模型R²达到0.915预测误差中位数仅1.86分历史平均分、稳定性、趋势是影响成绩的三大关键因素可用于教师预警系统提前发现潜在退步学生5.3 对学校/教师的建议角色建议学生选科时参考潜力指数重视进步趋势和稳定性保持稳定发挥比偶尔高分更重要教师关注成绩趋势为负的学生对波动大的学生进行学习方法指导学校根据选科热度合理配置教师资源建立成绩预警系统5.4 改进方向引入学生兴趣问卷、职业规划等非成绩因素使选科更全面加入教师教学质量、班级氛围等环境变量提升预测精度开发在线推荐系统实现实时交互教育数据分析的魅力在于“用数据说话”将经验驱动的决策转变为数据驱动的决策。希望这篇保姆级教程能帮助你掌握✅ 如何从原始成绩数据中提取有意义的特征✅ 如何构建多维度综合指数潜力指数✅ 如何训练对比回归模型并解读特征重要性如果你在复现过程中遇到任何问题欢迎在评论区留言我会尽力解答。也欢迎关注我的CSDN账号后续会分享更多教育数据挖掘的实战案例。觉得有用的话点个赞再走呗~