尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

时间序列预测Python实践:从环境搭建到模型回测避坑

发布时间:2026/9/28 17:46:24

资讯中心
01
ARTICLE

时间序列预测Python实践:从环境搭建到模型回测避坑

时间序列预测Python实践:从环境搭建到模型回测避坑
简介面向Python时间序列预测学习者的配套代码包聚焦金融、气象、销售等场景中的趋势与季节性预测问题适合具备基础Python语法、希望系统掌握时序建模完整流程的数据分析与机器学习从业者。资源共214个文件压缩包仅237KB181个Python脚本覆盖Pandas时间序列索引、缺失值与异常值处理、数据平滑、差分消除趋势与平稳性检验以及ARIMA、SARIMA、状态空间卡尔曼滤波与Prophet建模32个CSV文件提供气温、太阳黑子、每日出生人数等经典数据集另有1个Markdown说明梳理核心概念与用法。目前已有7257人学习下载。代码结构与典型数据集一一对应便于按章节对照实践通过运行和改造这些代码可掌握随机森林、SVM、神经网络等非线性预测方法学会用matplotlib/seaborn绘制时序图并依据MSE、RMSE、MAE、R²等指标评估模型效果快速迁移至实际业务预测项目。1. 这包 zip 不是给你收藏的先搞清楚它装的是什么大多数人下载完这个Code for Introduction to Time Series Forecasting with Python.zip之后解压、看一眼里面密密麻麻的 notebook然后默默盖上笔记本。这不是代码的问题是打开方式的问题。这个压缩包对应的是《Introduction to Time Series Forecasting with Python》这本书的配套代码里面有数据、有可运行的 Python 脚本和 Jupyter Notebook目的是让你把指数平滑、ARIMA、滞后特征这些概念从纸面变成能跑的结果。它不是一个成品预测系统而是一条让你在两天内跑通整个预测流程的路径。所以这篇落地笔记只回答四个问题包里的东西怎么组织、怎么搭环境跑起来、核心代码为什么那么写、以及哪些坑值得你花半小时提前绕开。适合的读者是已经会用 pandas 做基本数据处理、但还没系统做过时间序列预测的人也适合正在做销量或流量预测、想快速拿到一组可靠基线的从业者。代码包本身不复杂复杂的是你第一次打开它时的茫然和配置环境时反复翻车的五分钟。2. 先看地图再跑马从 zip 到第一个可复现的预测2.1 用 5 分钟摸清目录结构与数据文件解压之后先别急着双击 notebook先用命令行把目录结构看一遍。这包代码的常见组织方式是一个顶层目录下分data、notebooks、scripts三个文件夹数据文件以 CSV 为主有的版本还会把图表输出单独放一个figures目录。用下面的命令在三分钟内看清全貌cd Code_for_Introduction_to_Time_Series_Forecasting_with_Python find . -maxdepth 2 -type f | sort | head -50find的-maxdepth 2限制只向下探索两层目录sort让文件按路径顺序排好head -50先看前 50 行。命令输出会直接告诉你哪些是数据、哪些是脚本、哪些是 notebook比在文件管理器里翻半天直观得多。接着快速看每个 CSV 的列名和行数这一步能帮你决定先跑哪个章节wc -l data/*.csv head -5 data/airline.csvwc -l输出每个文件的行数行数差异很大的 CSV 通常代表不同采样频率的数据。head -5显示前五行重点看时间列是什么格式——是YYYY-MM-DD还是数值型月份这决定了后面pd.to_datetime怎么用。如果某个数据的行数只有几百行那它大概率是月度或季度数据如果有几万行通常是日频或小时频数据。搞清楚这些再进 notebook你就不容易把月度数据当日频数据喂进模型。2.2 搭环境Python 版本、依赖清单与一条命令装齐这套代码最常见的运行环境是 Python 3.9 到 3.11 之间的解释器依赖集中在 pandas、numpy、matplotlib、statsmodels、scikit-learn 这几件套。建议不要直接往系统 Python 里装用 virtualenv 或 conda 建一个干净环境否则一年后你会发现某个项目需要 pandas 2.0、另一个项目还锁在 1.5两边互相打架。python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install pandas numpy matplotlib statsmodels scikit-learn jupyter第一行创建虚拟环境目录.venv第二行激活它之后所有包都装在这个环境里不会污染全局。最后一行是核心把跑这套代码需要的库一次装齐。如果你在 Windows 上激活命令是.venv\Scripts\activate其余相同。装完之后顺手验证一遍版本避免跑代码时报“属性不存在”这种玄学错误python -c import pandas, statsmodels; print(pandas.__version__, statsmodels.__version__)这里有一个值得记下的判断标准statsmodels 版本在 0.13 以下时某些预测 API 的调用方式跟新版差异很大pandas 版本在 2.0 以上时部分旧代码里df.append这种写法会直接报错。如果打印出来的版本一个旧一个新宁可花十分钟把 pandas 升到 2.x、statsmodels 升到 0.14.x也不要带着旧版本硬跑。环境问题大多不是代码问题是版本组合问题。2.3 最小跑通把第一个 notebook 从头执行到尾环境搭好之后打开notebooks目录下的第一个 notebook比如01_Basics.ipynb用 Jupyter 直接跑。我的建议是把菜单栏里的Run All先放到一边手动从上往下逐格执行每格输出都扫一眼。原因很简单如果是数据加载的pd.read_csv没有指定编码导致中文列名乱码逐格跑你能第一时间看到异常位置Run All会让你在第十个 cell 才回头找第一个 cell 的错。jupyter notebook启动之后浏览器会打开 notebook 列表点进第一个文件。此时你不需要理解每一行代码只需要确认三件事数据能加载、图表能显示、训练预测那一段能输出结果数字。第一个 notebook 通常用了最基础的数据和最简单的方法它的意义是验证环境通畅。能跑通前一个 notebook后面 ARIMA 和机器学习部分才值得继续投入时间跑不通问题八成不在代码而在环境回到上一节把版本对齐再回来。3. 核心代码拆开讲时序数据预处理与评估指标3.1 时间索引解析与设置pd.to_datetime 的两种姿势时间序列预测的第一步永远是让 pandas 知道哪一列是时间。代码包里反复出现的模式是pd.to_datetime加set_index但这里藏着两个新手最容易踩的细节年份是两位数的数据会被怎么猜以及原始 CSV 里的时间列到底需不需要保留副本。import pandas as pd df pd.read_csv(data/airline.csv) df[date] pd.to_datetime(df[date], format%Y-%m-%d) df df.set_index(date).sort_index() print(df.head()) print(df.index.dtype)pd.to_datetime的format参数如果写成%Y-%m-%dpandas 就不再去做模糊推断解析速度更快且不会出现 2023 年被读成 2023 或 3023 的纠纷。set_index之后紧接着.sort_index()是必要的因为 CSV 里的时间顺序不一定严格递增而大多数时间序列模型和画图函数都假定索引有序。最后打印index.dtype看到datetime64[ns]说明解析成功如果看到object说明这列数据里混入了空字符串或非日期内容后面所有按时间切片的行为都会失控。代码包里常见的数据文件里日期格式五花八门有的是Jan-1960这种缩写文本有的是纯数字196001。如果遇到月份缩写format%b-%Y是对应写法如果是纯数字年月可以改用pd.to_datetime(df[date], format%Y%m)。你不需要记住所有格式码只需要知道一个原则任何to_datetime都尽量把format写出来让解析行为确定下来这是消除时序分析黑匣子的第一部。3.2 训练集和测试集到底怎么切时间序列不能随机抽样代码包里会给你一个切分训练测试集的例子这个例子的价值不只是拿到两组数据而是让你记住时间序列切分的铁律不能用train_test_split(random_state42)必须按时间先后切。随机切分会把未来的数据混进训练集让模型提前“看见”答案预测误差低到不真实上场全翻车。def temporal_split(df, test_size12): split_idx len(df) - test_size train df.iloc[:split_idx] test df.iloc[split_idx:] return train, test train, test temporal_split(df, test_size12) print(ftrain: {train.index[0]} ~ {train.index[-1]}, n{len(train)}) print(ftest: {test.index[0]} ~ {test.index[-1]}, n{len(test)})函数名temporal_split直接点明按时间切分参数test_size表示末尾预留多少个点做测试。常见的预留比例是从数据总量的 20% 到 30%但月度数据通常直接留 12 个点作为整年的样本这样测试集覆盖全部季节周期评估结果季节相关误差才不会被低估。这段函数的输出会打印训练集和测试集各自的时间范围确认测试集起始时间在训练集结束时间之后这就是“无泄漏”的最低证明。实战中还有个细节切分之后不要把test数据拿来做任何特征工程的统计计算比如滚动均值、标准化缩放。一旦这些统计量用了测试集的取值范围就相当于模型已经见过测试分布线上效果会比实验效果差一截。代码包里通常会把这一点写在注释里你照着注释理解逻辑就行但如果代码包没写你要知道这是你自己要补的边界。3.3 评估指标选哪个RMSE、MAE、MAPE 的取舍每个预测 notebook 的最后都会输出几个误差指标这些指标不是为了让数字好看而是为了回答“这个预测到底能不能用于业务决策”。代码包里最常见的是 RMSE、MAE 和 MAPE 三个一起算但它们的含义有微妙差别。from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np def evaluate_forecast(y_true, y_pred): mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100 return {MAE: mae, RMSE: rmse, MAPE: mape} metrics evaluate_forecast(test[value], forecast) print(metrics)MAE计算绝对误差的平均值它给每个样本相同的权重适合误差成本跟误差大小成正比的场景。RMSE先平方再开方大误差会被放大惩罚如果你更担心“偶尔一次特别离谱的预测”就看 RMSE。MAPE是百分比误差数值上可以直接跟业务方说“平均偏差大约百分之几”但它的弱点也很明确当真实值接近 0 时MAPE 会爆表甚至无穷大所以销售数据里有大量 0 值时慎用。如果你发现代码包里只算了前两个指标我建议补上 MAPE因为对于非技术同事来说“误差大约 8%”比“RMSE 是 1234.5”有说服力得多。如果你的业务是库存管理RMSE 更合适因为库存成本通常跟缺货或积压的绝对量挂钩而不在乎这个偏差是相对值还是绝对值。选哪个指标不是数学问题是业务问题。4. 预测模型代码在哪改从统计基线到机器学习4.1 统计基线Naive Forecast 与 Exponential Smoothing代码包大概率会从最简单的基线开始用上一个时间点的值作为下一个点的预测。这个朴素预测的作用不是让你真的用它上线而是给你一个“及格线”——任何复杂模型如果不能明显跑赢这个及格线说明你的特征工程或模型选择出了问题。from statsmodels.tsa.holtwinters import ExponentialSmoothing model ExponentialSmoothing( train[value], trendadd, seasonaladd, seasonal_periods12, ) fitted model.fit() forecast fitted.forecast(12)ExponentialSmoothing是经典的三重指数平滑trendadd表示趋势以加法形式叠加seasonaladd表示季节效应也是加法。seasonal_periods12是这里最关键的参数它告诉模型你这个序列的周期性是 12 个月。如果换成日频数据、周期为 7 天的数据这里要改成 7改错的话模型会把周规律当噪声处理预测曲线会异常平滑但错得相当稳定。fit()里没有手写任何迭代参数statsmodels 会用优化器自动选平滑系数。如果你发现fit之后有警告说收敛失败先别慌常见原因是季节周期和实际数据周期对不上或者训练数据量少于三个完整季节周期。至少需要 3 个完整周期才能稳定估计季节性数据不够时可以把seasonal改成None退化成 Holt 线性趋势模型牺牲季节捕捉换取稳定性。4.2 机器学习路线构造滞后特征与滚动窗口代码包后半部分通常会转向机器学习核心思想是把时间序列变成表格让随机森林或 XGBoost 这类模型去学。这里最关键的步骤是制造滞后特征即用前几个时间点的值预测当前点。滞后数量怎么定不是拍脑袋而是去看自相关图但代码包里往往直接给一个常用窗口。def make_lag_features(series, lags[1, 2, 3, 6, 12]): df series.to_frame(namey) for lag in lags: df[flag_{lag}] df[y].shift(lag) df df.dropna() return df feature_df make_lag_features(train[value]) print(feature_df.tail()).shift(lag)把整列下移lag行当下值读到的是lag个时间点之前的值。dropna()删掉开头因shift产生的空行但这也意味着模型可用的有效样本变少了滞后 12 就会损失前 12 行。lags列表可以根据业务节奏调整月度销量数据里滞后 1、12 基本必须有因为相邻月相关和年同期相关是确定的;滞后 6 或 7 是给周末周期或半年周期留的可能相关点。如果序列有周规律加入lag_7往往比盲目加lag_5更有效。构造完特征之后模型训练部分与普通回归无异from sklearn.ensemble import RandomForestRegressor features [c for c in feature_df.columns if c ! y] X_train, y_train feature_df[features], feature_df[y] model RandomForestRegressor(n_estimators300, random_state42) model.fit(X_train, y_train)n_estimators300在随机森林里属于中大型配置误差已经收敛但训练时间还能接受random_state42固定随机种子保证你调参前后结果可对比。这里要特别说一句随机森林的预测范围天然受限于训练集中目标值的范围遇到超出历史极值的销量峰值时会滞后这不是代码包的问题是所有树模型对插值外推的固有弱点。如果你预测的业务出现过远超历史的爆发增长树模型大概率跟不上那要换成能外推的线性模型或专门处理趋势的 Prophet。4.3 深度学习和 Prophet 的切入与依赖风险如果你的代码包版本比较全里面可能还有 Prophet 或 LSTM 的章节。Prophet 的优点是节假日效应处理得很好适合带明显活动节点的业务数据LSTM 的优点是能学长依赖但训练不稳定、对数据量要求高。代码包里这两部分经常被读者当成压轴内容但实际项目中它们的性价比不一定最高。# 仅展示结构实际使用需要安装 fbprophet 或 prophet from prophet import Prophet model Prophet( yearly_seasonalityTrue, weekly_seasonalityFalse, daily_seasonalityFalse, ) model.fit(train.reset_index().rename(columns{date: ds, value: y}))Prophet 要求输入列名严格命名为ds和yds是日期y是数值。代码里yearly_seasonalityTrue是让模型自动学习年度季节性如果你的训练数据不足两年这个开关建议改False否则模型会拿少于两个周期的数据去拟合年度曲线结果基本是噪声。weekly_seasonality关掉是因为月度数据没有周规律开着反而是多余的自由度。Prophet 剖分数据时如果你的测试集是 12 个月要注意 Prophet 内部也做了一次历史数据切分用于验证它的内部验证误差和你在测试集上算出来的误差是两回事不要混作一谈。LSTM 部分我一般建议先放着因为它需要先做时间窗口化、再归一化、再调epochs坑比统计模型多出一个数量级等你已经能熟练跑完前面的统计模型和随机森林再回来啃 LSTM 会顺得多。5. 避坑跑这套代码最常见的 5 个坑5.1 现象解压 zip 时提示“文件损坏”或解压后文件缺失原因下载不完整或文件名包含中文字符时部分解压工具按本地编码解析出错导致部分目录没释放出来。解决重新下载一次然后不要用系统自带资源管理器右键解压换成命令行或 7-Zip 解压。命令行可以校验 zip 完整性unzip -t Code_for_Introduction_to_Time_Series_Forecasting_with_Python.zip-t参数会测试压缩包内每个文件的完整性输出里如果出现OK就是完整可解压的。如果某个文件测试失败那就是压缩包本身的问题或下载中断不是解压工具的问题必须重新下载。5.2 现象直接pip install -r requirements.txt后运行代码报AttributeError: module pandas has no attribute append原因旧代码用了 pandas 已被移除的append方法或者依赖列表里没锁版本装到了与代码不兼容的 pandas 2.x。解决先把所有依赖升到当前最新版本再试因为老代码对 pandas 新版的兼容性修复通常已经在代码包更新里补了pip install --upgrade pandas numpy statsmodels scikit-learn如果升级后仍有问题退而求其次将 pandas 固定在 1.5.xpip install pandas1.5.3这里没有通吃答案我只能说先升后降千万别在一开始就锁旧版本因为新版往往已经修复了你还没遇到的坑。5.3 现象画图时横轴时间乱序或者数据明明按月排列折线图却呈锯齿状原因时间列没有解析成日期类型或者解析后没有排序pandas 在plot时把日期当成了字符串按字母序排列。解决回到数据加载那段确认pd.to_datetime后跟了set_index().sort_index()然后加一行校验代码assert df.index.is_monotonic_increasing, index is not sorted by timeis_monotonic_increasing返回布尔值断言通过就是时间索引有序这一步可以提前拦下一堆画图问题。如果断言失败执行df df.sort_index()即可修复。5.4 现象测试集误差很漂亮但模型上线后预测值严重偏低原因预处理阶段用了全局统计数据比如对全序列做了标准化或平滑导致测试集信息泄漏进了训练集或者滚动均值特征用到了包含当前点和未来点的窗口。解决检查所有特征构造代码里有没有shift(0)或没有shift就直接用的列。一个安全做法是给所有时间特征都带上shift(lag)且lag 1确保特征值在预测时刻是已经发生的。再检查缩放器是否先fit在训练集、再transform测试集禁止对全量数据fit_transform。5.5 现象statsmodels 的 ARIMA 报ValueError: Non-stationary starting autoregressive parameters或者长时间不收敛原因原始序列趋势太强ARIMA 的差分阶数设置不足或者数据里存在大段缺失值。解决先对序列做一阶差分再观察差分后序列的均值是否稳定diff train[value].diff().dropna() print(diff.mean(), diff.std())如果差分后均值接近 0、方差相对稳定说明差分阶数至少需要 1如果差分后还有明显趋势把d1改成d2。缺失值处理要在差分之前完成常见做法是先用前向填充df[value].fillna(methodffill)再做差分直接差分带缺失值会导致差分结果里连续出现多个 NaN模型直接罢工。6. 不要照抄它的输出改造成你自己的预测基线6.1 三个值得重写的函数split、evaluate、plot_forecast代码包里写的函数是为它的数据集服务的当你换成自己的业务数据时我建议保留函数骨架、替换三个关键部分。第一个是按业务日历切分——如果你的业务是自然年考核把test_size12改成你的考核周期长度第二个是评估指标——前面说过库存看 RMSE、汇报看 MAPE把函数返回值改动一下让它直接输出你关心的那个指标第三个是预测图把预测曲线和真实曲线的对比保存成 PNG而不是只在 notebook 里显示。def plot_forecast(history, forecast, titleforecast vs actual): ax history.plot(figsize(10, 4), labelhistory) forecast.plot(axax, labelforecast, linestyle--) ax.axvline(forecast.index[0], colorgray, linestyle:, alpha0.5) ax.legend() return axaxvline在预测起点画一条竖虚线让看图的人一眼分清哪段是真实历史、哪段是预测。这个函数在评估多个模型时尤其有用把每个模型的ax保存下来拼在一起视觉对比的冲击力比任何指标数字都强。如果你要评估多个模型的综合表现不要只看预测的平均误差把每个模型在plot_forecast里画在同一张图上观察它在波峰波谷处是否系统性滞后。滞后通常意味着特征集里缺少足够强的周期性特征。6.2 用固定原点回测验证你的模型不是巧合最后说一个我从代码包里学到、收获最大的习惯固定原点回测。它比一次性切分训练测试集更接近真实线上环境因为现实中的模型是定期重新训练的每多一个时间点模型就应该用上这个新点。def rolling_backtest(df, train_size, horizon, retrain_fn): errors [] start 0 while start train_size horizon len(df): train df.iloc[start:start train_size] test df.iloc[start train_size:start train_size horizon] pred retrain_fn(train, horizon) errors.append(abs(pred - test[value]).mean()) start horizon return errorsrolling_backtest用while循环不断推进起点每次把训练集向后移动horizon个点重新训练并预测下horizon个点最终得到一组误差序列。train_size设多大取决于你的模型对数据量的需求ARIMA 和指数平滑至少需要 3 个季节周期的数据我一般起步设 36 个月horizon设成你真实业务预测的步长。关键在于start horizon而不是start 1这样每次回测窗口不重叠计算开销小误差序列之间也相对独立。回测结果的解读有个实用经验如果回测误差波动很大说明模型稳定性差这时候优先去调seasonal_periods或滞后特征数量而不是换一个更高大上的模型。任何复杂模型的收益都不值得你用稳定性去换。回测跑完之后把误差序列按时间画出来找到误差最大的几个时间段回去看那段时间发生了什么——促销、断货、数据采集中断这些原因会成为你下一版特征工程的灵感。我自己的习惯是不管代码包里的模型多好我都会把回测脚本留在自己的项目里每次拿到新数据先跑一遍回测。回测跑通前不碰调参这是我在这个方向投入这么久换来的最实惠的一条经验。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。