Wie teile ich Daten in 3 Sätze auf (Zug, Validierung und Test)?

145

Ich habe einen Pandas-Datenrahmen und möchte ihn in 3 separate Sätze aufteilen. Ich weiß, dass man mit train_test_split von sklearn.cross_validationdie Daten in zwei Sätze (Zug und Test) aufteilen kann. Ich konnte jedoch keine Lösung für die Aufteilung der Daten in drei Sätze finden. Am liebsten hätte ich die Indizes der Originaldaten.

Ich weiß, dass eine Problemumgehung darin besteht, train_test_splitzwei Indizes zu verwenden und die Indizes irgendwie anzupassen. Aber gibt es eine standardmäßigere / integrierte Möglichkeit, die Daten in 3 statt in 2 Sätze aufzuteilen?

CentAu
quelle
5
Dies beantwortet Ihre spezifische Frage nicht, aber ich denke, der Standardansatz hierfür wäre, sich in zwei Sätze aufzuteilen, zu trainieren und zu testen und eine Kreuzvalidierung des Trainingssatzes durchzuführen, wodurch die Notwendigkeit eines eigenständigen "Entwicklungs" -Satzes entfällt .
David
1
Dies ist schon früher aufgetaucht, und soweit ich weiß, gibt es dafür noch keine eingebaute Methode.
Ayhan
5
Ich empfehle Hastie et al. The Elements of Statistical Learning für eine Diskussion darüber, warum drei Sätze anstelle von zwei verwendet werden sollen ( web.stanford.edu/~hastie/local.ftp/Springer/OLD/… Kapitel zur Modellbewertung und -auswahl).
Ayhan
2
@David In einigen Modellen sind 3 Sätze anstelle von 2 erforderlich, um eine Überanpassung zu verhindern. Da Sie bei Ihren Entwurfsentscheidungen die Parameter irgendwie optimieren, um die Leistung des Testsatzes zu verbessern. Um dies zu verhindern, ist ein Entwicklungssatz erforderlich. Die Verwendung der Kreuzvalidierung ist daher nicht ausreichend.
CentAu
6
@ayhan, eine korrigierte URL für dieses Buch lautet statweb.stanford.edu/~tibs/ElemStatLearn/printings/… , Kapitel 7 (S. 219).
Camille Goudeseune

Antworten:

160

Numpy Lösung. Wir werden zuerst den gesamten Datensatz mischen (df.sample (frac = 1)) und dann unseren Datensatz in die folgenden Teile aufteilen:

  • 60% - Zugset,
  • 20% - Validierungssatz,
  • 20% - Testsatz

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))]- ist ein indices_or_sectionsArray für numpy.split () .

Hier ist eine kleine Demo zur np.split()Verwendung - teilen wir das Array mit 20 Elementen in die folgenden Teile auf: 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
quelle
@root was genau macht der Parameter frac = 1?
SpiderWasp42
1
@ SpiderWasp42, frac=1weist die sample()Funktion an, alle ( 100%oder gebrochenen = 1.0) Zeilen zurückzugeben
MaxU
12
Danke @MaxU. Ich möchte zwei Dinge erwähnen, um die Dinge zu vereinfachen. Verwenden Sie zunächst np.random.seed(any_number)vor der Trennlinie, um bei jedem Lauf das gleiche Ergebnis zu erzielen. Zweitens, um ein ungleiches Verhältnis wie train:test:val::50:40:10Gebrauch zu machen [int(.5*len(dfn)), int(.9*len(dfn))]. Hier bezeichnet das erste Element die Größe für train(0,5%), das zweite Element die Größe für val(1-0,9 = 0,1%) und die Differenz zwischen den beiden die Größe für test(0,9-0,5 = 0,4%). Korrigieren Sie mich, wenn ich falsch
liege
hrmm ist es ein Fehler, wenn Sie sagen "Hier ist eine kleine Demo für die Verwendung von np.split () - teilen wir das Array mit 20 Elementen in die folgenden Teile auf: 90%, 10%, 10%:" Ich bin mir ziemlich sicher, dass Sie 80 meinen %, 10%, 10%
Kevin
Hey, @MaxU Ich hatte einen Fall, etwas ähnliches. Ich habe mich gefragt, ob Sie es sich ansehen könnten, damit ich es sehen und mir dort helfen kann. Hier ist meine Frage stackoverflow.com/questions/54847668/…
Deepak M
55

Hinweis:

Die Funktion wurde geschrieben, um das Seeding der zufälligen Set-Erstellung zu handhaben. Sie sollten sich nicht auf die Aufteilung von Sätzen verlassen, bei der die Sätze nicht zufällig angeordnet werden.

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

Demonstration

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

Geben Sie hier die Bildbeschreibung ein

train, validate, test = train_validate_test_split(df)

train

Geben Sie hier die Bildbeschreibung ein

validate

Geben Sie hier die Bildbeschreibung ein

test

Geben Sie hier die Bildbeschreibung ein

piRSquared
quelle
1
Ich glaube, diese Funktion erfordert ein df mit Indexwerten zwischen 1 und n. In meinem Fall habe ich die Funktion so geändert, dass df.loc verwendet wird, da meine Indexwerte nicht unbedingt in diesem Bereich liegen.
iOSBeginner
32

Allerdings ist ein Ansatz , um die Datenmenge in der zu Dividieren train, test, cvmit 0.6, 0.2, 0.2wäre es , die verwenden train_test_splitMethode zweimal.

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
quelle
Suboptimal für große
Datenmengen
@MaksymGanenko Kannst du das bitte näher erläutern?
blitu12345
Sie schlagen vor, Daten mit zwei separaten Vorgängen aufzuteilen. Bei jeder Datenaufteilung werden Daten kopiert. Wenn Sie also vorschlagen, zwei separate Split-Operationen anstelle einer zu verwenden, belasten Sie sowohl den RAM als auch die CPU künstlich. Ihre Lösung ist also nicht optimal. Die Datenaufteilung sollte mit einer einzigen Operation wie erfolgen np.split(). Außerdem ist keine zusätzliche Abhängigkeit von erforderlich sklearn.
Maksym Ganenko
@MaksymGanenko stimmte der zusätzlichen Belastung des Speichers zu, und für dasselbe können wir die Originaldaten aus dem Speicher löschen, dh (xtrain & label)! Und was Ihren Vorschlag für die Verwendung von numpy betrifft, ist er etwas auf nur ganzzahlige Datentypen beschränkt. Was ist mit anderen Datentypen?
blitu12345
1
Ein weiterer Vorteil dieses Ansatzes besteht darin, dass Sie die Schichtungsparameter verwenden können.
Ami Tavory
7

Hier ist eine Python-Funktion, die einen Pandas-Datenrahmen in Zug-, Validierungs- und Testdatenrahmen mit geschichteten Stichproben aufteilt. Diese Aufteilung wird durchgeführt, indem die Funktion von scikit-learn train_test_split()zweimal aufgerufen wird .

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

Unten finden Sie ein vollständiges Arbeitsbeispiel.

Stellen Sie sich einen Datensatz mit einer Bezeichnung vor, auf der Sie die Schichtung durchführen möchten. Dieses Etikett hat eine eigene Verteilung im Originaldatensatz, z. B. 75% foo, 15% barund 10% baz. Lassen Sie uns nun den Datensatz in Zug, Validierung und Test in Teilmengen unter Verwendung eines Verhältnisses von 60/20/20 aufteilen, wobei jede Aufteilung die gleiche Verteilung der Beschriftungen beibehält. Siehe die Abbildung unten:

Geben Sie hier die Bildbeschreibung ein

Hier ist der Beispieldatensatz:

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

Rufen wir nun die split_stratified_into_train_val_test()Funktion von oben auf, um Zug-, Validierungs- und Testdatenrahmen nach einem Verhältnis von 60/20/20 abzurufen.

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)

Die drei Datenrahmen df_train, df_valund df_testenthalten alle ursprünglichen Reihen , aber ihre Größe wird das obige Verhältnis folgen.

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 3)

Ferner hat jede der drei Teilungen die gleiche Verteilung des Etiketts, nämlich 75% foo, 15% barund 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
quelle
NameError: Name 'df' ist nicht definiert. Das 'df' in split_stratified_into_train_val_test () sollte durch 'df_input' ersetzt werden.
Fantasy Pollock
Vielen Dank. Ich habe es repariert. Das Problem lag in einem Fehlerbehandlungspfad des Codes.
stackoverflowuser2010
1

Es ist sehr bequem zu verwenden, train_test_splitohne eine Neuindizierung durchzuführen, nachdem Sie in mehrere Sätze aufgeteilt und keinen zusätzlichen Code geschrieben haben. Die beste Antwort oben erwähnt nicht, dass durch zweimaliges Trennen unter Verwendung train_test_splitnicht ändernder Partitionsgrößen keine ursprünglich beabsichtigte Partition erhalten wird:

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

Dann ändert sich der Teil der Validierungs- und Testsätze in der x_remain und könnte als gezählt werden

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 diesem Fall werden alle anfänglichen Partitionen gespeichert.

A.Ametov
quelle