一图胜千言,可视化是一种从数据中快速有效获取有效信息的方法。本文云朵君和大家一起学习使用图形探索时间序列数据。
我们将利用6种不同的图表来揭示时间序列数据的各个方面。重点介绍Python中的plotnine库,这是一种基于图形语法(Grammar of Graphics)的绘图工具。
探索性数据分析(Exploratory Data Analysis,EDA)旨在揭示数据集内在的结构和模式。这个过程几乎总是涉及使用可视化技术来呈现数据。
对于时间序列数据,使用图形进行分析可以帮助我们快速发现:
本文需要用到的python库如下:
from datasetsforecast.m3 import M3
import plotnine as p9
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import acf
from sklearn.model_selection import train_test_split
from mizani.breaks import date_breaks
from mizani.formatters import date_format
from numerize import numerize
from statsmodels.tsa.seasonal import STL
我们首先加载一个时间序列。这里我们将使用 M3 数据集中提供的月度时间序列。直接从datasetsforecast
库中获取它:
from datasetsforecast.m3 import M3
dataset, *_ = M3.load('./data', 'Monthly')
series = dataset.query(f'unique_id=="M400"')
注意,使用该方法是需要满足pandas版本要求,否则会报错。直接安装对应新版即可。
现在云朵君将和大家一起学习如何使用 plotnine 创建图形。这个库是 Python 的一种 ggplot2。
如果你还没有安装,直接安装即可。
从设置主题开始:
import plotnine as p9
MY_THEME = p9.theme_538(base_family='Palatino', base_size=12) + \
p9.theme(plot_margin=.025,
axis_text_y=p9.element_text(size=10),
panel_background=p9.element_rect(fill='white'),
plot_background=p9.element_rect(fill='white'),
strip_background=p9.element_rect(fill='white'),
legend_background=p9.element_rect(fill='white'),
axis_text_x=p9.element_text(size=10))
我们将使用基于theme_538
的主题并进行一些额外的修改。
绘制时间序列图是时间序列分析的第一步。时间序列图是一种线形图,用于展示数据值随时间的变化趋势。
import plotnine as p9
time_plot = p9.ggplot(data=series) + \
p9.aes(x='ds', y='y') + \
MY_THEME + \
p9.geom_line(color='#58a63e', size=1) + \
p9.labs(x='Datetime', y='value')
time_plot + p9.theme(figure_size=(8,3))
时间序列图
通过观察时间序列图,我们可以快速发现数据中存在的一些基本模式,如趋势、周期性等。同时,如果数据的均值或方差出现明显变化,在图上也能一目了然。
示例数据表现出一种随机趋势,数据值先是上升到一个拐点,之后开始下降。同时周期性的波动表明数据中可能存在季节性成分。
还可以使用分解数据构建时间图。首先,我们使用 STL 分解时间序列:
import pandas as pd
from statsmodels.tsa.seasonal import STL
ts_decomp = STL(series['y'], period=12).fit()
components = {
'Trend': ts_decomp.trend,
'Seasonal': ts_decomp.seasonal,
'Residuals': ts_decomp.resid,
}
components_df = pd.DataFrame(components).reset_index()
melted_data = components_df.melt('index')
然后,我们使用facet_grid为每个部分创建时间图,如下所示:
from numerize import numerize
# 一个在图形中总结大值的好技巧
labs = lambda lst: [numerize.numerize(x) for x in lst]
decomposed_timeplot = \
p9.ggplot(melted_data) + \
p9.aes(x= 'index' , y= 'value' ) + \
p9.facet_grid( 'variable ~.' , scales= 'free' ) + \
MY_THEME + \
p9.geom_line(color= '#58a63e' , size= 1 ) + \
p9.labs(x= 'Datetime index' ) + \
p9.scale_y_continuous(labels=labs)
时间序列分解图
此变体使检查每个组件变得更容易。在这种情况下,趋势和季节性影响变得清晰。
我们使用numerize使大数字更清晰易读。你也可以将此样式添加到任何其他绘图中。
滞后散点图是将时间序列的当前值与前一个值(滞后值)画在平面坐标系上。
X = [series['y'].shift(i) for i in list(range(2, 0, -1))]
X = pd.concat(X, axis=1).dropna()
X.columns = ['t-1', 't']
lag_plot = p9.ggplot(X) + \
p9.aes(x='t-1', y='t') + \
MY_THEME + \
p9.geom_point(color='#58a63e') + \
p9.labs(x='Series at time t-1',
y='Series at time t') + \
p9.scale_y_continuous(labels=labs) + \
p9.scale_x_continuous(labels=labs)
滞后散点图可以帮助我们了解时间序列的内在结构。如果数据点沿对角线密集分布,说明该时间序列存在自相关性,点分布越集中则自相关性越强。如果数据点分散分布,则表明该序列是随机的,前值对后值没有预测作用。
时间序列滞后图滞后散点图还可用于发现异常值,异常值点将远离数据点的密集区域。
示例数据的点倾向于沿对角线分布,但当值越大时,离散程度也越大。这种特征表明该序列可能存在自回归结构。
自相关性是衡量时间序列在过去值(滞后)中观察到的与自身相关的程度的指标。绘制自相关系数图有助于了解时间序列的内部结构。
你可以使用statsmodels来计算自相关:
import numpy as np
from statsmodels.tsa.stattools import acf
acf_x = acf(
series['y'],
nlags=24,
alpha=0.05,
bartlett_confint=True
)
acf_vals, acf_conf_int = acf_x[:2]
acf_df = pd.DataFrame({
'ACF': acf_vals,
'ACF_low': acf_conf_int[:, 0],
'ACF_high': acf_conf_int[:, 1],
})
acf_df['Lag'] = ['t'] + [f't-{i}' for i in range(1, 25)]
acf_df['Lag'] = pd.Categorical(acf_df['Lag'], categories=acf_df['Lag'])
然后,我们使用plotnine构建一个棒棒糖图:
significance_thr = 2 / np.sqrt(len(series['y']))
acf_plot = p9.ggplot(acf_df, p9.aes(x='Lag', y='ACF')) + \
p9.geom_hline(yintercept=significance_thr,
linetype='dashed',
color='#58a63e',
size=.8) + \
p9.geom_hline(yintercept=-significance_thr,
linetype='dashed',
color='#58a63e',
size=.8) + \
p9.geom_hline(yintercept=0, linetype='solid', color='black', size=1) + \
p9.geom_segment(p9.aes(x='Lag',
xend='Lag',
y=0, yend='ACF'),
size=1.5,
color='#58a63e'
) + \
p9.geom_point(size=4, color='darkgreen', ) + \
MY_THEME
自相关图
如果自相关系数随滞后阶数的增加而缓慢衰减,表明数据可能存在趋势成分;如果自相关系数呈现出明显的波动模式,峰值出现在特定的滞后阶数上,则说明数据中可能存在明显的周期性。如果自相关系数始终接近于0,则表明该序列可能是白噪声序列,即随机序列。
有些图形工具专门用于探究时间序列的季节性成分,如季节子序列图。
季节子序列图的绘制方法是:根据数据的季节周期,将整个序列分组,每组包含一个完整的季节周期。然后将每个周期的数据值绘制在同一张图上,从而可视化观察序列在不同季节的表现模式。如下所示:
grouped_df = series.groupby('Month')['y']
group_avg = grouped_df.mean()
group_avg = group_avg.reset_index()
series['Month'] = pd.Categorical(series['Month'],
categories=series['Month'].unique())
group_avg['Month'] = pd.Categorical(group_avg['Month'],
categories=series['Month'].unique())
seas_subseries_plot = \
p9.ggplot(series) + \
p9.aes(x='ds',
y='y') + \
MY_THEME + \
p9.theme(axis_text_x=p9.element_text(size=8, angle=90),
legend_title=p9.element_blank(),
strip_background_x=p9.element_text(color='#58a63e'),
strip_text_x=p9.element_text(size=11)) + \
p9.geom_line() + \
p9.facet_grid('. ~Month') + \
p9.geom_hline(data=group_avg,
mapping=p9.aes(yintercept='y'),
colour='darkgreen',
size=1) + \
p9.scale_y_continuous(labels=labs) + \
p9.scale_x_datetime(breaks=date_breaks('2 years'),
labels=date_format('%Y')) + \
p9.labs(y='value')
seas_subseries_plot + p9.theme(figure_size=(10,4))
季节子序列图
此图对于揭示季节内和跨季节的模式很有用。
在示例时间序列中,我们可以看到平均值在 3 月份最低。在某些月份(例如 5 月),该序列显示出强劲的正趋势。
现实中的时间序列数据往往会受到各种因素的干扰和影响,导致数据模式产生变化。
我们可以利用分组密度图等可视化工具,来观察这些干扰事件对数据的影响。将数据按照干扰事件进行分组,每组对应一个不同的状态,然后分别绘制每组数据的密度曲线,从而比较不同状态下数据的分布差异。如下所示:
# 某些事件发生在索引 23
change_index = 23
before, after = train_test_split(series, train_size=change_index, shuffle=False)
n_bf, n_af = before.shape[0], after.shape[0]
p1_df = pd.DataFrame({'Series': before['y'], 'Id': range(n_bf)})
p1_df['Part'] = 'Before change'
p2_df = pd.DataFrame({'Series': after['y'], 'Id': range(n_af)})
p2_df['Part'] = 'After change'
df = pd.concat([p1_df, p2_df])
df['Part'] = pd.Categorical(df['Part'], categories=['Before change', 'After change'])
group_avg = df.groupby('Part').mean()['Series']
density_plot = \
p9.ggplot(df) + \
p9.aes(x='Series', fill='Part') + \
MY_THEME + \
p9.theme(legend_position='top') + \
p9.geom_vline(xintercept=group_avg,
linetype='dashed',
color='steelblue',
size=1.1,
alpha=0.7) + \
p9.geom_density(alpha=.2)
在这个特定的例子中,索引 23 处发生了一些事件。这里随意选择了这个特定的时间步骤。但是,你可以使用变化点检测方法来检测重要的时间步骤。
分组密度图我们绘制了临界点前后的分布图。分布有明显的变化。
探索性数据分析是时间序列分析和预测的基础环节。本文介绍了6种有助于探索时间序列内在模式和结构的可视化图形技术:
长按👇关注- 数据STUDIO -设为星标,干货速递