简介:共享单车租赁数量预测是交通出行类数据挖掘的典型场景,项目使用Python完成数据分析和可视化,并以随机森林、支持向量机两种模型进行租赁量回归预测。代码约300行,既涵盖Pandas、NumPy的数据清洗与特征预处理,也包含Matplotlib、Seaborn的可视化探索,适合课程设计、竞赛练习以及希望系统学习数据挖掘全流程的初学者。压缩包共8个文件、约37.41MB,以两个可运行ipynb代码文件为主体,每个notebook都带有输出步骤;配套csv原始数据、模型预测结果png图、特征重要性png图和一个html预览版,便于快速查看图表效果;同时附送常用包及安装方法说明,可降低环境配置门槛。目前已有454人学习,学习者既能对比随机森林与SVM在同一任务上的表现,也能从数据预处理、特征工程到模型评估走通完整项目。具体可掌握缺失值填充、时间特征转换、标准化/归一化、K折交叉验证、MSE/MAE/R2评估,以及随机森林特征重要性排序、SVM核参数选择等关键技能,稍加改造即可迁移到其他租赁或销量预测场景。
1. 共享单车租赁预测:先用 300 行代码看清数据挖掘的完整漏斗
共享单车的租赁数量预测,本质上是一道回归题:给定天气、时间、季节等条件,预测某个小时的租车量。这个项目的价值不在于算法多新,而在于它把一条完整的数据挖掘链路压缩到了约 300 行代码里——从 pandas 加载与清洗、日期特征分解,到随机森林与支持向量机建模、交叉验证,再到特征重要性和预测结果的可视化。很多做数据分析的同事日常处理的报表只是“看过去”,而这一套流程解决的是“推算未来”,两者之间差的就是特征工程和模型评估的功力。对于想系统梳理 pandas、matplotlib、sklearn 的从业者,或者正在做课程设计、竞赛入门的人,这个压缩包里的train.csv、两个.ipynb和特征重要性图,恰好是一条清晰可走的路线:先复现,再改参,最后替换成自己的数据。
2. 数据清洗与特征工程:日期列才是时间序列建模的富矿
2.1 加载训练数据并处理缺失值
在随机森林和 SVM 介入之前,数据质量决定了模型上限。这个项目里train.csv通常是小时级别的租赁记录,包含时间戳、天气状况、温度、体感温度、湿度、风速以及 Casual 和 Registered 两类用户数量。第一步自然是用 pandas 读入并做基础体检,常见的做法是这样:
import pandas as pd import numpy as np df = pd.read_csv('train.csv', parse_dates=['datetime']) print(df.info()) print(df.isnull().sum()) # 时间序列缺失值处理策略:线性插值优于均值填充 df['humidity'] = df['humidity'].interpolate(method='linear')parse_dates让 pandas 直接把 datetime 列解析成datetime64类型,后续提取年、月、日、小时就不需要手动拼接。interpolate对连续型特征更为稳妥——均值填充会压低方差,影响随机森林对特征阈值的搜索空间,而线性插值保留了时间序列的局部趋势。实际处理时还应该看一眼df.describe(),风速为 0 的极端值、体感温度与气温的倒挂,都需要结合业务口径判断是保留还是修正,而不是机械地删行。
2.2 从时间戳中拆出可被模型消化的特征
共享单车的租赁量和时段强相关:早晚高峰、周末与工作日、季节交替都会改变需求曲线。原始时间戳字符串无法被 SVM 的距离公式直接计算,所以特征工程的第一步就是把它拆成数值:
df['hour'] = df['datetime'].dt.hour df['dayofweek'] = df['datetime'].dt.dayofweek df['month'] = df['datetime'].dt.month df['year'] = df['datetime'].dt.year df['day'] = df['datetime'].dt.day # 业务交叉特征:是否上下班高峰 df['is_rush'] = ((df['hour'] >= 7) & (df['hour'] <= 9) | (df['hour'] >= 17) & (df['hour'] <= 19)).astype(int)dt.hour提取小时数,dt.dayofweek返回 0-6 的整数用于区分工作日和周末。is_rush这个交叉特征在很多共享单车数据集中能显著提升模型表现,因为它直接把业务规则编码成强信号。不要小看这一列:随机森林可以自动学交互,但显式给出业务含义明确的特征,能让模型在小样本场景下少走弯路,SVM 也能因为特征的线性可分性提升而更快收敛。
2.3 离散特征编码与数值特征标准化
SVM 对特征尺度极其敏感,温度 0-40 和风速 0-60 不在同一量纲,直接喂进去会导致距离计算被数值大的维度主导;随机森林虽然不受单调变换影响,但标准化对它并无坏处。分类特征如季节、天气则需要处理成数值形式:
# 方法一:pandas 内置哑变量编码 df = pd.get_dummies(df, columns=['season', 'weather'], drop_first=True) # 方法二:手动映射有顺序的业务类别 df['weather_int'] = df['weather'].map({1: 0, 2: 1, 3: 2, 4: 3}) # 标准化 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() feat_cols = ['temp', 'atemp', 'humidity', 'windspeed', 'hour', 'is_rush'] df_scaled = scaler.fit_transform(df[feat_cols])drop_first=True会去掉一个哑变量以避免多重共线性,这对 SVM 这类基于距离的模型尤其关键。weather_int的映射保留了一级天气比四级更适宜骑行的语义,在特征重要性分析中更容易识别出业务层面的信号。StandardScaler把连续特征转为均值 0、标准差 1 的分布,能让 SVM 的 RBF 核函数在核宽度选择上更有效,训练迭代次数也明显减少。这里有一个常用做法:先 fit 在训练集上,再用同一个scaler.transform应用在测试集上,防止信息泄漏。
3. 可视化探索:从分布图到特征重要性图
3.1 直方图与箱线图定位数据形态
建模之前先看数据形态。Matplotlib 和 Seaborn 在这个项目中不是装饰品,它们承担着两个任务:验证特征是否符合常识、为特征选择提供定量依据。租车量通常是长尾分布——大部分时段租借量偏低,少数时段爆量,直接用原始值建模会被极端值拉偏评估指标。常见做法是取对数:
import matplotlib.pyplot as plt import seaborn as sns fig, axes = plt.subplots(1, 2, figsize=(12, 4)) sns.histplot(df['count'], bins=50, ax=axes[0]) axes[0].set_title('Original Count Distribution') df['log_count'] = np.log1p(df['count']) sns.histplot(df['log_count'], bins=50, ax=axes[1]) axes[1].set_title('Log-transformed Count') plt.tight_layout()np.log1p是log(x+1)的数值稳定写法,避免 x=0 时出现负无穷。左侧长尾分布会让模型对峰值时段的预测误差被平方放大,右侧对数化后分布接近正态,MSE 才能反映真实的相对误差。箱线图则用来快速抓取离群点,比如湿度为 0 但租赁量非零的记录,这类矛盾点要么是传感器故障,要么是数据录入错误,标注出来早处理比留给模型硬扛要好。
3.2 相关性矩阵与热力图定位冗余特征
相关性分析能揭示特征与目标之间的线性关联强度,同时暴露冗余特征。温度与体感温度往往高度相关,两者都进模型不仅增加计算开销,还会让随机森林的特征重要性在两者之间被稀释。热力图是这个阶段最高效的输出形式:
corr = df[['temp', 'atemp', 'humidity', 'windspeed', 'hour', 'dayofweek', 'month', 'count']].corr() plt.figure(figsize=(10, 8)) sns.heatmap(corr, annot=True, cmap='coolwarm', fmt='.2f') plt.title('Feature Correlation Matrix')对比temp和atemp对应的行,会发现相关系数经常超过 0.98,只保留一个即可。corr()计算的是 Pearson 系数,只能捕捉线性关系,随机森林里非线性组合可能仍然有价值,所以热力图的作用是“优先怀疑、二次验证”——先删强冗余,再用模型测试集对比验证删除后的结果,而不是看到高相关就一刀切。模型跑完后输出feature_importances_,和相关性矩阵对照观察,能发现哪些特征线性相关弱但非线性贡献强。
3.3 特征重要性可视化与模型输出的落地
随机森林的feature_importances_是基于基尼不纯度减少量的归一化统计,它回答的是“哪些因素对租赁量影响最大”这一业务问题。项目包里的特征重要性可视化.png正是这一段代码的产出:
importances = rf_model.feature_importances_ indices = np.argsort(importances)[::-1] plt.figure(figsize=(10, 6)) plt.title('Feature Importances in Random Forest') plt.bar(range(len(importances)), importances[indices], align='center') plt.xticks(range(len(importances)), [feat_cols[i] for i in indices], rotation=45) plt.tight_layout()柱子长度代表该特征在全部决策树分裂中带来的不纯度下降总和。小时特征排在首位几乎必然,天气状况和温度紧随其后也符合业务直觉。值得注意的是,特征重要性高不代表因果性强——如果数据里存在与目标间接相关的代理变量,它也可能排前面。把这张图和相关性热力图放在同一个报告里,比单贴一张图更能体现分析深度,这也是这个项目在结构化思路上值得借鉴的地方。
4. 随机森林与 SVM 建模:参数选择、交叉验证与结果对比
4.1 训练集与测试集的划分策略
时间序列数据不能用随机打乱的train_test_split,否则会发生数据泄漏——模型偷看了未来的数据,验证结果虚高。应当按时间顺序切分,比如用前 80% 的时间段训练,后 20% 预测:
from sklearn.model_selection import train_test_split X = df[feat_cols].values y = df['log_count'].values # 按时间顺序切分,shuffle=False 是关键 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, shuffle=False, random_state=42 )shuffle=False让前面 80% 的时间段作为训练数据,后面 20% 作为预测目标。如果使用默认的随机切分,模型会利用未来信息去预测过去,随机森林的集成投票机制会把这种泄漏信号当作强规律学习,测试集上的 R² 会虚高到不真实。K 折交叉验证也要注意:普通KFold不适用于时间序列,TimeSeriesSplit才是常见做法,始终用过去验证未来。
4.2 随机森林:从默认参数到网格搜索
随机森林的核心超参数是n_estimators(树的数量,越大越稳定但计算量线性增长)、max_depth(限制单棵树深度防过拟合)和min_samples_leaf(叶子节点最少样本数)。先跑一份基线:
from sklearn.ensemble import RandomForestRegressor rf_base = RandomForestRegressor(n_estimators=200, random_state=42) rf_base.fit(X_train, y_train) y_pred_rf = rf_base.predict(X_test)n_estimators=200是实践中最常见的起点——超过 500 后边际收益递减,而训练时间成倍增加。拟合后保存feature_importances_,接下来用网格搜索定位更优的参数组合,重点考察max_depth和min_samples_leaf的交叉影响:
from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [200, 300], 'max_depth': [10, 20, None], 'min_samples_leaf': [1, 3, 5] } rf_search = GridSearchCV(RandomForestRegressor(random_state=42), param_grid, cv=3, scoring='neg_mean_squared_error') rf_search.fit(X_train, y_train) print(rf_search.best_params_)GridSearchCV内部每次评估都保持相同的随机种子和交叉验证分割,结果可以稳定复现。scoring='neg_mean_squared_error'表示得分越高误差越小,网格搜索会自动挑分数最高的组合。调参后的随机森林预测值还需要做逆变换才能和真实租车量对比,即np.expm1(rf_best.predict(X_test)),否则 R² 再高也落不到业务口径上。
4.3 支持向量机:归一化之后才能发挥核函数威力
随机森林对数据尺度不敏感,但 SVM 的优化目标涉及特征向量的内积和距离计算,必须标准化。RBF 核是处理非线性回归的默认选择,其中C控制对误分类的惩罚力度,gamma决定 RBF 核的宽度——gamma过大会让模型只关注临近点,容易过拟合;过小则会让决策边界过于平滑,欠拟合。典型的三步走是:
from sklearn.svm import SVR from sklearn.pipeline import make_pipeline svr_pipe = make_pipeline(StandardScaler(), SVR( kernel='rbf', C=100, gamma=0.1, epsilon=0.1 )) svr_pipe.fit(X_train, y_train) y_pred_svr = svr_pipe.predict(X_test)make_pipeline把标准化器与 SVR 组装为流水线,保证训练和测试时特征落在同一尺度。C=100意味着模型会花较大代价去逼近训练数据点,gamma=0.1对应单个特征对距离的影响力集中在较小范围。epsilon是 SVR 独有参数,表示回归管道的宽度——管道内部的点不计入损失,这个值直接影响预测曲线的平滑程度。
训练完成后输出关键结论指标:
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def evaluate(y_true, y_pred, model_name): print(f'{model_name} RMSE: {np.sqrt(mean_squared_error(y_true, y_pred)):.4f}') print(f'{model_name} MAE : {mean_absolute_error(y_true, y_pred):.4f}') print(f'{model_name} R2 : {r2_score(y_true, y_pred):.4f}') evaluate(y_test, y_pred_rf, 'RandomForest')在多数共享单车数据流中,随机森林的 RMSE 会比 RBF-SVM 低 5%-15%。原因并不复杂:租赁量和小时、天气之间的关系呈现明显的分段特征,决策树天然擅长这种分段模式;而 SVM 需要精心调节gamma才能在非线性曲面上拟合同样的趋势。参数同等的计算时间下,随机森林在调用feature_importances_之后的解释性也更贴合业务汇报需求。
5. 实战对比与误差溯源:特征、参数和数据泄漏的三重验证
5.1 画预测值曲线,看模型在哪个时段失准
用 matplotlib 把真实值与随机森林、SVM 的预测值画在一起,排名靠前的特征差异会立刻暴露。如下代码选取测试集最后 72 小时:
plt.figure(figsize=(14, 5)) plt.plot(y_test[-72:], label='Actual', linewidth=2) plt.plot(y_pred_rf[-72:], label='Random Forest', linestyle='--') plt.plot(y_pred_svr[-72:], label='SVM', linestyle='-.') plt.legend() plt.title('Predictions vs Actual, Last 72 Hours') plt.xlabel('Hour Index') plt.ylabel('Log-transformed Count')观察曲线能发现最常见的两种失准:一是早高峰 7 点到 9 点被低估,二是雨天极低租赁量被高估。早高峰低估往往是因为is_rush特征过于粗糙——它只区分了是或不是,没有区分工作日和休息日;早期的dayofweek和is_rush包含的信息冗余但非线性组合潜力未被释放,随机森林又没有足够深的分裂去识别周日早晨与周二早晨的差异。雨天高估则指向特征编码问题:天气类别 3 的样例太少,模型学不到足够的“恶劣天气压低需求”的规则。此时可以引入降雨强度等级作为序数特征,或者用当日租赁均值和滞后一小时租赁量做滑动平均特征。
5.2 数据泄漏排查:标准化与交叉验证的常见误区
共享单车数据带时间戳,最容易犯两个错误。第一,在划分训练集和测试集之前对整个数据集做了StandardScaler().fit_transform(),导致测试集的均值与标准差参与了训练数据的标准化——测试集信息外泄,SVM 的成绩虚高。第二,用随机切分做 K 折交叉验证,模型学会了时间段的偏移特征,真实测试时预测全部失效。
快速自查方法:把训练好的模型在时间排序的测试集上重新评估一次 R²,对比随机切分时的结果;如果几乎没下降,可以考虑从数据泄漏或异常值影响的角度重新检查特征。此外,预测结果等于一个接近常数的低估值时,优先检查
is_rush和天气类别两个最高贡献特征的取值范围是否与测试集一致——时间跨度过大时数据分布可能偏移,在线监控中需要定时重训练。
5.3 随机森林缺失特征重要性的容错方案
随机森林能从时间戳中拟合大部分周期规律,SVM 的 RBF 核理论上也能处理非线性,但用于业务解释时二者存在一个明显差异:SVM 不直接提供特征重要性。项目包中只提供了随机森林的特征重要性图,这是简洁的选择,但在实际交付中通常补一个替代方法——用permutation_importance对训练好的 SVM 估计特征影响:
from sklearn.inspection import permutation_importance r = permutation_importance(svr_pipe, X_test, y_test, n_repeats=10, scoring='neg_mean_squared_error') for i in r.importances_mean.argsort()[::-1]: print(f'{feat_cols[i]}: {r.importances_mean[i]:.5f} ± {r.importances_std[i]:.5f}')n_repeats=10表示对每个特征随机打乱 10 次,每次打乱都会让模型误差增加多少。误差增量越大说明该特征对预测越重要。这个指标不需要重新训练模型,十分钟内就能对 SVM 的解释性做出定量补充,和随机森林的基尼重要性相互印证。最终呈现给业务方的图表,应该同时包含排列重要性的柱状图与随机森林的特征重要性图,跨模型的一致口径更有说服力。
5.4 超参数对训练时间的实际影响
规模不大、数据量在几千行级别的train.csv上,SVM 网格搜索的开销远高于随机森林。gamma的候选值按 0.01、0.1、1 三档扫一遍,C按 10、100、1000 三档扫一遍,RBF 核在矩阵求解上的复杂度近似 O(n²),数据量超过 1 万行后每次 fit 都会显著卡顿。传统做法是先用少量代表性样本跑一个小型网格,锁定合理区间后再全量训练;随机森林在此反而优势明显——n_estimators增加基本呈线性耗时,并行调参在多核机器上非常顺手。两种模型在数据量倍数增长时的耗时曲线相反,选型时要把重训练频率也纳入考量范围,而不是只盯着单次测试集上的 R²。
本文还有配套的精品资源,点击获取