時間序列是我們日常生活中最常見的資料型別之一。金融產品價格、天氣、家庭能源使用量、甚至體重都具有變化規律。幾乎每個資料科學家都會在日常工作中遇到時間序列,學習如何對時間序列進行建模是一項重要的資料科學技能。用於分析和預測週期性資料時,一種強大而簡單的方法是加法模型(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 dataimport quandl # pandas for data manipulation import pandas as pdquandl.ApiConfig.api_key = 'getyourownkey!' # Retrieve TSLA data from Quandltesla = quandl.get('WIKI/TSLA') # Retrieve the GM data from Quandlgm = 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 中的加法模型是必經之路!