[譯] Python 的時間序列分析:簡介

掘金翻譯計劃發表於2019-02-19

[譯] Python 的時間序列分析:簡介

時間序列建模的加和模型

時間序列是日常生活中其中一種最常見的資料型別。金融市場的價格、天氣、家庭耗能、甚至體重都是可以定期收集資料的例子。幾乎每個資料科學家都會在日常工作中碰到時間序列,而學習如何為時間序列建模是資料科學中重要的技能。用以分析和預測週期資料的加和模型便是一種簡單但強大的模型。背後直觀的概念是:把時間序列分成不同時間間隔和整體趨勢的組合,間隔可以是每天、每週、每季度、每年。你的家也許在夏天比懂冬天耗能,但整體上因為更有效率的能源使用呈遞減趨勢。加和模型能夠展現出規律/趨勢並根據這些觀察作出預測。

以下的圖展示一個時間序列分解成整體趨勢、年趨勢還有周趨勢。

[譯] Python 的時間序列分析:簡介

加和模型分解的例子

這篇文章會細講一個利用 Python 和 Facebook 研發的 Prophet 預測擴充包來為金融的時間序列資料建模的入門例子。我們同時會講到如何用 pandas 處理資料,用 Quandl 庫訪問金融資料,還有用 matplotlib 畫圖。我加入了引導性的程式碼而且我鼓勵大家看看在 GitHub 上看 Jupyter Notebook 完整的分析。這個入門介紹會為你展示所有你為時間序列建模時必須的步驟。

免責宣告:不得不說來打擊你的是金融資料的過往表現並不是未來表現的指標而且這個方法是不會讓你發財的。我選用股票是因為這些的日資料比較容易得到,運用起來也得心應手。

提取金融資料

一般來說一個資料科學專案中將近 80% 的時間是獲取和整理資料。多虧了 quandl 金融資料庫,這個專案所需的獲取和整理的時間減少至 5% 左右。Quandl 可以用命令列的 pip 安裝,讓你只用一行 Python 程式碼訪問成千上萬的金融指標,沒註冊的狀況下能發出不超於 50 個請求。註冊免費賬戶後你可以取得一個 API 金鑰併發出無限的請求。

首先我們匯入必要的庫並獲取一些資料。Quandl 自動把我們的資料整合到 pandas 的資料框,也就是資料科學的一種資料結構(把 “TSLA” 或 “GM” 換成別的股票代號便可以取得其他公司的資料,你還可以指定某個時間區間)。

# 用 quandl 取金融資料
import quandl

# 用 panda 作資料處理
import pandas as pd

quandl.ApiConfig.api_key = 'getyourownkey!'

# 從 Quandl 提取 TSLA 資料
tesla = quandl.get('WIKI/TSLA')

# 從 Quandl 提取 GM 資料
gm = quandl.get('WIKI/GM')
gm.head(5)
複製程式碼

[譯] Python 的時間序列分析:簡介

從 quandl 的到的 GM 資料截圖

quandl 上的資料量幾乎是無限的,但我想著重看兩個在同一個產業的公司,特斯拉和通用汽車。特斯拉的魅力不僅在於它是美國 111 年以來首家成功的汽車創業公司,它還不時成為 2017 年美國最具價值的汽車公司

[譯] Python 的時間序列分析:簡介

儘管它只賣四款車型。同樣爭奪最具價值汽車公司頭銜的對手通用汽車近年來有支援未來汽車的跡象,它也製造了一些新穎(但不好看)的全電動汽車。

答案顯而易見

我們可以花費大量時間尋找這些資料並下載成 csv 文件,不過幸好有 quandl,我們幾秒就可以得到我們所需要的。

資料探勘

我們建模之前最好畫幾幅圖以得到一些其結構的概念。這也讓我們檢查是否有異常值和遺漏資料的狀況。

matplotlib 可以輕易地畫出 Pandas 的資料框。如果這些作圖程式碼嚇到你了,不必擔心,我同樣是覺得 matplotlib 的程式碼不直觀所以常常複製貼上 Stack Overflow 上 quandl 的例子或者是在別的說明文件。程式設計的一個規則是不要重新編寫一個已經有的答案。

# 股票分割調整收市價,這是我們應該畫的資料
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();
複製程式碼

[譯] Python 的時間序列分析:簡介

原始股票價格

單單對比兩家公司的股票價格不會說明誰更有價值,因為一家公司的價值(市值)同樣取決於股票的數量(市值=股票價格 * 股票的數量)。Quandl 沒有股票數量的資料,但我能找到從谷歌簡單搜到兩家公司的年平均股票數量。雖然不精準,但足以應付我們的分析,有時就要將就一下!

我們用一些 pandas 的小技巧在資料框裡建立兩列資料,像是把索引一個列(reset_index)同時用 ix 索引資料框裡間隔的資料。

# 特斯拉和通用汽車年平均股票數量
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}

# 建立一個年的列
tesla['Year'] = tesla.index.year

# 把索引的日期轉移到日期列
tesla.reset_index(level=0, inplace = True)
tesla['cap'] = 0

# 計算所有年份的市值
for i, year in enumerate(tesla['Year']):
    # 提取那一年的股票數量
    shares = tesla_shares.get(year)
    
    # 市值列等於數量乘以價格
    tesla.ix[i, 'cap'] = shares * tesla.ix[i, 'Adj. Close']
複製程式碼

特斯拉的市值列就這樣生成。我們用同樣的方式生成 GM 的資料併合並兩組資料。合併是資料科學裡面一種必要的元素,因為它讓我們把共用一個列的資料連線起來。這個例子中我們想把兩個不同的公司的資料根據日期並在一起,用 “inner” 合併保留有在兩個公司資料框都有出現的日期。接著我們重新命名合併好的列便能知道哪組資料是哪個汽車公司的。

# 合併兩組資料並重新命名列
cars = gm.merge(tesla, how='inner', on='Date')

cars.rename(columns={'cap_x': 'gm_cap', 'cap_y': 'tesla_cap'}, inplace=True)

# 只選擇相關的列
cars = cars.ix[:, ['Date', 'gm_cap', 'tesla_cap']]

# 做除法得到 10 億美元為單位的市值
cars['gm_cap'] = cars['gm_cap'] / 1e9
cars['tesla_cap'] = cars['tesla_cap'] / 1e9

cars.head()
複製程式碼

[譯] Python 的時間序列分析:簡介

合併後的市值資料框

市值以十億美元為單位。我們看到通用汽車的市值在初期是特斯拉的 30 倍!隨時間推移發生了變化嗎?

plt.figure(figsize=(10, 8))
plt.plot(cars['Date'], cars['gm_cap'], 'b-', label = 'GM')
plt.plot(cars['Date'], cars['tesla_cap'], 'r-', label = 'TESLA')
plt.xlabel('Date'); plt.ylabel('Market Cap (Billions $)'); plt.title('Market Cap of GM and Tesla')
plt.legend();
複製程式碼

[譯] Python 的時間序列分析:簡介

市值歷史資料

我們看到在資料相關的時期中特斯拉迅速的增長和通用汽車的微弱增長。特斯拉身子在 2017 年超越了通用汽車!

import numpy as np

# 找到特斯拉市值高於通用的第一次和最後一次
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.
複製程式碼

在那段時期,特斯拉賣了大約 48,000 輛車通用賣了 1,500,000 。在數量位元斯拉多賣30倍的情況下通用竟然位元斯拉低!這絕對展現出有說服力的總裁和超高質量(如此低數量)的產品的威力。雖然現在特斯拉的市值比通用低,但我們可以預期特斯拉的再次超越嗎?什麼時候會發生?解答這個問題我們求助於用於預測未來的加和模型。

預言家的建模

Facebook 預言家擴充包 2017 年首發用於 Python 和 R,全世界的資料科學家都為之鼓舞。預言家的設計初衷是用作分析時間序列的日觀察值,而這些觀察值在不同時間區間會呈現不同的規律。它還具備分析節日在時間序列中的影響和應用自定節點的強大功能,我們暫且只看能讓模型正常運作的基本功能。預言家就像 quandl 一樣,可以用命令列的 pip 進行安裝。

我們首先匯入預言家並把資料中的列更名為正確的格式。日期列必須更名為 “ds” 還有想預測的數值列叫 “y”。我們接著建立預言家模型處理資料,跟 Scikit-Learn 非常相像:

import fbprophet

# 預言家要求列 ds 和 y
gm = gm.rename(columns={'Date': 'ds', 'cap': 'y'})

# 化單位為 10 億美元
gm['y'] = gm['y'] / 1e9

# 建立預言家模型用於資料
gm_prophet = fbprophet.Prophet(changepoint_prior_scale=0.15)
gm_prophet.fit(gm)
複製程式碼

建立預言家模型時我把 changepoint prior 設定在 0.15,高於預設的 0.05。這個超引數用於控制趨勢變化的敏感度,值越高表示越敏感,越低越不敏感。此值的價值在於對抗機器學習最本質的權衡問題:偏見 vs. 偏差

如果我們的模型太貼近訓練資料,也就是所謂的過於擬合,我們的偏差會過大,模型也難以泛化到其他新的資料。另一方面如果模型不能抓取訓練資料的趨勢,太多的偏見使得它不合適。當一個模型擬合程度低,加大 changepoint prior 值的讓模型更貼近資料;相反如果模型過於擬合,減少 prior 限制模型的敏感度。changepoint prior 的影響可以通過描繪一系列值的預測圖展現出來:

[譯] Python 的時間序列分析:簡介

changepoint prior 的程度越高,模型越靈活,越能擬合訓練資料。這似乎恰恰是我們想要的,但太貼近訓練資料會導致過於擬合,減弱模型利用新資料做預測的能力。因此我們需要找到一個既符合訓練資料又能泛化到其他資料的平衡點。利用我們的模型去抓取每天變動的股票價格,在嘗試一些資料後我增加了模型的靈活度。

決定用預言家模型後,我們可以在時間序列從增加到減少或者是慢速增加到快速增加的時候指定 changepoints(位置處在時間序列變化最快的地方)。Changepoints 可以對應像是新產品釋出或是巨集觀經濟動盪這些重大事件。在沒有指定的情況下,預言家會幫我們計算出來。

做預測需要用到未來資料框。我們指定要預測的時間區間的數量(兩年)和做預測的頻率(每天),然後用預言家模型和未來資料框做預測。

# 生成一個 2 年的資料框
gm_forecast = gm_prophet.make_future_dataframe(periods=365 * 2, freq='D')

# 做預測
gm_forecast = gm_prophet.predict(gm_forecast)
複製程式碼

我們的未來資料框包含了估算的特斯拉和通用汽車未來兩年的市值,用預言家的畫圖功能將其影象化。

gm_prophet.plot(gm_forecast, xlabel = 'Date', ylabel = 'Market Cap (billions $)')
plt.title('Market Cap of GM');
複製程式碼

[譯] Python 的時間序列分析:簡介

黑點代表實際資料(注意他們只到 2018 年初),藍色線表示預測值,淺藍色的區域是不確定性(通常是預測中至關重要的部分)。不確定區域隨著預測時間推移會逐漸擴大,因為起始的不確定性會隨著時間增加,如同天氣預報會因預告的時間越長準確率降低

我們也可以檢查模型檢測出的 changepoints。重申一點,changepoints 代表的是當時間序列的增速有明顯變化的時候(例如從增到減)。

tesla_prophet.changepoints[:10]

61    2010-09-24
122   2010-12-21
182   2011-03-18
243   2011-06-15
304   2011-09-12
365   2011-12-07
425   2012-03-06
486   2012-06-01
547   2012-08-28
608   2012-11-27
複製程式碼

我們可以對比一下這個時間段從谷歌搜尋到的特斯拉趨勢看看結果是否一致。changepoints(垂直線)和搜尋結果放在同一個圖中:

# 載入資料
tesla_search = pd.read_csv('data/tesla_search_terms.csv')

# 把月份轉換為 datetime
tesla_search['Month'] = pd.to_datetime(tesla_search['Month'])
tesla_changepoints = [str(date) for date in tesla_prophet.changepoints]
# 畫出搜尋頻率
plt.plot(tesla_search['Month'], tesla_search['Search'], label = 'Searches')

# 畫 changepoints
plt.vlines(tesla_changepoints, ymin = 0, ymax= 100, colors = 'r', linewidth=0.6, linestyles = 'dashed', label = 'Changepoints')

# 整理繪圖
plt.grid('off'); plt.ylabel('Relative Search Freq'); plt.legend()
plt.title('Tesla Search Terms and Changepoints');
複製程式碼

[譯] Python 的時間序列分析:簡介

特斯拉搜尋頻率和股票 changepoints

特斯拉市值的一些 changepoints 跟特斯拉搜尋頻率的變化一致,但不是全部。我認為谷歌搜尋頻率不能稱為股票變動的好指標。

我們依然需要知道特斯拉的市值什麼時候會超越通用汽車。既然有了接下來兩年的預測,我們可以合併兩個資料框後在同一幅圖中畫出兩個公司的市值。合併之前,列需要更名方便追蹤。

gm_names = ['gm_%s' % column for column in gm_forecast.columns]
tesla_names = ['tesla_%s' % column for column in tesla_forecast.columns]

# 合併的資料框
merge_gm_forecast = gm_forecast.copy()
merge_tesla_forecast = tesla_forecast.copy()

# 更名列
merge_gm_forecast.columns = gm_names
merge_tesla_forecast.columns = tesla_names

# 合併兩組資料
forecast = pd.merge(merge_gm_forecast, merge_tesla_forecast, how = 'inner', left_on = 'gm_ds', right_on = 'tesla_ds')

# 日期列更名
forecast = forecast.rename(columns={'gm_ds': 'Date'}).drop('tesla_ds', axis=1)
複製程式碼

首先我們會只畫估算值。估算值(預言家包的 “yhat”)除去一些資料中的噪音因而看著跟原始資料圖不太一樣。除雜的程度取決於 changepoint prior 的大小 - 高的 prior 值表示更多的模型靈活度和更多的高低起伏。

[譯] Python 的時間序列分析:簡介

通用和特斯拉的預測市值

我們的模型認為特斯拉 2017 年超越通用汽車是噪音,而且知道 2018 年特斯拉才真正的在預測中打敗通用。確切日期是 2018 年的 1 月 27 日,如果那真的發生了,我很樂意接受能預測未來的嘉許!

當生成以上的影象,我們遺漏了預測中最重要的一點:不確定性!我們可以用 matplotlib(參考 notebook)檢視有不確定的區域:

[譯] Python 的時間序列分析:簡介

這更好代表預測的結果。圖中顯示兩個公司預期會增長,特斯拉的增長速度會比通用更快。再強調一下,不確定性會隨著時間的推移而增加,而 2020 年特斯拉的下限比通用的上限高意味著通用可能會一直保持領先地位。

趨勢和規律

市值分析的最後一步是看整體趨勢和規律。預言家讓我們輕易地達到這個目的。

# 描繪趨勢和規律
gm_prophet.plot_components(gm_forecast)
複製程式碼

[譯] Python 的時間序列分析:簡介

通用汽車時間序列的分解

其趨勢很明顯:通用的股票價格正在並持續上漲。但年間有趣的現象是股價自年末上漲後夏天前都在緩慢地跌。我們可以檢驗一下年度市值和平均月銷量是否有相關性。我先從谷歌收集每月的銷售銷售量然後用 groupby 平均這些月份,這是由其重要的步驟因為我們常常想比較兩個範疇的資料,例如一個年齡層的使用者或者是一個廠商的不同汽車。我們的例子中要計算每月的平均銷售便要把月份加總後平均銷售。

gm_sales_grouped = gm_sales.groupby('Month').mean()
複製程式碼

[譯] Python 的時間序列分析:簡介

月銷售似乎跟市值沒什麼關係。八月月銷售最高但市值卻是最低!

從周趨勢看並不能發現有用的訊號(週末沒有股價資訊因此我們只能看工作日),這是可以預計的因為經濟中的隨機漫步說到單看每日的股價並不能告訴我們什麼規律。我們的分析也證明股票長期來看是增長的,但如果每天看,即使是用最好的模型我們也幾乎利用不了任何規律。

簡單看看道瓊斯工業平均指數(一支包含 30 家最大的公司股票的指數)很好地說明這點:

[譯] Python 的時間序列分析:簡介

道瓊斯工業平均指數(來源)

很明顯這要告訴你的資訊是回到 1900 年投資!或者是現實生活中股市跌的時候不要抽身因為歷史告訴我們它會漲回來。整體來說,每日的變動太小看不到,如果要每天都看股票那麼笨的話還不如投資整個市場然後長期拿著。

預言家也可以用在大規模的資料測量,像是國內生產總值(GDP),一個測量國家經濟規模的指標。我基於美國和中國歷史 GDP 資料用預言家模型做了以下預測。

[譯] Python 的時間序列分析:簡介

中國會超越美國 GDP 的確切日子是 2036!這個模型的不足在於觀察值太少(GDP 是每季測量但預言家的強項在於用每日資料),但能在巨集觀經濟知識匱乏的情況下有個基本的預測。

為時間序列建模的方法很多,從簡單的線性迴歸用 LSTM 建的迴圈神經網路。加和模型有用的地方在於它容易生成和訓練並通過可讀的規律產出帶不確定性的預測。預言家功能強大而我們只是看到它表面。我鼓勵大家利用這篇文章和你們的筆記本去探索 Quandl 提供的一些資料,或是你自己的時間序列。留意我在日常生活中的應用和用這些技能去分析預測體重變化的文章。加和模型是開始探索時間序列的不二之選!

我的郵箱是 wjk68@case.edu,歡迎指正和建設性批評。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章