feat: 完成共享单车课程设计
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
outputs/rerun.log
|
||||||
|
.DS_Store
|
||||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# 共享单车日骑行量预测与可视化系统
|
||||||
|
|
||||||
|
## 项目内容
|
||||||
|
|
||||||
|
本项目完成《大数据分析技术》课程设计中“基于机器学习的共享单车日骑行量预测与可视化系统”的全部任务:
|
||||||
|
|
||||||
|
1. 多模型构建与初步筛选
|
||||||
|
2. 特征重要性解析
|
||||||
|
3. 模型评估与 2013 年前 100 天预测输出
|
||||||
|
4. 轻量级 GUI 预测可视化系统
|
||||||
|
|
||||||
|
## 目录说明
|
||||||
|
|
||||||
|
- `analyze_bike_sharing.py`:完整数据分析、建模、评估、预测、图表生成脚本
|
||||||
|
- `bike_demand_gui.py`:tkinter 轻量级预测系统
|
||||||
|
- `generate_report.py`:课程设计报告生成脚本
|
||||||
|
- `outputs/model_comparison.csv`:模型对比结果
|
||||||
|
- `outputs/cross_validation_summary.csv`:交叉验证结果
|
||||||
|
- `outputs/feature_importance.csv`:特征重要性
|
||||||
|
- `outputs/2013前100天骑行量预测结果.xlsx`:最终预测结果
|
||||||
|
- `outputs/models/best_bike_demand_model.pkl`:最佳模型文件
|
||||||
|
- `outputs/figures/`:报告图表
|
||||||
|
- `outputs/共享单车日骑行量预测课程设计报告.docx`:课程设计报告
|
||||||
|
|
||||||
|
## 运行环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /root/bike_course_design
|
||||||
|
python3 -m venv .venv
|
||||||
|
.venv/bin/pip install pymupdf openpyxl pandas matplotlib seaborn scikit-learn python-docx joblib
|
||||||
|
```
|
||||||
|
|
||||||
|
## 重新生成分析结果
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /root/bike_course_design
|
||||||
|
.venv/bin/python analyze_bike_sharing.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 启动 GUI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /root/bike_course_design
|
||||||
|
.venv/bin/python bike_demand_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
GUI 支持输入季节、年月、节假日、星期、工作日、天气、气温、体感温度、湿度、风速等特征,输出单场景预测值,并可加入多场景对比表。
|
||||||
|
|
||||||
|
## 本次运行核心结论
|
||||||
|
|
||||||
|
- 数据量:731 条历史日度记录,预测集 100 条记录。
|
||||||
|
- 最佳模型:梯度提升树。
|
||||||
|
- 测试集指标:RMSE 约 651.14,MAE 约 449.00,R² 约 0.8943。
|
||||||
|
- 五折交叉验证:RMSE 均值约 655.12,R² 均值约 0.8819。
|
||||||
|
- 关键特征:气温、年份、季节、体感温度、湿度。
|
||||||
265
analyze_bike_sharing.py
Normal file
265
analyze_bike_sharing.py
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import joblib
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import seaborn as sns
|
||||||
|
from sklearn.compose import ColumnTransformer
|
||||||
|
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
|
||||||
|
from sklearn.inspection import permutation_importance
|
||||||
|
from sklearn.linear_model import LinearRegression
|
||||||
|
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
|
||||||
|
from sklearn.model_selection import KFold, cross_validate, train_test_split
|
||||||
|
from sklearn.neural_network import MLPRegressor
|
||||||
|
from sklearn.pipeline import Pipeline
|
||||||
|
from sklearn.preprocessing import OneHotEncoder, StandardScaler
|
||||||
|
from sklearn.svm import SVR
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
|
DATA_PATH = Path('/root/.hermes-web-ui/upload/default/28752a235eb6ef9d.xlsx')
|
||||||
|
OUT_DIR = BASE_DIR / 'outputs'
|
||||||
|
FIG_DIR = OUT_DIR / 'figures'
|
||||||
|
MODEL_DIR = OUT_DIR / 'models'
|
||||||
|
for directory in [OUT_DIR, FIG_DIR, MODEL_DIR]:
|
||||||
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'WenQuanYi Zen Hei', 'SimHei', 'DejaVu Sans']
|
||||||
|
plt.rcParams['axes.unicode_minus'] = False
|
||||||
|
sns.set_theme(style='whitegrid')
|
||||||
|
|
||||||
|
FEATURES = ['季节', '年', '月', '节假日', '星期', '工作日', '天气', '气温', '体感温度', '湿度', '风速']
|
||||||
|
CATEGORICAL_FEATURES = ['季节', '年', '月', '节假日', '星期', '工作日', '天气']
|
||||||
|
NUMERIC_FEATURES = ['气温', '体感温度', '湿度', '风速']
|
||||||
|
TARGET = '总骑行量'
|
||||||
|
|
||||||
|
|
||||||
|
def rmse(y_true, y_pred):
|
||||||
|
return float(np.sqrt(mean_squared_error(y_true, y_pred)))
|
||||||
|
|
||||||
|
|
||||||
|
def evaluate_model(model, x_train, x_test, y_train, y_test):
|
||||||
|
model.fit(x_train, y_train)
|
||||||
|
pred = model.predict(x_test)
|
||||||
|
return {
|
||||||
|
'RMSE': rmse(y_test, pred),
|
||||||
|
'MAE': float(mean_absolute_error(y_test, pred)),
|
||||||
|
'R2': float(r2_score(y_test, pred)),
|
||||||
|
}, pred
|
||||||
|
|
||||||
|
|
||||||
|
def make_preprocessor(scale_numeric=False):
|
||||||
|
numeric_transformer = StandardScaler() if scale_numeric else 'passthrough'
|
||||||
|
return ColumnTransformer(
|
||||||
|
transformers=[
|
||||||
|
('cat', OneHotEncoder(handle_unknown='ignore'), CATEGORICAL_FEATURES),
|
||||||
|
('num', numeric_transformer, NUMERIC_FEATURES),
|
||||||
|
],
|
||||||
|
remainder='drop',
|
||||||
|
verbose_feature_names_out=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_models():
|
||||||
|
return {
|
||||||
|
'线性回归': Pipeline([
|
||||||
|
('preprocess', make_preprocessor(scale_numeric=True)),
|
||||||
|
('model', LinearRegression()),
|
||||||
|
]),
|
||||||
|
'随机森林': Pipeline([
|
||||||
|
('preprocess', make_preprocessor()),
|
||||||
|
('model', RandomForestRegressor(n_estimators=500, random_state=42, min_samples_leaf=2, n_jobs=-1)),
|
||||||
|
]),
|
||||||
|
'梯度提升树': Pipeline([
|
||||||
|
('preprocess', make_preprocessor()),
|
||||||
|
('model', GradientBoostingRegressor(random_state=42, n_estimators=300, learning_rate=0.05, max_depth=3)),
|
||||||
|
]),
|
||||||
|
'支持向量回归': Pipeline([
|
||||||
|
('preprocess', make_preprocessor(scale_numeric=True)),
|
||||||
|
('model', SVR(C=200, gamma='scale', epsilon=50)),
|
||||||
|
]),
|
||||||
|
'神经网络': Pipeline([
|
||||||
|
('preprocess', make_preprocessor(scale_numeric=True)),
|
||||||
|
('model', MLPRegressor(hidden_layer_sizes=(80, 40), activation='relu', alpha=0.001, max_iter=3000, random_state=42, early_stopping=True)),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_data():
|
||||||
|
daily = pd.read_excel(DATA_PATH, sheet_name='每天数据')
|
||||||
|
future = pd.read_excel(DATA_PATH, sheet_name='预测集')
|
||||||
|
daily['日期'] = pd.to_datetime(daily['日期'])
|
||||||
|
future['日期'] = pd.to_datetime(future['日期'])
|
||||||
|
return daily, future
|
||||||
|
|
||||||
|
|
||||||
|
def feature_importance(best_model, x_test, y_test):
|
||||||
|
model_name = type(best_model.named_steps['model']).__name__
|
||||||
|
if hasattr(best_model.named_steps['model'], 'feature_importances_'):
|
||||||
|
transformed_names = best_model.named_steps['preprocess'].get_feature_names_out()
|
||||||
|
raw_importances = pd.Series(best_model.named_steps['model'].feature_importances_, index=transformed_names)
|
||||||
|
collapsed = {}
|
||||||
|
for feature in FEATURES:
|
||||||
|
collapsed[feature] = raw_importances[[idx == feature or idx.startswith(feature + '_') for idx in raw_importances.index]].sum()
|
||||||
|
importance = pd.Series(collapsed).sort_values(ascending=False)
|
||||||
|
method = f'{model_name} 内置重要性'
|
||||||
|
else:
|
||||||
|
result = permutation_importance(best_model, x_test, y_test, n_repeats=30, random_state=42, scoring='neg_root_mean_squared_error', n_jobs=-1)
|
||||||
|
importance = pd.Series(result.importances_mean, index=FEATURES).sort_values(ascending=False)
|
||||||
|
method = '排列重要性'
|
||||||
|
return importance, method
|
||||||
|
|
||||||
|
|
||||||
|
def save_figures(daily, y_test, y_pred, residuals, importance, future_pred_df):
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
sns.lineplot(data=daily, x='日期', y=TARGET, color='#2563eb')
|
||||||
|
plt.title('2011-2012 年共享单车每日总骑行量趋势')
|
||||||
|
plt.xlabel('日期')
|
||||||
|
plt.ylabel('总骑行量')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(FIG_DIR / 'historical_trend.png', dpi=180)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
plt.figure(figsize=(8, 6))
|
||||||
|
sns.scatterplot(x=y_test, y=y_pred, color='#16a34a', edgecolor='white')
|
||||||
|
low, high = min(y_test.min(), y_pred.min()), max(y_test.max(), y_pred.max())
|
||||||
|
plt.plot([low, high], [low, high], '--', color='#ef4444', linewidth=1.5)
|
||||||
|
plt.title('测试集真实值-预测值散点图')
|
||||||
|
plt.xlabel('真实总骑行量')
|
||||||
|
plt.ylabel('预测总骑行量')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(FIG_DIR / 'actual_vs_predicted.png', dpi=180)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
plt.figure(figsize=(8, 5))
|
||||||
|
sns.histplot(residuals, kde=True, color='#f97316')
|
||||||
|
plt.axvline(0, color='#111827', linestyle='--', linewidth=1)
|
||||||
|
plt.title('测试集残差分布')
|
||||||
|
plt.xlabel('残差(真实值 - 预测值)')
|
||||||
|
plt.ylabel('频数')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(FIG_DIR / 'residual_distribution.png', dpi=180)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
plt.figure(figsize=(9, 6))
|
||||||
|
plot_imp = importance.sort_values(ascending=True)
|
||||||
|
plt.barh(plot_imp.index, plot_imp.values, color='#7c3aed')
|
||||||
|
plt.title('特征重要性排序')
|
||||||
|
plt.xlabel('重要性')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(FIG_DIR / 'feature_importance.png', dpi=180)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
plt.figure(figsize=(12, 6))
|
||||||
|
sns.lineplot(data=future_pred_df, x='日期', y='预测总骑行量', marker='o', color='#0891b2')
|
||||||
|
plt.title('2013 年前 100 天共享单车骑行量预测趋势')
|
||||||
|
plt.xlabel('日期')
|
||||||
|
plt.ylabel('预测总骑行量')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(FIG_DIR / 'future_prediction_trend.png', dpi=180)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
daily, future = load_data()
|
||||||
|
missing = daily[FEATURES + [TARGET]].isna().sum().to_dict()
|
||||||
|
duplicates = int(daily.duplicated(subset=['日期']).sum())
|
||||||
|
|
||||||
|
x = daily[FEATURES]
|
||||||
|
y = daily[TARGET]
|
||||||
|
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)
|
||||||
|
|
||||||
|
models = make_models()
|
||||||
|
initial_rows = []
|
||||||
|
predictions = {}
|
||||||
|
for name, model in models.items():
|
||||||
|
metrics, pred = evaluate_model(model, x_train, x_test, y_train, y_test)
|
||||||
|
initial_rows.append({'模型': name, **metrics})
|
||||||
|
predictions[name] = pred
|
||||||
|
joblib.dump(model, MODEL_DIR / f'{name}.pkl')
|
||||||
|
|
||||||
|
initial_df = pd.DataFrame(initial_rows).sort_values('RMSE')
|
||||||
|
best_name = initial_df.iloc[0]['模型']
|
||||||
|
best_model = models[best_name]
|
||||||
|
best_pred = predictions[best_name]
|
||||||
|
residuals = y_test.to_numpy() - best_pred
|
||||||
|
|
||||||
|
cv = KFold(n_splits=5, shuffle=True, random_state=42)
|
||||||
|
cv_result = cross_validate(
|
||||||
|
best_model,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
cv=cv,
|
||||||
|
scoring={'rmse': 'neg_root_mean_squared_error', 'mae': 'neg_mean_absolute_error', 'r2': 'r2'},
|
||||||
|
n_jobs=-1,
|
||||||
|
)
|
||||||
|
cv_summary = {
|
||||||
|
'模型': best_name,
|
||||||
|
'RMSE均值': float(-cv_result['test_rmse'].mean()),
|
||||||
|
'RMSE标准差': float(cv_result['test_rmse'].std()),
|
||||||
|
'MAE均值': float(-cv_result['test_mae'].mean()),
|
||||||
|
'MAE标准差': float(cv_result['test_mae'].std()),
|
||||||
|
'R2均值': float(cv_result['test_r2'].mean()),
|
||||||
|
'R2标准差': float(cv_result['test_r2'].std()),
|
||||||
|
}
|
||||||
|
|
||||||
|
importance, importance_method = feature_importance(best_model, x_test, y_test)
|
||||||
|
|
||||||
|
future_pred = best_model.predict(future[FEATURES])
|
||||||
|
future_pred_df = future.copy()
|
||||||
|
future_pred_df['预测总骑行量'] = np.maximum(np.round(future_pred), 0).astype(int)
|
||||||
|
|
||||||
|
save_figures(daily, y_test, best_pred, residuals, importance, future_pred_df)
|
||||||
|
|
||||||
|
initial_df.to_csv(OUT_DIR / 'model_comparison.csv', index=False, encoding='utf-8-sig')
|
||||||
|
pd.DataFrame([cv_summary]).to_csv(OUT_DIR / 'cross_validation_summary.csv', index=False, encoding='utf-8-sig')
|
||||||
|
importance.rename('重要性').to_csv(OUT_DIR / 'feature_importance.csv', encoding='utf-8-sig')
|
||||||
|
future_pred_df.to_excel(OUT_DIR / '2013前100天骑行量预测结果.xlsx', index=False)
|
||||||
|
|
||||||
|
test_pred_df = pd.DataFrame({
|
||||||
|
'日期': daily.loc[x_test.index, '日期'].values,
|
||||||
|
'真实总骑行量': y_test.values,
|
||||||
|
'预测总骑行量': np.round(best_pred).astype(int),
|
||||||
|
'残差': np.round(residuals, 2),
|
||||||
|
}).sort_values('日期')
|
||||||
|
test_pred_df.to_csv(OUT_DIR / 'test_set_predictions.csv', index=False, encoding='utf-8-sig')
|
||||||
|
|
||||||
|
joblib.dump(best_model, MODEL_DIR / 'best_bike_demand_model.pkl')
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
'data': {
|
||||||
|
'rows': int(len(daily)),
|
||||||
|
'columns': int(daily.shape[1]),
|
||||||
|
'future_rows': int(len(future)),
|
||||||
|
'missing': missing,
|
||||||
|
'duplicate_dates': duplicates,
|
||||||
|
'target_min': int(y.min()),
|
||||||
|
'target_max': int(y.max()),
|
||||||
|
'target_mean': float(y.mean()),
|
||||||
|
},
|
||||||
|
'initial_comparison': initial_df.to_dict(orient='records'),
|
||||||
|
'best_model': best_name,
|
||||||
|
'best_test_metrics': initial_df.iloc[0].to_dict(),
|
||||||
|
'cross_validation': cv_summary,
|
||||||
|
'importance_method': importance_method,
|
||||||
|
'top_features': importance.head(8).to_dict(),
|
||||||
|
'future_prediction': {
|
||||||
|
'min': int(future_pred_df['预测总骑行量'].min()),
|
||||||
|
'max': int(future_pred_df['预测总骑行量'].max()),
|
||||||
|
'mean': float(future_pred_df['预测总骑行量'].mean()),
|
||||||
|
},
|
||||||
|
'outputs': {
|
||||||
|
'figures': [str(p) for p in sorted(FIG_DIR.glob('*.png'))],
|
||||||
|
'best_model': str(MODEL_DIR / 'best_bike_demand_model.pkl'),
|
||||||
|
'prediction_excel': str(OUT_DIR / '2013前100天骑行量预测结果.xlsx'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(OUT_DIR / 'analysis_summary.json').write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||||
|
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
129
bike_demand_gui.py
Normal file
129
bike_demand_gui.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, messagebox
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import joblib
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
|
MODEL_PATH = BASE_DIR / 'outputs' / 'models' / 'best_bike_demand_model.pkl'
|
||||||
|
FEATURES = ['季节', '年', '月', '节假日', '星期', '工作日', '天气', '气温', '体感温度', '湿度', '风速']
|
||||||
|
|
||||||
|
|
||||||
|
class BikeDemandApp:
|
||||||
|
def __init__(self, root):
|
||||||
|
self.root = root
|
||||||
|
self.root.title('共享单车日骑行量预测系统')
|
||||||
|
self.root.geometry('820x560')
|
||||||
|
self.model = joblib.load(MODEL_PATH)
|
||||||
|
self.entries = {}
|
||||||
|
self.history = []
|
||||||
|
self.build_ui()
|
||||||
|
|
||||||
|
def build_ui(self):
|
||||||
|
container = ttk.Frame(self.root, padding=16)
|
||||||
|
container.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
left = ttk.LabelFrame(container, text='输入特征', padding=12)
|
||||||
|
left.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 12))
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
('季节', '1=春 2=夏 3=秋 4=冬', '1'),
|
||||||
|
('年', '0=2011 1=2012 2=2013', '2'),
|
||||||
|
('月', '1~12', '1'),
|
||||||
|
('节假日', '0=否 1=是', '0'),
|
||||||
|
('星期', '0=周日 ... 6=周六', '1'),
|
||||||
|
('工作日', '0=否 1=是', '1'),
|
||||||
|
('天气', '1=晴好 2=多云 3=小雨雪 4=恶劣', '1'),
|
||||||
|
('气温', '归一化 0~1', '0.32'),
|
||||||
|
('体感温度', '归一化 0~1', '0.34'),
|
||||||
|
('湿度', '归一化 0~1', '0.70'),
|
||||||
|
('风速', '归一化 0~1', '0.15'),
|
||||||
|
]
|
||||||
|
for row, (name, hint, default) in enumerate(fields):
|
||||||
|
ttk.Label(left, text=name).grid(row=row, column=0, sticky='w', pady=4)
|
||||||
|
entry = ttk.Entry(left, width=14)
|
||||||
|
entry.insert(0, default)
|
||||||
|
entry.grid(row=row, column=1, sticky='ew', pady=4)
|
||||||
|
ttk.Label(left, text=hint, foreground='#666').grid(row=row, column=2, sticky='w', padx=6)
|
||||||
|
self.entries[name] = entry
|
||||||
|
|
||||||
|
ttk.Button(left, text='预测单场景', command=self.predict_one).grid(row=len(fields), column=0, columnspan=3, sticky='ew', pady=(12, 4))
|
||||||
|
ttk.Button(left, text='加入对比列表', command=self.add_to_history).grid(row=len(fields) + 1, column=0, columnspan=3, sticky='ew', pady=4)
|
||||||
|
ttk.Button(left, text='清空对比列表', command=self.clear_history).grid(row=len(fields) + 2, column=0, columnspan=3, sticky='ew', pady=4)
|
||||||
|
|
||||||
|
right = ttk.Frame(container)
|
||||||
|
right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
result_box = ttk.LabelFrame(right, text='预测结果', padding=16)
|
||||||
|
result_box.pack(fill=tk.X)
|
||||||
|
self.result_var = tk.StringVar(value='请输入特征后点击预测')
|
||||||
|
ttk.Label(result_box, textvariable=self.result_var, font=('Arial', 22, 'bold'), foreground='#0f766e').pack(anchor='w')
|
||||||
|
self.level_var = tk.StringVar(value='')
|
||||||
|
ttk.Label(result_box, textvariable=self.level_var, font=('Arial', 12)).pack(anchor='w', pady=(8, 0))
|
||||||
|
|
||||||
|
table_box = ttk.LabelFrame(right, text='多场景对比', padding=8)
|
||||||
|
table_box.pack(fill=tk.BOTH, expand=True, pady=(12, 0))
|
||||||
|
columns = ('序号', '月', '天气', '气温', '工作日', '预测总骑行量')
|
||||||
|
self.tree = ttk.Treeview(table_box, columns=columns, show='headings', height=12)
|
||||||
|
for col in columns:
|
||||||
|
self.tree.heading(col, text=col)
|
||||||
|
self.tree.column(col, width=90, anchor='center')
|
||||||
|
self.tree.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
def collect_features(self):
|
||||||
|
values = {}
|
||||||
|
for feature, entry in self.entries.items():
|
||||||
|
try:
|
||||||
|
values[feature] = float(entry.get())
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(f'{feature} 必须是数字') from exc
|
||||||
|
for feature in ['季节', '年', '月', '节假日', '星期', '工作日', '天气']:
|
||||||
|
values[feature] = int(values[feature])
|
||||||
|
for feature in ['气温', '体感温度', '湿度', '风速']:
|
||||||
|
if not 0 <= values[feature] <= 1:
|
||||||
|
raise ValueError(f'{feature} 应在 0~1 之间')
|
||||||
|
return values
|
||||||
|
|
||||||
|
def predict_value(self):
|
||||||
|
values = self.collect_features()
|
||||||
|
frame = pd.DataFrame([values], columns=FEATURES)
|
||||||
|
prediction = int(max(round(self.model.predict(frame)[0]), 0))
|
||||||
|
return values, prediction
|
||||||
|
|
||||||
|
def predict_one(self):
|
||||||
|
try:
|
||||||
|
_, prediction = self.predict_value()
|
||||||
|
except Exception as exc:
|
||||||
|
messagebox.showerror('输入错误', str(exc))
|
||||||
|
return
|
||||||
|
self.result_var.set(f'预计日骑行量:{prediction:,} 次')
|
||||||
|
if prediction >= 5000:
|
||||||
|
level = '需求等级:高,需要提前调度车辆。'
|
||||||
|
elif prediction >= 3000:
|
||||||
|
level = '需求等级:中,维持常规运维。'
|
||||||
|
else:
|
||||||
|
level = '需求等级:低,可减少热点补车频率。'
|
||||||
|
self.level_var.set(level)
|
||||||
|
|
||||||
|
def add_to_history(self):
|
||||||
|
try:
|
||||||
|
values, prediction = self.predict_value()
|
||||||
|
except Exception as exc:
|
||||||
|
messagebox.showerror('输入错误', str(exc))
|
||||||
|
return
|
||||||
|
self.history.append((values, prediction))
|
||||||
|
idx = len(self.history)
|
||||||
|
self.tree.insert('', 'end', values=(idx, values['月'], values['天气'], values['气温'], values['工作日'], prediction))
|
||||||
|
self.predict_one()
|
||||||
|
|
||||||
|
def clear_history(self):
|
||||||
|
self.history.clear()
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
self.tree.delete(item)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
root = tk.Tk()
|
||||||
|
app = BikeDemandApp(root)
|
||||||
|
root.mainloop()
|
||||||
64
course_design_requirements.txt
Normal file
64
course_design_requirements.txt
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
6
|
||||||
|
三、基于机器学习的共享单车日骑行量预测与可视化系统
|
||||||
|
1. 课题背景
|
||||||
|
在构建绿色、低碳城市的进程中,“最后一公里”出行难题一直是阻碍公共交通
|
||||||
|
高效利用的关键瓶颈。共享单车作为一种创新的分时租赁模式,有效填补了这一空
|
||||||
|
白,不仅提升了居民使用公共交通的整体意愿,还促进了绿色出行理念的普及。然
|
||||||
|
而,共享单车的运营效率高度依赖于对用户需求的精准预判。骑行需求受多种复杂
|
||||||
|
因素影响,如天气状况、季节变化、是否为工作日或节假日等。因此,利用历史运
|
||||||
|
营数据,通过数据挖掘与机器学习技术建立精准的需求预测模型,对于优化车辆调
|
||||||
|
度、提升用户体验及实现企业精细化运营具有重要的现实意义。
|
||||||
|
2. 数据描述
|
||||||
|
本课题采用的数据集源自美国华盛顿特区的Capital Bikeshare 系统,是UCI 机器
|
||||||
|
学习库中的经典数据集(Bike Sharing Dataset)。我们选用其中的日度统计数据,见“共
|
||||||
|
享单车骑行量数据.xlsx”的第一张工作表“每天数据”,数据集共包含731 条记录,覆
|
||||||
|
盖了2011 年与2012 年两年完整的骑行数据,共16 列,各列含义见第二张工作表“特
|
||||||
|
征说明”。
|
||||||
|
此外,数据集还提供了待预测的2013 年前100 天的相关特征,见第三张工作表
|
||||||
|
“预测集”,用于最终模型的骑行量预测。
|
||||||
|
3. 设计任务
|
||||||
|
本课题围绕共享单车每日总骑行量预测展开,要求完成“模型构建与对比→特征
|
||||||
|
影响剖析→性能评估与预测→可视化系统开发”的完整技术研究流程,全面锻炼数据
|
||||||
|
建模、特征分析、模型优化及GUI 开发的综合能力。
|
||||||
|
任务一:多模型构建与初步筛选
|
||||||
|
目标:建立包含基线模型和候选模型的完整模型池,为后续分析提供严谨的性
|
||||||
|
能基准。
|
||||||
|
核心步骤:
|
||||||
|
(1)数据准备:从数据集第一张工作表中提取特征(12 维)与目标变量,并将
|
||||||
|
数据集划分为训练集和测试集;
|
||||||
|
(2)模型选型:
|
||||||
|
基线模型:选择线性回归模型作为基准;
|
||||||
|
候选模型:至少选择两类非线性回归模型(如树模型+ 核方法/神经网络),体
|
||||||
|
现方法多样性;
|
||||||
|
(3)基础训练:对每类模型使用默认或合理初始参数进行训练;
|
||||||
|
(4)对比:在验证集上计算各模型的RMSE/MAE/R²,形成初步性能排序;
|
||||||
|
(5)输出:保存各模型对象及初步评估结果,为任务三的深入评估和任务二的
|
||||||
|
特征分析提供候选模型池。
|
||||||
|
|
||||||
|
7
|
||||||
|
任务二:特征重要性解析
|
||||||
|
目标:挖掘影响骑行量的关键因子,解释模型决策逻辑。
|
||||||
|
核心步骤:
|
||||||
|
(1)选定最优模型:基于任务一结果,选取性能最佳模型;
|
||||||
|
(2)量化与可视化:通过内置属性(树模型)或排列重要性(其他模型)计算
|
||||||
|
特征贡献,绘制条形图;
|
||||||
|
(3)业务解读:结合领域知识分析关键特征的影响机制;
|
||||||
|
(4)输出:特征重要性报告,为模型精简提供依据。
|
||||||
|
任务三:模型评估与预测输出
|
||||||
|
目标:验证模型泛化能力,生成最终预测结果。
|
||||||
|
核心步骤:
|
||||||
|
(1)全面评估:通过交叉验证计算RMSE/MAE/R²,绘制真实值-预测值散点图
|
||||||
|
及残差分布;
|
||||||
|
(2)确定最优模型:综合指标与稳定性,选定部署模型;
|
||||||
|
(3)预测与可视化:基于2013 年前100 天特征数据生成预测值,输出表格及
|
||||||
|
趋势折线图;
|
||||||
|
(4)输出:保存模型文件(如.pkl)和预测结果,作为GUI 系统的数据源。
|
||||||
|
任务四:轻量级预测可视化系统开发
|
||||||
|
目标:实现分析成果的交互式应用。
|
||||||
|
核心步骤:
|
||||||
|
(1)框架选型:采用轻量GUI 工具(如Streamlit、tkinter、PyQT);
|
||||||
|
(2)界面设计:左侧输入区(日期、天气、温度等)+右侧结果展示区;
|
||||||
|
(3)模型集成:加载最优模型,编写特征处理与预测函数;
|
||||||
|
(4)动态可视化:让用户直观理解预测结果,同时支持单场景查询(数字+仪
|
||||||
|
表盘)和多场景对比(折线图)两种核心需求;
|
||||||
|
(5)优化与输出:实现输入校验与实时响应,交付可运行程序及使用说明。
|
||||||
124
generate_report.py
Normal file
124
generate_report.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from docx import Document
|
||||||
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
|
from docx.shared import Inches, Pt
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
|
OUT_DIR = BASE_DIR / 'outputs'
|
||||||
|
FIG_DIR = OUT_DIR / 'figures'
|
||||||
|
REPORT_PATH = OUT_DIR / '共享单车日骑行量预测课程设计报告.docx'
|
||||||
|
|
||||||
|
|
||||||
|
def add_heading(document, text, level):
|
||||||
|
heading = document.add_heading(text, level=level)
|
||||||
|
for run in heading.runs:
|
||||||
|
run.font.name = '宋体'
|
||||||
|
return heading
|
||||||
|
|
||||||
|
|
||||||
|
def add_paragraph(document, text):
|
||||||
|
paragraph = document.add_paragraph(text)
|
||||||
|
paragraph.paragraph_format.first_line_indent = Pt(24)
|
||||||
|
paragraph.paragraph_format.line_spacing = 1.5
|
||||||
|
for run in paragraph.runs:
|
||||||
|
run.font.name = '宋体'
|
||||||
|
run.font.size = Pt(11)
|
||||||
|
return paragraph
|
||||||
|
|
||||||
|
|
||||||
|
def add_table_from_df(document, df, title=None):
|
||||||
|
if title:
|
||||||
|
add_paragraph(document, title)
|
||||||
|
table = document.add_table(rows=1, cols=len(df.columns))
|
||||||
|
table.style = 'Table Grid'
|
||||||
|
hdr = table.rows[0].cells
|
||||||
|
for i, col in enumerate(df.columns):
|
||||||
|
hdr[i].text = str(col)
|
||||||
|
for _, row in df.iterrows():
|
||||||
|
cells = table.add_row().cells
|
||||||
|
for i, value in enumerate(row):
|
||||||
|
if isinstance(value, float):
|
||||||
|
cells[i].text = f'{value:.4f}'
|
||||||
|
else:
|
||||||
|
cells[i].text = str(value)
|
||||||
|
document.add_paragraph('')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
summary = json.loads((OUT_DIR / 'analysis_summary.json').read_text(encoding='utf-8'))
|
||||||
|
comparison = pd.read_csv(OUT_DIR / 'model_comparison.csv')
|
||||||
|
cv = pd.read_csv(OUT_DIR / 'cross_validation_summary.csv')
|
||||||
|
importance = pd.read_csv(OUT_DIR / 'feature_importance.csv')
|
||||||
|
future = pd.read_excel(OUT_DIR / '2013前100天骑行量预测结果.xlsx')
|
||||||
|
|
||||||
|
document = Document()
|
||||||
|
section = document.sections[0]
|
||||||
|
section.top_margin = Inches(0.8)
|
||||||
|
section.bottom_margin = Inches(0.8)
|
||||||
|
section.left_margin = Inches(0.9)
|
||||||
|
section.right_margin = Inches(0.9)
|
||||||
|
|
||||||
|
title = document.add_heading('基于机器学习的共享单车日骑行量预测与可视化系统', 0)
|
||||||
|
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
subtitle = document.add_paragraph('《大数据分析技术》课程设计报告')
|
||||||
|
subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
document.add_paragraph('')
|
||||||
|
|
||||||
|
add_heading(document, '一、课题背景与研究目标', 1)
|
||||||
|
add_paragraph(document, '共享单车是解决城市公共交通“最后一公里”问题的重要方式。其运营效率依赖于车辆供需匹配能力,而日骑行量会受到季节、天气、温度、湿度、风速、工作日等多因素共同影响。本课程设计以 Capital Bikeshare 日度数据为基础,构建机器学习模型预测共享单车每日总骑行量,并形成可视化分析结果与轻量级预测系统。')
|
||||||
|
add_paragraph(document, '本项目围绕“模型构建与对比、特征影响剖析、性能评估与预测、GUI 系统开发”四项任务展开,最终输出模型文件、评估结果、预测表格、图表和可运行程序。')
|
||||||
|
|
||||||
|
add_heading(document, '二、数据说明与预处理', 1)
|
||||||
|
data = summary['data']
|
||||||
|
add_paragraph(document, f'历史数据来自 Excel 第一张工作表“每天数据”,共 {data["rows"]} 条记录、{data["columns"]} 列,覆盖 2011—2012 年完整日度骑行数据;预测集来自第三张工作表“预测集”,包含 2013 年前 {data["future_rows"]} 天的待预测特征。')
|
||||||
|
add_paragraph(document, f'目标变量为“总骑行量”,历史样本最小值为 {data["target_min"]},最大值为 {data["target_max"]},均值为 {data["target_mean"]:.2f}。经检查,建模字段不存在缺失值,日期字段无重复记录。')
|
||||||
|
add_paragraph(document, '建模特征选取季节、年、月、节假日、星期、工作日、天气、气温、体感温度、湿度、风速等 11 个字段。类别型特征采用 One-Hot 编码,数值型特征在对线性回归、SVR 和神经网络建模时进行标准化处理。')
|
||||||
|
document.add_picture(str(FIG_DIR / 'historical_trend.png'), width=Inches(6.2))
|
||||||
|
|
||||||
|
add_heading(document, '三、多模型构建与初步筛选', 1)
|
||||||
|
add_paragraph(document, '按照课程设计要求,模型池包含一个基线模型和多类非线性候选模型。基线模型采用线性回归;候选模型包括随机森林、梯度提升树、支持向量回归和神经网络,覆盖树模型、核方法和神经网络方法。数据按 8:2 划分训练集和测试集,使用 RMSE、MAE、R² 进行比较。')
|
||||||
|
add_table_from_df(document, comparison, '表 1 模型初步对比结果')
|
||||||
|
best = summary['best_test_metrics']
|
||||||
|
add_paragraph(document, f'从测试集结果看,{summary["best_model"]} 的综合表现最佳,RMSE={best["RMSE"]:.2f},MAE={best["MAE"]:.2f},R²={best["R2"]:.4f}。因此后续特征解释、交叉验证与 2013 年预测均以该模型作为最终模型。')
|
||||||
|
|
||||||
|
add_heading(document, '四、特征重要性解析', 1)
|
||||||
|
add_paragraph(document, f'由于最优模型为梯度提升树,项目采用模型内置特征重要性计算特征贡献,并将 One-Hot 后的同源类别特征聚合回原始字段。特征重要性结果显示,气温、年、季节、体感温度、湿度对骑行量影响最大。')
|
||||||
|
document.add_picture(str(FIG_DIR / 'feature_importance.png'), width=Inches(6.2))
|
||||||
|
add_table_from_df(document, importance.head(8), '表 2 前 8 个重要特征')
|
||||||
|
add_paragraph(document, '业务上,气温和体感温度直接影响用户骑行意愿,舒适天气下出行需求更高;年份特征反映共享单车系统规模和用户习惯随时间增长;季节与月份体现周期性需求变化;湿度、风速和天气体现不良气象条件对骑行行为的抑制作用。')
|
||||||
|
|
||||||
|
add_heading(document, '五、模型全面评估', 1)
|
||||||
|
add_paragraph(document, '为了验证模型泛化能力,对最佳模型进行五折交叉验证,并绘制测试集真实值-预测值散点图和残差分布图。')
|
||||||
|
add_table_from_df(document, cv, '表 3 五折交叉验证结果')
|
||||||
|
cv_row = summary['cross_validation']
|
||||||
|
add_paragraph(document, f'交叉验证 RMSE 均值为 {cv_row["RMSE均值"]:.2f},标准差为 {cv_row["RMSE标准差"]:.2f};R² 均值为 {cv_row["R2均值"]:.4f},说明模型在不同数据划分下较稳定。')
|
||||||
|
document.add_picture(str(FIG_DIR / 'actual_vs_predicted.png'), width=Inches(5.8))
|
||||||
|
document.add_picture(str(FIG_DIR / 'residual_distribution.png'), width=Inches(5.8))
|
||||||
|
add_paragraph(document, '真实值-预测值散点大体分布在对角线附近,说明模型能较好拟合总体变化趋势。残差分布集中在 0 附近,表明模型不存在明显系统性偏差,但在极端低需求或高需求日期仍可能出现较大误差。')
|
||||||
|
|
||||||
|
add_heading(document, '六、2013 年前 100 天预测结果', 1)
|
||||||
|
fp = summary['future_prediction']
|
||||||
|
add_paragraph(document, f'使用最终梯度提升树模型对预测集进行推断,2013 年前 100 天预测骑行量最小值为 {fp["min"]},最大值为 {fp["max"]},均值为 {fp["mean"]:.2f}。完整结果已保存为 Excel 文件“2013前100天骑行量预测结果.xlsx”。')
|
||||||
|
document.add_picture(str(FIG_DIR / 'future_prediction_trend.png'), width=Inches(6.2))
|
||||||
|
sample_future = future[['日期', '季节', '月', '天气', '气温', '湿度', '预测总骑行量']].head(10).copy()
|
||||||
|
sample_future['日期'] = sample_future['日期'].dt.strftime('%Y-%m-%d')
|
||||||
|
add_table_from_df(document, sample_future, '表 4 预测结果样例(前 10 天)')
|
||||||
|
|
||||||
|
add_heading(document, '七、轻量级预测可视化系统', 1)
|
||||||
|
add_paragraph(document, '系统采用 Python tkinter 开发,程序文件为 bike_demand_gui.py。界面左侧提供季节、年份、月份、节假日、星期、工作日、天气、气温、体感温度、湿度、风速等输入项,右侧展示单场景预测结果与多场景对比表。')
|
||||||
|
add_paragraph(document, '系统启动时加载 outputs/models/best_bike_demand_model.pkl,并对用户输入进行数字类型和归一化范围校验。用户可点击“预测单场景”获得预计日骑行量,也可将多组输入加入对比列表,用于比较不同天气或温度条件下的需求变化。')
|
||||||
|
add_paragraph(document, '运行方式:在项目目录执行 `.venv/bin/python bike_demand_gui.py`。')
|
||||||
|
|
||||||
|
add_heading(document, '八、结论', 1)
|
||||||
|
add_paragraph(document, '本课程设计完成了从数据读取、预处理、模型训练、模型比较、特征解释、交叉验证、未来预测到 GUI 系统开发的完整流程。实验结果表明,梯度提升树在该共享单车日骑行量预测任务中优于线性回归、随机森林、SVR 和神经网络,能够较好刻画天气、季节和时间因素与骑行需求之间的非线性关系。')
|
||||||
|
add_paragraph(document, '后续可进一步引入节假日类型、降水量、重大活动、站点级空间信息等外部变量,并采用时间序列交叉验证或集成学习优化模型,以提升极端天气和异常日期下的预测鲁棒性。')
|
||||||
|
|
||||||
|
document.save(REPORT_PATH)
|
||||||
|
print(REPORT_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
BIN
outputs/2013前100天骑行量预测结果.xlsx
Normal file
BIN
outputs/2013前100天骑行量预测结果.xlsx
Normal file
Binary file not shown.
100
outputs/analysis_summary.json
Normal file
100
outputs/analysis_summary.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"rows": 731,
|
||||||
|
"columns": 16,
|
||||||
|
"future_rows": 100,
|
||||||
|
"missing": {
|
||||||
|
"季节": 0,
|
||||||
|
"年": 0,
|
||||||
|
"月": 0,
|
||||||
|
"节假日": 0,
|
||||||
|
"星期": 0,
|
||||||
|
"工作日": 0,
|
||||||
|
"天气": 0,
|
||||||
|
"气温": 0,
|
||||||
|
"体感温度": 0,
|
||||||
|
"湿度": 0,
|
||||||
|
"风速": 0,
|
||||||
|
"总骑行量": 0
|
||||||
|
},
|
||||||
|
"duplicate_dates": 0,
|
||||||
|
"target_min": 22,
|
||||||
|
"target_max": 8714,
|
||||||
|
"target_mean": 4504.3488372093025
|
||||||
|
},
|
||||||
|
"initial_comparison": [
|
||||||
|
{
|
||||||
|
"模型": "梯度提升树",
|
||||||
|
"RMSE": 651.1440567037434,
|
||||||
|
"MAE": 448.99523765137667,
|
||||||
|
"R2": 0.8942640825672749
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"模型": "随机森林",
|
||||||
|
"RMSE": 703.9966504917254,
|
||||||
|
"MAE": 450.0664823777129,
|
||||||
|
"R2": 0.8764025354772573
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"模型": "支持向量回归",
|
||||||
|
"RMSE": 738.1033329574317,
|
||||||
|
"MAE": 529.5340809792003,
|
||||||
|
"R2": 0.8641365279788986
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"模型": "线性回归",
|
||||||
|
"RMSE": 796.4617765623753,
|
||||||
|
"MAE": 583.0197588300691,
|
||||||
|
"R2": 0.8418029967286178
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"模型": "神经网络",
|
||||||
|
"RMSE": 809.7923656712804,
|
||||||
|
"MAE": 609.9584660642089,
|
||||||
|
"R2": 0.836463110808386
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"best_model": "梯度提升树",
|
||||||
|
"best_test_metrics": {
|
||||||
|
"模型": "梯度提升树",
|
||||||
|
"RMSE": 651.1440567037434,
|
||||||
|
"MAE": 448.99523765137667,
|
||||||
|
"R2": 0.8942640825672749
|
||||||
|
},
|
||||||
|
"cross_validation": {
|
||||||
|
"模型": "梯度提升树",
|
||||||
|
"RMSE均值": 655.1166606641779,
|
||||||
|
"RMSE标准差": 42.75460340121525,
|
||||||
|
"MAE均值": 466.577603572732,
|
||||||
|
"MAE标准差": 14.719481812429436,
|
||||||
|
"R2均值": 0.8818566774188483,
|
||||||
|
"R2标准差": 0.023087453303387278
|
||||||
|
},
|
||||||
|
"importance_method": "GradientBoostingRegressor 内置重要性",
|
||||||
|
"top_features": {
|
||||||
|
"气温": 0.38973625494728426,
|
||||||
|
"年": 0.2984069767787937,
|
||||||
|
"季节": 0.0932052430981216,
|
||||||
|
"体感温度": 0.07101028400532663,
|
||||||
|
"湿度": 0.05891447646010377,
|
||||||
|
"天气": 0.029590235728090393,
|
||||||
|
"风速": 0.026177907657402667,
|
||||||
|
"月": 0.01607120463407045
|
||||||
|
},
|
||||||
|
"future_prediction": {
|
||||||
|
"min": 1319,
|
||||||
|
"max": 6025,
|
||||||
|
"mean": 3775.15
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"figures": [
|
||||||
|
"/root/bike_course_design/outputs/figures/actual_vs_predicted.png",
|
||||||
|
"/root/bike_course_design/outputs/figures/feature_importance.png",
|
||||||
|
"/root/bike_course_design/outputs/figures/future_prediction_trend.png",
|
||||||
|
"/root/bike_course_design/outputs/figures/historical_trend.png",
|
||||||
|
"/root/bike_course_design/outputs/figures/residual_distribution.png"
|
||||||
|
],
|
||||||
|
"best_model": "/root/bike_course_design/outputs/models/best_bike_demand_model.pkl",
|
||||||
|
"prediction_excel": "/root/bike_course_design/outputs/2013前100天骑行量预测结果.xlsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
outputs/cross_validation_summary.csv
Normal file
2
outputs/cross_validation_summary.csv
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
模型,RMSE均值,RMSE标准差,MAE均值,MAE标准差,R2均值,R2标准差
|
||||||
|
梯度提升树,655.1166606641779,42.75460340121525,466.577603572732,14.719481812429436,0.8818566774188483,0.023087453303387278
|
||||||
|
12
outputs/feature_importance.csv
Normal file
12
outputs/feature_importance.csv
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
,重要性
|
||||||
|
气温,0.38973625494728426
|
||||||
|
年,0.2984069767787937
|
||||||
|
季节,0.0932052430981216
|
||||||
|
体感温度,0.07101028400532663
|
||||||
|
湿度,0.05891447646010377
|
||||||
|
天气,0.029590235728090393
|
||||||
|
风速,0.026177907657402667
|
||||||
|
月,0.01607120463407045
|
||||||
|
星期,0.00832340663049113
|
||||||
|
工作日,0.004855667817542203
|
||||||
|
节假日,0.003708342242773187
|
||||||
|
BIN
outputs/figures/actual_vs_predicted.png
Normal file
BIN
outputs/figures/actual_vs_predicted.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
outputs/figures/feature_importance.png
Normal file
BIN
outputs/figures/feature_importance.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
outputs/figures/future_prediction_trend.png
Normal file
BIN
outputs/figures/future_prediction_trend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
BIN
outputs/figures/historical_trend.png
Normal file
BIN
outputs/figures/historical_trend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
BIN
outputs/figures/residual_distribution.png
Normal file
BIN
outputs/figures/residual_distribution.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
6
outputs/model_comparison.csv
Normal file
6
outputs/model_comparison.csv
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
模型,RMSE,MAE,R2
|
||||||
|
梯度提升树,651.1440567037434,448.99523765137667,0.8942640825672749
|
||||||
|
随机森林,703.9966504917254,450.0664823777129,0.8764025354772573
|
||||||
|
支持向量回归,738.1033329574317,529.5340809792003,0.8641365279788986
|
||||||
|
线性回归,796.4617765623753,583.0197588300691,0.8418029967286178
|
||||||
|
神经网络,809.7923656712804,609.9584660642089,0.836463110808386
|
||||||
|
BIN
outputs/models/best_bike_demand_model.pkl
Normal file
BIN
outputs/models/best_bike_demand_model.pkl
Normal file
Binary file not shown.
BIN
outputs/models/支持向量回归.pkl
Normal file
BIN
outputs/models/支持向量回归.pkl
Normal file
Binary file not shown.
BIN
outputs/models/梯度提升树.pkl
Normal file
BIN
outputs/models/梯度提升树.pkl
Normal file
Binary file not shown.
BIN
outputs/models/神经网络.pkl
Normal file
BIN
outputs/models/神经网络.pkl
Normal file
Binary file not shown.
BIN
outputs/models/线性回归.pkl
Normal file
BIN
outputs/models/线性回归.pkl
Normal file
Binary file not shown.
BIN
outputs/models/随机森林.pkl
Normal file
BIN
outputs/models/随机森林.pkl
Normal file
Binary file not shown.
148
outputs/test_set_predictions.csv
Normal file
148
outputs/test_set_predictions.csv
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
日期,真实总骑行量,预测总骑行量,残差
|
||||||
|
2011-01-03,1349,1577,-228.44
|
||||||
|
2011-01-11,1263,1199,64.1
|
||||||
|
2011-01-24,1416,1465,-49.06
|
||||||
|
2011-01-31,1501,1554,-52.9
|
||||||
|
2011-02-01,1360,950,410.36
|
||||||
|
2011-02-03,1550,1504,46.2
|
||||||
|
2011-02-09,1605,1455,150.06
|
||||||
|
2011-02-14,1913,2320,-406.86
|
||||||
|
2011-02-19,1635,1886,-251.38
|
||||||
|
2011-02-24,1807,1201,606.29
|
||||||
|
2011-02-25,1461,1134,327.36
|
||||||
|
2011-03-02,2134,2191,-56.9
|
||||||
|
2011-03-05,2077,2130,-53.29
|
||||||
|
2011-03-07,1872,1694,177.93
|
||||||
|
2011-03-08,2133,2248,-115.27
|
||||||
|
2011-03-11,1977,1676,300.91
|
||||||
|
2011-03-12,2132,2349,-217.47
|
||||||
|
2011-03-14,2046,2226,-179.8
|
||||||
|
2011-03-18,3239,4044,-804.76
|
||||||
|
2011-03-19,3117,3370,-253.27
|
||||||
|
2011-03-20,2471,2064,407.14
|
||||||
|
2011-03-21,2077,2692,-615.29
|
||||||
|
2011-03-23,2121,2103,18.4
|
||||||
|
2011-03-26,2496,2591,-94.52
|
||||||
|
2011-03-28,2028,2460,-431.86
|
||||||
|
2011-04-01,2227,1954,273.39
|
||||||
|
2011-04-08,1471,1773,-301.99
|
||||||
|
2011-04-12,2034,3717,-1682.75
|
||||||
|
2011-04-20,3944,4423,-478.72
|
||||||
|
2011-04-29,4595,4603,-8.29
|
||||||
|
2011-05-01,3351,3850,-499.24
|
||||||
|
2011-05-12,4864,4534,330.31
|
||||||
|
2011-05-14,3409,4328,-918.66
|
||||||
|
2011-05-16,3958,3809,148.76
|
||||||
|
2011-05-29,4788,4370,417.83
|
||||||
|
2011-06-05,4906,4317,588.98
|
||||||
|
2011-06-08,4401,4109,291.88
|
||||||
|
2011-06-14,4891,4949,-58.17
|
||||||
|
2011-06-15,5180,4954,225.68
|
||||||
|
2011-06-24,4991,5067,-75.7
|
||||||
|
2011-07-01,5362,5370,-7.84
|
||||||
|
2011-07-12,4258,4201,57.37
|
||||||
|
2011-07-16,5923,5163,760.0
|
||||||
|
2011-07-18,4458,4614,-156.27
|
||||||
|
2011-07-19,4541,4128,413.2
|
||||||
|
2011-07-30,4475,3982,493.17
|
||||||
|
2011-07-31,4302,3854,448.1
|
||||||
|
2011-08-01,4266,4499,-232.75
|
||||||
|
2011-08-07,3785,4459,-673.9
|
||||||
|
2011-08-09,4602,4177,424.81
|
||||||
|
2011-08-16,4725,4796,-70.79
|
||||||
|
2011-08-23,5895,4925,970.15
|
||||||
|
2011-08-24,5130,4796,334.21
|
||||||
|
2011-09-02,4727,4418,309.5
|
||||||
|
2011-09-05,3351,3770,-419.29
|
||||||
|
2011-09-12,4713,5118,-404.54
|
||||||
|
2011-09-17,4511,4179,331.63
|
||||||
|
2011-09-23,2395,3520,-1124.77
|
||||||
|
2011-09-24,5423,4669,754.39
|
||||||
|
2011-10-03,3570,3909,-338.53
|
||||||
|
2011-10-14,3644,4314,-669.66
|
||||||
|
2011-10-18,4748,4912,-164.41
|
||||||
|
2011-10-28,3747,3665,82.18
|
||||||
|
2011-10-30,3331,3567,-235.88
|
||||||
|
2011-11-03,3974,3630,344.25
|
||||||
|
2011-11-11,3368,2273,1094.97
|
||||||
|
2011-11-16,1817,2447,-630.11
|
||||||
|
2011-11-23,2566,3439,-873.31
|
||||||
|
2011-11-24,1495,2390,-895.02
|
||||||
|
2011-11-25,2792,3982,-1189.72
|
||||||
|
2011-11-28,3867,4444,-576.52
|
||||||
|
2011-12-02,3940,3851,88.98
|
||||||
|
2011-12-05,3811,3527,284.14
|
||||||
|
2011-12-07,705,1651,-946.28
|
||||||
|
2011-12-09,3620,3786,-165.78
|
||||||
|
2011-12-11,2743,2912,-169.37
|
||||||
|
2011-12-17,2739,2998,-259.32
|
||||||
|
2011-12-18,2431,2814,-383.19
|
||||||
|
2011-12-19,3403,3491,-88.36
|
||||||
|
2011-12-22,3068,2795,272.56
|
||||||
|
2011-12-23,2209,1710,498.59
|
||||||
|
2011-12-24,1011,2143,-1131.82
|
||||||
|
2011-12-27,1162,1523,-360.64
|
||||||
|
2012-01-04,2368,2742,-374.41
|
||||||
|
2012-01-05,3272,3353,-81.08
|
||||||
|
2012-01-11,2177,2724,-547.45
|
||||||
|
2012-01-16,2298,2465,-167.13
|
||||||
|
2012-02-04,2832,2833,-0.88
|
||||||
|
2012-02-09,3830,3233,597.12
|
||||||
|
2012-02-28,4363,4302,60.68
|
||||||
|
2012-03-01,4990,6011,-1020.55
|
||||||
|
2012-03-06,3956,3165,791.34
|
||||||
|
2012-03-10,4118,3709,408.86
|
||||||
|
2012-03-12,5298,5856,-558.23
|
||||||
|
2012-03-19,6153,6305,-151.59
|
||||||
|
2012-03-21,6230,6325,-95.16
|
||||||
|
2012-03-24,3372,5652,-2279.64
|
||||||
|
2012-03-25,4996,4696,300.25
|
||||||
|
2012-04-01,6041,4352,1688.79
|
||||||
|
2012-04-10,5918,6416,-497.99
|
||||||
|
2012-04-12,5409,4941,468.22
|
||||||
|
2012-04-13,6398,6410,-12.48
|
||||||
|
2012-04-14,7460,6934,526.08
|
||||||
|
2012-04-18,4367,5904,-1537.08
|
||||||
|
2012-04-25,6196,6642,-445.52
|
||||||
|
2012-05-28,6043,6531,-487.78
|
||||||
|
2012-05-29,5743,6794,-1050.6
|
||||||
|
2012-06-07,7494,7312,181.84
|
||||||
|
2012-06-11,6664,6500,164.39
|
||||||
|
2012-06-20,6211,6244,-32.85
|
||||||
|
2012-06-26,7442,6989,453.43
|
||||||
|
2012-06-29,5463,5797,-334.07
|
||||||
|
2012-07-04,7403,5172,2230.86
|
||||||
|
2012-07-11,7264,7181,82.86
|
||||||
|
2012-07-13,7499,7513,-13.89
|
||||||
|
2012-07-22,7410,6212,1198.03
|
||||||
|
2012-07-23,6966,6820,145.94
|
||||||
|
2012-07-26,6861,6310,551.18
|
||||||
|
2012-08-01,7580,6910,669.83
|
||||||
|
2012-08-03,7175,6090,1085.44
|
||||||
|
2012-08-18,7865,7339,526.24
|
||||||
|
2012-08-20,6530,6418,112.26
|
||||||
|
2012-08-24,7582,7232,350.11
|
||||||
|
2012-08-25,6053,6488,-435.36
|
||||||
|
2012-08-28,7040,7101,-60.93
|
||||||
|
2012-08-29,7697,7241,455.71
|
||||||
|
2012-08-30,7713,7287,425.85
|
||||||
|
2012-09-16,7333,7350,-16.69
|
||||||
|
2012-09-21,8167,7771,396.26
|
||||||
|
2012-09-24,7436,7609,-172.87
|
||||||
|
2012-09-25,7538,7702,-164.34
|
||||||
|
2012-10-05,8156,7832,323.83
|
||||||
|
2012-10-12,7282,7113,168.73
|
||||||
|
2012-10-29,22,3514,-3491.84
|
||||||
|
2012-11-06,5686,4806,879.7
|
||||||
|
2012-11-09,5992,5555,436.88
|
||||||
|
2012-11-14,5495,4837,657.66
|
||||||
|
2012-11-21,5146,5562,-416.47
|
||||||
|
2012-11-28,5260,4875,385.29
|
||||||
|
2012-11-30,5668,5264,404.17
|
||||||
|
2012-12-04,6606,6580,26.22
|
||||||
|
2012-12-12,5319,5046,272.6
|
||||||
|
2012-12-18,5557,5442,115.19
|
||||||
|
2012-12-19,5267,4985,281.86
|
||||||
|
2012-12-27,2114,1858,255.82
|
||||||
|
2012-12-28,3095,2515,579.82
|
||||||
|
2012-12-30,1796,1942,-145.6
|
||||||
|
BIN
outputs/共享单车日骑行量预测课程设计报告.docx
Normal file
BIN
outputs/共享单车日骑行量预测课程设计报告.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user