资讯动态

成绩、录取率与经济指标:多源数据对齐与预测建模实战

发布时间:2026/10/9 9:12:09 来源:尧图企业网站定制
简介这份资源面向具备Python基础、希望提升数据清洗与建模能力的中高级学习者与数据分析师围绕成绩、大学录取、收入与社会经济四类真实数据集系统演示统计描述、缺失值插补、相关性分析与一元及多元线性回归的完整流程。内容涵盖异常值处理、虚拟变量生成、数据合并与可视化等关键环节可帮助读者独立完成课程作业或项目练习深化对Pandas与Numpy的运用。资源包共1个docx文档约17KB以文字与代码说明形式呈现任务步骤与结果解释结构紧凑便于按模块查阅。目前已有85人学习下载适合作为数据科学实操训练与建模思路参考的配套材料。1. 成绩、录取率和经济指标三个数据集为什么总在同一个脚本里打架带过几届做数据分析的人之后我发现一个规律凡是标题里同时出现「成绩」「录取率」「经济指标」的最后八成会卡在同一个地方——不是模型不会调而是三份数据的口径对不上。成绩表按学期走录取率按年份走经济指标按季度走直接merge出来的结果看着有几千行实际上有效样本可能只剩几十条。这篇笔记就围绕这个标题把从数据清洗、特征构造、统计描述到预测建模的完整链路拆开讲一遍重点放在「怎么让三张表能对齐」「预测时哪些参数必须调」「哪些坑我踩过」。适合已经会写 pandas 但一上真实数据就翻车的人也适合想把统计和预测串成一条流水线的从业者。我一般会把这类任务拆成四步先统一时间粒度再做缺失和异常处理然后用统计量把数据讲清楚最后才上预测模型。顺序反了后面全是返工。下面按这个顺序展开中间会给出可直接抄的代码和参数说明。2. 把三张表对齐时间粒度统一与合并策略2.1 为什么不能直接 merge粒度冲突的三种表现成绩数据通常是「学生-学期-课程」三级录取率是「年份-地区-批次」经济指标是「季度-地区-指标名」。直接pd.merge会出现三种典型问题一是行数爆炸因为一个学生一学期有多门课和年度录取率合并后每门课都复制一份年度值二是时间错位学期跨年时按年份合并会把秋季学期算到错误年份三是地区口径不一致有的用省份有的用城市合并后大量 NaN。常见做法是先定义一个统一的时间键。我一般用「年份季度」作为主键学期映射到季度春季学期归 Q1-Q2秋季学期归 Q3-Q4。这样三张表都能落到同一个粒度上。地区键则统一到省级城市级数据向上聚合。import pandas as pd import numpy as np # 成绩表学生-学期-课程 grades pd.DataFrame({ student_id: [1,1,2,2,3], term: [2022春,2022秋,2022春,2022秋,2022春], course: [数学,数学,英语,英语,数学], score: [88, 92, 76, 81, 95] }) # 学期映射到年份和季度 term_map { 2022春: (2022, Q1), 2022秋: (2022, Q3), } grades[[year,quarter]] grades[term].apply( lambda t: pd.Series(term_map[t]) ) # 录取率表年份-地区-批次 admission pd.DataFrame({ year: [2022,2022,2023,2023], region: [A省,B省,A省,B省], batch: [本科,本科,本科,本科], admit_rate: [0.62, 0.55, 0.65, 0.58] }) # 经济指标表季度-地区-指标 economy pd.DataFrame({ year: [2022,2022,2023,2023], quarter: [Q1,Q3,Q1,Q3], region: [A省,A省,B省,B省], gdp_per_capita: [52000, 54000, 48000, 49500] })这段代码的关键在term_map它把学期字符串转成(year, quarter)元组后续所有合并都基于这两个字段。参数上要注意apply返回 Series 时要用pd.Series包装否则列名会丢。如果学期命名不规整建议先用正则提取年份和季节再映射。2.2 合并顺序与聚合方式先聚合再合并还是先合并再聚合很多人习惯先把成绩和录取率合并再算平均分。这样做的问题是录取率会被重复计算。正确顺序是先把成绩聚合到「年份-季度-地区」粒度算出该地区该季度的平均分、及格率、样本数再把录取率和经济指标按同样粒度合并。这样每个地区每个季度只有一行不会出现重复计数。# 假设成绩表已关联到地区 grades[region] [A省,A省,B省,B省,A省] # 先聚合到 年份-季度-地区 grade_agg grades.groupby([year,quarter,region]).agg( avg_score(score,mean), pass_rate(score, lambda s: (s60).mean()), student_cnt(student_id,nunique) ).reset_index() # 再合并录取率和经济指标 merged grade_agg.merge( admission, on[year,region], howleft ).merge( economy, on[year,quarter,region], howleft ) print(merged.head())聚合时pass_rate用 lambda 计算及格比例student_cnt用nunique避免同一学生多门课被重复计数。合并用howleft保留成绩表的所有记录如果某地区没有录取率数据后续要单独处理缺失而不是直接删行。参数上groupby的as_indexFalse和reset_index()效果一样我习惯用后者链式调用时更直观。2.3 缺失值处理三种填充策略的适用场景合并后必然出现缺失。录取率缺失通常是因为该地区该年份没有招生数据经济指标缺失可能是季度数据未发布。我一般分三种情况处理如果缺失比例低于 5%用同地区相邻季度均值填充如果缺失集中在某些地区用该地区历史中位数填充如果缺失比例超过 30%直接标记为「数据不足」并排除出建模样本。# 查看缺失比例 missing_ratio merged.isnull().mean() print(missing_ratio) # 按地区分组填充经济指标 merged[gdp_per_capita] merged.groupby(region)[gdp_per_capita].transform( lambda s: s.fillna(s.median()) ) # 录取率缺失用同地区均值填充 merged[admit_rate] merged.groupby(region)[admit_rate].transform( lambda s: s.fillna(s.mean()) ) # 仍然缺失的行标记并排除 merged[data_flag] merged[[admit_rate,gdp_per_capita]].isnull().any(axis1) model_data merged[~merged[data_flag]].copy()transform和apply的区别在这里很关键transform返回与原表等长的序列适合填充apply会改变形状。填充后一定要检查data_flag的行数如果超过总行数 20%说明数据源本身有问题需要回头核对采集口径而不是硬填。3. 统计描述与特征构造让数据先开口说话3.1 描述性统计的四个必看指标在建模之前我至少会看四个统计量均值、中位数、标准差、偏度。成绩数据常见左偏高分多录取率常见右偏低录取率地区多经济指标通常近似正态但可能有长尾。偏度绝对值超过 1 时考虑做对数变换或分箱。desc model_data[[avg_score,admit_rate,gdp_per_capita]].describe().T desc[skew] model_data[[avg_score,admit_rate,gdp_per_capita]].skew() desc[missing] model_data[[avg_score,admit_rate,gdp_per_capita]].isnull().sum() print(desc)describe()默认给出 count、mean、std、min、25%、50%、75%、max。我额外加偏度和缺失数是为了判断是否需要变换以及缺失是否集中在某列。如果admit_rate的偏度大于 1.5我会先做 logit 变换再进模型。3.2 构造交叉特征录取率与经济的交互项单纯把录取率和 GDP 放进去模型往往学不到「经济好但录取率低」这种组合模式。我一般会构造两个交互特征录取率除以 GDP表示单位经济产出对应的录取机会以及录取率与平均分的差值表示录取难度与成绩水平的匹配度。# 交互特征 model_data[admit_per_gdp] model_data[admit_rate] / (model_data[gdp_per_capita] / 10000) model_data[score_admit_gap] model_data[avg_score] / 100 - model_data[admit_rate] # 分箱特征经济水平分位数 model_data[gdp_level] pd.qcut( model_data[gdp_per_capita], q4, labels[低,中低,中高,高] )admit_per_gdp除以 10000 是为了让系数不至于太小方便解读。pd.qcut按分位数分箱避免等宽分箱在长尾数据上失效。分箱后可以进一步做 one-hot 编码但树模型可以直接用类别编码。3.3 时间序列特征滞后项与滚动窗口如果数据按季度排列滞后项是强特征。我一般会加lag1和lag4去年同期以及 4 季度滚动均值。注意滞后项只能在排序后生成且第一行必然缺失。model_data model_data.sort_values([region,year,quarter]) model_data[admit_lag1] model_data.groupby(region)[admit_rate].shift(1) model_data[admit_lag4] model_data.groupby(region)[admit_rate].shift(4) model_data[admit_roll4] model_data.groupby(region)[admit_rate].transform( lambda s: s.rolling(4, min_periods2).mean() )shift(1)生成上一季度值shift(4)生成去年同期值。rolling(4, min_periods2)表示窗口 4 但至少 2 个非空值才计算避免早期数据全空。生成后要检查缺失比例如果admit_lag4缺失超过 30%说明数据跨度不足 4 个季度需要放弃该特征。4. 预测建模从线性回归到梯度提升的选型与调参4.1 基线模型线性回归为什么仍然值得先跑不管最后用什么模型我都会先跑一个线性回归。原因有两个一是系数可解释能快速判断特征方向是否合理二是作为基线后续复杂模型的提升幅度才有参照。如果线性回归的 R² 已经到 0.85上梯度提升的收益可能很小。from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_absolute_error features [avg_score,gdp_per_capita,admit_per_gdp,score_admit_gap,admit_lag1] X model_data[features].fillna(0) y model_data[admit_rate] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) lr LinearRegression() lr.fit(X_train, y_train) pred_lr lr.predict(X_test) print(R2:, r2_score(y_test, pred_lr)) print(MAE:, mean_absolute_error(y_test, pred_lr)) print(系数:, dict(zip(features, lr.coef_)))fillna(0)是临时处理正式建模前应该用上一节的填充策略。random_state42保证可复现。系数符号很重要如果avg_score系数为负说明成绩越高录取率越低这通常意味着数据口径有问题需要回头检查。4.2 梯度提升树的关键参数学习率、深度、早停梯度提升在我这类任务里通常是最终选择。关键参数有三个learning_rate控制每棵树贡献max_depth控制单棵树复杂度n_estimators配合早停决定树的数量。我一般先用learning_rate0.05、max_depth3、n_estimators500跑一轮看验证集曲线。from sklearn.ensemble import GradientBoostingRegressor from sklearn.model_selection import cross_val_score gbr GradientBoostingRegressor( learning_rate0.05, max_depth3, n_estimators500, subsample0.8, random_state42 ) scores cross_val_score(gbr, X_train, y_train, cv5, scoringr2) print(CV R2:, scores.mean(), scores.std()) gbr.fit(X_train, y_train) pred_gbr gbr.predict(X_test) print(Test R2:, r2_score(y_test, pred_gbr))subsample0.8表示每棵树用 80% 样本能降低过拟合。cross_val_score的cv5给出 5 折交叉验证标准差大于 0.1 说明模型不稳定需要检查数据分布或增加样本。如果测试集 R² 比 CV 低很多通常是过拟合优先降max_depth或减n_estimators。4.3 特征重要性解读哪些特征真正在驱动预测树模型训练后feature_importances_给出每个特征的重要性。我一般会看前五个如果某个交互特征重要性远高于原始特征说明交互构造有效。但要注意重要性高不代表因果只代表模型依赖。import pandas as pd importance pd.DataFrame({ feature: features, importance: gbr.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head())如果admit_lag1重要性最高说明录取率有强自相关这时候要警惕数据泄漏如果滞后项在预测时不可得就不能用。我一般会做两个版本一个含滞后项用于事后分析一个不含用于实时预测。5. 避坑与排查五个让我返工的血泪经验5.1 合并后行数暴涨但有效样本没增加现象merge后行数从几千变成几万但drop_duplicates后只剩几百。原因成绩表一个学生多门课和年度录取率合并时每门课都复制一份年度值。解决先聚合到目标粒度再合并聚合时用nunique统计学生数避免重复计数。5.2 时间字段类型不一致导致合并为空现象merge后所有行都是 NaN检查发现两边都有数据。原因一边year是 int一边是 stringpandas 不报错但匹配不上。解决合并前统一astype(str)或astype(int)并在合并后检查shape[0]是否等于预期。5.3 填充缺失后模型 R² 虚高现象填充后 R² 从 0.7 跳到 0.95。原因用目标变量的均值填充了特征造成信息泄漏。解决填充只能用训练集的统计量且不能使用目标变量。我一般把填充逻辑写在Pipeline里用fit只在训练集上计算。5.4 滞后特征在预测时不可得现象离线评估 R² 很高上线后预测偏差大。原因lag1在预测时点还没发生。解决明确预测时点只使用该时点之前已知的特征。如果必须用滞后项至少滞后 2 期以上。5.5 经济指标量纲差异导致系数不可比现象GDP 系数是 0.0001成绩系数是 0.5看起来成绩重要得多。原因GDP 单位是元数值几万成绩是百分制。解决建模前做标准化StandardScaler后系数才可比。树模型不受量纲影响但线性模型必须处理。6. 进阶技巧用分位数回归看录取率的尾部风险普通回归预测的是均值但录取率这种指标尾部风险往往更值得关注。比如某地区录取率可能骤降到 0.3均值模型会平滑掉这种极端情况。分位数回归可以给出 10%、50%、90% 分位数的预测区间帮助判断「最差情况」和「最好情况」。from sklearn.ensemble import GradientBoostingRegressor from sklearn.metrics import mean_pinball_loss # 分别训练三个分位数模型 quantiles [0.1, 0.5, 0.9] models {} for q in quantiles: gbr_q GradientBoostingRegressor( lossquantile, alphaq, learning_rate0.05, max_depth3, n_estimators300, random_state42 ) gbr_q.fit(X_train, y_train) models[q] gbr_q # 预测区间 pred_low models[0.1].predict(X_test) pred_mid models[0.5].predict(X_test) pred_high models[0.9].predict(X_test) # 评估pinball loss for q in quantiles: loss mean_pinball_loss(y_test, models[q].predict(X_test), alphaq) print(fQuantile {q} pinball loss: {loss:.4f})lossquantile和alphaq是分位数回归的核心参数。mean_pinball_loss是分位数回归的标准评估指标值越小越好。预测区间宽度pred_high - pred_low可以作为一个新特征宽度大说明该样本不确定性高适合单独标记出来人工复核。我一般会把分位数模型的输出和均值模型对比如果 90% 分位数预测远高于均值说明该地区有上升潜力如果 10% 分位数很低说明有下行风险。这个思路在录取率预测里特别有用因为录取率受政策影响大尾部事件并不罕见。最后说一个习惯每次跑完模型我都会把特征重要性、分位数区间、缺失比例三张表并排看一遍。如果特征重要性里出现明显不合理的特征或者分位数区间宽度异常先别调参回头查数据。模型不会骗人但数据会。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑