ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

二手车价格预测实战:数据清洗、特征工程与随机森林建模全流程解析

二手车价格预测实战:数据清洗、特征工程与随机森林建模全流程解析 简介针对Python、数据分析方向期末大作业与项目实战需求这份资源以二手车价格预测为案例围绕数据清洗、特征工程、模型训练到结果分析的完整数据挖掘流程展开。资源共26个文件压缩包34.86MB核心内容包括可运行的Python源码、用于训练的CSV数据集、详细实验报告、结果可视化图片以及工程配置文件其中源码完成算法主体数据集支撑训练验证报告梳理实现步骤便于按模块对照学习。项目经导师指导并获得98分评审难度适中源码经本地编译调试确保直接可用特别适合正在准备期末大作业、毕业设计或想提升数据挖掘实战能力的计算机相关专业学生。目前已有168人学习/下载无论完成类似选题还是系统练习完整项目流程这套资料都能提供清晰的操作框架与报告写作参考具有较强的借鉴价值。1. 二手车价格预测一份能跑通全流程的数据挖掘大作业二手车价格预测是数据挖掘课程里出现频率最高的综合型题目之一。它不像图像分类那样需要 GPU也不像 NLP 那样依赖预训练模型核心就是表格数据的清洗、特征工程、回归建模和结果解释正好覆盖 pandas、sklearn、matplotlib 这些 Python 数据科学生态里最常用的工具。这份项目资源包含完整源码、数据集和实验报告代码经过本地编译调试实验报告也有导师审核通过的痕迹适合正在做期末大作业、毕业设计或者想完整走一遍数据挖掘流程的读者。我自己拆完这份项目后最大的感受是:它的价值不在模型精度有多高而在把「拿到一份脏数据后该怎么下手」这件事讲完整了——从缺失值处理到特征构造再到模型对比和报告撰写每一步都能对上实际工作的习惯。2. 数据清洗与特征工程先让数据集满足建模条件2.1 原始数据的常见问题与预处理思路二手车数据集里最典型的字段构成一般是品牌、车系、车型、上牌年份、表显里程、排量、变速箱类型、排放标准、车辆所在地、价格等。这类数据有个共同特点——结构化程度高但脏数据类型集中。拿到手先别急着训练模型我一般会按三步走先看缺失值分布再处理文本和类别字段最后检查数值列的量纲和异常点。这份项目里用到的预处理逻辑很标准核心代码大致如下import pandas as pd import numpy as np df pd.read_csv(car_data.csv, encodinggbk) # 1. 查看缺失值比例 missing_ratio df.isnull().mean().sort_values(ascendingFalse) print(missing_ratio[missing_ratio 0]) # 2. 删除缺失比例过高的列 drop_cols missing_ratio[missing_ratio 0.5].index.tolist() df df.drop(columnsdrop_cols) # 3. 数值列用中位数填充类别列用众数填充 num_cols df.select_dtypes(include[np.number]).columns cat_cols df.select_dtypes(include[object]).columns df[num_cols] df[num_cols].fillna(df[num_cols].median()) df[cat_cols] df[cat_cols].fillna(df[cat_cols].mode().iloc[0])这段代码的操作逻辑是先用isnull().mean()计算每列缺失占比超过 50% 的列直接丢弃避免后续填充引入大量噪声数值列用中位数填充是因为价格、里程这类字段的分布通常右偏中位数比均值更稳健类别列用众数填充则是保留最大概率出现的信息。实际处理时要注意encodinggbk这个参数很多二手车数据集来自国内爬虫或导出工具用 UTF-8 读取会直接报编码错误。2.2 里程、年限与品牌特征的构造方法原始字段不能直接全部丢进模型原因有两个一是上牌日期是字符串需要转换成数值型特征二是品牌和车系这类高基数类别字段直接做 One-Hot 会让特征矩阵膨胀到几千维。常见做法是提取「车龄」——用当前年份减去上牌年份这个特征对价格的影响比绝对年份更直观品牌则按平均售价做目标编码这样既保留品牌信息又不会让维度爆炸。# 构造车龄特征 df[car_age] 2023 - df[register_year] # 品牌目标编码按品牌分组计算价格均值 brand_mean_price df.groupby(brand)[price].transform(mean) df[brand_encoded] brand_mean_price # 里程分箱减少极端值影响 df[mileage_bin] pd.cut(df[mileage], bins[0, 3, 6, 10, 15, 30], labels[0-3万, 3-6万, 6-10万, 10-15万, 15万])这里有个容易被忽视的点register_year如果存在明显的离群值比如 1900 年算出来的car_age会变成一百多直接拉偏模型。处理方式是在构造之前先做一次范围过滤超出合理区间的置为缺失再填充。另外里程分箱这个操作在决策树模型里效果一般因为树模型自己能找到切分点但对于线性回归来说分箱能明显提升对非线性关系的拟合能力。2.3 特征相关性检查与筛选特征工程做完后下一步是看特征之间的相关性这一步的目的不是选特征而是发现冗余。比如brand_encoded和price的相关性可能高达 0.6而brand和car_age之间可能有交互效应。项目里用热力图来观察这一点import matplotlib.pyplot as plt import seaborn as sns # 选择数值型特征计算相关性矩阵 num_features [price, car_age, mileage, displacement, brand_encoded, mileage_bin] # 将分箱列转为数值编码 df[mileage_bin_code] df[mileage_bin].cat.codes corr_matrix df[num_features [mileage_bin_code]].corr() plt.figure(figsize(10, 8)) sns.heatmap(corr_matrix, annotTrue, cmapRdBu, fmt.2f) plt.show()相关性矩阵在这个项目里的价值主要是辅助判断如果两个特征相关系数超过 0.8比如排量和马力那就只保留其中一个避免多重共线性影响线性模型的系数解释。但对于随机森林这类树模型多重共线性影响不大所以这一步做不做取决于后续选的模型。项目里同时跑了线性回归和树模型因此这里只做观察不强制删除。3. 模型选型与参数调优线性回归到随机森林的对比逻辑3.1 基线模型线性回归为什么是必要的起点很多初学者一上来就直接上 XGBoost这其实是个误区。线性回归在这个项目里的作用不是拿高分而是作为基线——它能告诉你特征和价格之间的大致线性关系是否成立也能暴露数据里是否存在严重的非线性模式。项目代码里先用train_test_split划分数据集然后训练线性回归并输出 R² 和 RMSEfrom sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score, mean_squared_error # 特征与标签分离 X df[[car_age, mileage, displacement, brand_encoded, mileage_bin_code]] y df[price] 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) y_pred_lr lr.predict(X_test) print(fR²: {r2_score(y_test, y_pred_lr):.4f}) print(fRMSE: {mean_squared_error(y_test, y_pred_lr, squaredFalse):.2f})random_state42固定随机种子是为了保证结果可复现这在实验报告里非常重要——否则每次运行划分的样本不同分数对不上导师复查时很难验证。特征矩阵里同时包含了mileage原始值和mileage_bin_code分箱编码这是有意的设计线性回归可以同时建模线性趋势和分段效应。3.2 随机森林与梯度提升树的效果对比线性回归跑完后项目进入核心对比环节随机森林和梯度提升树。这里的选型逻辑很清晰——二手车价格和特征之间明显存在非线性关系比如同样一年的车里程从 2 万涨到 5 万可能只降价几千但从 10 万涨到 15 万可能直接腰斩。决策树模型天然能捕捉这种分段变化。from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor # 随机森林基线 rf RandomForestRegressor( n_estimators200, max_depth10, min_samples_leaf3, random_state42 ) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) print(fRandom Forest R²: {r2_score(y_test, y_pred_rf):.4f}) print(fRandom Forest RMSE: {mean_squared_error(y_test, y_pred_rf, squaredFalse):.2f})n_estimators200是随机森林的树数量在这个数据量级下 200 棵已经能稳定收敛max_depth10限制单棵树的深度防止过拟合min_samples_leaf3要求叶子节点至少 3 个样本能让预测结果更平滑。如果数据量只有几千条max_depth设到 15 以上就很容易在训练集上拿到接近 1.0 的 R² 但测试集表现很差。3.3 网格搜索与交叉验证的坑项目里用GridSearchCV做了参数调优重点搜索的是n_estimators、max_depth和min_samples_split三个参数。但实际跑的时候有个常见问题网格搜索的参数量是指数级的三个参数每个 4 个候选值就是 64 组组合再加上 5 折交叉验证总共要训练 320 次。如果每一轮都用 200 棵树时间会非常感人。from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200], max_depth: [8, 10, 12], min_samples_leaf: [2, 3, 5] } grid_search GridSearchCV( RandomForestRegressor(random_state42), param_grid, cv5, scoringneg_mean_squared_error, n_jobs-1, verbose1 ) grid_search.fit(X_train, y_train) print(fBest params: {grid_search.best_params_}) print(fBest CV MSE: {-grid_search.best_score_:.2f})n_jobs-1表示用所有 CPU 核心并行训练能明显缩短时间。scoringneg_mean_squared_error这个设置很关键——sklearn 里的 GridSearchCV 默认以「越大越好」为标准所以 MSE 要取负数才能保持一致的优化方向。很多人在这里直接用默认的 R² 做评分结果搜出来的参数在测试集上并不理想因为 R² 对离群值不敏感MSE 才是真正反映预测误差的指标。4. 数据增强与集成思路单一模型之外的可优化方向4.1 为什么需要扩充训练样本二手车价格预测的数据量通常不大几万条已经算不错几千条也很常见。数据量不足时模型很容易在价格的高值区间比如豪车、性能车表现很差因为这部分样本在整体里占比太低。项目里没有额外引入外部数据但提供了一条可操作的思路对连续特征做小幅扰动生成合成样本这本质上是一种简单版的数据增强。# 对里程做 ±2% 的扰动生成第二条训练样本 np.random.seed(42) noise np.random.normal(1.0, 0.02, sizelen(df)) df_augmented df.copy() df_augmented[mileage] df[mileage] * noise # 合并原始数据与增强数据 df_combined pd.concat([df, df_augmented], axis0, ignore_indexTrue)注意扰动的幅度不能太大否则会改变特征和价格之间的真实关系。2% 的噪声会让同一辆车在里程上产生几百公里的波动这在真实交易中是合理的测量误差模型因此能学到更平滑的价格曲面。如果扰动超过 10%模型会把噪声当成真实信号测试集上的 RMSE 反而会变差。4.2 基于聚类的价格分层建模另一个实用技巧是按照品牌档次或价格区间分组建模而不是用一个全局模型硬扛所有样本。这个项目里虽然没有显式做分层但数据探索部分按品牌计算了平均价格这其实已经为分层建模打了基础。实操时我会这样做先用价格分位数把样本分成低、中、高三档每档单独训练随机森林最后预测时根据样本落在哪一档用对应的模型。这样做的收益在于低价车3 万以内的代步车的价格主要由里程和车龄决定而高价车30 万以上的豪华品牌的价格更多受品牌保值率和车况影响。用一个全局模型拟合的时候这两类样本的权重会互相拉扯导致两头都不准确。分层之后每个模型只需关注自己那一档的具体规律测试集 RMSE 通常能下降 5% 到 10%。4.3 集成学习的边界与调参策略项目报告里对比了单独随机森林和简单平均集成随机森林 梯度提升树的预测值取平均的结果。简单平均看似粗糙但对于相关性不太高的两个模型来说平均后的方差一定会下降。# 简单平均集成 y_pred_ensemble (y_pred_rf y_pred_gbdt) / 2 print(fEnsemble R²: {r2_score(y_test, y_pred_ensemble):.4f}) print(fEnsemble RMSE: {mean_squared_error(y_test, y_pred_ensemble, squaredFalse):.2f})集成效果的判断标准不是 R² 一定提升而是 RMSE 是否下降。R² 反映的是模型解释了百分之多少的方差RMSE 才是实际预测误差。项目里集成的结果比单模型 RMSE 低大约 3%——不算多但在期末答辩时属于能讲的亮点。更重要的是随机森林和梯度提升树在这个数据集上的残差相关性不高这意味着它们的错误模式不同简单平均就能有效对冲。5. 跑通之后的加分项残差诊断与结果解释数据科学项目做到模型训练和评估还不够期末大作业要拿高分关键在最后两步残差分析和特征重要度解释。这两步放在实验报告里导师一眼就能看出你是真的理解了模型而不是只调了调参。import matplotlib.pyplot as plt # 残差计算 residuals y_test - y_pred_rf # 残差 vs 预测值散点图 plt.figure(figsize(8, 6)) plt.scatter(y_pred_rf, residuals, alpha0.5) plt.axhline(y0, colorred, linestyle--) plt.xlabel(Predicted Price) plt.ylabel(Residuals) plt.show() # 特征重要度排序 feature_importance pd.Series( rf.feature_importances_, indexX.columns ).sort_values(ascendingFalse) print(feature_importance)残差图里有几个信息量很大的特征如果残差在预测值比较大的区域出现喇叭形分布说明模型在高价区间有系统性低估如果残差整体偏移不为零说明有未建模的因素。在这个项目里car_age和mileage的特征重要度通常会排在前两位这符合直觉——车龄和里程是决定二手车价格最核心的两个因素。brand_encoded排第三也合理因为品牌决定了保值率基线。这个项目真正让我觉得值回票价的是它把「从拿到数据到写出报告」的路径走完整了。代码结构清晰数据量适中模型复杂度可控每个环节都能在实验报告里找到对应的描述。对于正在找期末大作业参考的读者我建议不要直接照搬跑一遍就完事而是从数据清洗开始逐个环节改成自己的思路——比如换一种缺失值填充方式、调一下特征组合、跑一次交叉验证对比然后把这些改动和原始结果做对比写进报告里。这样的作业在答辩时给人的感觉是真正做过研究的而不是交了一份网上扒下来的代码。本文还有配套的精品资源点击获取
返回列表