стратифицированная выборка python 1: 1 для каждой группы

Как можно выполнить стратифицированную выборку 1: 1 в python?

Предположим, что кадр данных Pandas df сильно несбалансирован. Он содержит бинарную группу и несколько столбцов категориальных подгрупп.

df = pd.DataFrame({'id':[1,2,3,4,5], 'group':[0,1,0,1,0], 'sub_category_1':[1,2,2,1,1], 'sub_category_2':[1,2,2,1,1], 'value':[1,2,3,1,2]})
display(df)
display(df[df.group == 1])
display(df[df.group == 0])
df.group.value_counts()

Для каждого члена основного group==1 мне нужно найти единственное совпадение group==0 с.

StratifiedShuffleSplit от scikit-learn вернет только случайную часть данных, а не совпадение 1:1.


person Georg Heiler    schedule 12.02.2019    source источник
comment
В вашем примере ввода нет общих значений между sub_category_2 между группой 1 и группой 0. Ну, есть только одно.   -  person Dani Mesejo    schedule 12.02.2019
comment
Ну, вы не можете получить то же распределение, что и группа 1, потому что в sub_category_2 нет элементов со значением 2, достаточно просто добавить еще одну строку.   -  person Dani Mesejo    schedule 12.02.2019
comment
Добавил одну строку.   -  person Georg Heiler    schedule 12.02.2019


Ответы (1)


Если я правильно понял, вы можете использовать np.random.permutation :

import numpy as np
import pandas as pd

np.random.seed(42)

df = pd.DataFrame({'id': [1, 2, 3, 4, 5], 'group': [0, 1, 0, 1, 0], 'sub_category_1': [1, 2, 2, 1, 1],
                   'sub_category_2': [1, 2, 2, 1, 1], 'value': [1, 2, 3, 1, 2]})

# create new column with an identifier for a combination of categories
columns = ['sub_category_1', 'sub_category_2']
labels = df.loc[:, columns].apply(lambda x: ''.join(map(str, x.values)), axis=1)
values, keys = pd.factorize(labels)
df['label'] = labels.map(dict(zip(keys, values)))

# build distribution of sub-categories combinations
distribution = df[df.group == 1].label.value_counts().to_dict()

# select from group 0 only those rows that are in the same sub-categories combinations
mask = (df.group == 0) & (df.label.isin(distribution))

# do random sampling
selected = np.ravel([np.random.permutation(group.index)[:distribution[name]] for name, group in df.loc[mask].groupby(['label'])])

# display result
result = df.drop('label', axis=1).iloc[selected]
print(result)

Вывод

   group  id  sub_category_1  sub_category_2  value
4      0   5               1               1      2
2      0   3               2               2      3

Обратите внимание, что это решение предполагает, что размер каждой возможной комбинации подкатегорий group 1 меньше размера соответствующей подгруппы в group 0. Более надежная версия предполагает использование np.random.choice с заменой:

selected = np.ravel([np.random.choice(group.index, distribution[name], replace=True) for name, group in df.loc[mask].groupby(['label'])])

Версия с выбором не имеет такого же предположения, как версия с перестановкой, хотя для каждой комбинации подкатегорий требуется по крайней мере один элемент.

person Dani Mesejo    schedule 12.02.2019
comment
Ваше предположение 1 ‹ 0 в порядке. Однако вы вычисляете только случайную подвыборку для группы 0. Вместо этого мне нужно также учитывать все подкатегории. Сначала я объединил все столбцы, то есть group_sub_category_1_sub_category_2, для создания новых классов, но, как уже упоминалось, застрял с StratifiedShuffleSplit . - person Georg Heiler; 12.02.2019
comment
Конечно. И для sub_category_1, и для sub_category_2 (на самом деле для реального набора данных это около 10 столбцов). - person Georg Heiler; 12.02.2019
comment
Это также должно иметь место. Но, если возможно, было бы здорово, если бы он был достаточно устойчивым, чтобы также обрабатывать случай, если они равны или меньше. Но первый случай уже был бы велик. - person Georg Heiler; 12.02.2019
comment
@GeorgHeiler Обновил ответ! - person Dani Mesejo; 12.02.2019