原文:《How to Convert a Time Series to a Supervised Learning Problem in Python》---Jason Brownlee
一、前言
像深度學(xué)習(xí)這樣的機(jī)器學(xué)習(xí)方法可以用于時間序列預(yù)測。
在機(jī)器學(xué)習(xí)方法可以被使用前,時間序列預(yù)測問題必須重新構(gòu)建成監(jiān)督學(xué)習(xí)問題,從一個單純的序列變成一對序列輸入和輸出。
在這個教程中,你將了解如何將單變量和多變量時間序列預(yù)測問題轉(zhuǎn)換為與機(jī)器學(xué)習(xí)算法一起使用的監(jiān)督學(xué)習(xí)問題。
在你完成本教程后,你將知道:
- 如何開發(fā)一個功能,將時間序列數(shù)據(jù)集轉(zhuǎn)換為監(jiān)督學(xué)習(xí)數(shù)據(jù)集。
- 如何變換單變量時間序列數(shù)據(jù)進(jìn)行機(jī)器學(xué)習(xí)。
- 如何變換多元時間序列數(shù)據(jù)進(jìn)行機(jī)器學(xué)習(xí)。
讓我們開始吧。
二、時間序列與監(jiān)督學(xué)習(xí)
在我們開始之前,讓我們花點時間來更好地理解時間序列和監(jiān)督學(xué)習(xí)數(shù)據(jù)的形式。
時間序列是由時間索引排序的一系列數(shù)字, 這可以被認(rèn)為是有序值列表或列。
例如:
0
1
2
3
4
5
6
7
8
9
監(jiān)督學(xué)習(xí)問題由輸入模式(X)和輸出模式(y)組成,使得算法可以學(xué)習(xí)如何從輸入模式預(yù)測輸出模式。
例如:
X, y
1 2
2, 3
3, 4
4, 5
5, 6
6, 7
7, 8
8, 9
有關(guān)這個主題更多的信息,請參考如下文章:
三、Pandas shift()方法介紹
幫助將時間序列數(shù)據(jù)轉(zhuǎn)化為監(jiān)督學(xué)習(xí)問題的關(guān)鍵方法是Pandas shift()函數(shù)。
給定一個DataFrame,可以使用shift()函數(shù)來創(chuàng)建向前推送的列的副本(NaN值的行添加到前面)或拉回(添加到最后的NaN值的行)。
這是創(chuàng)建滯后觀察列以及監(jiān)督學(xué)習(xí)格式的時間序列數(shù)據(jù)集的預(yù)測觀測列所需的行為。
我們來看一些shift()函數(shù)的實際使用案例。
我們可以定義一個由10個數(shù)字組成的序列來模擬時間序列數(shù)據(jù)集,在這種情況下,DataFrame中的單個列如下所示:
from pandas import DataFrame
df = DataFrame()
df['t'] = [x for x in range(10)]
print(df)
運(yùn)行上面的例子,按行打印時間序列數(shù)據(jù),輸出如下:
t
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
我們可以通過在頂部插入一個新的行來將所有的觀察結(jié)果向下移動一步。 由于新行沒有數(shù)據(jù),我們可以使用NaN來表示“無數(shù)據(jù)”。
shift()函數(shù)可以為我們做到這一點,我們可以插入這個移位列在我們原始列的旁邊。
from pandas import DataFrame
df = DataFrame()
df['t'] = [x for x in range(10)]
df['t-1'] = df['t'].shift(1)
print(df)
運(yùn)行示例,我們發(fā)現(xiàn)數(shù)據(jù)集中有了兩列的值,第一個是原來的列和一個新的shift()函數(shù)產(chǎn)生的列。
我們可以看到,將序列向前移動一步,我們構(gòu)造出了一個原始的監(jiān)督學(xué)習(xí)問題,盡管X和y的順序是錯誤的。 忽略行標(biāo)簽的那一列,由于NaN值,第一行需要被丟棄。 第二行顯示第二列(輸入或X)中的輸入值0.0和第一列(輸出或y)中的值1。
t t-1
0 0 NaN
1 1 0.0
2 2 1.0
3 3 2.0
4 4 3.0
5 5 4.0
6 6 5.0
7 7 6.0
8 8 7.0
9 9 8.0
我們可以看到,如果我們可以重復(fù)上述過程,通過移動2步,3步和更多的移位,我們?nèi)绾蝿?chuàng)建長的輸入序列(X),用來預(yù)測輸出值(y)。
移位運(yùn)算符也可以接受一個負(fù)整數(shù)值。 這樣做的結(jié)果是通過在最后插入新行來提取結(jié)果。 下面是一個例子:
from pandas import DataFrame
df = DataFrame()
df['t'] = [x for x in range(10)]
df['t+1'] = df['t'].shift(-1)
print(df)
運(yùn)行該示例,顯示了一個最后一行值為NaN的新列。
我們可以看到,原始列可以作為輸入(X),第二個新列作為輸出值(y)。 那就是輸入值0可以用來預(yù)測1的輸出值。
t t+1
0 0 1.0
1 1 2.0
2 2 3.0
3 3 4.0
4 4 5.0
5 5 6.0
6 6 7.0
7 7 8.0
8 8 9.0
9 9 NaN
在技術(shù)上,在時間序列預(yù)測術(shù)語中,當(dāng)前時間(t)和未來時間(t + 1,t + n)是預(yù)測時間,過去的觀測值(t-1,t-n)被用于預(yù)測。
我們可以看到正向和負(fù)向的移動可以用來創(chuàng)建一個新的數(shù)據(jù)幀,從而轉(zhuǎn)變成監(jiān)督學(xué)習(xí)問題的時間序列的輸入和輸出模式。
這不僅允許經(jīng)典的X - > y預(yù)測,而且允許X - > Y,其中輸入和輸出都可以是序列。
此外,移位函數(shù)也適用于所謂的多元時間序列問題。 我們有多個(例如溫度和壓力),而不是有一組時間序列的觀測值。 時間序列中的所有變量可以向前或向后移動以創(chuàng)建多元輸入和輸出序列。 我們將在本教程稍后討論這個問題。
四、series_to_supervised()函數(shù)介紹
我們可以通過給定的輸入和輸出序列的長度,使用Pandas中的shift()函數(shù)自動創(chuàng)建新的時間序列問題的框架。
這將是一個有用的工具,因為它可以讓我們使用機(jī)器學(xué)習(xí)算法探索不同框架的時間序列問題,來找到更好的模型。
在本節(jié)中,我們將定義一個名為series_to_supervised()的新Python函數(shù),它采用單變量或多變量時間序列,并將其作為監(jiān)督學(xué)習(xí)數(shù)據(jù)集。
該函數(shù)有四個參數(shù):
- 數(shù)據(jù):序列,列表或二維的NumPy數(shù)組。 必需的參數(shù)。
- n_in:作為輸入的滯后步數(shù)(X)。 值可能介于[1..len(data)],可選參數(shù)。 默認(rèn)為1。
- n_out:作為輸出的移動步數(shù)(y)。 值可以在[0..len(data)-1]之間, 可選參數(shù)。 默認(rèn)為1。
- dropnan:Boolean是否刪除具有NaN值的行。 可選參數(shù)。 默認(rèn)為True。
該函數(shù)返回一個單一的值:
- 返回:作為監(jiān)督學(xué)習(xí)序列的Pandas DataFrame類型值。
新的數(shù)據(jù)集被構(gòu)造為一個DataFrame,每一列都適當(dāng)?shù)匾钥勺償?shù)量和時間步長命名。 這允許您從給定的單變量或多變量時間序列中設(shè)計各種不同的時間步長序列類型預(yù)測問題。
一旦DataFrame返回,您可以決定如何將返回的DataFrame的行分割為X和Y兩部分,以便以任何您希望的方式監(jiān)督學(xué)習(xí)。
這個函數(shù)是用默認(rèn)參數(shù)定義的,所以如果你只用你的數(shù)據(jù)調(diào)用它,它將構(gòu)造一個DataFrame,其中t-1為X,t為y。
該函數(shù)可以在Python 2和Python 3中運(yùn)行,下面列出了完整的功能,包括功能注釋:
from pandas import DataFrame
from pandas import concat
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
"""
Frame a time series as a supervised learning dataset.
Arguments:
data: Sequence of observations as a list or NumPy array.
n_in: Number of lag observations as input (X).
n_out: Number of observations as output (y).
dropnan: Boolean whether or not to drop rows with NaN values.
Returns:
Pandas DataFrame of series framed for supervised learning.
"""
n_vars = 1 if type(data) is list else data.shape[1]
df = DataFrame(data)
cols, names = list(), list()
# input sequence (t-n, ... t-1)
for i in range(n_in, 0, -1):
cols.append(df.shift(i))
names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
# forecast sequence (t, t+1, ... t+n)
for i in range(0, n_out):
cols.append(df.shift(-i))
if i == 0:
names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
else:
names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
# put it all together
agg = concat(cols, axis=1)
agg.columns = names
# drop rows with NaN values
if dropnan:
agg.dropna(inplace=True)
return agg
如果你發(fā)現(xiàn)什么好的方法,可以使上面的函數(shù)更強(qiáng)大或更可讀,請在下面的評論中告訴我。
現(xiàn)在我們有了全部的函數(shù),我們可以探索如何使用它。
五、移動一步的單變量預(yù)測
在時間序列預(yù)測中的標(biāo)準(zhǔn)做法是使用過去的觀察值(例如t-1)作為輸入變量來預(yù)測當(dāng)前的時間步長(t),這被稱為一步預(yù)測。
下面的例子演示了使用過去的時間步(t-1)來預(yù)測當(dāng)前時間步長(t)的一個例子。
from pandas import DataFrame
from pandas import concat
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
"""
Frame a time series as a supervised learning dataset.
Arguments:
data: Sequence of observations as a list or NumPy array.
n_in: Number of lag observations as input (X).
n_out: Number of observations as output (y).
dropnan: Boolean whether or not to drop rows with NaN values.
Returns:
Pandas DataFrame of series framed for supervised learning.
"""
n_vars = 1 if type(data) is list else data.shape[1]
df = DataFrame(data)
cols, names = list(), list()
# input sequence (t-n, ... t-1)
for i in range(n_in, 0, -1):
cols.append(df.shift(i))
names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
# forecast sequence (t, t+1, ... t+n)
for i in range(0, n_out):
cols.append(df.shift(-i))
if i == 0:
names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
else:
names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
# put it all together
agg = concat(cols, axis=1)
agg.columns = names
# drop rows with NaN values
if dropnan:
agg.dropna(inplace=True)
return agg
values = [x for x in range(10)]
data = series_to_supervised(values)
print(data)
運(yùn)行上面的代碼,輸出結(jié)果如下:
var1(t-1) var1(t)
1 0.0 1
2 1.0 2
3 2.0 3
4 3.0 4
5 4.0 5
6 5.0 6
7 6.0 7
8 7.0 8
9 8.0 9
我們可以看到,列值被命名為“var1”,輸入列值被命名為(t-1),輸出時間步長命名為(t)。
我們還可以看到,具有NaN值的行已經(jīng)從DataFrame中自動刪除。
我們可以用任意數(shù)量的長度輸入序列(如3)來重復(fù)這個例子,這可以通過指定輸入序列的長度作為參數(shù)來完成; 例如:
data = series_to_supervised(values, 3)
完整的例子如下所示:
from pandas import DataFrame
from pandas import concat
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
"""
Frame a time series as a supervised learning dataset.
Arguments:
data: Sequence of observations as a list or NumPy array.
n_in: Number of lag observations as input (X).
n_out: Number of observations as output (y).
dropnan: Boolean whether or not to drop rows with NaN values.
Returns:
Pandas DataFrame of series framed for supervised learning.
"""
n_vars = 1 if type(data) is list else data.shape[1]
df = DataFrame(data)
cols, names = list(), list()
# input sequence (t-n, ... t-1)
for i in range(n_in, 0, -1):
cols.append(df.shift(i))
names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
# forecast sequence (t, t+1, ... t+n)
for i in range(0, n_out):
cols.append(df.shift(-i))
if i == 0:
names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
else:
names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
# put it all together
agg = concat(cols, axis=1)
agg.columns = names
# drop rows with NaN values
if dropnan:
agg.dropna(inplace=True)
return agg
values = [x for x in range(10)]
data = series_to_supervised(values, 3)
print(data)
再次運(yùn)行該示例,并打印重新構(gòu)建的序列。 我們可以看到,輸入序列是按照正確的從左到右的順序,輸出變量是在最右邊預(yù)測的。
var1(t-3) var1(t-2) var1(t-1) var1(t)
3 0.0 1.0 2.0 3
4 1.0 2.0 3.0 4
5 2.0 3.0 4.0 5
6 3.0 4.0 5.0 6
7 4.0 5.0 6.0 7
8 5.0 6.0 7.0 8
9 6.0 7.0 8.0 9
六、多步或者序列預(yù)測
另一種類型的預(yù)測問題是使用過去的值來預(yù)測未來的序列值,這可以被稱為序列預(yù)測或多步預(yù)測。
我們可以通過指定另一個參數(shù)來構(gòu)建序列預(yù)測的時間序列。 例如,我們可以用2個過去的觀測值的輸入序列來構(gòu)造一個預(yù)測問題,以便預(yù)測2個未來的觀測值如下:
data = series_to_supervised(values, 2, 2)
完整的代碼如下:
from pandas import DataFrame
from pandas import concat
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
"""
Frame a time series as a supervised learning dataset.
Arguments:
data: Sequence of observations as a list or NumPy array.
n_in: Number of lag observations as input (X).
n_out: Number of observations as output (y).
dropnan: Boolean whether or not to drop rows with NaN values.
Returns:
Pandas DataFrame of series framed for supervised learning.
"""
n_vars = 1 if type(data) is list else data.shape[1]
df = DataFrame(data)
cols, names = list(), list()
# input sequence (t-n, ... t-1)
for i in range(n_in, 0, -1):
cols.append(df.shift(i))
names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
# forecast sequence (t, t+1, ... t+n)
for i in range(0, n_out):
cols.append(df.shift(-i))
if i == 0:
names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
else:
names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
# put it all together
agg = concat(cols, axis=1)
agg.columns = names
# drop rows with NaN values
if dropnan:
agg.dropna(inplace=True)
return agg
values = [x for x in range(10)]
data = series_to_supervised(values, 2, 2)
print(data)
運(yùn)行上面的代碼,輸出結(jié)果如下,t-2和t-1作為輸入序列,t和t+1作為輸出序列:
var1(t-2) var1(t-1) var1(t) var1(t+1)
2 0.0 1.0 2 3.0
3 1.0 2.0 3 4.0
4 2.0 3.0 4 5.0
5 3.0 4.0 5 6.0
6 4.0 5.0 6 7.0
7 5.0 6.0 7 8.0
8 6.0 7.0 8 9.0
七、多變量預(yù)測
另一個重要的時間序列稱為多元時間序列。
這是我們可以觀察到多種不同的方式,并有興趣預(yù)測其中的一個或多個。
例如,我們可能有兩組時間序列觀測obs1和obs2,我們希望預(yù)測其中的一個或兩個。
我們可以以完全相同的方式調(diào)用series_to_supervised(),如下:
from pandas import DataFrame
from pandas import concat
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
"""
Frame a time series as a supervised learning dataset.
Arguments:
data: Sequence of observations as a list or NumPy array.
n_in: Number of lag observations as input (X).
n_out: Number of observations as output (y).
dropnan: Boolean whether or not to drop rows with NaN values.
Returns:
Pandas DataFrame of series framed for supervised learning.
"""
n_vars = 1 if type(data) is list else data.shape[1]
df = DataFrame(data)
cols, names = list(), list()
# input sequence (t-n, ... t-1)
for i in range(n_in, 0, -1):
cols.append(df.shift(i))
names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
# forecast sequence (t, t+1, ... t+n)
for i in range(0, n_out):
cols.append(df.shift(-i))
if i == 0:
names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
else:
names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
# put it all together
agg = concat(cols, axis=1)
agg.columns = names
# drop rows with NaN values
if dropnan:
agg.dropna(inplace=True)
return agg
raw = DataFrame()
raw['ob1'] = [x for x in range(10)]
raw['ob2'] = [x for x in range(50, 60)]
values = raw.values
data = series_to_supervised(values)
print(data)
運(yùn)行示例將打印數(shù)據(jù),為顯示一個時間步長但是包含兩個變量的輸入模式,以及一個時間步長兩個變量的輸出模式。
同樣,根據(jù)問題的具體情況,可以任意選擇將列分成X和Y,例如,如果當(dāng)前觀察到的var1也作為輸入提供,并且只有var2被預(yù)測。
var1(t-1) var2(t-1) var1(t) var2(t)
1 0.0 50.0 1 51
2 1.0 51.0 2 52
3 2.0 52.0 3 53
4 3.0 53.0 4 54
5 4.0 54.0 5 55
6 5.0 55.0 6 56
7 6.0 56.0 7 57
8 7.0 57.0 8 58
9 8.0 58.0 9 59
通過指定輸入和輸出序列的長度,您可以看到如何使用多元時間序列輕松地進(jìn)行序列預(yù)測。
例如,下面是以1個時間步驟作為輸入和2個時間步驟作為預(yù)測序列的重新構(gòu)造的示例。
from pandas import DataFrame
from pandas import concat
def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):
"""
Frame a time series as a supervised learning dataset.
Arguments:
data: Sequence of observations as a list or NumPy array.
n_in: Number of lag observations as input (X).
n_out: Number of observations as output (y).
dropnan: Boolean whether or not to drop rows with NaN values.
Returns:
Pandas DataFrame of series framed for supervised learning.
"""
n_vars = 1 if type(data) is list else data.shape[1]
df = DataFrame(data)
cols, names = list(), list()
# input sequence (t-n, ... t-1)
for i in range(n_in, 0, -1):
cols.append(df.shift(i))
names += [('var%d(t-%d)' % (j+1, i)) for j in range(n_vars)]
# forecast sequence (t, t+1, ... t+n)
for i in range(0, n_out):
cols.append(df.shift(-i))
if i == 0:
names += [('var%d(t)' % (j+1)) for j in range(n_vars)]
else:
names += [('var%d(t+%d)' % (j+1, i)) for j in range(n_vars)]
# put it all together
agg = concat(cols, axis=1)
agg.columns = names
# drop rows with NaN values
if dropnan:
agg.dropna(inplace=True)
return agg
raw = DataFrame()
raw['ob1'] = [x for x in range(10)]
raw['ob2'] = [x for x in range(50, 60)]
values = raw.values
data = series_to_supervised(values, 1, 2)
print(data)
運(yùn)行上面的代碼,輸出如下:
var1(t-1) var2(t-1) var1(t) var2(t) var1(t+1) var2(t+1)
1 0.0 50.0 1 51 2.0 52.0
2 1.0 51.0 2 52 3.0 53.0
3 2.0 52.0 3 53 4.0 54.0
4 3.0 53.0 4 54 5.0 55.0
5 4.0 54.0 5 55 6.0 56.0
6 5.0 55.0 6 56 7.0 57.0
7 6.0 56.0 7 57 8.0 58.0
8 7.0 57.0 8 58 9.0 59.0
嘗試使用自己的數(shù)據(jù)集,并嘗試使用多個不同的框架,以查看最佳效果。
八、總結(jié)
在本教程中,您發(fā)現(xiàn)了如何將時間序列數(shù)據(jù)集重新組織為有監(jiān)督的Python學(xué)習(xí)問題。
具體來說,你了解到:
- 關(guān)于Pandas shift()函數(shù)及其如何用于從時間序列數(shù)據(jù)中自動定義監(jiān)督學(xué)習(xí)數(shù)據(jù)集。
- 如何將單變量時間序列重構(gòu)為一步多步監(jiān)督學(xué)習(xí)問題。
- 如何將多元時間序列重構(gòu)為一步多步監(jiān)督學(xué)習(xí)問題。