使用Python进行时间序列分析的教程

本文概述

  • 导入包和数据
  • 整理数据
  • 一点探索性数据分析(EDA)
  • 时间序列数据的趋势和季节性
  • 总结
在1月4日的会议上的Facebook Live代码中, 我们检查了关键字” 饮食” , “ 健身房” 和” 财务” 的Google趋势数据, 以了解它们随时间的变化。当我们都试图翻开新书时, 我们问自己1月是否还会有更多搜索这些术语的内容?
在本教程中, 你将逐步讲解我们在会话中编写的代码。你不会做太多的数学运算, 但是会执行以下操作:
  • 搜集数据
  • 整理数据
  • 探索性数据分析
  • 时间序列数据的趋势和季节性
    • 识别趋势
    • 季节性模式
      • 一阶差分
      • 周期性和自相关
本教程的重点将直接放在有关数据集的可视化探索上。
有关大熊猫的更多信息, 请查看srcmini的Python数据处理指南。有关熊猫的时间序列的更多信息, 请查看” Python中的时间序列数据” 课程。
导入包和数据 所以问题仍然存在:当我们都试图翻开新书页时, 一月份是否可以对这些词进行更多搜索?
让我们通过转到此处并检查数据来进行查找。请注意, 本教程的灵感来自此FiveThirtyEight篇。
你也可以将数据以.csv格式下载, 保存到文件中并导入到你自己的Python环境中以执行自己的分析。你现在就要做。我们去取得它!
首先, 你将导入一些软件包:在这种情况下, 将使用numpy, pandas, matplotlib和seaborn。
另外, 如果你希望在Jupyter Notebook中绘制图像, 则可以通过在代码中添加%matplotlib内联来利用IPython的魔力。另外, 你也可以使用sns.set()切换到Seaborn默认设置:
# Import packagesimport numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport seaborn as sns%matplotlib inlinesns.set()

导入使用.read_csv()下载的数据, 并使用.head()检出前几行。
请注意, 你添加了skiprows参数以跳过文件开头的第一行。
df = pd.read_csv('data/multiTimeline.csv', skiprows=1)df.head()

饮食:(全球) 健身房:(全球) 金融:(全球)
0 2004-01 100 31 48
1 2004-02 75 26 49
2 2004-03 67 24 47
3 2004-04 70 22 48
4 2004-05 72 22 43
你还可以使用.info()方法来检查数据类型, 行数等等:
df.info()

< class 'pandas.core.frame.DataFrame'> RangeIndex: 168 entries, 0 to 167Data columns (total 4 columns):Month168 non-null objectdiet: (Worldwide)168 non-null int64gym: (Worldwide)168 non-null int64finance: (Worldwide)168 non-null int64dtypes: int64(3), object(1)memory usage: 5.3+ KB

现在, 你已经从Google趋势中导入了数据, 并对其进行了简要介绍, 现在该对数据进行整理, 并整理成想要为数据分析准备的表单。
整理数据 你要做的第一件事是重命名DataFrame df的列, 以使它们中没有空格。有多种方法可以执行此操作, 但是现在, 你将重新分配给df.columns一个你希望列被调用的列表。
通过调用df.head()仔细检查重新分配的结果:
df.columns = ['month', 'diet', 'gym', 'finance']df.head()

饮食 健身房 金融
0 2004-01 100 31 48
1 2004-02 75 26 49
2 2004-03 67 24 47
3 2004-04 70 22 48
4 2004-05 72 22 43
接下来, 将” month” 列转换为DateTime数据类型, 并使其成为DataFrame的索引。
请注意, 你这样做是因为在.info()方法的结果中看到” 月” 列实际上是数据类型对象。现在, 该通用数据类型封装了从字符串到整数等的所有内容。当你要查看时间序列数据时, 这并不是你想要的。这就是为什么要使用.to_datetime()将DataFrame中的” month” 列转换为DateTime的原因。
小心!设置DataFrame df的索引时, 请确保包含inplace参数, 以便你实际上更改原始索引并将其设置为” month” 列。
df.month = pd.to_datetime(df.month)df.set_index('month', inplace=True)

df.head()

饮食 健身房 金融
2004-01-01 100 31 48
2004-02-01 75 26 49
2004-03-01 67 24 47
2004-04-01 70 22 48
2004-05-01 72 22 43
现在是时候以可视方式浏览你的DataFrame了。
一点探索性数据分析(EDA) 你可以使用内置的熊猫可视化方法.plot()将数据作为3条线图绘制在单个图形上(每列一个, 即” 饮食” , “ 健身房” 和” 财务” )。
请注意, 你还可以为此方法指定一些参数, 例如figsize, linewidth和fontsize, 以分别设置图形的图形大小, 线宽和字体大小。
另外, 你会看到在x轴上看到的不是默认标签建议的月份, 而是年份。为了使绘图更加准确, 你可以将x轴上的标签指定为” 年” , 并将字体大小设置为20。
提示:如果要抑制Matplotlib输出, 只需添加分号即可;到最后一行代码!
df.plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
请注意, 此数据是相对的。你可以阅读Google趋势:
数字表示相对于图表上给定区域和时间的最高点的搜索兴趣。值100是该词的最高人气。值50表示该术语的受欢迎程度是其一半。同样, 分数为0表示该术语的流行度不到峰值的1%。
如果需要, 还可以单独绘制” 饮食” 列作为时间序列:
df[['diet']].plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
注意:首先要注意的是季节性:每年一月都有很大的增长。同样, 似乎有一种趋势:它似乎先上升, 然后下降, 再上升, 然后再下降。换句话说, 这些时间序列似乎存在趋势和季节性因素。
考虑到这一点, 你将学习如何确定时间序列中的趋势!
时间序列数据的趋势和季节性 确定时间序列趋势
有几种方法可以考虑确定时间序列的趋势。一种流行的方法是采用滚动平均值, 这意味着, 对于每个时间点, 你均需取其任一侧的平均值。请注意, 点数由窗口大小指定, 你需要选择窗口大小。
然后会发生什么, 因为你采用平均值会趋于消除噪声和季节性。你现在将看到一个示例。使用内置的熊猫方法查看” 饮食” 的滚动平均值。
【使用Python进行时间序列分析的教程】在确定窗口大小时, 在这里, 你首先要尝试十二个月之一, 因为你正在谈论年度季节性。
diet = df[['diet']]diet.rolling(12).mean().plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
请注意, 在上面的代码块中, 你使用了两组方括号来提取” 饮食” 列作为DataFrame;如果你要使用一套, 例如df [‘ diet’ ], 那么你将创建一个熊猫系列。
在上面的代码块中, 你还链接了方法:在一个对象上一个又一个地调用方法。方法链接非常流行, pandas是其中的一种, 它确实允许你最大程度地使用这种编程风格!
现在, 你正在寻找所需的潮流!与上一幅图相比, 你已删除了大部分季节性。
你还可以使用内置的pandas方法绘制” gym” 的滚动平均值, 并使用与” 饮食” 数据相同的窗口大小:
gym = df[['gym']]gym.rolling(12).mean().plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
你已经成功消除了季节性因素, 并且看到” 健身房” 的上升趋势!但是, 这两个搜索词如何比较?
你可以通过在单个图形上绘制” 健身房” 和” 饮食” 的趋势来解决这一问题:
df_rm = pd.concat([diet.rolling(12).mean(), gym.rolling(12).mean()], axis=1)df_rm.plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
你创建了一个新的DataFrame df_rm, 它具有两列, 滚动平均值为’ diet’ 和’ gym’ 。你使用了pd.concat()函数, 该函数将列的列表作为第一个参数, 并且由于要将它们串联为列, 因此还添加了axis参数, 该参数设置为1。
接下来, 就像之前一样, 使用plot()方法绘制了DataFrame!因此, 现在除去季节性因素, 你会发现饮食可能具有某种形式的季节性因素, 而健身房实际上正在增加!
在确定了数据趋势之后, 就该考虑季节性了, 这是时间序列的重复性质。正如你在本教程开始时所看到的那样, 数据时间序列似乎存在趋势和季节性因素。
时间序列数据中的季节性模式
考虑数据时间序列的季节性组成部分的一种方法是从时间序列中删除趋势, 以便你可以更轻松地调查季节性。要删除趋势, 可以从原始信号中减去上面计算的趋势(滚动平均值)。但是, 这取决于平均的数据点数。
消除趋势的另一种方法称为” 差异” , 你可以查看连续数据点之间的差异(称为” 一阶差异” , 因为你仅查看一个数据点与其之前的数据点之间的差异)。 。
一阶微分 你可以使用pandas以及diff()和plot()方法来计算和绘制” 饮食” 系列的一阶差异:
diet.diff().plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
看到你已消除了大部分趋势, 并可以真正看到每年一月份的峰值。每年一月, 你看到的最高搜索量都会有20%或更高的峰值!
注意:你还可以执行二阶微分, 这意味着如果趋势尚未完全消除, 你将查看一个数据点与该数据点之前的两个数据点之间的差异。有关差异的更多信息, 请参见此处。
差分对将时间序列转换为固定时间序列非常有帮助。你在这里不会过多了解这些信息, 但是平稳的时间序列是其统计属性(例如均值和方差)不会随时间变化的时间序列。这些时间序列很有用, 因为许多时间序列预测方法都基于时间序列近似固定的假设。
有了这些, 你现在就可以通过查看其时间自相关函数来分析时间序列中的周期性。但是在此之前, 你将绕过一段简短的关联。
周期性和自相关 如果一个时间序列以相等的间隔(例如每12个月)重复一次, 则它是周期性的。
另一种思考方式是, 如果时间序列在某个地方达到峰值, 那么它将在那之后的12个月达到峰值, 如果在某个地方出现了低谷, 那么在那之后的12个月也将达到一个低谷。
对此的另一种思考方式是, 时间序列与自身相差12个月。这意味着, 如果你采用时间序列并将其向前或向后移动12个月, 它将以某种方式映射到自身。
考虑到时间序列与其本身的这种偏移版本的相关性, 可以通过自相关概念来捕获。
你将在一分钟内完成此操作。
首先, 让我们提醒自己相关性, 并采用直观的方法来理解这个概念!
两个变量的相关系数反映了它们之间的线性关系。为了理解这一点, 你将借助虹膜数据集查看一个实际示例, 该数据集包含花朵的测量值。
为了更详细地研究这一点, 你将从scikit-learn导入虹膜数据集, 将其转换为DataFrame并借助.head()查看第一行:
from sklearn import datasetsiris = datasets.load_iris()df_iris = pd.DataFrame(data= http://www.srcmini.com/np.c_[iris['data'], iris['target']], columns= iris['feature_names'] + ['target'])df_iris.head()

sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) 目标
0 5.1 3.5 1.4 0.2 0.0
1 4.9 3.0 1.4 0.2 0.0
2 4.7 3.2 1.3 0.2 0.0
3 4.6 3.1 1.5 0.2 0.0
4 5.0 3.6 1.4 0.2 0.0
提醒你理解此数据集, 所有花朵都包含萼片和花瓣。萼片包围花瓣, 并且通常是绿色的和叶状的, 而花瓣通常是有色的叶子。 “ 目标” 列是目标变量, 是鸢尾花的种类, 可以是Versicolor, Virginica或Setosa。在上表中, 它们被编码为0、1和2。
现在, 要考虑相关性, 你将了解鸢尾花的萼片长度与萼片宽度如何相关。为此, 你将使用pandas或seaborn来构建” sepal length” 相对于” sepal width” 的散点图:
sns.lmplot(x='sepal length (cm)', y='sepal width (cm)', fit_reg=False, data=http://www.srcmini.com/df_iris);

使用Python进行时间序列分析的教程

文章图片
请注意, 你通过将fit_reg参数设置为False来关闭线性回归。
在所有花中, 萼片的长度和宽度是正相关还是负相关?它们在每个物种内是正相关还是负相关?这是本质区别。
请记住, 前者是指随着萼片长度的增加, 萼片宽度也以线性方式增加。后者意味着如果间隔长度增加, 则间隔宽度将以线性方式减小。
乍一看, 上面的图中似乎存在负相关:随着萼片长度的增加, 你会发现萼片宽度略有减小。
现在让我们用目标(种类)为颜色绘制” 分隔长度” 与” 分隔宽度” 的散点图:
sns.lmplot(x='sepal length (cm)', y='sepal width (cm)', fit_reg=False, data=http://www.srcmini.com/df_iris, hue='target');

使用Python进行时间序列分析的教程

文章图片
乍一看, 上面的图似乎呈正相关:对于每种鸢尾花, 你都会发现当萼片长度增加时, 萼片宽度也会增加。
可视化是获得相关直觉的一种好方法, 但是你可以更详细地考虑这一点的方法是实际计算相关系数。
你可以借助.corr()方法来计算每对测量的相关系数:
df_iris.corr()

sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) 目标
sepal length (cm) 1.000000 -0.109369 0.871754 0.817954 0.782561
sepal width (cm) -0.109369 1.000000 -0.420516 -0.356544 -0.419446
petal length (cm) 0.871754 -0.420516 1.000000 0.962757 0.949043
petal width (cm) 0.817954 -0.356544 0.962757 1.000000 0.956464
目标 0.782561 -0.419446 0.949043 0.956464 1.000000
请注意, “ 书房长度(cm)” 和” 书房宽度(cm)” 似乎呈负相关!而且, 它们在所测量的全部花朵中都可以。你会看到相关系数为-0.1。但是, 它们在每个物种内没有负相关, 因为系数为0.78。
对于那些感兴趣的人, 这被称为辛普森悖论, 在考虑因果推理时必不可少。你可以在这里阅读更多。
让我们进一步探讨。让我们计算每个物种内每对测量的相关系数。完成此操作的方法是链接.groupby()和.corr()方法, 以按目标分组并打印相关系数:
df_iris.groupby(['target']).corr()

petal length (cm) petal width (cm) sepal length (cm) sepal width (cm)
目标
0.0 petal length (cm) 1.000000 0.306308 0.263874 0.176695
petal width (cm) 0.306308 1.000000 0.279092 0.279973
sepal length (cm) 0.263874 0.279092 1.000000 0.746780
sepal width (cm) 0.176695 0.279973 0.746780 1.000000
1.0 petal length (cm) 1.000000 0.786668 0.754049 0.560522
petal width (cm) 0.786668 1.000000 0.546461 0.663999
sepal length (cm) 0.754049 0.546461 1.000000 0.525911
sepal width (cm) 0.560522 0.663999 0.525911 1.000000
2.0 petal length (cm) 1.000000 0.322108 0.864225 0.401045
petal width (cm) 0.322108 1.000000 0.281108 0.537728
sepal length (cm) 0.864225 0.281108 1.000000 0.457228
sepal width (cm) 0.401045 0.537728 0.457228 1.000000
在此相关矩阵中, 你可以看到:
  • 对于目标0, 萼片的长度和宽度具有0.75的相关性;
  • 对于目标1, 系数为0.5;对于目标1, 系数为0.5。和
  • 对于目标2, 相关系数为0.46。
这些都是正相关的递减量, 但是与原始负相关相比, 它们的正相关都高得多。
这令人难以置信, 说明了彻底分析数据的重要性。
现在, 你已经更加仔细地研究了相关性, 现在就可以通过查看其自相关函数来分析时间序列中的周期性!
首先, 请重新绘制所有时间序列, 以提醒自己它们的外观:
df.plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
然后, 借助.corr()计算所有这些时间序列的相关系数:
df.corr()

饮食 健身房 金融
饮食 1.000000 -0.100764 -0.034639
健身房 -0.100764 1.000000 -0.284279
金融 -0.034639 -0.284279 1.000000
现在, 以上内容告诉你什么?
让我们专注于” 饮食” 和” 健身房” ;它们是负相关的。真有趣!请记住, 你具有季节性和趋势成分。根据相关系数, “ 饮食” 和” 健身房” 呈负相关。但是, 从时间序列来看, 它们的季节成分似乎呈正相关, 而其趋势则呈负相关。
实际的相关系数实际上捕获了这两者。
你现在想要做的是绘制这些时间序列的一阶差异, 然后计算它们的相关性, 因为这大约是季节性成分的相关性。请记住, 消除趋势可能会揭示季节性相关性。
首先在.diff()和.plot()的帮助下绘制一阶差异:
df.diff().plot(figsize=(20, 10), linewidth=5, fontsize=20)plt.xlabel('Year', fontsize=20);

使用Python进行时间序列分析的教程

文章图片
你会发现, 删除趋势后, “ 饮食” 和” 健身房” 之间的关联就非常紧密。现在, 你将计算这些时间序列的一阶差分的相关系数:
df.diff().corr()

饮食 健身房 金融
饮食 1.000000 0.758707 0.373828
健身房 0.758707 1.000000 0.301111
金融 0.373828 0.301111 1.000000
再次注意, 当你考虑趋势和季节成分时, 存在轻微的负相关。现在, 你可以看到, “ 季节” 和” 饮食” 与季节成分高度相关, 系数为0.76。
自相关 现在, 你已经深入研究了变量的相关性和时间序列的相关性, 是时候绘制” 饮食” 序列的自相关了:在x轴上有滞后, 在y轴上有如何在那个滞后时间序列与其自身相关。
因此, 这意味着如果原始时间序列每两天重复一次, 则你会希望在2天时看到自相关函数的峰值。
在这里, 你将看到该图, 并且你应该看到的是自相关函数在12个月时达到峰值:时间序列与自身相差12个月。
使用熊猫的绘图界面, 该界面具有autocorrelation_plot()函数。你可以使用此函数来绘制时间序列” 饮食” :
pd.plotting.autocorrelation_plot(diet);

使用Python进行时间序列分析的教程

文章图片
如果你在轴上包含更多的滞后, 你会发现在12个月时你有一个巨大的相关峰值。你在24个月的时间间隔中又有一个高峰, 该高峰也与自身相关。你在36处有另一个峰值, 但是随着距离的增加, 相关性越来越少。
当然, 你与自己之间的相关性为滞后0。
上图中的虚线实际上告诉你相关性的统计意义。在这种情况下, 你可以说” 饮食” 系列与十二个月的滞后确实是自相关的。
你已经确定了这12个月重复的季节性!
总结 在本教程中, 你学习了很多内容!你查看了关键字” 饮食” , “ 健身房” 的Google趋势数据, 然后粗略地查看了” 财务” , 以了解它们随着时间的变化。你涵盖了诸如季节性, 趋势, 相关性, 自相关, … 等概念。
对于那些急切的数据科学家, 你可以立即做两件事:
  • 你可以查看” 财务” 列并报告你发现的内容;
  • 使用ARIMA建模对这些搜索趋势在未来几年中的情况做出一些时间序列预测。 Machine Learning Mastery的Jason Brownlee提供了一个很棒的Python ARIMA建模教程, srcmini具有R的出色ARIMA Modeling, 并且今年还将开设并运行Python时间序列预测课程。

    推荐阅读