简介:面向Python数据分析与时序预测学习者的完整代码包,源于《Introduction to Time Series Forecasting with Python》教程,适合具备基础Python知识、希望系统掌握时序建模与预测流程的读者。压缩包共214个文件,以181个Python脚本为主,另含32个CSV实验数据集与1份Markdown说明文档,整体仅237KB,轻量易用。脚本覆盖数据预处理、差分平稳化、ARIMA/SARIMA建模、状态空间模型、基于随机森林与神经网络的机器学习预测,以及Facebook Prophet应用和MSE/RMSE/MAE等评估指标计算;配套数据包括每日最低气温、太阳黑子、每日女性出生人数等经典时序数据集,便于直接复现与对比。目前已有7257人学习参考,适合想通过完整代码演练理解时间序列预测全流程、并迁移到金融或销售场景的初学者。
1. 这个 zip 里装的不是答案:是一整套能复现的时序预测实验
拿到手是一个叫Code for Introduction to Time Series Forecasting with Python.zip的文件,很多人会把它当成“答案包”,急着解压翻代码,想从里面抄一段能用的预测脚本。真正这样操作的人,一半卡在环境装不起来,一半卡在不知道每段代码在为什么问题服务。这个包里装的其实是配合《Introduction to Time Series Forecasting with Python》这本书的示例工程,覆盖了数据加载、评估指标、统计学模型、机器学习模型到深度学习模型的完整链路。它不是让你直接抄答案的,而是让你顺着它的目录结构,把“书里讲了什么”和“代码里写了什么”对应起来。适合两类人:刚入门想跑通全流程的 Python 学习者,以及已经会调包、想找一套干净模板快速搭实验骨架的工程师。把这本书的代码跑通一遍,你对时序预测的认知会从“看过”变成“能复现”。
2. 先把环境跑通:从解压 zip 到 notebook 出图的 30 分钟
拿到代码包的第一件事不是读代码,而是让代码能跑起来。这个过程看似简单,但 zip 解压、依赖安装、内核选择每一步都有常见翻车点。
2.1 解压与目录结构:先看清包里到底是什么布局
Linux 下最稳的解压命令是用unzip,重点是把-d参数用上,避免 zip 内容散落到当前目录:
# 文件名带空格,必须加引号;-d 指定解压目录 unzip "Code for Introduction to Time Series Forecasting with Python.zip" -d tsf-code-d tsf-code会把所有文件释放到tsf-code目录下,解压后第一件事是cd tsf-code && ls -R看目录结构。常见布局是data/放 CSV 数据集,notebooks/放按章节拆分的笔记本,scripts/放纯 Python 脚本,有的版本还把results/或figures/单独拎出来。Windows 用户右键“全部解压缩”即可,但如果解压后中文文件名乱码,多半是 zip 本身用了 GBK 编码,Linux 下可以用unzip -O GBK指定编码,Windows 下用 7-Zip 打开后手动解压通常能绕过去。
提示:zip 文件名带空格,在命令行里不写引号会直接把文件名拆成两个参数,这是新手最容易踩的第一个坑。
2.2 依赖清单与安装顺序:虚拟环境别嫌麻烦
这套代码的运行依赖并不复杂,但直接往系统 Python 里pip install后面会吃苦头。尤其是 statsmodels、scikit-learn、tensorflow 三者的 NumPy 版本要求经常互相打架。我建议从第一步就建虚拟环境,避免把系统环境搞乱:
python -m venv .venv # Linux/macOS 激活 source .venv/bin/activate # Windows 激活命令不同 .venv\Scripts\activate # 有 requirements.txt 就直接装,没有就按下面的最小清单装 pip install -r requirements.txt需求文件里通常包含下面这些库,我把它们的作用列成一张表方便你对照:
| 依赖库 | 在代码里负责什么 |
|---|---|
| pandas、numpy | 数据加载、日期解析、滞后特征构造 |
| matplotlib | 画出时序曲线、残差图、预测对比图 |
| statsmodels | ARIMA、ETS 等统计学模型的拟合与推断 |
| scikit-learn | 数据切分、缩放、机器学习模型的评估 |
| tensorflow / keras | LSTM 等深度学习模型的训练(只有后面章节用得到) |
如果requirements.txt不存在,最小化安装建议先装前五个,等跑到深度学习那章再单独装 tensorflow。这样做的理由是:tensorflow 在装的时候会改写 NumPy 版本,而 statsmodels 对 NumPy 版本很敏感,后装能少折腾一轮。
2.3 跑通第一个 notebook:先确认内核和环境匹配
依赖装好后,用 Jupyter 打开 notebooks 目录下的第一个文件。启动命令很简单:
jupyter lab启动后浏览器会打开 Jupyter Lab 界面。这一步最常见的坑是:明明在虚拟环境里装了包,notebook 却报ModuleNotFoundError,原因通常是 notebook 内核选的是系统 Python 而不是当前虚拟环境。我一般在 VSCode 里做 Python 环境配置,打开.ipynb文件后点右上角内核选择器,选.venv对应的解释器;如果列表里没有,先执行一次python -m ipykernel install --user --name=tsf注册内核。确认内核后,跑第一个 cell 里类似import pandas as pd; df = pd.read_csv(...)的代码,能正常打印数据框就说明环境通关了。
3. 数据加载与评估:时序预测代码的第一道分水岭
环境跑通只是热身,真正的分水岭在数据这一步。时序预测的数据处理和普通机器学习不一样:日期必须解析成时间索引,切分必须按时间顺序,评估指标也有自己的讲究。这套代码里体现得很清楚,照着它的思路走能省很多事。
3.1 三种数据来源的加载方式
代码包里最常见的场景是读一个单变量 CSV。比如经典的国际航班乘客数据,一行是月份,一行是乘客数。加载标准写法是:
import pandas as pd df = pd.read_csv( "data/airline-passengers.csv", parse_dates=["Month"], # 把 Month 列解析成 datetime 类型 index_col="Month", # 把日期列设为索引 ) print(df.head()) print(df.index) # 确认索引是 DatetimeIndex 而不是普通 RangeIndexparse_dates是这套代码的灵魂参数。时序模型需要依赖时间顺序,索引必须是DatetimeIndex才能做滞后、重采样、画图这类操作。如果数据没在读取时解析日期,后面到处都会出问题。第二种来源是直接从接口或数据库落到 DataFrame,常见做法是先用pd.to_datetime()统一格式化再设索引,因为从数据库拉出来的时间字段往往不是标准字符串。第三种来源是多列宽表,比如每列是一个城市的销量,处理时会把 DataFrame 按列循环,对每一列单独走预测流程,代码的核心逻辑和单变量完全一样。
3.2 按时间顺序切分而不是随机切分
新手最容易犯的错是把train_test_split那套 shuffle 思路带进来。时间序列一旦随机打乱,等于把未来数据混进了训练集,指标会好看得离谱,真实部署直接翻车。这套代码里的切分逻辑非常清晰,取最后 N 个时间点做测试集:
# 单变量月度序列,习惯用最后 12 个月做测试 n_test = 12 train = df.iloc[:-n_test] # 从开头到倒数第 13 行 test = df.iloc[-n_test:] # 最后 12 行 print(f"训练集范围: {train.index.min()} ~ {train.index.max()}") print(f"测试集范围: {test.index.min()} ~ {test.index.max()}")iloc按行位置切分,不受索引是否连续影响,适合日期有缺失的情况。如果你的数据是每日粒度,测试集一般取 7 天或 30 天;如果是月粒度,12 个点几乎成了默认配置,够评估季节性规律又不会让训练集太短。还有一种更严谨的 walk-forward 切分在代码包后面出现,本质是不断把测试集的一个点并进训练集,重新训练再预测下一个点,代价是训练次数多,适合样本量小的场景。
3.3 MSE、RMSE、MAE 怎么选怎么算
评估代码在包里被封装成一个函数,不同的指标对应不同的业务语义。最小实现是这样的:
from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np def evaluate_forecast(y_true, y_pred): mse = mean_squared_error(y_true, y_pred) rmse = np.sqrt(mse) mae = mean_absolute_error(y_true, y_pred) return {"MSE": mse, "RMSE": rmse, "MAE": mae} # 用法示例 result = evaluate_forecast(test["Passengers"], predictions) print(result)三个指标我一般按场景取舍:MSE 因为做了平方,对大误差极其敏感,模型只要在个别点上预测离谱,MSE 就会暴涨,适合用来“发现”模型有没有大偏差;RMSE 是 MSE 开根号,单位回到原始数据的量纲,方便和业务方解释;MAE 是平均绝对误差,最贴近业务直觉,比如预测销量平均偏差 300 件。如果同一组数据上 RMSE 明显大于 MAE,说明误差分布里存在少数特别大的点,这时候不要急着调模型,先去看那几个点是不是统计异常值。
4. 模型代码怎么组织:从 ARIMA 到 XGBoost 再到 LSTM
这套 zip 里的代码把模型分成了三个梯队:统计学模型、机器学习模型、深度学习模型。每个梯队的数据处理方式完全不同,代码结构也因此不同。理解它们的组织方式,比背诵 API 有用得多。
4.1 ARIMA 与 ETS 的拟合套路就是三个步骤
统计学模型的调用逻辑高度统一:定义模型、拟合数据、预测未来。以 ARIMA 为例,现代 statsmodels 的 API 写法是:
from statsmodels.tsa.arima.model import ARIMA # order=(p, d, q),意思是自回归阶数、差分阶数、移动平均阶数 model = ARIMA(train, order=(1, 1, 1)) fitted = model.fit() # 预测测试集长度的未来值 forecast_result = fitted.get_forecast(steps=len(test)) pred_mean = forecast_result.predicted_mean # 点预测结果 conf_int = forecast_result.conf_int() # 置信区间,默认 95% print(fitted.summary()) # 查看系数的 P 值,判断模型是否有效order三个参数是理解这套代码的钥匙。p=1表示用最近的 1 个历史值做回归,d=1表示做一阶差分把非平稳序列变平稳,q=1表示误差项用 1 阶移动平均。初学者直接套(1,1,1)是安全的起手式,但这不叫调参,只是给了模型一个默认起点。get_forecast比老式的forecast()多返回置信区间,这个能力在画图时非常有用,能直观看出预测的不确定性随步长加宽的过程。
同类的 ETS 模型参数更直白,看名字就知道在干什么:
from statsmodels.tsa.holtwinters import ExponentialSmoothing model = ExponentialSmoothing( train, trend="add", # 趋势项用加法,如果趋势呈线性 seasonal="add", # 季节项用加法,适合波动幅度稳定的序列 seasonal_periods=12 # 月度数据一年 12 期 ) fitted = model.fit()选加法还是乘法,要看序列的季节波动幅度:波动幅度常年稳定,加法够用;节假日效应强、夏季销量是冬季三倍的序列,乘法更合适。这套代码里经常同时展示两种设置,原因是让你直接在结果里对比哪个误差更小。
4.2 把时间序列改造成监督学习才能喂给 XGBoost
统计学模型天然理解时间结构,而 XGBoost 这种机器学习模型不认时间,它只认特征矩阵。因此代码里会有一段关键的“滞后特征构造”,把单变量序列变成带特征和标签的表格:
def make_lag_features(series, lags=(1, 2, 3, 6, 12)): df = series.to_frame(name="y") # 用 shift 把历史值平移到当前行作为特征 for lag in lags: df[f"lag_{lag}"] = df["y"].shift(lag) # shift 会产生 NaN,丢掉前若干行 return df.dropna() features = make_lag_features(train["Passengers"]) X = features.drop(columns="y") y = features["y"]shift(lag)是这个函数的核心:lag_1是上一个时间点的值,lag_12是一年前同期的值。当数据存在明显的年度周期性时,lag_12往往是 XGBoost 的特征重要性第一名,这个现象在代码运行后的 feature importance 图里看得很清楚。特征构造完,训练测试流程和普通回归完全一样,用 sklearn 的Pipeline包一层就能配合GridSearchCV调max_depth、n_estimators这些参数。注意一个细节:测试集的特征也要用训练集的同一套构造逻辑生出来,测试集的第一行会因为没有上一步的历史值而缺失,常见做法是把训练集末尾几行拼到测试集前面补特征。
4.3 LSTM 的最小可跑结构:先归一化,再切窗口
LSTM 的代码结构比前两者复杂一个维度,但核心就三件事:归一化、构造时间窗口、训练循环。时序数据喂给 LSTM 前必须缩放到 0~1 区间,否则激活函数容易饱和导致梯度消失:
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) scaled_train = scaler.fit_transform(train[["Passengers"]]) # 只在训练集上 fit scaled_test = scaler.transform(test[["Passengers"]]) # 测试集只用 transformfit_transform和transform分开写不是洁癖,是数据泄漏问题:如果对整条序列一次性fit_transform,缩放器就“偷看”了测试集的取值范围,预测结果会虚高。构造窗口的常见做法是设定lookback=12,即用过去 12 个月预测下一个月,然后把窗口数组 reshape 成 LSTM 要求的(样本数, 时间步长, 特征数)三维形状。训练部分就是一个标准的 keras 顺序模型:一个LSTM(50)层接一个Dense(1)输出层,编译时用adam优化器和mse损失。训练轮数不要贪多,我在跑这类代码时习惯加一个EarlyStopping,监控验证集损失,连续 10 轮不下降就停,这能省掉大量空等时间。
5. 避坑手册:跑这套代码最常见的 5 个翻车点
代码本身没问题,跑不起来大多是因为环境或使用方式踩了坑。我把这些年见过最多的问题整理成五条,每条都是“现象 → 原因 → 解决”的套路,照着排查能省下大量瞎试的时间。
5.1 statsmodels API 报错,同一个函数名字却在不同的位置
现象:代码里import statsmodels.api as sm然后sm.tsa.ARIMA(...)报错,或者提示ARIMA类位置已变更;也有的是拟合时报ValueError: non-stationary starting autoregressive parameters。
原因:statsmodels 在 0.12 之后把 ARIMA 从statsmodels.tsa.arima_model迁移到了statsmodels.tsa.arima.model,旧写法在新版本里直接失效或行为改变。另一个常见原因是传入的数据序列是 int 类型而不是 float 类型,导致估计器内部矩阵运算报错。
解决:统一改用新 APIfrom statsmodels.tsa.arima.model import ARIMA;在运行代码前先打印statsmodels.__version__确认环境版本。如果数据列是整型,先astype(float)转换再传入拟合。
5.2 解压后中文文件名乱码,pandas 读取路径报错
现象:Windows 上右键解压出来的文件,中文名变成一堆乱码;代码里用pd.read_csv("data/乘客数.csv")直接报FileNotFoundError,或者虽然能读但路径里的中文在终端里显示成转义字符。
原因:zip 包内部文件名用的是 GBK 编码,而 Windows 解压时按 UTF-8 解码导致乱码;更深一层的原因是脚本代码文件本身保存成了 GBK,Python 3 默认 UTF-8 读源码就报错。
解决:把解压目录和所有文件名改成纯英文,这是最省事的路;如果乱码已经产生,在 Linux 下用unzip -O GBK重新解压。代码文件保存时统一选 UTF-8 编码,VSCode 右下角可以直接切换。路径带空格的问题也一样,处理方式是代码里统一用下划线替换空格列名。
5.3 归一化时整条序列一起 fit_transform,预测结果虚高
现象:LSTM 或神经网络模型在测试集上 RMSE 奇低,画图时预测曲线和真实曲线几乎重合,但一部署到新数据上误差暴涨。
原因:scaler.fit_transform(df)把训练集和测试集放在一起做了归一化,缩放器提前看到了测试集的最大值和最小值,模型在“开卷考试”中拿到了答案。这个数据泄漏在时序场景里非常隐蔽,因为代码不报错,指标还挺好看。
解决:严格按“训练集fit_transform,测试集只transform”的写法。需要计算整个序列的统计量时,也只允许使用训练集的统计值。这个坑是最值得花十分钟自查的,我自己带过的项目里至少有三次线上事故和它有关。
5.4 LSTM 每次跑结果都不一样,甚至第二次跑指标差一倍
现象:同一个 notebook,什么代码都没改,第二天重跑一轮,loss 曲线和预测结果完全不同,有时 RMSE 从 20 变成 50。
原因:深度学习模型有随机初始化,权重、dropout、数据打乱都依赖随机数。科学计算库和 Python 的随机数是各自独立的,只设一个random.seed(0)完全不够,导致每次实验的起点不一样。
解决:在训练代码开头把所有随机种子一口气固定住。
import random import numpy as np import tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)这套四行代码应该成为每个深度学习 notebook 的固定开场。另外,LSTM 训练时把shuffle=False保持时间顺序,或固定 shuffle 的随机种子,输出会更稳定。
5.5 Jupyter 里画图不显示,或者中文全部变成方块
现象:notebook 里执行含plt.plot()的 cell 没任何输出;另一台机器上跑通了,图也能出,但标题和图例里的中文全是方块。
原因:前者的典型原因是 notebook 内核环境没有启用 matplotlib 的 inline 模式;后者是 osx 或 Linux 系统里 matplotlib 默认英文字体,中文渲染直接掉了。
解决:在 notebook 第一个 cell 写入%matplotlib inline,这是行业标配做法。中文字体问题则是在画图前设置字体回退。
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei", "Noto Sans CJK SC", "Arial Unicode MS"] plt.rcParams["axes.unicode_minus"] = False # 修复负号显示成方块的问题有这张配置打底,中英文混排的图基本都能正常渲染。远程服务器上跑代码时,记得改用plt.savefig(...)落盘而不是plt.show(),否则没有显示器的环境会直接抛错。
6. 把这套代码改造成自己的模板:一条从跑通到复用的路径
跑通整套代码只是第一步,价值在于把里面散落的脚本整合成自己能复用的模板。我从这套 zip 里提炼三条改造路径,都是能立刻上手的技巧。
把数据集路径和列名抽成配置。原始代码里散落着大量的airline-passengers.csv和Passengers这类硬编码。我习惯把它们收进一个字典,统一放在代码顶部。
config = { "data_path": "data/airline-passengers.csv", "target_col": "Passengers", "date_col": "Month", "test_size": 12, "order": (1, 1, 1), }这样每次换数据只需要改配置,不用翻遍代码逐个替换。换到多站点销量预测时,target_col可以改成循环遍历的列名列表,整个实验流程不需要动。
用一个评估循环对比多个模型。这套代码里每个模型是独立的脚本,不利于横向对比。我一般会把模型切面统一成一个接口:输入训练集和测试集,输出预测结果。然后写一个循环,把 ARIMA、ETS、XGBoost 依次跑一遍,输出指标的对比 DataFrame。这个做法能让你一眼看出哪个模型在你的数据上真正占优,而不是凭感觉拍板。
每次实验留一份记录。时序预测的实验变量多,参数、窗口、数据版本都会影响结果。我现在每跑一轮会把配置字典和评估指标一起追加写进 CSV,文件名带上日期时间戳。几天后回看时能知道当时的结论是怎么得出的,这是给未来的自己留的后悔药。我现在的习惯是:先把基线跑通,再谈调参优化;任何改动前先记录当时的版本结果。希望这个思路对你有帮助。
本文还有配套的精品资源,点击获取