データを3つのセット(トレーニング、検証、テスト)に分割する方法は?


146

私はパンダのデータフレームを持っていて、それを3つの別々のセットに分割したいと思います。からtrain_test_splitを使用sklearn.cross_validationすると、データを2つのセット(trainとtest)に分割できることがわかります。しかし、データを3つのセットに分割することに関する解決策は見つかりませんでした。できれば、元のデータのインデックスが欲しいです。

回避策は、train_test_split2回使用し、インデックスを調整することです。しかし、データを2つではなく3つのセットに分割する、より標準的な/組み込みの方法はありますか?


5
これはあなたの特定の質問には答えませんが、より標準的なアプローチは、トレーニングとテストの2つのセットに分割し、トレーニングセットで相互検証を実行することで、スタンドアロンの「開発」セットの必要性をなくすことになると思います。
デビッド

1
これは以前からありましたが、私が知る限り、組み込みの方法はまだありません。
ayhan 2016

5
Hastie et al。のElements of Statistical Learningを使用して、2 つではなく3つのセットを使用する理由について説明することをお勧めします(web.stanford.edu/~hastie/local.ftp/Springer/OLD/…モデルの評価と選択の章)
ayhan 2016

2
@David一部のモデルでは、過剰適合を防ぐために、2つではなく3つのセットが必要です。設計の選択では、テストセットのパフォーマンスを改善するためにパラメーターを調整するためです。これを防ぐには、開発セットが必要です。したがって、相互検証を使用するだけでは不十分です。
CentAu 16

6
@ayhan、その本の正しいURLはstatweb.stanford.edu/~tibs/ElemStatLearn/printings/…、第7章(p。219)です。
Camille Goudeseune

回答:


161

Numpyソリューション。最初にデータセット全体をシャッフルし(df.sample(frac = 1))、次にデータセットを次の部分に分割します。

  • 60%-列車セット、
  • 20%-検証セット、
  • 20%-テストセット

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))]-あるindices_or_sectionsの配列)(numpy.split

これはnp.split()使用法の小さなデモです-20要素の配列を次の部分に分割しましょう: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 frac = 1パラメータは正確には何をしているのですか?
SpiderWasp42 2017年

1
@ SpiderWasp42、すべての(または端数= )行を返すfrac=1ようにsample()関数に指示します100%1.0
MaxU

12
@MaxUに感謝します。簡略化するために、2つのことを述べておきます。最初np.random.seed(any_number)に、分割線の前に使用して、すべての実行で同じ結果を取得します。第二に、train:test:val::50:40:10使用のように不均等な比率にすること[int(.5*len(dfn)), int(.9*len(dfn))]。ここで、最初の要素はtrain(0.5%)のサイズを示し、2番目の要素はval(1-0.9 = 0.1%)のサイズを示し、2つの要素の差はtest(0.9-0.5 = 0.4%)のサイズを示します。私が間違っている場合は修正してください:)
dataLeo

hrmmが「np.split()の使用法の小さなデモです-20要素の配列を次の部分に分割しましょう:90%、10%、10%:」それは間違いです80を意味します%、10%、10%
ケビン

ちょっと、@ MaxU私はケースがありましたが、何か似ています。あなたがそれを見て、それがあるかどうかを確認し、そこで私を助けることができるかどうか疑問に思っていました。これが私の質問です。stackoverflow.com
ディーパックM

55

注意:

関数は、ランダム化されたセット作成のシードを処理するために作成されました。セットをランダム化しないセット分割に依存しないでください。

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

デモンストレーション

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

ここに画像の説明を入力してください

train, validate, test = train_validate_test_split(df)

train

ここに画像の説明を入力してください

validate

ここに画像の説明を入力してください

test

ここに画像の説明を入力してください


1
この関数には、1〜nの範囲のインデックス値を持つdfが必要だと思います。私の場合、インデックス値が必ずしもこの範囲内にあるとは限らなかったため、関数をdf.locを使用するように変更しました。
iOSBeginner

32

しかし、にデータセットを分割する1つのアプローチはtraintestcv0.60.20.2を使用することであろうtrain_test_split二回方法を。

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)

大規模なデータセットに最適ではない
Maksym Ganenko

@MaksymGanenko詳しく説明していただけますか?
blitu12345

2つの別々の操作でデータを分割することをお勧めします。各データ分割には、データのコピーが含まれます。したがって、1つではなく2つの別々の分割操作を使用することを提案すると、RAMとCPUの両方に人為的に負担がかかります。したがって、あなたのソリューションは最適ではありません。データ分割は、などの単一の操作で行う必要がありますnp.split()。また、への追加の依存関係は必要ありませんsklearn
Maksym Ganenko

@MaksymGanenkoはメモリへの余分な負担について合意しました。同じために、メモリから元のデータを削除できますie(xtrain&labels)!そして、numpyを使用するためのあなたの提案については、整数データ型のみにいくらか制限されていますが、他のデータ型についてはどうですか?
blitu12345

1
このアプローチのもう1つの利点は、層別化パラメーターを使用できることです。
Ami Tavory

7

以下は、Pandasデータフレームをトレーニング、検証、およびテストデータフレームに層別サンプリングで分割するPython関数です。scikit-learnの関数をtrain_test_split()2回呼び出すことにより、この分割を実行します。

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

以下は完全に機能する例です。

階層化を実行するラベルが付いたデータセットを考えます。このラベルには、元のデータセットで独自の分布があります。たとえば、75%foo、15%bar、10%bazです。次に、60/20/20の比率を使用してデータセットをトレーニング、検証、およびテストに分割し、各分割でラベルの同じ分布が維持されるようにします。下の図を参照してください。

ここに画像の説明を入力してください

以下はデータセットの例です。

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

次に、split_stratified_into_train_val_test()上から関数を呼び出して、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)

3つのデータフレームdf_traindf_valおよびdf_testすべての元の行が、そのサイズは、上記の比率に従います含まれています。

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 3)

さらに、3つの分割のそれぞれは、ラベルの同じ分布、つまり75%foo、15%bar、および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:名前「df」が定義されていません。split_stratified_into_train_val_test()の「df」は「df_input」に置き換える必要があります。
Fantasy Pollock

ありがとう。それを私が直した。問題は、コードのエラー処理パスにありました。
stackoverflowuser2010

1

train_test_splitいくつかのセットに分割し、追加のコードを書かずに再インデックスを実行せずに使用すると非常に便利です。上記のベストアンサーでtrain_test_splitは、パーティションサイズを変更しないで2回分離しても、最初に意図したパーティションが得られないことに言及していません。

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

次に、x_remainの検証セットとテストセットの部分が変更され、次のようにカウントされます。

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)

この場合、すべての初期パーティションが保存されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.