Bagaimana cara membagi data menjadi 3 set (latih, validasi, dan tes)?

157

Saya memiliki kerangka data panda dan saya ingin membaginya menjadi 3 set terpisah. Saya tahu bahwa menggunakan train_test_split from sklearn.cross_validation, seseorang dapat membagi data dalam dua set (melatih dan menguji). Namun, saya tidak dapat menemukan solusi apa pun tentang membagi data menjadi tiga set. Lebih disukai, saya ingin memiliki indeks dari data asli.

Saya tahu bahwa solusinya adalah menggunakan train_test_splitdua kali dan entah bagaimana menyesuaikan indeks. Tetapi apakah ada cara yang lebih standar / built-in untuk membagi data menjadi 3 set, bukan 2?

CentAu
sumber
5
Ini tidak menjawab pertanyaan spesifik Anda, tetapi menurut saya pendekatan yang lebih standar untuk ini adalah membagi menjadi dua set, melatih dan menguji, dan menjalankan validasi silang pada set pelatihan sehingga menghilangkan kebutuhan untuk set "pengembangan" yang berdiri sendiri .
David
1
Ini muncul sebelumnya, dan sejauh yang saya tahu belum ada metode bawaan untuk itu.
ayhan
5
Saya menyarankan Hastie dkk. The Elements of Statistics Learning untuk diskusi tentang mengapa menggunakan tiga set, bukan dua ( web.stanford.edu/~hastie/local.ftp/Springer/OLD/… Bab penilaian dan pemilihan model)
ayhan
2
@David Pada beberapa model untuk mencegah overfitting, ada kebutuhan untuk 3 set, bukan 2. Karena dalam pilihan desain Anda, Anda entah bagaimana menyetel parameter untuk meningkatkan kinerja pada set pengujian. Untuk mencegahnya, diperlukan set pengembangan. Jadi, menggunakan validasi silang tidak akan cukup.
CentAu
6
@ayhan, URL yang diperbaiki untuk buku itu adalah statweb.stanford.edu/~tibs/ElemStatLearn/printings/… , bab 7 (p. 219).
Camille Goudeseune

Jawaban:

176

Solusi numpy. Kami akan mengocok seluruh dataset terlebih dahulu ( df.sample(frac=1, random_state=42)) dan kemudian membagi kumpulan data kami menjadi beberapa bagian berikut:

  • 60% - set kereta,
  • 20% - set validasi,
  • 20% - set tes

In [305]: train, validate, test = \
              np.split(df.sample(frac=1, random_state=42), 
                       [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))]- adalah indices_or_sections larik untuk numpy.split () .

Berikut ini demo kecil untuk np.split()penggunaan - mari kita pisahkan larik 20-elemen menjadi beberapa bagian berikut: 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
sumber
@ root apa sebenarnya yang dilakukan parameter frac = 1?
SpiderWasp42
1
@ SpiderWasp42, frac=1menginstruksikan sample()fungsi untuk mengembalikan semua ( 100%atau fraksi = 1.0) baris
MaxU
14
Terima kasih @MaxU. Saya ingin menyebutkan 2 hal agar semuanya tetap sederhana. Pertama, gunakan np.random.seed(any_number)sebelum garis pemisah untuk mendapatkan hasil yang sama dengan setiap proses. Kedua, membuat rasio yang tidak sama seperti train:test:val::50:40:10digunakan [int(.5*len(dfn)), int(.9*len(dfn))]. Di sini elemen pertama menunjukkan ukuran untuk train(0,5%), elemen kedua menunjukkan ukuran untuk val(1-0,9 = 0,1%) dan perbedaan antara keduanya menunjukkan ukuran untuk test(0,9-0,5 = 0,4%). Koreksi saya jika saya salah :)
sync11
hrmm apakah itu kesalahan ketika Anda mengatakan "Ini adalah demo kecil untuk penggunaan np.split () - mari kita bagi array 20-elemen menjadi beberapa bagian berikut: 90%, 10%, 10%:" Saya cukup yakin maksud Anda 80 %, 10%, 10%
Kevin
Hai, @MaxU Saya punya kasus, sesuatu yang agak mirip. Saya ingin tahu apakah Anda dapat melihatnya untuk saya dan membantu saya di sana. Ini pertanyaan saya stackoverflow.com/questions/54847668/…
Deepak M
57

catatan:

Fungsi ditulis untuk menangani penyemaian pembuatan set acak. Anda tidak boleh mengandalkan pemisahan set yang tidak mengacak 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

Demonstrasi

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

masukkan deskripsi gambar di sini

train, validate, test = train_validate_test_split(df)

train

masukkan deskripsi gambar di sini

validate

masukkan deskripsi gambar di sini

test

masukkan deskripsi gambar di sini

piRSquared
sumber
1
Saya yakin fungsi ini membutuhkan df dengan nilai indeks mulai dari 1 hingga n. Dalam kasus saya, saya memodifikasi fungsi untuk menggunakan df.loc karena nilai indeks saya belum tentu dalam kisaran ini.
iOSBeginner
36

Namun, salah satu pendekatan untuk membagi dataset ke dalam train, test, cvdengan 0.6, 0.2, 0.2akan menggunakan train_test_splitmetode dua kali.

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
sumber
Suboptimal untuk kumpulan data besar
Maksym Ganenko
@MaksymGanenko Bisakah Anda menjelaskan lebih lanjut?
blitu12345
Anda menyarankan untuk membagi data dengan dua operasi terpisah. Setiap pemisahan data melibatkan penyalinan data. Jadi, ketika Anda menyarankan untuk menggunakan dua operasi terpisah, bukan satu, Anda secara artifisial membuat beban pada RAM dan CPU. Jadi, solusi Anda kurang optimal. Pemisahan data harus dilakukan dengan satu operasi seperti np.split(). Selain itu, tidak memerlukan ketergantungan tambahan pada sklearn.
Maksym Ganenko
2
Manfaat lain dari pendekatan ini adalah Anda dapat menggunakan parameter stratifikasi.
Ami Tavory
1
Pendekatan yang sederhana dan cukup mudah!
mgokhanbakal
10

Berikut adalah fungsi Python yang membagi dataframe Pandas menjadi train, validasi, dan menguji dataframe dengan pengambilan sampel bertingkat. Ia melakukan pemisahan ini dengan memanggil fungsi scikit-learn train_test_split()dua kali.

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 bawah ini adalah contoh kerja lengkap.

Pertimbangkan kumpulan data yang memiliki label tempat Anda ingin melakukan stratifikasi. Label ini memiliki distribusinya sendiri di kumpulan data asli, misalnya 75% foo, 15%, bardan 10% baz. Sekarang mari kita pisahkan kumpulan data menjadi train, validation, dan test menjadi beberapa subset menggunakan rasio 60/20/20, di mana setiap pemisahan mempertahankan distribusi label yang sama. Lihat ilustrasi di bawah ini:

masukkan deskripsi gambar di sini

Berikut contoh datasetnya:

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

Sekarang, mari panggil split_stratified_into_train_val_test()fungsi dari atas untuk melatih, memvalidasi, dan menguji kerangka data mengikuti rasio 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)

Tiga kerangka data df_train,, df_valdan df_testberisi semua baris asli tetapi ukurannya akan mengikuti rasio di atas.

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 3)

Selanjutnya masing-masing dari ketiga pemisahan tersebut akan memiliki distribusi label yang sama yaitu 75% foo, 15% bardan 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
sumber
NameError: nama 'df' tidak ditentukan. 'Df' dalam split_stratified_into_train_val_test () harus diganti dengan 'df_input'.
Fantasy Pollock
Terima kasih. Aku telah memperbaikinya. Masalahnya ada di jalur penanganan kesalahan kode.
stackoverflowuser2010
2

Sangat nyaman digunakan train_test_splittanpa melakukan pengindeksan ulang setelah membagi ke beberapa set dan tidak menulis beberapa kode tambahan. Jawaban terbaik di atas tidak menyebutkan bahwa dengan memisahkan dua kali menggunakan train_test_splittidak mengubah ukuran partisi tidak akan memberikan partisi yang dimaksudkan pada awalnya:

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

Kemudian porsi validasi dan set pengujian di x_remain berubah dan dapat dihitung sebagai

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)

Dalam kesempatan ini semua partisi awal disimpan.

A. Metov
sumber
1

Dalam kasus supervised learning, Anda mungkin ingin membagi X dan y (di mana X adalah masukan Anda dan y adalah keluaran kebenaran dasar). Anda hanya perlu memperhatikan mengocok X dan y dengan cara yang sama sebelum memisahkan.

Di sini, baik X dan y berada dalam kerangka data yang sama, jadi kami mengocoknya, memisahkannya, dan menerapkan pemisahan untuk masing-masing (seperti dalam jawaban yang dipilih), atau X dan y berada dalam dua kerangka data yang berbeda, jadi kami mengacak X, menyusun ulang y dengan cara yang sama seperti X yang dikocok dan menerapkan pemisahan ke masing-masing.

# 1st case: df contains X and y (where y is the "target" column of df)
df_shuffled = df.sample(frac=1)
X_shuffled = df_shuffled.drop("target", axis = 1)
y_shuffled = df_shuffled["target"]

# 2nd case: X and y are two separated dataframes
X_shuffled = X.sample(frac=1)
y_shuffled = y[X_shuffled.index]

# We do the split as in the chosen answer
X_train, X_validation, X_test = np.split(X_shuffled, [int(0.6*len(X)),int(0.8*len(X))])
y_train, y_validation, y_test = np.split(y_shuffled, [int(0.6*len(X)),int(0.8*len(X))])
Ken
sumber