基于 python 中的多个特征进行训练测试拆分的分层交叉验证或抽样

Stratified Cross Validation or Sampling for train-test split based on multiple features in python

sklearn 的 train_test_split , StratifiedShuffleSplit and StratifiedKFold 所有分层都基于 class 标签(y 变量或 target_column)。如果我们想根据特征列 (x 变量)和 而不是目标列 进行采样怎么办?如果它只是一个特征,那么很容易根据该单列进行分层,但是如果有很多特征列并且我们想保留所选样本中的总体比例怎么办?

下面我创建了一个 df 人口倾斜,其中低收入人群较多,女性较多,来自 CA 的人数最少,而来自 MA 的人数最多。我希望所选样本具有这些特征,即更多的低收入者、更多的女性、最少的来自 CA 的人和大多数来自 MA 的人

import random
import string
import pandas as pd
N = 20000 # Total rows in data
names    = [''.join(random.choices(string.ascii_uppercase, k = 5)) for _ in range(N)]
incomes  = [random.choices(['High','Low'], weights=(30, 70))[0] for _ in range(N)]
genders  = [random.choices(['M','F'], weights=(40, 60))[0] for _ in range(N)]
states   = [random.choices(['CA','IL','FL','MA'], weights=(10,20,30,40))[0] for _ in range(N)]
targets_y= [random.choice([0,1]) for _ in range(N)]

df = pd.DataFrame(dict(
        name     = names,
        income   = incomes,
        gender   = genders,
        state    = states,
        target_y = targets_y
    ))

当对于某些特征,我们只有很少的示例并且我们希望在所选样本中包含至少 n 个示例时,会出现更多的复杂性。考虑这个例子:

single_row = {'name' : 'ABC',
'income' : 'High',
'gender' : 'F',
'state' : 'NY',
'target_y' : 1}

df = df.append(single_row, ignore_index=True)

df

.

我希望这个添加的行始终包含在测试拆分中(n=1)。

这可以使用 pandas groupby:

来实现

我们先来看看人口特征:

grps = df.groupby(['state','income','gender'], group_keys=False)
grps.count()

接下来让我们用 20% 的原始数据创建一个测试集

test_proportion = 0.2
at_least = 1
test = grps.apply(lambda x: x.sample(max(round(len(x)*test_proportion), at_least)))
test

测试集特征:

test.groupby(['state','income','gender']).count()

接下来我们创建训练集作为原始 df 和测试集的差异

print('No. of samples in test  =', len(test))
train = set(df.name) - set(test.name)
print('No. of samples in train =', len(train))

No. of samples in test = 4000

No. of samples in train = 16001