什么是趋势?
在时间序列中,趋势成分表示序列均值持续的、长期的变化。趋势是一个序列中移动最慢的部分,但却代表最重要的时间尺度。在产品销售的时间序列中,随着越来越多的人逐年了解该产品,市场扩张就可能会产生增长的趋势。
移动平均图
通常情况下,为了了解时间序列可能具有什么样的趋势,我们可以使用移动平均图。为了绘制时间序列的移动平均图,我们需要计算一定宽度的滑动窗口(window)的平均值,移动平均线上的每个点表示序列中落在窗口两端之间的所有值的平均值,这样做的目的是消除短期波动,只留下长期变化。
注意上图中的莫纳罗亚火山是如何年复一年地重复上下运动的——这是一种短期的季节性变化,要使一个变化成为趋势的一部分,它应该比任何季节变化发生的时间更长。因此,为了使趋势可视化,我们取了相对较宽的窗口(12 码的窗口),以便使其平滑。
趋势的设计
一旦确定了趋势的形状,我们就可以尝试使用时间步长对其建模,最简单的一种方式是使用时间虚拟变量本身来模拟线性趋势:
target = a * time + b
我们也可以通过时间虚拟变量的变换来拟合许多其他类型的趋势。如果趋势是二次曲线抛物线,我们只需要将时间虚拟变量的平方添加到特征集中:
target = a * time ** 2 + b * time + c
示例:隧道交通
在本例中,我们会刻画出一个隧道交通数据集的趋势模型。
from pathlib import Path
from warnings import simplefilter
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# Configuration
simplefilter("ignore")
sns.set(style="whitegrid")
plt.rc("figure", autolayout=True, figsize=(11, 5))
plt.rc(
"axes",
labelweight="bold",
labelsize="large",
titleweight="bold",
titlesize=14,
titlepad=10,
)
plot_params = dict(
color="0.75",
style=".-",
markeredgecolor="0.25",
markerfacecolor="0.25",
legend=False,
)
%config InlineBackend.figure_format = "retina"
# Load Tunnel Traffic dataset
data_dir = Path("data/ts-course-data")
tunnel = pd.read_csv(data_dir / "tunnel.csv", parse_dates=["Day"])
tunnel = tunnel.set_index("Day").to_period()
tunnel.head()
NumVehicles | |
---|---|
Day | |
2003-11-01 | 103536 |
2003-11-02 | 92051 |
2003-11-03 | 100795 |
2003-11-04 | 102352 |
2003-11-05 | 106569 |
首先,我们通过一个移动平均图来看看该时间序列具有什么样的趋势,由于该序列为每日观测,我们选择 365 天的窗口来平滑一年内的任何短期变化。
moving_average = tunnel.rolling(
window=365,
center=True,
min_periods=183
).mean()
ax = tunnel.plot(style=".", color="0.5")
moving_average.plot(
ax=ax, linewidth=3, title="Tunnel Traffic - 365-Day Moving Average", legend=False,
)
<Axes: title={'center': 'Tunnel Traffic - 365-Day Moving Average'}, xlabel='Day'>
statsmodels
中的 DeterministicProcess
可以帮助我们很容易来设置时间虚拟变量,其中 order
参数表示多项式的顺序:1
表示线性;2
表示二次;3
表示三次,依次类推。
from statsmodels.tsa.deterministic import DeterministicProcess
dp = DeterministicProcess(
index=tunnel.index,
constant=True,
order=1,
drop=True,
)
# `in_sample` creates features for the dates given in the `index` argument
X = dp.in_sample()
X.head()
const | trend | |
---|---|---|
Day | ||
2003-11-01 | 1.0 | 1.0 |
2003-11-02 | 1.0 | 2.0 |
2003-11-03 | 1.0 | 3.0 |
2003-11-04 | 1.0 | 4.0 |
2003-11-05 | 1.0 | 5.0 |
# Trend Model
from sklearn.linear_model import LinearRegression
y = tunnel['NumVehicles']
model = LinearRegression(fit_intercept=False)
model.fit(X, y)
y_pre = pd.Series(model.predict(X), index=X.index)
ax = tunnel.plot(style='.', color='0.5', title='Tunnel Traffic - Linear Trend')
_ = y_pre.plot(ax=ax, linewidth=3, label='Trend')
我们将模型应用于“样本外”特征,“样本外”是指超出训练数据观察期的序列,下面我们进行 30 天的天气预报:
X = dp.out_of_sample(steps=30)
y_fore = pd.Series(model.predict(X), index=X.index)
y_fore.head()
2005-11-17 114981.801146
2005-11-18 115004.298595
2005-11-19 115026.796045
2005-11-20 115049.293494
2005-11-21 115071.790944
Freq: D, dtype: float64
ax = tunnel['2005-05':].plot(title='Tunnel Traffic - Linear Trend Forecast', **plot_params)
ax = y_pre['2005-05':].plot(ax=ax, linewidth=3, label='Trend')
ax = y_fore.plot(ax=ax, linewidth=3, label='Trend Forecast', color='C3')
_ = ax.legend()
本文介绍的趋势模型在实际应用中可以作为更复杂模型的起点,同时也可以作为带有无法学习趋势的算法(如 XGBoost 和 RF)的混合模型中的组件。