选自towardsdatascience
作者:William Koehrsen
机器之心编译
参与:Jane W、蒋思源
作为探索时间序列的第一步,Python 中的加法模型是必经之路。本文使用Facebook 开发的预测工具Prophet和金融数据集探索如何对时序数据进行建模与分析。加法模型可以快速构建与部署,并解释和预测不确定性,是我们进一步采用LSTM等深度模型进行建模的基础。
时间序列是我们日常生活中最常见的数据类型之一。金融产品价格、天气、家庭能源使用量、甚至体重都具有变化规律。几乎每个数据科学家都会在日常工作中遇到时间序列,学习如何对时间序列进行建模是一项重要的数据科学技能。用于分析和预测周期性数据时,一种强大而简单的方法是加法模型(additive model)。这个想法很简单:将时间序列表示为每日、每周、每季度和每年度等不同时间维度的组合模式,并加以整体趋势。你的能源使用量可能会在夏天上升,在冬天下降,但是随着你家庭能源使用效率的提高,能源使用量总体呈下降趋势。加法模型可以向我们展示数据的模式/趋势,并根据这些观察结果进行预测。
下图显示了一个时间序列的加法模型,它分解为整体趋势、年度趋势和每周趋势。
加法模型分解的例子
本文将介绍使用由 Facebook 开发的 Python Prophet 预测软件包 创建金融时间序列数据的加法模型。同时,我们也将介绍如何使用 Pandas 进行数据操作,以及使用 Quandl 库访问金融数据。本文中已经包含了主要代码,完整的代码请详见 GitHub 上 Jupyter Notebook 中的全面分析。本文将从头开始介绍时间序列建模的每个步骤。
完整代码:https://github.com/WillKoehrsen/Data-Analysis/tree/master/additive_models
免责声明:金融数据过去的表现并不能作为未来表现的指标,你不能使用这里的方法来致富!这里选择使用股票数据的原因是因为它表现出某种每日波动频率。如果你真的想变得富有,只要学习数据科学就够了!
获得金融数据
通常,一个数据科学的项目有大约 80%的时间花在获取和清洗数据上。本项目中,Quandl 库可以将这个工作量减少到 5%左右。Quandl 可以在命令行中通过 pip 命令安装:
pip install quandl
Quandl 是免费的,你可以每天提出 50 个访问请求而无需注册。如果注册一个免费的帐户,你会得到一个 API 密钥,允许无限制次数的请求。
首先,引入所需的库并获取数据。Quandl 中的数据几乎是无限的,但我想集中比较同行业中的两家公司,即特斯拉和通用汽车。特斯拉是一个引人注目的公司,不仅因为它是 111 年以来美国第一个成功的汽车创业公司,它也是 2017 年美国最值钱的汽车公司。它的竞争者是通用汽车,通用汽车最近已经通过制造一些非常酷的全电动车来展现拥抱未来汽车的迹象。
我们可以通过一句简单的 quandl 命令来获得两家公司的每日股票市值:
# quandl for financial data
import quandl
# pandas for data manipulation
import pandas as pd
quandl.ApiConfig.api_key = 'getyourownkey!'
# Retrieve TSLA data from Quandl
tesla = quandl.get('WIKI/TSLA')
# Retrieve the GM data from Quandl
gm = quandl.get('WIKI/GM')
gm.head(5)
GM(通用汽车)数据快照
Quandly 自动将数据放入 Pandas 数据框(DataFrame)中,DataFrame 是数据科学家的首选数据类型。(对于其他公司,只需用「TSLA」或「GM」替换股票代码,你也可以指定日期范围)
数据探索
在建模之前,最好先了解一下数据的结构和范围。这也将有助于找出需要纠正的异常值或缺失值。
Pandas dataframe 可以很容易地用内置方法绘图:
# The adjusted close accounts for stock splits, so that is what we should graph
plt.plot(gm.index, gm['Adj. Close'])
plt.title('GM Stock Price')
plt.ylabel('Price ($)');
plt.show()
plt.plot(tesla.index, tesla['Adj. Close'], 'r')
plt.title('Tesla Stock Price')
plt.ylabel('Price ($)');
plt.show();
原始股票价格
仅仅比较这两家公司的股票价格,并没有显示出哪个更有价值,因为公司的总市值也取决于股票数量(市值=股价*数量)。Quandl 没有免费的股票数量数据,但是我找到了两家公司的平均年度股票数。这是不精确的,但是对我们的分析来说足够准确。有时我们不得不使用不完善的数据!
在这里,我们使用 Pandas 的一些技巧,如改变列的索引(reset_index),同时使用 ix 命令添加索引和更改 dataframe 中的值。
# Yearly average number of shares outstanding for Tesla and GM
tesla_shares = {2018: 168e6, 2017: 162e6, 2016: 144e6, 2015: 128e6, 2014: 125e6, 2013: 119e6, 2012: 107e6, 2011: 100e6, 2010: 51e6}
gm_shares = {2018: 1.42e9, 2017: 1.50e9, 2016: 1.54e9, 2015: 1.59e9, 2014: 1.61e9, 2013: 1.39e9, 2012: 1.57e9, 2011: 1.54e9, 2010:1.50e9}
# Create a year column
tesla['Year'] = tesla.index.year
# Take Dates from index and move to Date column
tesla.reset_index(level=0, inplace = True)
tesla['cap'] = 0
# Calculate market cap for all years
for i, year in enumerate(tesla['Year']):
# Retrieve the shares for the year
shares = tesla_shares.get(year)
# Update the cap column to shares times the price
tesla.ix[i, 'cap'] = shares * tesla.ix[i, 'Adj. Close']
这为特斯拉创建了名为「cap」的列。我们对通用汽车数据进行同样的处理,然后将两者关联(merge)。关联实质上是数据科学工作流的一部分,因为它允许我们在共享列的基础上合并不同的数据集。在这种情况下,该列是日期。我们进行「inner」关联,只保存两个数据框中有相同日期的数据行。
# Merge the two datasets and rename the columns
cars = gm.merge(tesla, how='inner', on='Date')
cars.rename(columns={'cap_x': 'gm_cap', 'cap_y': 'tesla_cap'}, inplace=True)
# Select only the relevant columns
cars = cars.ix[:, ['Date', 'gm_cap', 'tesla_cap']]
# Divide to get market cap in billions of dollars
cars['gm_cap'] = cars['gm_cap'] / 1e9
cars['tesla_cap'] = cars['tesla_cap'] / 1e9
cars.head()
关联后的市值数据框
市值的单位为十亿美元。我们可以看到,开始时通用汽车的市场份额超过特斯拉 30 倍。随着时间推移,事情会保持不变吗?
市值的历史数据
我们观察到特斯拉的急剧上升以及通用汽车在期间的小幅上涨。特斯拉在 2017 年甚至超过了通用汽车!
import numpy as np
# Find the first and last time Tesla was valued higher than GM
first_date = cars.ix[np.min(list(np.where(cars['tesla_cap'] > cars['gm_cap'])[0])), 'Date']
last_date = cars.ix[np.max(list(np.where(cars['tesla_cap'] > cars['gm_cap'])[0])), 'Date']
print("Tesla was valued higher than GM from {} to {}.".format(first_date.date(), last_date.date()))
Tesla was valued higher than GM from 2017-04-10 to 2017-09-21.
在此期间,特斯拉销售约 4.8 万辆汽车,而通用汽车售出 150 万辆。即使销售了 30 多倍汽车,通用汽车的价值仍低于特斯拉。这绝对显示了有号召力的执行官和高质量的产品(如果极低产量)的力量。尽管特斯拉的价值现在低于通用汽车,但是一个好问题可能是,我们可以预测特斯拉再次超越通用汽车吗?什么时候会发生?为此,我们转向预测加法模型,预测未来。
用 Prophet 建模
Facebook 于 2017 年发布基于 Python 和 R 的 Prophet 包,它极大地帮助了数据科学家的工作。Prophet 设计目的是用日常观测数据分析时间序列,这些数据在不同尺度衡量下具有模式规律。它同时对建模节日效应的时间序列和添加人工变化点(changepoint)有出色的能力,但在本文中我们将仅运用基本功能来建模和运行。
我们首先引入 prophet,并将我们数据中的列重新命名为正确的格式。日期列必须被称为「ds」,数值列被称为「y」。在这里,数值列是市值。然后,我们创建 prophet 模型并传入数据训练,就像 Scikit-Learn 机器学习模型一样:
import fbprophet
# Prophet requires columns ds (Date) and y (value)
gm = gm.rename(columns={'Date': 'ds', 'cap': 'y'})
# Put market cap in billions
gm['y'] = gm['y'] / 1e9
# Make the prophet model and fit on the data
gm_prophet = fbprophet.Prophet(changepoint_prior_scale=0.15)
gm_prophet.fit(gm)
创建 prophet 模型时,我将 changepoint 先验设置为 0.15,高于默认值 0.05。这个超参数用于控制趋势对变化的敏感程度,数值越高越敏感,数值越低越不敏感。这个数值用于权衡机器学习中最基本的一对统计量:偏差与方差。
偏差与方差
如果我们的预测曲线过于贴近训练数据,这称为过拟合,此时方差很大,并且模型将不能很好地推广到新的数据。另一方面,如果我们的模型没有捕捉到我们的训练数据中的趋势,这称为欠拟合,此时偏差很大。当模型欠拟合时,增加先验变化点可以使模型具有更大的灵活性来拟合数据;如果模型过拟合,需要减少先验来限制灵活性。由于股票日常变化很大,我们希望模型能够捕捉到这一点,所以我增加了灵活性以更好地拟合数据。在创建一个 prophet 模型中,我们也可以指定变化点,如时间,当希望序列从上升到下降趋势时,反之亦然;如节日,当希望影响时间序列时。如果我们不指定变化点,prophet 会为我们计算它们。
为了进行预测,我们需要用 prophet 模型创建所谓的用于预测的未来数据框。我们指定预测的未来时期区间(两年)和预测的频率(每天)。
# Make a future dataframe for 2 years
gm_forecast = gm_prophet.make_future_dataframe(periods=365 * 2, freq='D')
# Make predictions
gm_forecast = gm_prophet.predict(gm_forecast)
我们的未来数据框包含未来两年特斯拉和通用汽车的估计市值。我们可以用 prophet 的绘图函数来可视化预测。
gm_prophet.plot(gm_forecast, xlabel = 'Date', ylabel = 'Market Cap (billions $)')
plt.title('Market Cap of GM');
黑点代表实际值(注意实际值测量截止到 2018 年初),蓝线表示预测值,淡蓝色阴影区域表示不确定性(这是预测的关键部分)。未来时间距离越远,不确定性区域越大,因为初始的不确定性随着时间的推移而增长。在天气预报中也观察到这种情况,时间越远天气预报越不准确。
我们也可以观察由模型确定的变化点。变化点代表时间序列从上升到下降(或相反)的趋势。为了便于比较,我们可以在此时间范围内查看特斯拉的谷歌搜索趋势,看看这些变化是否一致。我们在一张图上同时画出变化点(垂直线)和搜索趋势:
特斯拉搜索频率和股价基点变化
某些特斯拉市值的变化点与特斯拉搜索的频率变化一致,但不是全部一致。从这个角度来说,我认为谷歌搜索频率并不是反映股票变化的一个很好的指标。
我们仍然需要计算出何时特斯拉的市值将超过通用汽车的市值。由于我们有两家公司未来两年的预测,那么在合并数据框之后,我们可以在同一个图上画出这两家公司的市值变化。
gm_names = ['gm_%s' % column for column in gm_forecast.columns]
tesla_names = ['tesla_%s' % column for column in tesla_forecast.columns]
# Dataframes to merge
merge_gm_forecast = gm_forecast.copy()
merge_tesla_forecast = tesla_forecast.copy()
# Rename the columns
merge_gm_forecast.columns = gm_names
merge_tesla_forecast.columns = tesla_names
# Merge the two datasets
forecast = pd.merge(merge_gm_forecast, merge_tesla_forecast, how = 'inner', left_on = 'gm_ds', right_on = 'tesla_ds')
# Rename date column
forecast = forecast.rename(columns={'gm_ds': 'Date'}).drop('tesla_ds', axis=1)
首先,我们将只画出估计值。估计值(在 prophet 包中称为「yhat」)平滑了数据中的一些噪音,因此看起来与原图略有不同:
通用汽车和特斯拉的预测市值
我们的模型认为,特斯拉在 2017 年短暂超越通用汽车的事件只是噪音,在预测中,特斯拉到 2018 年初之后才会超越通用汽车。确切的日期是 2018 年 1 月 27 日,所以如果这个事件发生了,我将准确地预测了未来!
在做上面的图表时,我们忽略了预测中最重要的部分:不确定性!我们可以使用 matplotlib 来画出存疑的区域:
上图更好地显示了预测内容。图中显示两家公司的市值都将预计增加,但特斯拉将比通用汽车增长更快。同样,随着时间的推移,不确定性会随着时间的推移而增加,而特斯拉的预测下限低于通用汽车的预测上限,这意味着或许通用汽车在 2020 年仍将处于领先地位。
趋势和模式
市值分析的最后一步是看整体趋势和模式。prophet 让我们可以很容易地看到整体趋势和不同维度的模式:
# Plot the trends and patterns
gm_prophet.plot_components(gm_forecast)
通用汽车公司的时间序列分解
这个趋势非常明显:通用汽车的股价正在上涨并将继续上涨。年度模式很有意思,因为这似乎揭示了通用汽车的股价在年底会有所增长,但随后会缓慢下滑直到夏季。因此,我们可以尝试计算年度市值与通用汽车在此期间平均每月的销售额之间是否存在相关关系。
看起来月销量与市值不相关。八月份的月销售额是第二高的,但此时是市值的最低点!
而且,每周趋势没有如预期显示出意义。经济学中的随机游走理论指出,股票价格每天都没有可预测的模式。正如我们的分析所证明的那样,长期来看,股票往往会上涨,但在每日来看,几乎没有我们可以利用的模式。
道琼斯工业平均指数(反映证券交易所 30 家最大公司的市场指数)很简单地说明了这一点:
道琼斯工业平均指数
显然,要是回到 1900 年投资,你就发财啦!或者实际上,当市场下跌的时候,不要撤资,因为根据历史规律它会回升。从全局来看,日常波动太小,甚至不能被看到,如果我们像数据科学家那样思考,会意识到,与投资全体市场并持有长期相比,短线投资股票是没有意义的。
Prophet 也可以应用于更大规模的数据测量,如国内生产总值(衡量一个国家经济总体规模)。我根据美国和中国的历史 GDP 创建了 prophet 模型并做了以下预测。
中国的 GDP 将超过美国的具体时间是 2036 年!由于观测频率低(每年一次),这个模型是有限的,但它提供了一个不基于宏观经济知识要求的基本预测。
有很多方法来模拟时间序列,从简单线性回归到具有 LSTM 的循环神经网络(recurrent neural network)。加法模型是有用的,因为它们可以快速开发和运行,可以解释并预测不确定性。Prophet 的能力令人印象深刻,我们在这里只涉及到基本功能。我鼓励你使用本文和 notebook 来探索 Quandl 提供的一些数据或者利用你自己的时间序列数据。作为探索时间序列的第一步,Python 中的加法模型是必经之路!
原文链接:https://towardsdatascience.com/time-series-analysis-in-python-an-introduction-70d5a5b1d52a
本文为机器之心编译,转载请联系本公众号获得授权。
✄------------------------------------------------
加入机器之心(全职记者/实习生):hr@jiqizhixin.com
投稿或寻求报道:content@jiqizhixin.com
广告&商务合作:bd@jiqizhixin.com