Pandas中的分层抽样

15

我有一个类似下面的pandas DataFrame:

cli_id | X1 | X2 | X3 | ... | Xn |  Y  |
----------------------------------------
123    | 1  | A  | XX | ... | 4  | 0.1 |
456    | 2  | B  | XY | ... | 5  | 0.2 |
789    | 1  | B  | XY | ... | 5  | 0.3 |
101    | 2  | A  | XX | ... | 4  | 0.1 |
...

我有客户ID、几个分类属性和Y值,Y的值是从0到1以0.1为单位的事件概率。

我需要在每个组(即10个文件夹)中对大小为200的Y进行分层抽样

当进行训练/测试拆分时,我经常使用这种方法来进行分层抽样:

def stratifiedSplit(X,y,size):
    sss = StratifiedShuffleSplit(y, n_iter=1, test_size=size, random_state=0)

    for train_index, test_index in sss:
        X_train, X_test = X.iloc[train_index], X.iloc[test_index]
        y_train, y_test = y.iloc[train_index], y.iloc[test_index]

    return X_train, X_test, y_train, y_test

但是我不知道在这种情况下如何修改它。

2个回答

34

如果每个组的样本数量相同,或者每个组的比例恒定,您可以尝试这样做:

df.groupby('Y').apply(lambda x: x.sample(n=200))
或者
df.groupby('Y').apply(lambda x: x.sample(frac=.1))

要根据多个变量执行分层抽样,只需根据更多变量进行分组。为此可能需要构建新的分组变量。

但是,如果相对于比例(例如分组大小为1,比例为0.25)而言,分组大小太小,则不会返回任何项。这是由于Python对int函数的四舍五入实现造成的,int(0.25)=0


假设我有一个包含 100,000 行的 DataFrame,我想从中抽取 10,000 行,但每个组至少要有 10 个样本,你会如何处理?使用您的代码,我可以从每个组中获取 10 个样本,但这将导致样本数量为 70k。 - joddm
当我使用你的方法时,得到了70k个样本。我想将其减少到10k,同时保证每个剩余组中至少有10个样本。 - joddm
你可以直接写例如10,而不是frac。这样,你将从每个组中收到10个样本。你能否创建一个新问题并链接到这个问题? - Quickbeam2k1
2
如果您想在此命令之后得到一个普通的DataFrame(而不是MultiIndex),请执行:df_test = df_stratified.droplevel(level=0)。然后,您可以使用索引来获取训练集拆分:df_train = df[~df.index.isin(df_test.index)] - NumesSanguis
我的修改后的答案是从2018年开始的。在pandas 1.1.0发布于2020年之后,GroupBy对象上的sample才可用。 - Quickbeam2k1
显示剩余2条评论

4

我不确定你是指这个:

strats = []
for k in range(11):
    y_val = k*0.1
    dummy_df = your_df[your_df['Y'] == y_val]
    stats.append( dummy_df.sample(200) )

这将创建一个仅包含所需Y值的虚拟数据框,并随机抽取200个样本。

好的,所以你需要不同的块具有相同的结构。我猜这有点困难,以下是我的解决方法:

首先,我会获得X1的直方图:

hist, edges = np.histogram(your_df['X1'], bins=np.linespace(min_x, max_x, nbins))

我们现在有一个包含nbins个bin的直方图。
现在的策略是根据它们的X1值绘制一定数量的行。我们将从具有更多观测值的bin中绘制更多行,而从具有较少观测值的bin中绘制较少行,以保持X的结构。
特别地,每个bin的相对贡献应为:
rel = [float(i) / sum(hist) for i in hist]

这将类似于[0.1, 0.2, 0.1, 0.3, 0.3]
如果我们想要200个样本,就需要进行采样:
draws_in_bin = [int(i*200) for i in rel]

现在我们知道了每个箱子要绘制多少个观测值:
strats = []
for k in range(11):
        y_val = k*0.1

        #get a dataframe for every value of Y
        dummy_df = your_df[your_df['Y'] == y_val]

        bin_strat = []
        for left_edge, right_edge, n_draws in zip(edges[:-1], edges[1:], draws_in_bin):

             bin_df = dummy_df[ (dummy_df['X1']> left_edge) 
                              & (dummy_df['X1']< right_edge) ]

             bin_strat.append(bin_df.sample(n_draws))
             # this takes the right number of draws out 
             # of the X1 bin where we currently are
             # Note that every element of bin_strat is a dataframe
             # with a number of entries that corresponds to the 
             # structure of draws_in_bin
        #
        #concatenate the dataframes for every bin and append to the list
        strats.append( pd.concat(bin_strat) )

好的,这将DataFrame分成11个折叠,并以随机方式填充每个折叠中的200行。这是我目标的一部分。第二个目标是使这些折叠层化,例如X1在每个折叠中具有大致相同的结构。 - HonzaB
其实我认为这就是我所需要的。我会放宽分层限制,使用随机样本进行工作。 - HonzaB
1
啊!我刚刚回复了如何处理它的方式。如果你感兴趣可以看一下。虽然不是非常直接或优雅,但我想应该能够工作。 - elelias
我喜欢你解决分层问题的想法。非常有用。谢谢! - HonzaB
一般来说,如果 X1Y 之间没有相关性,你应该通过随机抽样得到分层结果。但是,仅有200个样本,那么很可能会观察到差异。 - elelias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接