scikit-learn 中纵向/面板数据的交叉验证

Posted

技术标签:

【中文标题】scikit-learn 中纵向/面板数据的交叉验证【英文标题】:Cross Validation for longitudinal/panel data in scikit-learn 【发布时间】:2020-12-09 11:19:56 【问题描述】:

我有一些纵向/面板数据,格式如下(数据输入代码在问题下方)。 X 和 y 的观测值按时间和国家/地区编制索引(例如,美国在时间 1,美国在时间 2,CAN 在时间 1)。

    time x  y
USA 1    5  10
USA 2    5  12
USA 3    6  13
CAN 1    2  2
CAN 2    2  3
CAN 3    4  5

我正在尝试使用 sklearn 预测 y。对于一个可重现的例子,我们可以使用线性回归。

为了执行 CV,我不能使用test_train_split,因为这样拆分可能会将来自time = 3 的数据放入X_train,并将来自time = 2 的数据放入y_test。这将无济于事,因为在time = 2,当我们试图预测y 时,我们还没有真正在time = 3 有数据进行训练。

我正在尝试使用 TimeSeriesSplit 来实现 CV,如下图所示:

(来源:https://stats.stackexchange.com/questions/14099/using-k-fold-cross-validation-for-time-series-model-selection)

y = df.y
X = df.drop(['y'], 1)
print(y)
print(X)
from sklearn.model_selection import TimeSeriesSplit

X = X.to_numpy()

from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits = 2, max_train_size=3)
print(tscv)
for train_index, test_index in tscv.split(X):
    print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]

这可以满足我的需要,但并不完全:

TRAIN: [0 1] TEST: [2 3]
TRAIN: [1 2 3] TEST: [4 5]
我现在如何使用 TimeSeriesSplit 索引来交叉验证模型?

我认为复杂的可能是我的数据不是严格的时间序列:它不仅由time 索引,而且还由country 索引,因此数据的纵向/面板性质。

我想要的输出是:

    一系列测试和训练指标让我能够执行“向前走”CV

例如

TRAIN: [1] TEST: [2]
TRAIN: [1 2] TEST: [3]

    X_trainx_testy_testy_train 根据time 的值使用上面的索引拆分,或者明确我是否需要这样做。

    使用“向前走”CV 方法交叉验证的任何模型(例如线性回归)的准确度得分。

编辑:感谢@sabacherli 回答了我的问题的第一部分,并修复了出现的错误。

数据输入代码

import numpy as np
import pandas as pd

data = np.array([['country','time','x','y'],
                ['USA',1, 5, 10],
                ['USA',2, 5, 12],
                ['USA',3,6, 13],
                ['CAN',1,2, 2],
                ['CAN',2,2, 3],
                ['CAN',3,4, 5]],                
               )
                
df = pd.DataFrame(data=data[1:,1:],
                  index=data[1:,0],
                  columns=data[0,1:])

df

【问题讨论】:

如何使用 X.to_numpy() 将您的 X DataFrame 转换为 numpy 对象 @sabacherli 修复了错误!太感谢了。我可能会编辑这个问题,以便我可以在下一步寻求帮助:如果可以的话,如何使用您刚刚帮助我获得的时间序列拆分索引来实现时间序列交叉验证? 【参考方案1】:

TimeSeriesSplit 假定您的数据集是按时间索引的,这意味着每一行都属于不同的时间步长。那么为什么不unstack 数据这样你只有时间作为索引然后拆分。拆分后,您可以再次stack 数据形状以获取您的基础表进行训练。

data = np.array([['country','time','x','y'],
                ['USA',1, 5, 10],
                ['USA',2, 5, 12],
                ['USA',3,6, 13],
                ['CAN',1,2, 2],
                ['CAN',2,2, 3],
                ['CAN',3,4, 5]],                
               )

df = pd.DataFrame(data=data[1:,1:],
                  index=data[1:,0],
                  columns=data[0,1:])

df1 = df.reset_index().set_index(['time','index']).unstack(-1)
print(df1)
        x       y    
index CAN USA CAN USA
time                 
1       2   5   2  10
2       2   5   3  12
3       4   6   5  13

现在,由于每一行都按时间索引,您可以轻松地将这些数据分成组,然后在拆分后再次堆叠以获得您的 X_train X_test 等...

from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits = 2, max_train_size=3)

X_cols = ['time', 'index', 'x']
y_cols = ['y']

for train_index, test_index in tscv.split(df1):
    print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = df1.iloc[train_index].stack(-1).reset_index()[X_cols].to_numpy(), df1.iloc[test_index].stack(-1).reset_index()[X_cols].to_numpy()
    y_train, y_test = df1.iloc[train_index].stack(-1).reset_index()[y_cols].to_numpy(), df1.iloc[test_index].stack(-1).reset_index()[y_cols].to_numpy()
TRAIN: [0] TEST: [1]
TRAIN: [0 1] TEST: [2]

您可以打印最新折叠的 X_train 和 y_train 以查看发生了什么 -

print('For - TRAIN: [0 1] TEST: [2]')
print(" ")
print("X_train:")
print(X_train)
print(" ")
print("X_test:")
print(X_test)
print(" ")
print("y_train:")
print(y_train)
print(" ")
print("y_test:")
print(y_test)
print("X_train:")
print(X_train)
print(" ")
print("X_test:")
print(X_test)
print(" ")
print("y_train:")
print(y_train)
print(" ")
print("y_test:")
print(y_test)
For - TRAIN: [0 1] TEST: [2]

X_train:
[['1' 'CAN' '2']
 ['1' 'USA' '5']
 ['2' 'CAN' '2']
 ['2' 'USA' '5']]
 
X_test:
[['3' 'CAN' '4']
 ['3' 'USA' '6']]
 
y_train:
[['2']
 ['10']
 ['3']
 ['12']]
 
y_test:
[['5']
 ['13']]

因此,现在您可以按时间拆分数据帧,并将其扩展回训练所需的形状。

【讨论】:

对于下一步,一旦我进行了时间序列拆分,我可以将拆分传递给GridSearchCV,如下所示:GridSearchCV(my_pipeline, param_grid, cv = tscv.split(df1), ...)?这就是我目前正在做的事情,我想我会和你核实一下,这是有道理的。再次感谢您。

以上是关于scikit-learn 中纵向/面板数据的交叉验证的主要内容,如果未能解决你的问题,请参考以下文章

scikit-learn 中每个数据拆分的交叉验证指标

在交叉验证后对所有训练数据进行 scikit-learn 训练

回归中的 scikit-learn 交叉验证分数

Scikit-learn SVC 在随机数据交叉验证中总是给出准确度 0

R 与 scikit-learn 中用于线性回归 R2 的交叉验证

评估 scikit-learn GridSearchCV 中交叉验证分数的平均值、标准差