125 lines
9.1 KiB
Python
125 lines
9.1 KiB
Python
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()
|