本系列是时序算法ARIMA的第一部分。

ARIMA算法是时序算法的经典算法。网上有很多博客,笔者认为都比较数学化,不是那么让初学者一读就能懂得,换句话说,不是很通俗。本篇博客是基于英文博客来组织的,不是直接翻译,但代码,原始数据都来自这篇英文博客,所以说是翻译而来的。 

1. 背景

ARIMA 算法的本质就是把数据中带有趋势的(trend)的,带有季节性的(seasonal)的, 带有业务场景周期性(domain cycle)的规律先找出来,一层一层将有规律的信息从数据中抽出来,最后的数据就剩下没有规律的,或叫噪声,理想的时候是白噪声。本文的代码是python写的,基本上都是原版英文博客中来的。如果先自己实现代码,可以下载下面的两个数据集。

1. Tractor-Sales.csv: http://www.ucanalytics.com/blogs/wp-content/uploads/2015/06/Tractor-Sales.csv​​
​​​​​2. dummy-sales.csv: http://ucanalytics.com/blogs/wp-content/uploads/2017/08/dummy-sales.csv

本文是用jupyter工具来调试的,怎样安装jupyter工具的博客在这里


2. 例子和代码

我们先以Tractor-sales.csv 为数据源,来开始将有规律的信息从数据中一层一层剥离出来。一般而言,有规律的数据指前述提到过的三种情况,

  1. 带有趋势的 (trend)
  2. 带有季节性的 (seasonal)
  3. 带有业务场景周期性的 (cycle)

那么我们拿到数据后,先对Tractor-sales.csv的字段处理一下,原来月和年份在一起,我们将他们分隔开来,在以下的篇幅中,读者自然明白为什么要把月和年份分隔开来,主要是观察季节性的规律。

我们先了解数据,我们有以下代码:

# 导入必要的packages 或模块
import warnings
import itertools

import pandas as pd
import numpy as np

import statsmodels.api as sm
import statsmodels.tsa.api as smt
import statsmodels.formula.api as smf

import matplotlib.pyplot as plt
%matplotlib inline

plt.style.use('bmh')

这一步,主要是导入python的库和模块。 

# 列出前5行数据,看看表头或字段
sales_data = pd.read_csv("Tractor-Sales.csv")
sales_data.head(5)

现在,我们开始导入原始数据,并看它的表头和前5行数据,我们看到表头或字段有两个,一个是Month-Year,一个是Number of Tractor Sold, 这两个字段名都不尽人意。下一步,我们有特征值工程,就是把第一个字段拆分。第二个字段名会被改名。 

# 改字段,让年和月份分开,先建立自然月份
dates = pd.date_range(start='2003-01-01', freq='MS', periods=len(sales_data))

 先创建dates,大家在自己的jupyter里,可以输出dates,看看到底是怎样的值,这里主要是获取2003年1月1日的值,dates的每一个元素是date类型。频率是每一个月,长度是原始数据长度。

import calendar
# 增加字段month
sales_data['Month'] = dates.month
# 新增加的字段month是参照月份的英文缩写
sales_data['Month'] = sales_data['Month'].apply(lambda x: calendar.month_abbr[x])
# 增加字段年
sales_data['Year'] = dates.year
sales_data.head(5)

从这个操作可以看出,我们就是新建立了两个字段,一个是月份,也就是Month,一个是年,也就是Year。那么接下来就可以删去我们不需要的Month-Year字段了。 

# 现在可以删除旧的字段
sales_data.drop(['Month-Year'], axis=1, inplace=True)
# 改名数据字段
sales_data.rename(columns={'Number of Tractor Sold':'Tractor-Sales'}, inplace=True)
sales_data = sales_data[['Month', 'Year', 'Tractor-Sales']]

这一个步,我们删除了不需要的字段和改了另一个字段的名称。 

# 建立数据索引
sales_data.set_index(dates, inplace=True)

接下来,就是建立索引。 

sales_ts = sales_data['Tractor-Sales']

这一步,把要研究的数据,就是要画图了解的数据单独抽取出来,建立一个列,主要是为了绘图。 

# 画图
plt.figure(figsize=(10, 5))
plt.plot(sales_ts)
plt.xlabel('Years')
plt.ylabel('Tractor Sales')

现在我们看到了销售数据的第一张图。从这张图,我们直接地看出,每年的销售数是增长的,但是螺旋形增长的。说明也是有季节型的。那么接下来,让我们把这些规律先剥去,看看剩下来的数据表达什么意思。如果是噪声,就说明我们剥去了所有有规律的因素。

让我们进一步了解数据,

# 2x2的图展示,第一行两张图,第二行两张图
fig, axes = plt.subplots(2, 2, sharey=False, sharex=False)
fig.set_figwidth(14)
fig.set_figheight(8)

# 第一行,第一列的图先是原始数据,然后是将4个月的销售数据取平均,作为一个点,然后绘图
axes[0][0].plot(sales_ts.index, sales_ts, label='Original')
axes[0][0].plot(sales_ts.index, sales_ts.rolling(window=4).mean(), label='4-Months Rolling Mean')
axes[0][0].set_xlabel("Years")
axes[0][0].set_ylabel("Number of Tractor's Sold")
axes[0][0].set_title("4-Months Moving Average")
axes[0][0].legend(loc='best')

# 第一行,第二列的图先是原始数据,然后是将6个月的销售数据取平均,作为一个点,然后绘图
axes[0][1].plot(sales_ts.index, sales_ts, label='Original')
axes[0][1].plot(sales_ts.index, sales_ts.rolling(window=6).mean(), label='6-Months Rolling Mean')
axes[0][1].set_xlabel("Years")
axes[0][1].set_ylabel("Number of Tractor's Sold")
axes[0][1].set_title("6-Months Moving Average")
axes[0][1].legend(loc='best')

# 第二行,第一列的图先是原始数据,然后是将8个月的销售数据取平均值,作为一个点,然后绘图
axes[1][0].plot(sales_ts.index, sales_ts, label='Original')
axes[1][0].plot(sales_ts.index, sales_ts.rolling(window=8).mean(), label='8-Months Rolling Mean')
axes[1][0].set_xlabel("Years")
axes[1][0].set_ylabel("Number of Tractor's Sold")
axes[1][0].set_title("8-Months Moving Average")
axes[1][0].legend(loc='best')

# 第二行,第二列的图先是原始数据,然后是将12个月的销售数据取平均值,作为一个点,然后绘图
axes[1][1].plot(sales_ts.index, sales_ts, label='Original')
axes[1][1].plot(sales_ts.index, sales_ts.rolling(window=12).mean(), label='12-Months Rolling Mean')
axes[1][1].set_xlabel("Years")
axes[1][1].set_ylabel("Number of Tractor's Sold")
axes[1][1].set_title("12-Months Moving Average")
axes[1][1].legend(loc='best')

plt.tight_layout()
plt.show()

代码的含义就是按照4个月,6个月,8个月,12个月取移动平均,然后绘画,和原始数据比较,我们的愿望是进一步了解数据。图绘出后,如下显示:

从这四张图,我们看出按12个月移动平均绘图,正好是一条直线,我们从另外三张图的红线可以看到,4个月,6个月,8个月的移动平均都是凹凸的,就像一条穿了皱褶的裤子一样,而只有12个月的移动平均像是裤子熨过了一样。

通过这一步的数据描述,我们进一步了解了数据,就是数据有趋势的(trend),那么我们直接试试库函数decompose,偷个懒看看,我们能不能把数据的规律性的因素都剥离出来。

decomposition = sm.tsa.seasonal_decompose(sales_ts, model='multiplicative')
fig = decomposition.plot()
fig.set_figwidth(12)
fig.set_figheight(8)
fig.suptitle('Decomposition of multiplicative time series')
plt.show()

让我们看看数据分解后的情况,

从分解的图,我们看出,这个数据有趋势的(trend), 有季节性的(Seasonal), 接下来的就是渣渣了(residue),这些渣渣就是噪声了。所以分解完成。分解的函数有几个,我们选择一个作为解释概念。具体可以看看statsmodels的api的链接

至此,我们对数据有了很好的了解。(第一部分完,待续)

3. 数学原理

待续

4. 其他

待续

5. 参考文献

1. http://ucanalytics.com/blogs/wp-content/uploads/2017/08/ARIMA-TimeSeries-Analysis-of-Tractor-Sales.html

2. https://www.statsmodels.org/stable/api.html

3. 

6. 编辑历史

1. 本系列第一部分创建于 2021年5月27日。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐