¿Cómo dividir los datos en 3 conjuntos (tren, validación y prueba)?

146

Tengo un marco de datos de pandas y deseo dividirlo en 3 conjuntos separados. Sé que usando train_test_split de sklearn.cross_validation, uno puede dividir los datos en dos conjuntos (entrenar y probar). Sin embargo, no pude encontrar ninguna solución para dividir los datos en tres conjuntos. Preferiblemente, me gustaría tener los índices de los datos originales.

Sé que una solución alternativa sería usar train_test_splitdos veces y de alguna manera ajustar los índices. Pero, ¿hay una forma más estándar / integrada de dividir los datos en 3 conjuntos en lugar de 2?

CentAu
fuente
55
Esto no responde a su pregunta específica, pero creo que el enfoque más estándar para esto sería dividirse en dos conjuntos, entrenar y probar, y ejecutar la validación cruzada en el conjunto de entrenamiento, eliminando así la necesidad de un conjunto de "desarrollo" independiente. .
David
1
Esto surgió antes, y que yo sepa, todavía no hay un método incorporado para eso.
ayhan
55
Sugiero los Elementos de aprendizaje estadístico de Hastie et al. Para una discusión sobre por qué usar tres conjuntos en lugar de dos ( web.stanford.edu/~hastie/local.ftp/Springer/OLD/… Modelo de evaluación y capítulo de selección)
ayhan
2
@David En algunos modelos para evitar el sobreajuste, se necesitan 3 conjuntos en lugar de 2. Porque en sus elecciones de diseño, de alguna manera está ajustando parámetros para mejorar el rendimiento en el conjunto de prueba. Para evitar eso, se requiere un conjunto de desarrollo. Por lo tanto, usar la validación cruzada no será suficiente.
CentAu
66
@ayhan, una URL corregida para ese libro es statweb.stanford.edu/~tibs/ElemStatLearn/printings/… , capítulo 7 (p. 219).
Camille Goudeseune

Respuestas:

161

Numpy solucion. Primero barajaremos todo el conjunto de datos (df.sample (frac = 1)) y luego dividiremos nuestro conjunto de datos en las siguientes partes:

  • 60% - juego de trenes,
  • 20% - conjunto de validación,
  • 20% - conjunto de prueba

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

Aquí hay una pequeña demostración para su np.split()uso: dividamos la matriz de 20 elementos en las siguientes 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
fuente
@root, ¿qué está haciendo exactamente el parámetro frac = 1?
SpiderWasp42
1
@ SpiderWasp42, frac=1indica a la sample()función que devuelva todas las filas ( 100%o fracción = 1.0)
MaxU
12
Gracias @MaxU. Me gustaría mencionar 2 cosas para mantener las cosas simplificadas. Primero, use np.random.seed(any_number)antes de la línea de división para obtener el mismo resultado con cada ejecución. En segundo lugar, hacer una relación desigual como el train:test:val::50:40:10uso [int(.5*len(dfn)), int(.9*len(dfn))]. Aquí el primer elemento denota el tamaño para train(0.5%), el segundo elemento denota el tamaño para val(1-0.9 = 0.1%) y la diferencia entre los dos denota el tamaño para test(0.9-0.5 = 0.4%).
Corrígeme
hrmm es un error cuando dices "Aquí hay una pequeña demostración para el uso de np.split () - dividamos la matriz de 20 elementos en las siguientes partes: 90%, 10%, 10%:" Estoy bastante seguro de que quieres decir 80 %, 10%, 10%
Kevin
Hola, @MaxU Tuve un caso, algo similar. Me preguntaba si podrías mirarlo para ver si es así y ayudarme allí. Aquí está mi pregunta stackoverflow.com/questions/54847668/…
Deepak M
55

Nota:

La función se escribió para manejar la inicialización de la creación de conjuntos aleatorios. No debe confiar en la división de conjuntos que no aleatoriza los conjuntos.

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

Demostración

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

ingrese la descripción de la imagen aquí

train, validate, test = train_validate_test_split(df)

train

ingrese la descripción de la imagen aquí

validate

ingrese la descripción de la imagen aquí

test

ingrese la descripción de la imagen aquí

piRSquared
fuente
1
Creo que esta función requiere un df con valores de índice que varían de 1 a n. En mi caso, modifiqué la función para usar df.loc ya que mis valores de índice no estaban necesariamente en este rango.
iOSBeginner
32

Sin embargo, un enfoque para dividir el conjunto de datos en train, test, cvcon 0.6, 0.2, 0.2sería utilizar el train_test_splitmétodo de dos veces.

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
fuente
Subóptimo para grandes conjuntos de datos
Maksym Ganenko
@MaksymGanenko ¿Puedes dar más detalles?
blitu12345
Sugiere dividir datos con dos operaciones separadas. Cada división de datos implica la copia de datos. Entonces, cuando sugiere usar dos operaciones separadas en lugar de una, crea artificialmente una carga tanto en la RAM como en la CPU. Entonces su solución es subóptima. La división de datos debe hacerse con una sola operación como np.split(). Además, no requiere dependencia adicional de sklearn.
Maksym Ganenko
¡@MaksymGanenko estuvo de acuerdo con la carga adicional en la memoria, y por lo mismo podemos eliminar los datos originales de la memoria, es decir (xtrain y etiquetas)! Y sobre su sugerencia para usar numpy se limita de alguna manera a solo tipos de datos enteros ¿qué pasa con otros tipos de datos?
blitu12345
1
Otro beneficio de este enfoque es que puede utilizar los parámetros de estratificación.
Ami Tavory
7

Aquí hay una función de Python que divide un marco de datos Pandas en marcos de datos de tren, validación y prueba con muestreo estratificado. Realiza esta división llamando a la función scikit-learn train_test_split()dos veces.

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

A continuación se muestra un ejemplo de trabajo completo.

Considere un conjunto de datos que tenga una etiqueta sobre la cual desea realizar la estratificación. Esta etiqueta tiene su propia distribución en el conjunto de datos original, digamos 75% foo, 15% bary 10% baz. Ahora dividamos el conjunto de datos en tren, validación y prueba en subconjuntos usando una relación 60/20/20, donde cada división conserva la misma distribución de las etiquetas. Vea la siguiente ilustración:

ingrese la descripción de la imagen aquí

Aquí está el conjunto de datos de ejemplo:

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

Ahora, llamemos a la split_stratified_into_train_val_test()función desde arriba para obtener marcos de datos de entrenamiento, validación y prueba siguiendo una relación 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)

Los tres marcos de datos df_train, df_valy df_testcontienen todas las filas originales, pero sus tamaños seguirán la relación anterior.

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 3)

Además, cada una de las tres divisiones tendrá la misma distribución de la etiqueta, es decir, 75% foo, 15% bary 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
fuente
NameError: el nombre 'df' no está definido. El 'df' en split_stratified_into_train_val_test () debe reemplazarse con 'df_input'.
Fantasy Pollock
Gracias. Lo arreglé. El problema estaba en una ruta de manejo de errores del código.
stackoverflowuser2010
1

Es muy conveniente usarlo train_test_splitsin realizar una reindexación después de dividirlo en varios conjuntos y no escribir algún código adicional. La mejor respuesta anterior no menciona que al separarse dos veces usando train_test_splitno cambiar los tamaños de partición no se obtendrá la partición inicialmente prevista:

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

Luego, la parte de los conjuntos de validación y prueba en x_remain cambia y podría contarse 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)

En esta ocasión se guardan todas las particiones iniciales.

A.Ametov
fuente