Як розділити дані на 3 групи (поїзд, валідація та тест)?


146

У мене є фрейм даних панд, і я хочу поділити його на 3 окремих набори. Я знаю, що, використовуючи train_test_split від sklearn.cross_validation, можна розділити дані на два набори (поїзд та тест). Однак я не зміг знайти жодного рішення щодо розділення даних на три набори. Переважно, я хотів би мати індекси вихідних даних.

Я знаю, що рішенням було б скористатися train_test_splitдва рази та якось скорегувати показники. Але чи існує більш стандартний / вбудований спосіб розділити дані на 3 набори замість 2?


5
Це не відповідає вашому конкретному запитанню, але я думаю, що більш стандартним підходом до цього було б розбиття на два набори, тренування та тестування та проведення перехресної перевірки на навчальному наборі, тим самим усуваючи потребу в самостійному наборі «розробки» .
Девід

1
Це було раніше, і наскільки я знаю, для цього ще немає вбудованого методу.
айхан

5
Я пропоную Хасті та ін. Елементи статистичного навчання для обговорення того, чому використовувати три набори замість двох ( web.stanford.edu/~hastie/local.ftp/Springer/OLD/… Розділ щодо оцінки та вибору моделі)
айхан

2
@David У деяких моделях для запобігання надмірному оснащенню потрібні 3 набори замість 2. Оскільки у виборі дизайну ви якимось чином налаштовуєте параметри для підвищення продуктивності тестового набору. Щоб цього не допустити, потрібен набір розробок. Отже, використання перехресної перевірки буде недостатньою.
CentAu

6
@ayhan, виправлена ​​URL-адреса цієї книги є statweb.stanford.edu/~tibs/ElemStatLearn/printings/… , глава 7 (стор. 219).
Каміль Ґудсейне

Відповіді:


161

Річковий розчин. Ми спочатку перемістимо весь набір даних (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

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%), другий елемент позначає розмір для val(1-0,9 = 0,1%) і різницю між двома позначає розмір для test(0,9-0,5 = 0,4%). Виправте мене, якщо я помиляюся :)
dataLeo

hrmm - це помилка, коли ви говорите "Ось невелика демонстрація використання np.split () - розділимо 20-елементний масив на такі частини: 90%, 10%, 10%:" Я впевнений, що ти маєш на увазі 80 %, 10%, 10%
Кевін

Гей, @MaxU У мене був випадок, щось дещо подібне. Мені було цікаво, чи можете ви подивитися на мене, щоб побачити, чи є, і допомогти мені там. Ось моє запитання stackoverflow.com/questions/54847668/…
Deepak 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
Я вважаю, що для цієї функції потрібен df зі значеннями індексу від 1 до n. У моєму випадку я змінив функцію на використання df.loc, оскільки значення мого індексу не обов'язково знаходилися в цьому діапазоні.
iOSBeginner

32

Тим НЕ менше, один з підходів до поділу набору даних в train, test, cvз 0.6, 0.2, 0.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)

Субоптимальний для великих наборів даних
Максим Ганенко

@MaksymGanenko Чи можете ви, будь ласка, докладно?
blitu12345

Ви пропонуєте розділити дані двома окремими операціями. Кожне розділення даних включає копіювання даних. Отже, коли ви пропонуєте використовувати дві окремі операції розділення замість однієї, ви штучно створюєте навантаження як на оперативну пам'ять, так і на процесор. Тож ваше рішення є неоптимальним. Розбиття даних слід проводити за допомогою однієї операції, як-от np.split(). Крім того, він не потребує додаткової залежності від sklearn.
Максим Ганенко

@MaksymGanenko погодився на додаткове навантаження на пам'ять, і для того ж ми можемо видалити вихідні дані з пам'яті, тобто (xtrain & labels)! А щодо вашої пропозиції щодо використання numpy дещо обмежується лише цілими типами даних, що стосується інших типів даних?
blitu12345

1
Ще одна перевага цього підходу полягає в тому, що ви можете використовувати параметри стратифікації.
Амі Таворі

7

Ось функція Python, яка розбиває фрейм даних Pandas на поїзд, валідацію та тестування кадрів даних із стратифікованою вибіркою. Він виконує цей розкол, викликаючи функцію scikit-learn train_test_split()двічі.

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)

Три рамки даних df_train, df_valі df_testмістять усі вихідні рядки, але їх розміри будуть відповідати вищевказаному співвідношенню.

df_train.shape
#(60, 3)

df_val.shape
#(20, 3)

df_test.shape
#(20, 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' не визначено. 'Df' у split_stratified_into_train_val_test () слід замінити на 'df_input'.
Фантазії Поллок

Дякую. Я полагодив це. Проблема полягала в шляху керування помилками коду.
stackoverflowuser2010

1

Це дуже зручно використовувати, train_test_splitне виконуючи реіндексінг після поділу на кілька наборів і не записуючи якийсь додатковий код. Найкраща відповідь вище не зазначає, що, розділяючи два рази, train_test_splitне змінюючи розміри розділів, ви не дасте початково призначеного розділу:

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)

У цьому випадку всі початкові розділи зберігаються.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.