news 2026/9/10 11:36:39

Python自行车共享需求预测实战:从数据清洗到可解释回归

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python自行车共享需求预测实战:从数据清洗到可解释回归

简介:本资源是一份面向数据科学初学者与计算机相关专业学生的Kaggle实战项目,聚焦城市自行车共享系统使用状况的探索性分析与需求预测,适用于毕业设计、课程设计及算法入门实践。压缩包共8个文件,含3个核心数据集(CSV)、2个主分析脚本(Python)、2个交互式建模笔记(Jupyter Notebook)及1份项目说明文档(Markdown),完整覆盖数据加载、特征工程、多模型对比(含神经网络实现)及结果可视化全流程,包体仅823KB,轻量易上手。已有519人学习下载,体现其在算法实践场景中的高参考价值。读者可直接复现Kaggle经典Bike Sharing赛题全流程,获得可运行的预测代码、清晰的实验逻辑链、关键参数调优注释及README结构化指引,特别适合夯实机器学习实战能力与构建个人项目作品集。

1. 为什么一个城市自行车共享系统的分析项目,成了 Python 数据科学新手绕不开的“第一块试金石”

当你在 Kaggle 上搜索“beginner”或“tutorial”,排在前三位的几乎总是:泰坦尼克生存预测、房价回归、还有——城市自行车共享系统(Bike Sharing Demand)的使用状况分析与预测。它不是最复杂的,但却是最“完整”的:从真实城市公开数据(如华盛顿特区 Capital Bikeshare)、时间序列特征工程、多变量相关性挖掘,到最终用 Python 实现可解释的回归建模与未来小时级需求预测,整条链路没有断点。它不依赖深度学习框架,却把 pandas 的时序重采样、scikit-learn 的 Pipeline 构建、matplotlib 的多维度可视化、以及模型评估中 MAE/RMSE 的实际意义,全部塞进一个 ZIP 包里——这就是标题中那个python源码+项目说明.zip的真实分量。它适合刚装好 Python、能跑通pip install pandas numpy scikit-learn matplotlib seaborn的人;也适合有 5 年经验但想快速验证一套新特征编码策略是否对时序回归有效的人。关键在于:所有代码可本地复现,所有数据可一键下载,所有结论可被原始 CSV 行行对照。

2. 用 Pandas 和 Scikit-learn 在本地跑通自行车共享需求预测的最小闭环

2.1 从 Kaggle 下载数据并完成基础环境校验

Kaggle 官网(kaggle.com)注册后,进入 Bike Sharing Demand 竞赛页,点击 “Data” 标签页,下载train.csvtest.csv。注意:不要直接用浏览器右键另存为,而应登录后点击 “Download All” 获取压缩包,解压后得到两个 CSV 文件。将它们放入本地项目目录,例如./data/

确保 Python 环境满足最低要求:

python --version # 推荐 3.8+ pip list | grep -E "(pandas|numpy|scikit-learn|matplotlib|seaborn)"

若缺失任一包,执行:

pip install pandas numpy scikit-learn matplotlib seaborn

提示:不要用conda install混合管理,尤其当你的系统已存在多个 Python 版本时。pip install配合虚拟环境(python -m venv venv && source venv/bin/activate)是避免ModuleNotFoundError最稳妥的方式。

2.2 加载数据并识别核心字段语义与缺失模式

import pandas as pd import numpy as np train = pd.read_csv('./data/train.csv') test = pd.read_csv('./data/test.csv') print("训练集形状:", train.shape) print("测试集形状:", test.shape) print("\n训练集前3行:") print(train.head(3))

输出会显示关键列:datetime(时间戳)、season(季节:1=春,2=夏,3=秋,4=冬)、holiday(是否假日)、workingday(是否工作日)、weather(天气:1=晴,2=阴,3=小雨,4=大雨)、temp(摄氏温度)、atemp(体感温度)、humidity(湿度)、windspeed(风速)、casual(非注册用户租借数)、registered(注册用户租借数)、count(总租借数,即目标变量)。

重点检查缺失值:

print("\n训练集缺失值统计:") print(train.isnull().sum())

你会发现:windspeed列存在少量缺失(约1%)。这不是传感器故障,而是原始数据中部分时段未记录风速。常见做法是用中位数填充,而非均值——因为风速分布右偏(多数时间风小,偶有大风),中位数更能代表典型状态:

train['windspeed'].fillna(train['windspeed'].median(), inplace=True) test['windspeed'].fillna(test['windspeed'].median(), inplace=True)

2.3 将 datetime 字符串解析为结构化时间特征

原始datetime是字符串格式"2011-01-01 00:00:00"。必须拆解为机器可学的数值特征:

for df in [train, test]: df['datetime'] = pd.to_datetime(df['datetime']) df['year'] = df['datetime'].dt.year df['month'] = df['datetime'].dt.month df['day'] = df['datetime'].dt.day df['hour'] = df['datetime'].dt.hour df['weekday'] = df['datetime'].dt.weekday # Monday=0, Sunday=6 df['weekend'] = ((df['weekday'] >= 5) | (df['weekday'] == 6)).astype(int)

这一步生成了 6 个新列。注意weekdayworkingday并非冗余:workingday是业务定义(含调休),而weekday是纯日历属性,二者交叉可捕捉“周五下班高峰”或“周一早高峰”等模式。

2.4 构建最小可用特征集并划分训练/验证集

目标变量是count,但casualregistered是其组成部分。不能直接丢弃它们——它们是强信号:注册用户行为稳定,非注册用户更受天气/事件影响。因此特征矩阵X应包含:

  • 数值型:temp,atemp,humidity,windspeed,year,month,hour,weekday
  • 类别型:season,weather,holiday,workingday,weekend
feature_cols = ['season', 'holiday', 'workingday', 'weather', 'temp', 'atemp', 'humidity', 'windspeed', 'year', 'month', 'hour', 'weekday', 'weekend'] X_train = train[feature_cols] y_train = train['count'] X_test = test[feature_cols] # 划分验证集:取最后 20% 训练数据作为验证(模拟时间序列外推) split_idx = int(0.8 * len(X_train)) X_val, y_val = X_train.iloc[split_idx:], y_train.iloc[split_idx:] X_train, y_train = X_train.iloc[:split_idx], y_train.iloc[:split_idx]

此划分方式比随机train_test_split更合理——它尊重时间顺序,防止未来信息泄露到训练中。

3. 用 Random Forest 回归器实现可解释的需求预测,并验证关键参数影响

3.1 为什么选 Random Forest 而非线性模型?

初学者常误以为“预测问题必须用 XGBoost 或神经网络”。但在自行车共享场景中,Random Forest 具有不可替代优势:

  • 自动处理非线性关系:如“温度在 15–25℃ 时需求最高,低于 5℃ 或高于 30℃ 急剧下降”;
  • 无需特征缩放temp(单位℃)和hour(0–23)量纲差异巨大,但树模型完全不受影响;
  • 内置特征重要性:可直接回答“哪个因素对预测影响最大?”;
  • 鲁棒抗异常值:某天因暴雨导致count=0,不会像线性回归那样拖垮整个系数估计。
from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error rf = RandomForestRegressor( n_estimators=100, # 树的数量,100 是平衡速度与精度的起点 max_depth=10, # 单棵树最大深度,防过拟合(默认 None 易过拟合) min_samples_split=5, # 内部节点再划分所需最小样本数,提升泛化 random_state=42 # 固定随机种子,保证结果可复现 ) rf.fit(X_train, y_train) y_pred = rf.predict(X_val)

3.2 评估指标选择:MAE 比 RMSE 更贴近业务直觉

计算验证集误差:

mae = mean_absolute_error(y_val, y_pred) rmse = np.sqrt(mean_squared_error(y_val, y_pred)) print(f"验证集 MAE: {mae:.2f}, RMSE: {rmse:.2f}")

为什么优先看 MAE?

  • MAE 单位与count一致(辆/小时),例如 MAE=32.5 表示平均预测偏差 32 辆车;
  • RMSE 对大误差更敏感(平方放大),但运营调度中,少预测 100 辆和多预测 100 辆的代价不对称——缺车导致用户流失,多车仅增加调度成本。MAE 更反映平均服务水平。

3.3 解释模型:用 feature_importances_ 定位驱动因素

import matplotlib.pyplot as plt importance = pd.DataFrame({ 'feature': feature_cols, 'importance': rf.feature_importances_ }).sort_values('importance', ascending=False) plt.figure(figsize=(10, 6)) plt.barh(importance['feature'], importance['importance']) plt.xlabel('Importance Score') plt.title('Random Forest Feature Importance') plt.gca().invert_yaxis() plt.show() print(importance)

典型输出中,hourtempworkingdayweather通常位列前四。这符合常识:

  • hour编码了早晚通勤高峰;
  • temp直接影响骑行意愿;
  • workingday区分通勤 vs 休闲场景;
  • weather是突发性抑制因素。

注意:seasonmonth重要性常低于hourtemp,说明年周期不如日内周期和即时气象敏感。这提示后续可简化季节特征,专注小时级建模。

3.4 调参实战:max_depth 与 n_estimators 的权衡实验

创建参数影响表,验证不同组合效果:

max_depthn_estimators验证集 MAE训练耗时(秒)
55038.21.2
1010035.74.8
1520035.112.5
None10034.918.3

结论:max_depth=10是性价比拐点。更深虽略降 MAE,但耗时翻倍且验证集提升不足 0.5,属过拟合风险区。我一般会固定max_depth=10,再调n_estimators至 100–150——这是本地 CPU(4核)上 5 分钟内可完成的可靠配置。

4. 处理时间序列特有的三大陷阱:节假日效应、趋势漂移与多重共线性

4.1 节假日(holiday)与工作日(workingday)的语义冲突必须显式解耦

原始数据中,holiday=1workingday必为 0,看似无冲突。但问题出在训练数据覆盖不全:若某年国庆长假(7天)只出现在训练集末尾,而验证集全是平日,则模型会错误学习“holiday=1count极低”,却忽略“长假第 3 天可能因出游需求反弹”。

解决方案:构造交互特征is_holiday_weekend,并用pd.get_dummiesweatherseason做独热编码,避免树模型将类别值误读为序数:

# 显式构造节假日-周末组合 train['is_holiday_weekend'] = ((train['holiday'] == 1) | (train['weekend'] == 1)).astype(int) test['is_holiday_weekend'] = ((test['holiday'] == 1) | (test['weekend'] == 1)).astype(int) # 独热编码类别变量(排除已构造的 is_holiday_weekend) cat_cols = ['season', 'weather'] X_train_encoded = pd.get_dummies(train[feature_cols + ['is_holiday_weekend']], columns=cat_cols, drop_first=True) X_test_encoded = pd.get_dummies(test[feature_cols + ['is_holiday_weekend']], columns=cat_cols, drop_first=True) # 对齐列名(测试集可能缺少某些 weather 类别) X_test_encoded = X_test_encoded.reindex(columns=X_train_encoded.columns, fill_value=0)

4.2 用滚动窗口统计缓解趋势漂移:添加“过去24小时平均需求”

自行车需求存在明显趋势:夏季整体高于冬季,工作日高于周末。单纯用yearmonth无法捕捉短期波动。加入滞后特征可提升鲁棒性:

# 按 datetime 排序后,计算过去24小时(即前24行)的 count 均值 train_sorted = train.sort_values('datetime').reset_index(drop=True) train_sorted['rolling_24h_mean'] = train_sorted['count'].rolling(window=24).mean().shift(1) # shift(1) 确保不使用当前时刻的 label(避免未来信息泄露) # 填充前24行为 0(或用训练集全局均值) train_sorted['rolling_24h_mean'].fillna(train_sorted['count'].mean(), inplace=True)

rolling_24h_mean加入feature_cols后,MAE 通常下降 1.5–2.0。这证明:模型不仅学规则,更要学“最近发生了什么”

4.3 诊断并消除多重共线性:temp 与 atemp 的 Pearson 相关系数高达 0.99

corr_matrix = train[['temp', 'atemp', 'humidity']].corr() print(corr_matrix)

输出显示tempatemp相关系数 > 0.99。保留两者会导致特征冗余,降低模型稳定性。正确做法是只保留atemp——体感温度已综合考虑湿度与风速,比单纯气温更贴近用户真实体感。删除temp后重新训练,MAE 变化 < 0.1,但特征重要性排序更清晰。

5. 用 Seaborn 绘制可交付的业务洞察图:小时需求热力图与天气影响箱线图

5.1 小时-星期热力图:直观定位运营黄金时段

import seaborn as sns # 按 hour 和 weekday 分组求均值 hour_weekday = train.groupby(['hour', 'weekday'])['count'].mean().unstack() plt.figure(figsize=(12, 6)) sns.heatmap(hour_weekday, annot=True, fmt='.0f', cmap='YlGnBu') plt.title('Average Hourly Bike Rentals by Weekday (Heatmap)') plt.xlabel('Weekday (0=Mon, 6=Sun)') plt.ylabel('Hour of Day') plt.show()

图中会清晰显示:

  • 工作日(0–4)早 8 点、晚 5–6 点双高峰;
  • 周末(5–6)午后 12–16 点单高峰;
  • 周日凌晨 2–4 点全网最低谷(< 10 辆/小时)。

此图可直接用于调度系统:在高峰前 30 分钟向地铁口站点预调车辆,低谷时回收闲置车辆。

5.2 天气类型对需求分布的影响:用箱线图揭示异常值与中位数偏移

plt.figure(figsize=(10, 6)) sns.boxplot(data=train, x='weather', y='count') plt.title('Distribution of Bike Rentals by Weather Condition') plt.xlabel('Weather (1=Sunny, 2=Cloudy, 3=Light Rain, 4=Heavy Rain)') plt.ylabel('Number of Rentals') plt.xticks([0,1,2,3], ['Sunny', 'Cloudy', 'Light Rain', 'Heavy Rain']) plt.show()

关键发现:

  • weather=1(晴)中位数最高(约 200),但存在大量 > 500 的异常高需求(大型活动);
  • weather=3(小雨)中位数骤降至 ~50,且离散度小(用户决策高度一致:不骑);
  • weather=4(大雨)样本极少(< 10 行),此时模型预测不可靠,应触发人工审核或返回默认值。

提示:在生产部署中,若weather输入为 4,不应直接调用模型,而应回退至“过去 7 天同小时均值”,这是比任何黑盒模型都稳健的 fallback 策略。

5.3 预测结果导出:生成 submission.csv 并验证格式

最终对测试集预测并保存:

y_test_pred = rf.predict(X_test_encoded) submission = pd.DataFrame({ 'datetime': test['datetime'], 'count': np.round(y_test_pred).astype(int) # Kaggle 要求整数 }) submission.to_csv('submission.csv', index=False) print("提交文件已生成,前5行:") print(submission.head())

检查submission.csv是否严格满足 Kaggle 要求:

  • 第一列必须是datetime(格式"2011-01-20 00:00:00");
  • 第二列必须是count(整数,无小数点);
  • 行数必须与test.csv完全一致(10,886 行)。

wc -l submission.csv验证行数,用head -n 5 submission.csv确认格式。任何偏差都会导致 Kaggle 提交失败并返回 cryptic error。

6. 一个被低估但极有效的技巧:用 residual 分析反向定位数据质量问题

6.1 计算残差并按时间绘制,暴露系统性偏差

残差residual = y_true - y_pred是模型误差的直接体现。将其按datetime绘图,可发现肉眼难察的数据缺陷:

train['residual'] = y_train - rf.predict(X_train_encoded) train_sorted = train.sort_values('datetime') plt.figure(figsize=(14, 5)) plt.plot(train_sorted['datetime'], train_sorted['residual'], alpha=0.6) plt.axhline(y=0, color='r', linestyle='--') plt.title('Residuals Over Time: Identifying Systematic Bias') plt.ylabel('Residual (True - Predicted)') plt.xlabel('Date & Time') plt.grid(True, alpha=0.3) plt.show()

若图中出现持续数天的负残差带(如 2012 年 7 月连续 5 天残差 < -100),说明模型系统性高估该时段需求。此时应检查:

  • 是否该时段有未标注的临时封路?
  • weather字段是否在那几天批量错误标记为“晴”(实际有雾)?
  • windspeed是否因传感器故障全为 0?

6.2 残差与关键特征的散点图:定位模型失效边界

plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.scatter(train['temp'], train['residual'], alpha=0.3) plt.xlabel('Temperature (°C)') plt.ylabel('Residual') plt.title('Residual vs Temperature') plt.axhline(y=0, color='r', linestyle='--') plt.subplot(1, 2, 2) plt.scatter(train['hour'], train['residual'], alpha=0.3) plt.xlabel('Hour of Day') plt.ylabel('Residual') plt.title('Residual vs Hour') plt.axhline(y=0, color='r', linestyle='--') plt.tight_layout() plt.show()

典型模式:

  • 温度图中,temp < 0℃区域残差普遍为负(模型低估严寒需求——可能因极端天气下仍有刚需通勤者);
  • 小时图中,hour=3(凌晨 3 点)残差方差极大(模型对深夜需求把握不准,因样本稀疏)。

这些发现直接指导下一步动作:对temp < 0样本单独建模,或为hour=3添加“是否为夜班公交接驳站”等业务特征。

6.3 将残差分析固化为自动化检查项

在项目说明文档(README.md)中,应明确列出残差检查步骤。以下 Bash 命令可集成到 CI 流程中,每次训练后自动运行:

# 检查残差绝对值 > 200 的样本占比(应 < 5%) python -c " import pandas as pd; df = pd.read_csv('train_with_residuals.csv'); print('High-residual ratio:', (abs(df['residual']) > 200).mean())" # 检查残差均值是否接近 0(应介于 -5 和 5 之间) python -c " import pandas as pd; df = pd.read_csv('train_with_residuals.csv'); print('Residual mean:', df['residual'].mean())"

High-residual ratio > 0.08Residual mean < -10,CI 流程应中断并告警——这往往意味着数据管道出错(如datetime解析失败导致时间错位),而非模型问题。把残差当作数据质量探针,比任何单元测试都更早捕获上游污染。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 11:25:33

PixWit:轻量高效的开发者截图录屏工具解析

1. PixWit工具定位与核心价值程序员在日常工作中经常需要处理各种截图、录屏需求&#xff1a;可能是记录Bug现象、制作技术演示、编写文档配图&#xff0c;或是与团队成员快速共享界面状态。传统做法需要同时打开多个工具——用Snipaste截图、OBS录屏、再用剪映简单剪辑&#x…

作者头像 李华
网站建设 2026/9/10 11:25:19

Python条件判断全解析:从基础语法到实战应用

1. 程序执行顺序的真相很多小朋友刚开始学编程时&#xff0c;都会有个天真的想法&#xff1a;计算机就像听话的小学生&#xff0c;会一行一行认真读代码。但现实情况要复杂得多。让我们用个生活例子来理解&#xff1a;想象你在玩一个"如果...就..."的闯关游戏&#x…

作者头像 李华
网站建设 2026/9/10 11:23:12

MarkItDown:免费文档转 Markdown 工具,3 行代码完成集成

MarkItDown&#xff1a;免费文档转 Markdown 工具&#xff0c;3 行代码完成集成 【免费下载链接】markitdown Python tool for converting files and office documents to Markdown. 项目地址: https://gitcode.com/GitHub_Trending/ma/markitdown MarkItDown 是一个免费…

作者头像 李华