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()