如何将数据分为3组(训练,验证和测试)?


145

我有一个熊猫数据框,我希望将其分为3组。我知道使用train_test_splitsklearn.cross_validation,一个可以在两个集(训练集和测试)分割数据。但是,我找不到将数据分为三组的任何解决方案。最好是,我想拥有原始数据的索引。

我知道一种解决方法是使用train_test_split两次并以某种方式调整索引。但是,是否存在更标准/内置的方式将数据分为3组而不是2组?


5
这不能回答您的特定问题,但是我认为更标准的方法是将其分为两组,即训练和测试,并在训练集上进行交叉验证,从而消除了对单独的“开发”集的需求。
大卫

1
这是以前提出的,据我所知,还没有内置方法。
ayhan '16

5
我建议Hastie等人在“统计学习的要素”中讨论为什么要使用三组而不是两套(web.stanford.edu/~hastie/local.ftp/Springer/OLD/…模型评估和选择一章)
ayhan '16

2
@David在某些模型中,为了防止过度拟合,需要3个而不是2个。因为在您的设计选择中,您正在某种程度上调整参数以提高测试集的性能。为避免这种情况,需要开发套件。因此,使用交叉验证是不够的。
CentAu '16

6
@ayhan,该书的更正网址是statweb.stanford.edu/~tibs/ElemStatLearn/printings / ...,第7章(第219页)。
卡米尔·古德塞内

Answers:


161

脾气暴躁的解决方案。我们将首先对整个数据集进行洗牌(df.sample(frac = 1)),然后将数据集分为以下几部分:

  • 60%-火车
  • 20%-验证集,
  • 20%-测试集

In [305]: train, validate, test = np.split(df.sample(frac=1), [int(.6*len(df)), int(.8*len(df))])

In [306]: train
Out[306]:
          A         B         C         D         E
0  0.046919  0.792216  0.206294  0.440346  0.038960
2  0.301010  0.625697  0.604724  0.936968  0.870064
1  0.642237  0.690403  0.813658  0.525379  0.396053
9  0.488484  0.389640  0.599637  0.122919  0.106505
8  0.842717  0.793315  0.554084  0.100361  0.367465
7  0.185214  0.603661  0.217677  0.281780  0.938540

In [307]: validate
Out[307]:
          A         B         C         D         E
5  0.806176  0.008896  0.362878  0.058903  0.026328
6  0.145777  0.485765  0.589272  0.806329  0.703479

In [308]: test
Out[308]:
          A         B         C         D         E
4  0.521640  0.332210  0.370177  0.859169  0.401087
3  0.333348  0.964011  0.083498  0.670386  0.169619

[int(.6*len(df)), int(.8*len(df))]-是numpy.split()indices_or_sections数组。

这是一个小np.split()用法演示-让我们将20个元素的数组拆分为以下部分:80%,10%,10%:

In [45]: a = np.arange(1, 21)

In [46]: a
Out[46]: array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])

In [47]: np.split(a, [int(.8 * len(a)), int(.9 * len(a))])
Out[47]:
[array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16]),
 array([17, 18]),
 array([19, 20])]

@root frac = 1参数到底在做什么?
SpiderWasp42

1
@ SpiderWasp42,frac=1指示sample()函数返回所有(100%或小数= 1.0)行
MaxU

12
谢谢@MaxU。我想提两件事以使事情简化。首先,np.random.seed(any_number)在分割线前使用以获得每次运行相同的结果。其次,要使train:test:val::50:40:10使用比例不相等[int(.5*len(dfn)), int(.9*len(dfn))]。在此,第一个元素表示train(0.5%)的尺寸,第二个元素表示val(1-0.9 = 0.1%)的尺寸,两者之差表示test(0.9-0.5 = 0.4%)的尺寸。如果我错了,请纠正我:)
dataLeo '18年

当您说“这是一个有关np.split()使用的小演示-让我们将20个元素的数组拆分为以下部分时,是hrmm吗?90%,10%,10%:”我很确定您的意思是80 %,10%,10%
凯文

嘿,@ MaxU我有个案例,有点类似。我想知道您是否可以帮我看看它是否存在,并在那里帮助我。这里是我的问题stackoverflow.com/questions/54847668/...
迪派中号

55

注意:

编写函数来处理随机集创建的种子。您不应该依赖不会随机化集合的集合拆分。

import numpy as np
import pandas as pd

def train_validate_test_split(df, train_percent=.6, validate_percent=.2, seed=None):
    np.random.seed(seed)
    perm = np.random.permutation(df.index)
    m = len(df.index)
    train_end = int(train_percent * m)
    validate_end = int(validate_percent * m) + train_end
    train = df.iloc[perm[:train_end]]
    validate = df.iloc[perm[train_end:validate_end]]
    test = df.iloc[perm[validate_end:]]
    return train, validate, test

示范

np.random.seed([3,1415])
df = pd.DataFrame(np.random.rand(10, 5), columns=list('ABCDE'))
df

在此处输入图片说明

train, validate, test = train_validate_test_split(df)

train

在此处输入图片说明

validate

在此处输入图片说明

test

在此处输入图片说明


1
我相信此功能需要索引值为1到n的df。就我而言,我修改了函数以使用df.loc,因为我的索引值不一定在此范围内。
iOSBeginner

32

然而,一种方法将所述数据集成traintestcv0.60.20.2是使用该train_test_split方法的两倍。

from sklearn.model_selection import train_test_split

x, x_test, y, y_test = train_test_split(xtrain,labels,test_size=0.2,train_size=0.8)
x_train, x_cv, y_train, y_cv = train_test_split(x,y,test_size = 0.25,train_size =0.75)

大型数据集的次优
Maksym Ganenko '19

@MaksymGanenko您能详细说明一下吗?
blitu12345'9

您建议使用两个单独的操作拆分数据。每个数据拆分都涉及数据复制。因此,当您建议使用两个单独的拆分操作而不是一个时,会人为地给RAM和CPU造成负担。因此,您的解决方案不是最佳的。数据拆分应使用单个操作完成np.split()。此外,它不需要额外的依赖sklearn
Maksym Ganenko,

@MaksymGanenko同意增加内存负担,同样,我们也可以从内存中删除原始数据,即(xtrain&labels)!关于您使用numpy的建议在某种程度上仅限于整数数据类型,其他数据类型又如何呢?
blitu12345

1
这种方法的另一个好处是可以使用分层参数。
Ami Tavory

7

这是一个Python函数,可通过分层采样将Pandas数据帧分为训练,验证和测试数据帧。它通过train_test_split()两次调用scikit-learn的函数来执行此拆分。

import pandas as pd
from sklearn.model_selection import train_test_split

def split_stratified_into_train_val_test(df_input, stratify_colname='y',
                                         frac_train=0.6, frac_val=0.15, frac_test=0.25,
                                         random_state=None):
    '''
    Splits a Pandas dataframe into three subsets (train, val, and test)
    following fractional ratios provided by the user, where each subset is
    stratified by the values in a specific column (that is, each subset has
    the same relative frequency of the values in the column). It performs this
    splitting by running train_test_split() twice.

    Parameters
    ----------
    df_input : Pandas dataframe
        Input dataframe to be split.
    stratify_colname : str
        The name of the column that will be used for stratification. Usually
        this column would be for the label.
    frac_train : float
    frac_val   : float
    frac_test  : float
        The ratios with which the dataframe will be split into train, val, and
        test data. The values should be expressed as float fractions and should
        sum to 1.0.
    random_state : int, None, or RandomStateInstance
        Value to be passed to train_test_split().

    Returns
    -------
    df_train, df_val, df_test :
        Dataframes containing the three splits.
    '''

    if frac_train + frac_val + frac_test != 1.0:
        raise ValueError('fractions %f, %f, %f do not add up to 1.0' % \
                         (frac_train, frac_val, frac_test))

    if stratify_colname not in df_input.columns:
        raise ValueError('%s is not a column in the dataframe' % (stratify_colname))

    X = df_input # Contains all columns.
    y = df_input[[stratify_colname]] # Dataframe of just the column on which to stratify.

    # Split original dataframe into train and temp dataframes.
    df_train, df_temp, y_train, y_temp = train_test_split(X,
                                                          y,
                                                          stratify=y,
                                                          test_size=(1.0 - frac_train),
                                                          random_state=random_state)

    # Split the temp dataframe into val and test dataframes.
    relative_frac_test = frac_test / (frac_val + frac_test)
    df_val, df_test, y_val, y_test = train_test_split(df_temp,
                                                      y_temp,
                                                      stratify=y_temp,
                                                      test_size=relative_frac_test,
                                                      random_state=random_state)

    assert len(df_input) == len(df_train) + len(df_val) + len(df_test)

    return df_train, df_val, df_test

以下是一个完整的工作示例。

考虑具有标签的数据集,您要在该标签上进行分层。该标签在原始数据集中具有自己的分布,例如75%foo,15%bar和10%baz。现在,让我们使用60/20/20的比率将数据集分为训练,验证和测试子集,其中每个分割都保留标签的相同分布。请参见下图:

在此处输入图片说明

这是示例数据集:

df = pd.DataFrame( { 'A': list(range(0, 100)),
                     'B': list(range(100, 0, -1)),
                     'label': ['foo'] * 75 + ['bar'] * 15 + ['baz'] * 10 } )

df.head()
#    A    B label
# 0  0  100   foo
# 1  1   99   foo
# 2  2   98   foo
# 3  3   97   foo
# 4  4   96   foo

df.shape
# (100, 3)

df.label.value_counts()
# foo    75
# bar    15
# baz    10
# Name: label, dtype: int64

现在,让我们split_stratified_into_train_val_test()从上方调用该函数以按照60/20/20的比例获取训练,验证和测试数据帧。

df_train, df_val, df_test = \
    split_stratified_into_train_val_test(df, stratify_colname='label', frac_train=0.60, frac_val=0.20, frac_test=0.20)

三个数据框df_traindf_valdf_test包含所有原始行,但它们的大小将遵循上述比率。

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 3)

此外,三个拆分中的每个拆分将具有相同的标签分布,即75%foo,15%bar和10%baz

df_train.label.value_counts()
# foo    45
# bar     9
# baz     6
# Name: label, dtype: int64

df_val.label.value_counts()
# foo    15
# bar     3
# baz     2
# Name: label, dtype: int64

df_test.label.value_counts()
# foo    15
# bar     3
# baz     2
# Name: label, dtype: int64

NameError:未定义名称“ df”。split_stratified_into_train_val_test()中的“ df”应替换为“ df_input”。
幻想波洛克

谢谢。我修好了它。问题出在代码的错误处理路径中。
stackoverflowuser2010

1

train_test_split在划分为几组并且不编写一些其他代码之后不执行重新索引的情况下,使用起来非常方便。上面的最佳答案没有提到通过使用train_test_split不改变分区大小来分隔两次不会给出最初想要的分区:

x_train, x_remain = train_test_split(x, test_size=(val_size + test_size))

然后,验证和测试集中x_remain中的部分发生变化,可以算作

new_test_size = np.around(test_size / (val_size + test_size), 2)
# To preserve (new_test_size + new_val_size) = 1.0 
new_val_size = 1.0 - new_test_size

x_val, x_test = train_test_split(x_remain, test_size=new_test_size)

在这种情况下,将保存所有初始分区。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.