ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

时间序列建模必看:ACF与PACF图详解与定阶实战

时间序列建模必看:ACF与PACF图详解与定阶实战 简介一份面向数据分析与统计建模初学者的 PDF 教程聚焦 Python 时间序列分析中自相关图ACF与偏自相关图PACF的绘制与解读。内容以 statsmodels 库的 plot_acf、plot_pacf 为主线结合示例说明如何通过图形判断序列的拖尾与截尾特征并为 ARIMA 模型定阶提供参考同时补充 seaborn 热力图实现变量间相关性可视化的方法。全包仅包含 1 个 PDF 文件大小约 77KB便于离线阅读与快速查阅。教程还讨论了 ACF 对趋势和季节性的识别价值以及 PACF 在检测线性依赖中的作用帮助读者理解滞后阶数与自相关系数、偏自相关系数之间的关系。已有 11436 人学习下载适合需要快速上手时间序列建模、开展数据相关性探索的 Python 使用者。1. 拿到时间序列的第一件事先画 acf 和 pacf上个月我接到一个销售数据的预测需求第一版模型直接上了 LSTM训练完发现残差里还藏着明显的 7 日周期。后来才发现问题不在模型在数据进模型之前我连自相关结构都没看。时间序列分析里python 画自相关图acf和偏自相关图pacf是建模前必须做的一步它告诉你当前时刻和过去时刻之间到底有没有关系、关系能延续多远、该用 AR 还是 MA 项来吸收这些关系。这篇教程面向要动手建模的人——不管是做时间序列预测、异常检测还是刚把 pandas 的 Series 拿在手里不知道怎么开头我都按自己的操作路径来写先用 statsmodels 把 acf、pacf 算出来并画出来再讲怎么从图里判断截尾和拖尾最后给出定阶和避坑的具体经验。图不是画出来好看的是要在建模前替你回答“数据里到底有没有残留的相关性”这个问题的。2. 自相关图 ACF先搞懂它在算什么再动手画图2.1 自相关的直觉昨天的销量和今天的销量有关系吗自相关Autocorrelation衡量的是同一个序列在不同时间点上的相关性。具体说acf(k) 是序列在 t 时刻和 t-k 时刻之间的相关系数k 是滞后阶数。如果是日销量数据acf(1) 就是“今天销量和昨天销量有多像”acf(7) 就是“今天和上周同一天有多像”。数学定义是acf(k) Cov(y_t, y_{t-k}) / Var(y_t)其中 Cov 是协方差Var 是方差所以 acf 的值被归一化到 [-1, 1] 之间。这个定义和普通的样本相关系数几乎一样只是把“两个不同变量”换成了“同一个变量的不同时刻”。但要注意acf 计算的不是边缘相关而是“无条件的相关”也就是说 acf(2) 描述的是一阶相关性没有被剔除时的结果这也是后面 pacf 存在的意义。在 python 里计算 acfstatsmodels 是最常用的库因为它同时实现了计算和绘图两个功能还带了置信区间不需要自己手写协方差求和。下面是最小的可运行代码。import numpy as np import pandas as pd import matplotlib.pyplot as plt from statsmodels.graphics.tsaplots import plot_acf # 构造一个月度销售数据带趋势和季节性 np.random.seed(42) t np.arange(60) sales 20 0.5 * t 8 * np.sin(2 * np.pi * t / 12) np.random.normal(0, 1, size60) # 画自相关图滞后阶数取 24至少覆盖两个季节性周期 fig plot_acf(sales, lags24, alpha0.05, zeroFalse) plt.xlabel(Lag (月)) plt.ylabel(ACF 值) plt.title(月度销售数据的自相关图) plt.show()这段代码里 plot_acf 是核心函数。lags24 表示画 1 到 24 阶的自相关系数zeroFalse 表示不画第 0 阶——第 0 阶恒等于 1 且没有分析价值画出来会把纵轴压缩得很厉害。alpha0.05 表示置信水平 95%阴影条带就是置信区间落在区间之外的柱子通常认为是统计显著的。从这张图里你会看到几个典型特征lags12 和 lags24 附近出现高峰说明数据存在 12 个月的强季节周期整体拖尾而不是快速衰减到 0说明序列本身不平稳可能有必要先做差分或去趋势。这其实就是建模前最重要的信息我知道这个序列有周期性那特征工程里就要加入月份、滞后项或者季节差分否则任何模型都学不到这种规律。2.2 为什么要在建模前先看 acf白噪声检验、MA 阶数、季节周期acf 图有三个核心用途。第一是检验残差是否为白噪声如果模型拟合得够好残差的自相关在任意滞后阶数都应该接近 0全部落在置信区间内。如果你在残差的 acf 图上看到 lag1 处有漂亮的尖峰说明模型没有把一阶自相关吸收掉还有信息可以榨取模型结构或者滞后阶数一定不对。第二是识别 MA(q) 模型的阶数。MA 模型有一个很特别的统计性质理论上 MA(q) 过程的自相关函数在滞后 q 之后会“截尾”也就是 q 以后的 acf 系数直接变成 0。因此如果你看到 acf 图在 lag2 之后突然掉进置信区间那模型大概率是 MA(2)。这比靠 AIC 硬调参多给了你一个可解释的角度。第三是识别季节周期。我之前在做一个电力负荷数据时acf 在 lag48 处出现高峰那是 48 个 15 分钟间隔对应一天前的负荷水平。图上的这个规律告诉我要加的滞后项是 48 而不是 49 或者 72。这里想强调一点acf 图适合用来发现“周期性重复”的相关结构但要把周期长度精确化最好同时用快速傅里叶变换验证acf 给出的是时域视角。下面这段代码演示用 statsmodels 的 acf 函数直接拿数值方便你打印出来做进一步判断不只是看图。from statsmodels.tsa.stattools import acf # 返回从 lag0 开始的 acf 数值数组 acf_vals acf(sales, nlags24, fftTrue, adjustedTrue) # 只打印 1-6 阶的系数和对应是否显著 for i in range(1, 7): significant 显著 if abs(acf_vals[i]) 1.96 / np.sqrt(len(sales)) else 不显著 print(flag {i}: acf{acf_vals[i]:.4f} ({significant}))这里 fftTrue 会启用快速傅里叶算法来加速计算。样本量大时、nlags 也大时fft 计算比直接求和快很多但对样本量很小比如 n 小于 50的情况直接求和反而更稳定。adjustedTrue 表示使用的是调整后的自相关估计默认是 False那么算出来是未调整的系数两者在样本量小的时候差别明显调整后会对有限样本偏差做一些修正。还有一个容易忽略的关键参数 nlags。我一般取 min(24, n//2-1)也就是说当数据只有 60 个点时nlags 最多取 29。取值太大后面的滞后阶数对应的样本对很少估计方差变大图上会出现大量虚假的“显著”尖刺取值太小季节性周期可能完全暴露不出来。对月度数据至少覆盖 12 个月才能看到年度周期对日粒度数据我会取 30、60、90 这种覆盖自然周期的值而不是随便填个 20。3. 偏自相关图 PACF它剔除的到底是什么3.1 从 AR(1) 说起为什么光看 acf 会误判偏自相关Partial Autocorrelation这个名字里的“偏”字说的是它在计算 y_t 与 y_{t-k} 的相关性时剔除了中间 k-1 个滞后项 y_{t-1} ... y_{t-k1} 的间接影响。通俗讲pacf(k) 告诉你的是“多往前看 k 步除了已经被中间那几步解释掉的部分你还能获得多少额外信息”。这里有一个常见误判对 AR(1) 过程acf(2) 并不等于 0因为 y_{t-2} 可以通过 y_{t-1} 间接影响 y_t。如果你只看 acf 图看到 lag 2 也显著就可能误判成 MA(2)。而 pacf(2) 会把这个间接影响剔除掉对 AR(1) 来说 pacf(2) 几乎为 0。这就解释了为什么定阶不能只看一张图必须把 pacf 和 acf 对照着看一个看直接效应一个看总效应。pacf 的计算在理论上依赖 Yule-Walker 方程通过求解自回归系数得到。statsmodels 里 pacf 函数的 method 参数有几种选择默认方法是 ywunbiasedYule-Walker 无偏估计它适用于样本量较大、序列是平稳 AR 过程的情况。如果你拿不准就用默认大多数场景下结果差别不大。下面这句代码是画 pacf 图的最小写法from statsmodels.graphics.tsaplots import plot_pacf # methodywmle 是 Yule-Walker 的极大似然估计版本 fig plot_pacf(sales, lags24, methodywmle, alpha0.05, zeroFalse) plt.xlabel(Lag (月)) plt.ylabel(PACF 值) plt.title(月度销售数据的偏自相关图) plt.show()method 参数在 statsmodels 新版本里可以直接传 ywunbiased、/ywmle、ols 或 ldung-box。ols 是用普通最小二乘回归逼近 pacf样本量大的时候慢一点但有时会比 Yule-Walker 更稳定——特别是数据带有轻微非平稳性的时候。ansley 相关的方法能对 MA 过程得到更精确的估计但一般建模时用不到知道有这么个参数就行。3.2 pacf 图怎么看AR 阶数的显微镜pacf 图最大的价值在于确定 AR(p) 模型的阶数。因为偏自相关在理论上会在滞后 p 阶之后“截尾”为 0所以如果 pacf 图在 lag3 之后全部落在置信区间内那 AR(3) 模型就足够了。这里的逻辑和 MA 定阶对称acf 截尾指向 MApacf 截尾指向 AR。但截尾这件事在实际数据上从来不会表现得特别干净。统计显著性受样本量影响很大样本小的时候置信区间很宽截尾点的位置可能模糊不清样本大的时候真实为 0 的系数也可能因为随机波动偶尔冒出一个超出置信区间的点。我一般不看单根柱子而是看连续 3 个大滞后是否都落在区间内这样判断截尾比单点判断稳健得多。另外要注意pacf 图对非平稳序列几乎完全失去意义。因为非平稳序列的滞后相关性会被趋势主导pacf 即使剔除了中间项也无法消除趋势带来的“伪相关”。你在画 pacf 之前至少要用 adfuller 做一次 ADF 检验或者直接用差分后的序列来画图。否则 pacf 图里出现的可能只是趋势的影子不是真实的自回归结构。4. 从图到定阶截尾、拖尾对照表与完整案例4.1 四种典型形态AR、MA、ARMA 还是白噪声把 acf 和 pacf 拼在一起看最经典的就是下面这张判断表形态ACFPACF推荐模型AR(p)拖尾指数衰减或震荡衰减p 阶截尾AR(p)MA(q)q 阶截尾拖尾MA(q)ARMA(p,q)拖尾拖尾ARMA(p,q)白噪声全部落在置信区间内全部落在置信区间内无需建模我对这张表的理解是acf 的尾巴就像“剩余信息含量”pacf 的截尾点就像“最远的直接依赖距离”。如果 acf 拖尾的同时 pacf 在 2 阶截尾说明这个序列主要被过去两个时刻直接决定更多滞后时刻的影响都是通过这两个时刻间接传递的模型用 AR(2) 就够了。拖尾的具体形态有两种一种是正的指数衰减走的是 AR(1) 的典型路径另一种是震荡衰减比如正、负、正、负交替这经常是 AR(2) 的表现。看图时别只盯前两三根柱子要看整个拖尾的模式必要时把 lag 数调大到 30 甚至 40才能分辨清楚是指数衰减还是正弦型衰减。4.2 用模拟数据走一遍完整定阶流程下面我给一个完整的操作流程从数据生成到定阶衔接后续时间序列预测模型。先模拟一个 AR(2) 过程然后带大家用 acf 和 pacf 把它识别出来。import numpy as np import pandas as pd from statsmodels.tsa.arima_process import arma_generate_sample from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import matplotlib.pyplot as plt # 模拟 AR(2): y_t 0.6*y_{t-1} - 0.4*y_{t-2} e_t np.random.seed(1) ar_params np.array([1, -0.6, 0.4]) # 注意 arma_generate_sample 的符号约定 ma_params np.array([1, 0]) data arma_generate_sample(arar_params, mama_params, nsample200) # 先做平稳性检验 adf_res adfuller(data) print(fADF p-value: {adf_res[1]:.4f}) # 画图 fig, axes plt.subplots(2, 1, figsize(10, 8)) plot_acf(data, lags20, axaxes[0], zeroFalse) plot_pacf(data, lags20, axaxes[1], methodywmle, zeroFalse) plt.show()这段代码里有几个容易翻车的细节arma_generate_sample 里的 ar 参数用的是“1 - phi1 - phi2”这种约定所以想要 y_t 0.6*y_{t-1} - 0.4*y_{t-2}ar_params 要写 [1, -0.6, 0.4]而不是 [0.6, -0.4]。写成后者会得到完全不同的序列acf 和 pacf 图也会对不上。ADF 检验的 p 值如果小于 0.05 就说明序列平稳可以不用差分直接定阶。看图时会发现 pacf 在 lag2 处有一个明显的尖峰lag3 及以后全部落进置信区间acf 则衰减得比较慢呈震荡形态。按对照表这就是 AR(2)。判断完成以后我一般会用 ARIMA(2, 0, 0) 或 ARIMA(1, 0, 2) 这类候选去跑 AIC 对比但初始方向已经由 acf/pacf 定死了。这样做的价值是你不再是黑匣子式地把 p、q 全部交给自动搜索而是先通过图建立结构上的判断再让自动搜索在邻近区域里微调。值得一提的是acf/pacf 定阶对后续用 ARIMA 做预测、甚至对 LSTM 做时间序列预测都有帮助。LSTM 虽然理论上能自动学滞后关系但如果你先用 acf 发现残差中还有 7 阶峰值就知道该把 input window 至少提到 7或者干脆做一次季节差分。我见过很多 LSTM 模型效果差不是网络结构问题而是输入窗口根本没覆盖数据依赖的最远距离。5. 避坑acf/pacf 使用中最常见的 4 个翻车点5.1 不平稳数据直接画图拖尾全是假象现象acf 图从 lag 1 开始就是接近 1 的系数然后以极慢的速度单调递减30 个滞后全部超出置信区间看起来像是“无限阶拖尾”。pacf 图则出现一个极大的 lag 1 值其余全部不显著。原因序列里有明显的趋势或随机游走成分。趋势会让所有滞后都产生正向相关性acf 捕捉到的是这个虚假趋势相关而不是真实的自相关结构。这种情况下强行看拖尾截尾没有任何意义。解决先做 ADF 检验确认 p 值 0.05 或 0.01。如果不平稳对原始序列做一阶差分或者对带季节性的数据做季节差分然后对差分后的序列重新画 acf 和 pacf。5.2 lags 参数随便填季节周期被淹没或假峰值满天飞现象把 lags 设得太小比如 lags10结果月度数据里年周期完全看不见或者设得太大比如 100 条数据画 60 个滞后尾部一大片“显著”尖峰看着像有复杂结构其实都是噪声。原因每个滞后的 acf 值是基于 t-k 与 t 的样本对计算的。滞后越大有效样本量越小估计方差越大。尾部的高峰往往只是少数几个异常点造成的没有统计意义。解决lags 取 min(24, n//2-1) 或者 min(36, n//2-1)。日粒度数据直接考虑用 7 的倍数月度数据用 12 的倍数。判读时重点看前 1/3 的滞后区域后 2/3 的区域只用来识别季节性长周期不作为定阶依据。5.3 样本量太小无限数据才有的截尾在 30 条数据里不可能出现现象样本量只有 30-40acf 和 pacf 图里的置信区间宽到几乎覆盖所有柱子任何滞后都不显著你可能会误判成白噪声。另一种情况是偶尔有一两根柱子冒出来“显著”但你换一组随机数又完全变了。原因置信区间公式大致是 ±1.96/sqrt(n)n30 时区间宽度超过 ±0.35acf 值很难突破这个范围。另外小样本下 Yule-Walker 估计的偏差也比大样本更大pacf 结果更不稳定。解决如果样本量不足 50acf/pacf 图只做参考不要只依赖图。改用 Ljung-Box 检验做白噪声判断同时用 AIC/BIC 对几个候选阶数做交叉验证三种手段互相印证。如果数据已经不可能增加考虑引入先验知识比如业务上确定有天周期就硬编码这些滞后特征。5.4 statsmodels 版本差异plot_acf 的返回值在不同版本里不一样现象在旧版 statsmodels0.12 及更早里plot_acf 返回一个图形对象到了新版 0.13返回的是包含图和实例的元组。如果你写成ax plot_acf(series)并直接操作 ax新版本会报错或者拿到一个不可预期的对象。原因这是库官方调整了返回值类型为了兼容多个绘图对象的访问方式。这类问题在升级环境后最容易遇到网上旧的教程代码直接跑不起来。解决统一写成fig_result plot_acf(series, lags..., axax)然后把数据传进预先建好的 subplot。或者先result plot_acf(...)再用fig result[0] if isinstance(result, tuple) else result做兼容。更稳妥的做法是使用 plt.gcf() 获取当前图对象不依赖返回值类型。5.5 缺失值处理不当pandas 对齐错误导致 acf 全偏现象用 resample 聚合日数据变成周数据时某几周没有销量直接填入 0 或前向填充后画 acf结果出现诡异的负相关尖峰。原因填充方式破坏了数据自相关结构。填 0 会制造大量“这个点低、下一个点高”的伪交替负相关就出来了前向填充会制造相同值重复的块正相关性被高估。解决最稳妥的做法是保留原始时间索引用 dropna 丢弃缺失位置或者用插值后再画图。如果缺失比例超过 20%建议不要做 acf/pacf 分析因为插值算法本身也会改变序列的相关性结构。还有一种偷懒但实践中有效的方式用原始时间戳的间隔信息把 acf 的滞后对齐到真实时间间隔而不只是按样本序号对齐。6. 把 acf/pacf 封装进一个函数从画图到自动给出定阶建议最后一节我分享一个自己常用的做法把 acf/pacf 分析和 Ljung-Box 白噪声检验封装成一个辅助函数同时输出图和数值结果。这样每次拿到新数据跑一段代码就能得到全部需要的信息不再每次重复写同样的绘图流程。import numpy as np import pandas as pd import matplotlib.pyplot as plt from statsmodels.graphics.tsaplots import plot_acf, plot_pacf from statsmodels.stats.diagnostic import acorr_ljungbox from statsmodels.tsa.stattools import adfuller def ts_acf_pacf_report(series, lags24, diffTrue): 给一个 pandas Series输出平稳性检验 acf/pacf 图 白噪声检验 s series.copy() if diff: s s.diff().dropna() # 平稳性 adf_p adfuller(s)[1] # Ljung-Box 白噪声检验考察前 10 个滞后 lb_res acorr_ljungbox(s, lags[10], return_dfTrue) lb_p lb_res[lb_pvalue].iloc[0] print(fADF p-value: {adf_p:.4f}, Ljung-Box p-value: {lb_p:.4f}) # 绘图 fig, axes plt.subplots(2, 1, figsize(10, 8)) plot_acf(s, lagslags, axaxes[0], zeroFalse) plot_pacf(s, lagslags, axaxes[1], methodywmle, zeroFalse) plt.tight_layout() plt.show()参数 diff 默认为 True意味着默认对原始序列做一阶差分这适合绝大多数非平稳业务数据。如果你已经处理过平稳性调成 False 即可。这样封装的思路是绘图只是中间产物最终目的是得到三个结论——序列是否平稳ADF、是否白噪声Ljung-Box、自回归结构是什么acf/pacf 图的视觉判断。Ljung-Box 检验的 p 值如果大于 0.05说明残差没有显著自相关可以认为是白噪声p 值很小说明显著性很强还有信息没被提取。不过这只是一个整体检验无法告诉你具体是哪个滞后造成的所以仍然需要配合 acf 图定位。再分享一个进阶用法在 LSTM 时间序列预测任务里acf/pacf 不只是用来定阶更重要的是检查训练集和测试集切分的合理性。有一次我在做传感器异常检测数据里存在明显的日周期但切分 train/test 时直接按顺序切导致训练集覆盖不了完整的周期相位acf 图显示训练集的滞后相关性结构和测试集不一致。后来我改成按周期对齐切分模型的效果才算正常。对于做时间序列异常检测的人来说acf/pacf 还有一个特别实用的场景检测指标的“自相关突变”。正常运行数据如果存在稳定的周期自相关acf 图的峰值位置是固定的当系统发生异常自相关结构会显著变化比如原本 24 阶峰值突然消失这说明日周期被破坏了。这个特征比单纯看数值异常更稳健因为它直接反映了系统动态的变化。我现在拿到任何一段新的时序数据第一件事就是跑一遍 ts_acf_pacf_report亲眼确认三个问题序列平稳吗、是白噪声吗、依赖在哪几个滞后。姿势固定下来之后后面无论是传统 ARIMA 还是深度学习模型我都能在几分钟内说清楚这个数据的自相关结构长什么样而不是把数据扔给黑匣子模型去猜。这几个函数和读图习惯是踩过不少坑换来的血泪经验希望帮到你。本文还有配套的精品资源点击获取
返回列表