[轉(zhuǎn)]將時間序列預(yù)測問題轉(zhuǎn)換為python中的監(jiān)督學(xué)習(xí)問題

原文:《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í)問題。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容