Como dividir dados em 3 conjuntos (treinamento, validação e teste)?

146

Eu tenho um dataframe de pandas e desejo dividi-lo em 3 conjuntos separados. Eu sei que usando train_test_split de sklearn.cross_validation, é possível dividir os dados em dois conjuntos (train e test). No entanto, não consegui encontrar nenhuma solução para dividir os dados em três conjuntos. De preferência, eu gostaria de ter os índices dos dados originais.

Eu sei que uma solução alternativa seria usar train_test_splitduas vezes e de alguma forma ajustar os índices. Mas existe uma maneira mais padrão / interna de dividir os dados em 3 conjuntos em vez de 2?

CentAu
fonte
5
Isso não responde à sua pergunta específica, mas acho que a abordagem mais padrão para isso seria dividir em dois conjuntos, treinar e testar e executar validação cruzada no conjunto de treinamento, eliminando assim a necessidade de um conjunto de "desenvolvimento" independente .
David
1
Isso surgiu antes e, até onde eu sei, ainda não existe um método interno para isso.
Ayhan
5
Sugiro The Elements of Statistical Learning de Hastie et al. Para uma discussão sobre por que usar três conjuntos em vez de dois ( web.stanford.edu/~hastie/local.ftp/Springer/OLD/… capítulo de avaliação e seleção de modelos)
Ayhan
2
@ David Em alguns modelos para evitar o ajuste excessivo, são necessários 3 conjuntos em vez de 2. Como nas opções de design, você está ajustando os parâmetros para melhorar o desempenho no conjunto de teste. Para evitar isso, é necessário um conjunto de desenvolvimento. Portanto, o uso da validação cruzada não será suficiente.
CentAu 07/07
6
@ayhan, uma URL corrigida para esse livro é statweb.stanford.edu/~tibs/ElemStatLearn/printings/… , capítulo 7 (p. 219).
Camille Goudeseune

Respostas:

161

Solução Numpy. Vamos embaralhar o conjunto de dados inteiro primeiro (df.sample (frac = 1)) e depois dividimos nosso conjunto de dados nas seguintes partes:

  • 60% - conjunto de trem,
  • 20% - conjunto de validação,
  • 20% - conjunto de teste

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))]- é uma indices_or_sectionsmatriz para numpy.split () .

Aqui está uma pequena demonstração para np.split()uso - vamos dividir a matriz de 20 elementos nas seguintes partes: 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])]
MaxU
fonte
@root o que exatamente está fazendo o parâmetro frac = 1?
SpiderWasp42
1
@ SpiderWasp42, frac=1instrui sample()funcionar para retornar todos ( 100%ou fração = 1.0) fileiras
MaxU
12
Obrigado @MaxU. Eu gostaria de mencionar duas coisas para manter as coisas simplificadas. Primeiro, use np.random.seed(any_number)antes da linha de divisão para obter o mesmo resultado a cada execução. Segundo, para criar uma proporção desigual como o train:test:val::50:40:10uso [int(.5*len(dfn)), int(.9*len(dfn))]. Aqui, o primeiro elemento indica tamanho para train(0,5%), o segundo elemento indica tamanho para val(1-0,9 = 0,1%) e a diferença entre os dois denota tamanho para test(0,9-0,5 = 0,4%). Corrija-me se eu estiver errado :)
dataLeo
hrmm é um erro quando você diz "Aqui está uma pequena demonstração para o uso de np.split () - vamos dividir a matriz de 20 elementos nas seguintes partes: 90%, 10%, 10%:" Tenho certeza que você quer dizer 80 %, 10%, 10%
Kevin
Ei, @MaxU eu tive um caso, algo parecido. Fiquei me perguntando se você poderia olhar para mim para ver se é e me ajudar lá. Aqui está minha pergunta stackoverflow.com/questions/54847668/…
Deepak M
55

Nota:

A função foi escrita para lidar com a propagação da criação de conjuntos aleatórios. Você não deve confiar na divisão de conjuntos que não os randomiza.

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

Demonstração

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

insira a descrição da imagem aqui

train, validate, test = train_validate_test_split(df)

train

insira a descrição da imagem aqui

validate

insira a descrição da imagem aqui

test

insira a descrição da imagem aqui

piRSquared
fonte
1
Eu acredito que esta função requer um df com valores de índice variando de 1 a n. No meu caso, modifiquei a função para usar o df.loc, pois meus valores de índice não estavam necessariamente nesse intervalo.
iOSBeginner 16/04
32

No entanto, uma abordagem para dividir o conjunto de dados em train, test, cvcom 0.6, 0.2, 0.2seria usar o train_test_splitmétodo duas vezes.

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)
blitu12345
fonte
Suboptimal para grandes conjuntos de dados
Maksym Ganenko
@MaksymGanenko Você pode, por favor, elaborar?
Bloque12345 #
Você sugere dividir dados com duas operações separadas. Cada divisão de dados envolve cópia de dados. Portanto, quando você sugere usar duas operações separadas, em vez de uma, cria artificialmente carga na RAM e na CPU. Portanto, sua solução é abaixo do ideal. A divisão de dados deve ser feita com uma única operação como np.split(). Além disso, ele não requer dependência adicional sklearn.
Maksym Ganenko 02/09/19
O @MaksymGanenko concordou com a carga extra na memória e, pelo mesmo motivo, podemos excluir os dados originais da memória, ou seja, xtrain e etiquetas! E sua sugestão para o uso de numpy é um pouco limitada apenas a tipos de dados inteiros, e quanto a outros tipos de dados?
precisa saber é o seguinte
1
Outro benefício dessa abordagem é que você pode usar os parâmetros de estratificação.
Ami Tavory
7

Aqui está uma função Python que divide um quadro de dados do Pandas em quadros de dados de treinamento, validação e teste com amostragem estratificada. Ele realiza essa divisão chamando a função scikit-learn train_test_split()duas vezes.

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

Abaixo está um exemplo completo de trabalho.

Considere um conjunto de dados com um rótulo no qual você deseja executar a estratificação. Esse rótulo tem sua própria distribuição no conjunto de dados original, digamos 75% foo, 15% bare 10% baz. Agora vamos dividir o conjunto de dados em treinamento, validação e teste em subconjuntos usando uma proporção 60/20/20, em que cada divisão retém a mesma distribuição dos rótulos. Veja a ilustração abaixo:

insira a descrição da imagem aqui

Aqui está o exemplo do conjunto de dados:

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

Agora, vamos chamar a split_stratified_into_train_val_test()função de cima para obter quadros de dados de trem, validação e teste seguindo uma proporção de 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)

Os três dataframes df_train, df_vale df_testconter todas as linhas originais, mas seus tamanhos seguirá a relação acima.

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 3)

Além disso, cada uma das três divisões terá a mesma distribuição do rótulo, ou seja, 75% foo, 15% bare 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
stackoverflowuser2010
fonte
NameError: o nome 'df' não está definido. O 'df' em split_stratified_into_train_val_test () deve ser substituído por 'df_input'.
Fantasy Pollock
Obrigado. Eu consertei isso. O problema estava em um caminho de tratamento de erros do código.
stackoverflowuser2010
1

É muito conveniente usar train_test_splitsem executar a reindexação após dividir em vários conjuntos e sem escrever algum código adicional. A melhor resposta acima não menciona que, ao separar duas vezes, train_test_splitsem alterar os tamanhos das partições, você não fornecerá a partição pretendida inicialmente:

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

Em seguida, a parte dos conjuntos de validação e teste no x_remain muda e pode ser contada como

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)

Nesta ocasião, todas as partições iniciais são salvas.

A.Ametov
fonte