Come dividere i dati in 3 set (treno, validazione e test)?


146

Ho un frame di dati Panda e desidero dividerlo in 3 set separati. So che usando train_test_split di sklearn.cross_validation, si possono dividere i dati in due set (train e test). Tuttavia, non sono riuscito a trovare alcuna soluzione sulla suddivisione dei dati in tre set. Preferibilmente, vorrei avere gli indici dei dati originali.

So che una soluzione alternativa sarebbe quella di utilizzare train_test_splitdue volte e in qualche modo regolare gli indici. Ma esiste un modo più standard / integrato per dividere i dati in 3 set anziché in 2?


5
Questo non risponde alla tua domanda specifica, ma penso che l'approccio più standard per questo sarebbe dividere in due set, addestrare e testare ed eseguire la validazione incrociata sul set di addestramento eliminando così la necessità di un set di "sviluppo" autonomo .
David

1
Questo è emerso prima, e per quanto ne so non esiste ancora un metodo integrato per quello.
Ayhan,

5
Suggerisco The Elements of Statistical Learning di Hastie et al. Per una discussione sul perché usare tre set invece di due ( web.stanford.edu/~hastie/local.ftp/Springer/OLD/… capitolo Valutazione del modello e selezione)
Ayhan,

2
@David In alcuni modelli per evitare un overfitting, sono necessari 3 set anziché 2. Perché nelle scelte di progettazione, in qualche modo si stanno ottimizzando i parametri per migliorare le prestazioni sul set di test. Per evitare ciò, è necessario un set di sviluppo. Pertanto, l'utilizzo della convalida incrociata non sarà sufficiente.
CentAu,

6
@ayhan, un URL corretto per quel libro è statweb.stanford.edu/~tibs/ElemStatLearn/printings/… , capitolo 7 (p. 219).
Camille Goudeseune,

Risposte:


161

Soluzione numpy. Prima mescoleremo l'intero set di dati (df.sample (frac = 1)) e poi dividere il nostro set di dati nelle seguenti parti:

  • 60% - set di treni,
  • 20% - set di validazione,
  • 20% - set di test

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))]- è un indices_or_sectionsarray per numpy.split () .

Ecco una piccola demo per l' np.split()uso: suddividiamo un array di 20 elementi nelle seguenti parti: 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 cosa sta facendo esattamente il parametro frac = 1?
SpiderWasp42

1
@ SpiderWasp42, frac=1indica sample()alla funzione di restituire tutte ( 100%o frazione = 1.0) righe
MaxU

12
Grazie @MaxU. Vorrei menzionare 2 cose per semplificare le cose. Innanzitutto, utilizzare np.random.seed(any_number)prima della linea di divisione per ottenere lo stesso risultato ad ogni corsa. In secondo luogo, fare un rapporto ineguale come l' train:test:val::50:40:10uso [int(.5*len(dfn)), int(.9*len(dfn))]. Qui il primo elemento indica la dimensione per train(0,5%), il secondo elemento indica la dimensione per val(1-0,9 = 0,1%) e la differenza tra i due indica la dimensione per test(0,9-0,5 = 0,4%). Correggimi se sbaglio :)
dataLeo

hrmm è un errore quando dici "Ecco una piccola demo per l'utilizzo di np.split () - dividiamo un array di 20 elementi nelle seguenti parti: 90%, 10%, 10%:" Sono abbastanza sicuro che intendi 80 %, 10%, 10%
Kevin

Ehi, @MaxU ho avuto un caso, qualcosa di simile. Mi chiedevo se potevi guardarlo per vedere se lo è e aiutarmi lì. Ecco la mia domanda stackoverflow.com/questions/54847668/…
Deepak M

55

Nota:

La funzione è stata scritta per gestire il seeding della creazione di set randomizzati. Non devi fare affidamento sulla suddivisione dei set che non randomizza i set.

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

Dimostrazione

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

inserisci qui la descrizione dell'immagine

train, validate, test = train_validate_test_split(df)

train

inserisci qui la descrizione dell'immagine

validate

inserisci qui la descrizione dell'immagine

test

inserisci qui la descrizione dell'immagine


1
Credo che questa funzione richieda un df con valori di indice compresi tra 1 e n. Nel mio caso, ho modificato la funzione per utilizzare df.loc poiché i miei valori dell'indice non erano necessariamente compresi in questo intervallo.
iOSBeginner

32

Tuttavia, un approccio per dividere l'insieme di dati in train, test, cvcon 0.6, 0.2, 0.2sarebbe quella di utilizzare il train_test_splitmetodo due volte.

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)

Non ottimale per set di dati di grandi dimensioni
Maksym Ganenko,

@MaksymGanenko Puoi per favore elaborare?
blitu12345,

Suggerisci di dividere i dati con due operazioni separate. Ogni suddivisione dei dati comporta la copia dei dati. Pertanto, quando si suggerisce di utilizzare due operazioni separate separate anziché una, si crea artificialmente un carico sia sulla RAM che sulla CPU. Quindi la tua soluzione non è ottimale. La suddivisione dei dati dovrebbe essere eseguita con un'unica operazione come np.split(). Inoltre, non richiede ulteriore dipendenza sklearn.
Maksym Ganenko,

@MaksymGanenko ha concordato l'onere aggiuntivo per la memoria, e per lo stesso possiamo eliminare i dati originali dalla memoria, ad esempio (file esterni ed etichette)! E il tuo suggerimento sull'uso di numpy è in qualche modo limitato ai soli tipi di dati interi e agli altri tipi di dati?
blitu12345,

1
un altro vantaggio di questo approccio è che è possibile utilizzare i parametri di stratificazione.
Ami Tavory,

7

Ecco una funzione Python che divide un frame di dati Pandas in treni, validazione e test di frame di dati con campionamento stratificato. Esegue questa divisione chiamando train_test_split()due volte la funzione di 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

Di seguito è riportato un esempio di lavoro completo.

Si consideri un set di dati che ha un'etichetta su cui si desidera eseguire la stratificazione. Questa etichetta ha una sua distribuzione nel set di dati originale, diciamo 75% foo, 15% bare 10% baz. Ora dividiamo il set di dati in treno, convalida e test in sottoinsiemi usando un rapporto 60/20/20, dove ogni divisione mantiene la stessa distribuzione delle etichette. Vedi l'illustrazione seguente:

inserisci qui la descrizione dell'immagine

Ecco il set di dati di esempio:

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

Ora chiamiamo la split_stratified_into_train_val_test()funzione dall'alto per ottenere treni, convalida e test dei frame di dati seguendo un rapporto 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)

I tre dataframes df_train, df_vale df_testcontengono tutte le righe originali, ma le loro dimensioni seguiranno il rapporto di cui sopra.

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 3)

Inoltre, ciascuna delle tre divisioni avrà la stessa distribuzione dell'etichetta, vale a dire 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

NameError: il nome 'df' non è definito. 'Df' in split_stratified_into_train_val_test () deve essere sostituito con 'df_input'.
Fantasy Pollock,

Grazie. L'ho riparato. Il problema riguardava un percorso di gestione degli errori del codice.
stackoverflowuser2010

1

È molto comodo da usare train_test_splitsenza eseguire la reindicizzazione dopo aver diviso in più set e non aver scritto del codice aggiuntivo. La migliore risposta sopra non menziona il fatto che separando due volte usando train_test_splitnon cambiando le dimensioni della partizione non darà partizione inizialmente prevista:

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

Quindi la parte di convalida e set di test nella modifica x_remain può essere conteggiata come

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)

In questa occasione vengono salvate tutte le partizioni iniziali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.