時(shí)間序列預(yù)測(cè):探索性數(shù)據(jù)分析和特征工程的實(shí)用指南
時(shí)間序列分析是數(shù)據(jù)科學(xué)和機(jī)器學(xué)習(xí)領(lǐng)域最廣泛的主題之一:無論是預(yù)測(cè)金融事件、能源消耗、產(chǎn)品銷售還是股票市場(chǎng)趨勢(shì),這一領(lǐng)域一直是企業(yè)非常感興趣的領(lǐng)域。
隨著機(jī)器學(xué)習(xí)模型的不斷進(jìn)步,使除了傳統(tǒng)的統(tǒng)計(jì)預(yù)測(cè)方法(如回歸模型、ARIMA模型、指數(shù)平滑)外,與機(jī)器學(xué)習(xí)(如基于樹的模型)和深度學(xué)習(xí)(如LSTM網(wǎng)絡(luò)、cnn、基于Transformer的模型)相關(guān)的技術(shù)已經(jīng)出現(xiàn)了一段時(shí)間。
盡管這些技術(shù)之間存在巨大差異,但無論模型是什么,都必須完成一個(gè)初步步驟:探索性數(shù)據(jù)分析。
在統(tǒng)計(jì)學(xué)中,探索性數(shù)據(jù)分析(Exploratory Data Analysis, EDA)是對(duì)數(shù)據(jù)進(jìn)行分析和可視化,以總結(jié)數(shù)據(jù)的主要特征并從中獲得相關(guān)信息的一門學(xué)科。這在數(shù)據(jù)科學(xué)領(lǐng)域非常重要,因?yàn)樗梢詾榱硪粋€(gè)重要步驟奠定基礎(chǔ):特征工程。
所以我們今天這篇文章將總結(jié)一個(gè)時(shí)間序列數(shù)據(jù)的分析模板,可以總結(jié)和突出數(shù)據(jù)集的最重要特征。我們將使用一些常見的Python庫(kù),如Pandas、Seaborn和Statsmodel。
為了方便演示,將使用Kaggle的小時(shí)能耗數(shù)據(jù)。該數(shù)據(jù)集與PJM小時(shí)能源消耗數(shù)據(jù)有關(guān),PJM是美國(guó)的一個(gè)區(qū)域輸電組織,為幾個(gè)州提供電力。每小時(shí)的電力消耗數(shù)據(jù)來自PJM的網(wǎng)站,單位是兆瓦。
我在本文中我們將EDA總結(jié)為六個(gè)步驟:描述性統(tǒng)計(jì)、時(shí)間圖、季節(jié)圖、箱形圖、時(shí)間序列分解、滯后分析。
描述性統(tǒng)計(jì)
描述性統(tǒng)計(jì)是一種匯總統(tǒng)計(jì),用于定量地描述或總結(jié)結(jié)構(gòu)化數(shù)據(jù)集合中的特征。
一些通常用于描述數(shù)據(jù)集的度量是:集中趨勢(shì)度量(例如平均值,中位數(shù)),分散度量(例如范圍,標(biāo)準(zhǔn)差)和位置度量(例如百分位數(shù),四分位數(shù))。所有這些都可以用所謂的五數(shù)總結(jié)來概括,即分布的最小值、第一四分位數(shù)(Q1)、中位數(shù)或第二四分位數(shù)(Q2)、第三四分位數(shù)(Q3)和最大值。
在Python中,這些信息可以使用Pandas中眾所周知的describe方法輕松檢索:
import pandas as pd
# Loading and preprocessing steps
df = pd.read_csv('../input/hourly-energy-consumption/PJME_hourly.csv')
df = df.set_index('Datetime')
df.index = pd.to_datetime(df.index)
df.describe()
時(shí)間曲線圖
最明顯且直觀的圖表是時(shí)間圖。觀測(cè)結(jié)果是根據(jù)觀測(cè)時(shí)間繪制的,連續(xù)的觀測(cè)結(jié)果用線條連接起來。
在Python中,我們可以使用Pandas和Matplotlib:
import matplotlib.pyplot as plt
# Set pyplot style
plt.style.use("seaborn")
# Plot
df['PJME_MW'].plot(title='PJME - Time Plot', figsize=(10,6))
plt.ylabel('Consumption [MW]')
plt.xlabel('Date')
這張圖可以為我們提供一下的信息:
- 模式顯示出每年的季節(jié)性。
- 如果只關(guān)注一年,似乎會(huì)發(fā)現(xiàn)更多的規(guī)律。由于用電量較大,冬季和夏季的用電量可能會(huì)出現(xiàn)高峰。
- 這個(gè)數(shù)據(jù)多年來沒有明顯的增加/減少趨勢(shì),平均消費(fèi)量保持平穩(wěn)。
- 2013年前后有一個(gè)異常值,可以進(jìn)行特殊的分析
季節(jié)性
季節(jié)性圖基本上是一個(gè)時(shí)間圖,其中數(shù)據(jù)是根據(jù)它們所屬的系列的各個(gè)“季節(jié)”繪制的。
關(guān)于能源消耗,我們通常有每小時(shí)可用的數(shù)據(jù),因此可以有幾個(gè)季節(jié)性:每年,每周,每天。在深入研究這些圖之前,讓我們首先在Pandas中設(shè)置一些變量:
# Defining required fields
df['year'] = [x for x in df.index.year]
df['month'] = [x for x in df.index.month]
df = df.reset_index()
df['week'] = df['Datetime'].apply(lambda x:x.week)
df = df.set_index('Datetime')
df['hour'] = [x for x in df.index.hour]
df['day'] = [x for x in df.index.day_of_week]
df['day_str'] = [x.strftime('%a') for x in df.index]
df['year_month'] = [str(x.year) + '_' + str(x.month) for x in df.index]
1、年消耗量
一個(gè)非常有趣的圖是按年按月分組的能源消耗,這突出了年度季節(jié)性,可以告訴我們多年來的上升/下降趨勢(shì)。
import numpy as np
# Defining colors palette
np.random.seed(42)
df_plot = df[['month', 'year', 'PJME_MW']].dropna().groupby(['month', 'year']).mean()[['PJME_MW']].reset_index()
years = df_plot['year'].unique()
colors = np.random.choice(list(mpl.colors.XKCD_COLORS.keys()), len(years), replace=False)
# Plot
plt.figure(figsize=(16,12))
for i, y in enumerate(years):
if i > 0:
plt.plot('month', 'PJME_MW', data=df_plot[df_plot['year'] == y], color=colors[i], label=y)
if y == 2018:
plt.text(df_plot.loc[df_plot.year==y, :].shape[0]+0.3, df_plot.loc[df_plot.year==y, 'PJME_MW'][-1:].values[0], y, fnotallow=12, color=colors[i])
else:
plt.text(df_plot.loc[df_plot.year==y, :].shape[0]+0.1, df_plot.loc[df_plot.year==y, 'PJME_MW'][-1:].values[0], y, fnotallow=12, color=colors[i])
# Setting labels
plt.gca().set(ylabel= 'PJME_MW', xlabel = 'Month')
plt.yticks(fnotallow=12, alpha=.7)
plt.title("Seasonal Plot - Monthly Consumption", fnotallow=20)
plt.ylabel('Consumption [MW]')
plt.xlabel('Month')
plt.show()
這張圖顯示,每年都有一個(gè)預(yù)定義的模式:冬季消耗顯著增加,夏季達(dá)到峰值(由于供暖/制冷系統(tǒng)),而春季和秋季通常不需要供暖或制冷時(shí)消耗最小。這張圖還告訴我們,在多年的總消費(fèi)量中,并沒有明顯的增加/減少模式。
2、周消耗量
另一個(gè)有用的圖表是每周圖表,它描述了幾個(gè)月來每周的消費(fèi)情況,還可以表明每周在一年內(nèi)是否以及如何變化。
# Defining colors palette
np.random.seed(42)
df_plot = df[['month', 'day_str', 'PJME_MW', 'day']].dropna().groupby(['day_str', 'month', 'day']).mean()[['PJME_MW']].reset_index()
df_plot = df_plot.sort_values(by='day', ascending=True)
months = df_plot['month'].unique()
colors = np.random.choice(list(mpl.colors.XKCD_COLORS.keys()), len(months), replace=False)
# Plot
plt.figure(figsize=(16,12))
for i, y in enumerate(months):
if i > 0:
plt.plot('day_str', 'PJME_MW', data=df_plot[df_plot['month'] == y], color=colors[i], label=y)
if y == 2018:
plt.text(df_plot.loc[df_plot.mnotallow==y, :].shape[0]-.9, df_plot.loc[df_plot.mnotallow==y, 'PJME_MW'][-1:].values[0], y, fnotallow=12, color=colors[i])
else:
plt.text(df_plot.loc[df_plot.mnotallow==y, :].shape[0]-.9, df_plot.loc[df_plot.mnotallow==y, 'PJME_MW'][-1:].values[0], y, fnotallow=12, color=colors[i])
# Setting Labels
plt.gca().set(ylabel= 'PJME_MW', xlabel = 'Month')
plt.yticks(fnotallow=12, alpha=.7)
plt.title("Seasonal Plot - Weekly Consumption", fnotallow=20)
plt.ylabel('Consumption [MW]')
plt.xlabel('Month')
plt.show()
3、每日消費(fèi)量
最后,我們看看每日消費(fèi)量。它代表了一天中消費(fèi)的變化。數(shù)據(jù)首先按星期進(jìn)行分組,然后按平均值進(jìn)行匯總。
import seaborn as sns
# Defining the dataframe
df_plot = df[['hour', 'day_str', 'PJME_MW']].dropna().groupby(['hour', 'day_str']).mean()[['PJME_MW']].reset_index()
# Plot using Seaborn
plt.figure(figsize=(10,8))
sns.lineplot(data = df_plot, x='hour', y='PJME_MW', hue='day_str', legend=True)
plt.locator_params(axis='x', nbins=24)
plt.title("Seasonal Plot - Daily Consumption", fnotallow=20)
plt.ylabel('Consumption [MW]')
plt.xlabel('Hour')
plt.legend()
這張圖顯示了一個(gè)非常典型的模式,有人稱之為“M輪廓”,因?yàn)槿障乃坪趺枥L了一個(gè)“M”。有時(shí)這種模式是清晰的
這些圖通常在一天的中間(從上午10點(diǎn)到下午2點(diǎn))顯示一個(gè)相對(duì)峰值,然后是一個(gè)相對(duì)最小值(從下午2點(diǎn)到下午6點(diǎn))和另一個(gè)峰值(從下午6點(diǎn)到晚上8點(diǎn))。它還顯示了周末和其他日期的消費(fèi)差異。
4、特征工程
我們?nèi)绾螌⑦@些信息用于特征工程呢?假設(shè)我們正在使用一些需要高質(zhì)量特征的ML模型(例如ARIMA模型或基于樹的模型)。
- 年消費(fèi)量多年來變化不大這表明如果可能的話,可以使用來自滯后或外生變量的年季節(jié)性特征。
- 每周消費(fèi)在幾個(gè)月內(nèi)遵循相同的模式,可以使用來自滯后或外生變量的每周特征。
- 每天的消費(fèi)可以使用工作日和周末的分類特征來進(jìn)行編碼
箱線圖
箱線圖是識(shí)別數(shù)據(jù)分布的有效方法。箱線圖描繪了百分位數(shù),它代表了分布的第一個(gè)(Q1)、第二個(gè)(Q2/中位數(shù))和第三個(gè)(Q3)四分位數(shù),而箱須則代表了數(shù)據(jù)的范圍。超過須的每一個(gè)值都可以被認(rèn)為是一個(gè)離群值,更深入地說,須通常被計(jì)算為:
讓我們首先計(jì)算總消耗的箱線圖,這可以很容易地在Seaborn中完成:
plt.figure(figsize=(8,5))
sns.boxplot(data=df, x='PJME_MW')
plt.xlabel('Consumption [MW]')
plt.title(f'Boxplot - Consumption Distribution');
這個(gè)圖看起來沒有多少信息,但是它能告訴我們數(shù)據(jù)是一個(gè)類似高斯分布的分布,尾巴更向右突出。
下面是日/月箱形圖。它是通過創(chuàng)建一個(gè)“日/月”變量并根據(jù)它對(duì)消費(fèi)進(jìn)行分組而獲得的。以下是2017年以后的代碼
df['year'] = [x for x in df.index.year]
df['month'] = [x for x in df.index.month]
df['year_month'] = [str(x.year) + '_' + str(x.month) for x in df.index]
df_plot = df[df['year'] >= 2017].reset_index().sort_values(by='Datetime').set_index('Datetime')
plt.title(f'Boxplot Year Month Distribution');
plt.xticks(rotatinotallow=90)
plt.xlabel('Year Month')
plt.ylabel('MW')
sns.boxplot(x='year_month', y='PJME_MW', data=df_plot)
plt.ylabel('Consumption [MW]')
plt.xlabel('Year Month')
可以看到,在夏季/冬季(即當(dāng)我們有高峰時(shí))的不確定性較小,而在春季/秋季(即當(dāng)溫度變化較大時(shí))則更加分散。2018年夏季的消費(fèi)量高于2017年,這可能是由于夏季更溫暖。當(dāng)進(jìn)行特征工程時(shí),記得包括(如果可用的話)溫度曲線,可能它可以用作外生變量。
另一個(gè)有用的圖是一周內(nèi)的分布,這類似于每周消費(fèi)季節(jié)性圖。
df_plot = df[['day_str', 'day', 'PJME_MW']].sort_values(by='day')
plt.title(f'Boxplot Day Distribution')
plt.xlabel('Day of week')
plt.ylabel('MW')
sns.boxplot(x='day_str', y='PJME_MW', data=df_plot)
plt.ylabel('Consumption [MW]')
plt.xlabel('Day of week')
如前所述,周末的消費(fèi)明顯較低。有幾個(gè)異常值能夠看到,像“星期幾”這樣的日歷特征肯定是有用的,但又不能完全解釋。
最后我們來看小時(shí)圖。它類似于日消費(fèi)季節(jié)性圖,因?yàn)樗峁┝艘惶熘邢M(fèi)的分布情況。代碼如下:
plt.title(f'Boxplot Hour Distribution');
plt.xlabel('Hour')
plt.ylabel('MW')
sns.boxplot(x='hour', y='PJME_MW', data=df)
plt.ylabel('Consumption [MW]')
plt.xlabel('Hour')
之前看到的“M”形現(xiàn)在更碎了。此外,還有很多異常值,這告訴我們數(shù)據(jù)不僅依賴于日常季節(jié)性(例如,今天12點(diǎn)的消費(fèi)量與昨天12點(diǎn)的消費(fèi)量相似),還依賴于其他一些東西,可能是一些外生氣候特征,如溫度或濕度。
時(shí)間序列分解
時(shí)間序列數(shù)據(jù)可以顯示各種模式。將時(shí)間序列分成幾個(gè)組件是有幫助的,每個(gè)組件表示一個(gè)潛在的模式類別。
我們可以認(rèn)為一個(gè)時(shí)間序列由三個(gè)部分組成:趨勢(shì)部分,季節(jié)部分和剩余(偏差)部分(包含時(shí)間序列中的任何其他部分)。對(duì)于某些時(shí)間序列(例如,能源消耗序列),可以有多個(gè)季節(jié)分量,對(duì)應(yīng)于不同的季節(jié)周期(日、周、月、年)。
分解有兩種主要類型:加性分解和乘法分解。
對(duì)于加性分解,我們將一個(gè)序列(整數(shù))表示為季節(jié)分量(??)、趨勢(shì)分量(??)和余數(shù)(??)的和:
類似地,乘法分解可以寫成:
一般來說,加性分解最適合方差恒定的序列,而乘法分解最適合方差非平穩(wěn)的時(shí)間序列。
在Python中,時(shí)間序列分解可以通過Statsmodel庫(kù)輕松實(shí)現(xiàn):
df_plot = df[df['year'] == 2017].reset_index()
df_plot = df_plot.drop_duplicates(subset=['Datetime']).sort_values(by='Datetime')
df_plot = df_plot.set_index('Datetime')
df_plot['PJME_MW - Multiplicative Decompose'] = df_plot['PJME_MW']
df_plot['PJME_MW - Additive Decompose'] = df_plot['PJME_MW']
# Additive Decomposition
result_add = seasonal_decompose(df_plot['PJME_MW - Additive Decompose'], model='additive', period=24*7)
# Multiplicative Decomposition
result_mul = seasonal_decompose(df_plot['PJME_MW - Multiplicative Decompose'], model='multiplicative', period=24*7)
# Plot
result_add.plot().suptitle('', fnotallow=22)
plt.xticks(rotatinotallow=45)
result_mul.plot().suptitle('', fnotallow=22)
plt.xticks(rotatinotallow=45)
plt.show()
以上圖為2017年的數(shù)據(jù)。我們看到趨勢(shì)有幾個(gè)局部峰值,夏季的值更高。從季節(jié)成分來看,可以看到實(shí)際上有幾個(gè)周期性,該圖更突出了周周期性,但如果我們關(guān)注同年的一個(gè)特定月份(1月),也會(huì)出現(xiàn)日季節(jié)性:
df_plot = df[(df['year'] == 2017)].reset_index()
df_plot = df_plot[df_plot['month'] == 1]
df_plot['PJME_MW - Multiplicative Decompose'] = df_plot['PJME_MW']
df_plot['PJME_MW - Additive Decompose'] = df_plot['PJME_MW']
df_plot = df_plot.drop_duplicates(subset=['Datetime']).sort_values(by='Datetime')
df_plot = df_plot.set_index('Datetime')
# Additive Decomposition
result_add = seasonal_decompose(df_plot['PJME_MW - Additive Decompose'], model='additive', period=24*7)
# Multiplicative Decomposition
result_mul = seasonal_decompose(df_plot['PJME_MW - Multiplicative Decompose'], model='multiplicative', period=24*7)
# Plot
result_add.plot().suptitle('', fnotallow=22)
plt.xticks(rotatinotallow=45)
result_mul.plot().suptitle('', fnotallow=22)
plt.xticks(rotatinotallow=45)
plt.show()
滯后分析
在時(shí)間序列預(yù)測(cè)中,滯后僅僅是序列的過去值。例如,對(duì)于每日序列,第一個(gè)滯后是指該序列前一天的值,第二個(gè)滯后是指再前一天的值,以此類推。
滯后分析是基于計(jì)算序列和序列本身的滯后版本之間的相關(guān)性,這也稱為自相關(guān)。對(duì)于一個(gè)序列的k滯后版本,我們定義自相關(guān)系數(shù)為:
其中y 表示序列的平均值,k表示滯后值。
自相關(guān)系數(shù)構(gòu)成了序列的自相關(guān)函數(shù)(ACF),描繪了自相關(guān)系數(shù)與考慮的滯后數(shù)的關(guān)系。
當(dāng)數(shù)據(jù)具有趨勢(shì)時(shí),小滯后的自相關(guān)通常很大且為正,因?yàn)闀r(shí)間上接近的觀測(cè)值在值上也相近。當(dāng)數(shù)據(jù)顯示季節(jié)性時(shí),季節(jié)性滯后(及其季節(jié)周期的倍數(shù))的自相關(guān)值會(huì)比其他滯后的大。具有趨勢(shì)和季節(jié)性的數(shù)據(jù)將顯示這些效應(yīng)的組合。
在實(shí)踐中,更有用的函數(shù)是偏自相關(guān)函數(shù)(PACF)。它類似于ACF但是它只顯示兩個(gè)滯后之間的直接自相關(guān)。例如,滯后3的偏自相關(guān)指的是滯后1和2無法解釋的唯一相關(guān)性?;蛘哒f偏相關(guān)指的是某個(gè)滯后對(duì)當(dāng)前時(shí)間值的直接影響。
如果序列是平穩(wěn)的,則自相關(guān)系數(shù)會(huì)更清晰地顯現(xiàn),因此通常最好先對(duì)序列進(jìn)行差分以穩(wěn)定信號(hào)。
下面是繪制一天中不同時(shí)段PACF的代碼:
from statsmodels.graphics.tsaplots import plot_pacf
actual = df['PJME_MW']
hours = range(0, 24, 4)
for hour in hours:
plot_pacf(actual[actual.index.hour == hour].diff().dropna(), lags=30, alpha=0.01)
plt.title(f'PACF - h = {hour}')
plt.ylabel('Correlation')
plt.xlabel('Lags')
plt.show()
PACF只是繪制不同滯后的Pearson偏自相關(guān)系數(shù)。非滯后序列顯示出與自身完美的自相關(guān),因此滯后0將始終為1。藍(lán)色帶表示置信區(qū)間:如果滯后超過該帶,則它具有統(tǒng)計(jì)顯著性,我們可以斷言它具有非常重要的意義。
工程特性
滯后分析是時(shí)間序列特征工程中最具影響力的研究之一。具有高相關(guān)性的滯后是序列中重要的特征,因此應(yīng)該考慮在內(nèi)。
一個(gè)廣泛使用的特征工程技術(shù)是對(duì)數(shù)據(jù)集進(jìn)行按小時(shí)劃分。將數(shù)據(jù)分成24個(gè)子集,每個(gè)子集對(duì)應(yīng)一天中的一個(gè)小時(shí)。這樣做可以規(guī)范和平滑信號(hào),使預(yù)測(cè)變得更簡(jiǎn)單。
然后應(yīng)對(duì)每個(gè)子集進(jìn)行特征工程、訓(xùn)練和微調(diào)。最終的預(yù)測(cè)將通過結(jié)合這24個(gè)模型的結(jié)果來實(shí)現(xiàn)。每個(gè)小時(shí)模型都有其特點(diǎn),大多數(shù)將涉及重要的滯后。
我們簡(jiǎn)單介紹在進(jìn)行滯后分析時(shí)可以處理的兩種類型的滯后:
- 自回歸滯后:接近滯后0的滯后,我們預(yù)期這些滯后值較高(最近的滯后更有可能預(yù)測(cè)當(dāng)前值)。它們是序列顯示趨勢(shì)的表現(xiàn)。
- 季節(jié)性滯后:指季節(jié)周期的滯后。在按小時(shí)分割數(shù)據(jù)時(shí),它們通常代表周度季節(jié)性。
自回歸滯后1也可以被認(rèn)為是序列的日度季節(jié)性滯后。
以我們看到的上圖為例:
夜間時(shí)間(0,4)的消耗更多地依賴于自回歸,而不是每周滯后,因?yàn)樽钕嚓P(guān)的都在前五個(gè)小時(shí)。像7、14、21、28這樣的季節(jié)似乎并不太重要,所以特征工程師時(shí)特別注意滯后1到滯后5。
白天時(shí)間(8,12,16,20)的消耗表現(xiàn)出自回歸和季節(jié)性滯后。這在8小時(shí)和12小時(shí)尤其如此,因?yàn)檫@兩個(gè)小時(shí)的消耗量特別高,而隨著夜幕降臨,季節(jié)性滯后變得不那么重要了。對(duì)于這些子集,我們還應(yīng)該包括季節(jié)性滯后和自回歸。
最后還有一些特征工程滯后的提示:
不要考慮太多的滯后,因?yàn)檫@可能會(huì)導(dǎo)致過度擬合。一般自回歸滯后為1 ~ 7,周滯后為7、14、21、28。但并不是必須把它們都當(dāng)作特征。
考慮非自回歸或季節(jié)性的滯后通常是一個(gè)壞主意,因?yàn)樗鼈円部赡軐?dǎo)致過擬合。
對(duì)滯后進(jìn)行一些簡(jiǎn)單的轉(zhuǎn)換通??梢援a(chǎn)生更強(qiáng)大的特征。例如,季節(jié)性滯后可以使用加權(quán)平均值進(jìn)行匯總,以創(chuàng)建代表該系列季節(jié)性的單個(gè)特征。
總結(jié)
本文的目的是為時(shí)間序列預(yù)測(cè)提供一個(gè)全面的探索性數(shù)據(jù)分析模板。
EDA是任何類型的數(shù)據(jù)科學(xué)研究的基本步驟,它允許理解數(shù)據(jù)的性質(zhì)和特性,并為特征工程奠定基礎(chǔ),而特征工程反過來又可以顯著提高模型性能。
我們描述了一些最常用的時(shí)間序列EDA分析,這些分析可以是統(tǒng)計(jì)/數(shù)學(xué)和圖形。這項(xiàng)工作的目的只是提供一個(gè)實(shí)用的框架來開始,后續(xù)的調(diào)查需要根據(jù)所檢查的歷史系列的類型和業(yè)務(wù)背景進(jìn)行。