在 pandas 数据帧中对多个 class 训练示例(行)进行过采样和欠采样以指定值

Over and under sample multi-class training examples (rows) in a pandas dataframe to specified values

我想使多 class pandas 数据帧更加平衡以进行训练。我的训练集的简化版本如下所示:

不平衡数据帧:class 0、1 和 2 的计数分别为 7、3 和 1

   animal  class
0    dog1      0
1    dog2      0
2    dog3      0
3    dog4      0
4    dog5      0
5    dog6      0
6    dog7      0
7    cat1      1
8    cat2      1
9    cat3      1
10  fish1      2

我用代码做了这个:

import pandas as pd    
data = {'animal': ['dog1', 'dog2', 'dog3', 'dog4','dog5', 'dog6', 'dog7', 'cat1','cat2', 'cat3', 'fish1'], 'class': [0,0,0,0,0,0,0,1,1,1,2]}   
df = pd.DataFrame(data)  

现在我想对大多数 class(es) 随机抽样不足,并随机对少数 class(es) 抽样到每个 class 的指定值以获得更多平衡数据框。

问题是我可以在网上找到的所有 pandas 教程或​​关于此主题的 Whosebug 上的其他问题都涉及随机对少数 class 抽样到多数 class(例如:)或随机抽样多数 class 到少数 class.

的水平

由于我面临极度不平衡,我无法使多数class的大小等于少数class的大小。因此,我能找到的这些代码片段通常对我不起作用。理想情况下,我将能够指定每个 class 的样本的确切数量,然后通过过度或欠采样生成(取决于我为 class 指定的数量和样本数量class 包含)。

例如,

如果我指定:

我想成为这样的人:

更平衡的数据帧:class 0、1 和 2 的计数分别为 5、4 和 3

   animal  class
0    dog2      0
1    dog3      0
2    dog5      0
3    dog6      0
4    dog7      0
5    cat1      1
6    cat2      1
7    cat3      1
8    cat2      1
9   fish1      2
10  fish1      2
11  fish1      2

解决这个问题的最佳方法是什么?

因为 groupby.sample 不允许 n 大于组大小,如果 replace 不是 True 但替换为 True 意味着即使在本可以下采样的组中也会发生替换。

让我们尝试使用 groupby.apply + sample 并有条件地为每个组启用 replace。 创建一个字典,将每个 class 映射到样本数,并使用条件逻辑来确定是否替换:

sample_amounts = {0: 5, 1: 4, 2: 3}

s = (
    df.groupby('class').apply(lambda g: g.sample(
        # lookup number of samples to take
        n=sample_amounts[g.name],
        # enable replacement if len is less than number of samples expected
        replace=len(g) < sample_amounts[g.name]  
    ))
)

s:

         animal  class
class                 
0     5    dog6      0
      3    dog4      0
      6    dog7      0
      4    dog5      0
      2    dog3      0
1     9    cat3      1
      8    cat2      1
      7    cat1      1
      8    cat2      1
2     10  fish1      2
      10  fish1      2
      10  fish1      2

droplevel可用于保留初始索引(如果重要):

sample_amounts = {0: 5, 1: 4, 2: 3}

s = (
    df.groupby('class').apply(lambda g: g.sample(
        n=sample_amounts[g.name],
        replace=len(g) < sample_amounts[g.name]
    ))
    .droplevel(0)
)

s:

   animal  class
6    dog7      0
3    dog4      0
2    dog3      0
4    dog5      0
1    dog2      0
7    cat1      1
8    cat2      1
8    cat2      1
8    cat2      1
10  fish1      2
10  fish1      2
10  fish1      2

reset_index如果索引不重要可以用:

sample_amounts = {0: 5, 1: 4, 2: 3}

s = (
    df.groupby('class').apply(lambda g: g.sample(
        n=sample_amounts[g.name],
        replace=len(g) < sample_amounts[g.name]
    ))
    .reset_index(drop=True)
)

s:

   animal  class
0    dog1      0
1    dog2      0
2    dog4      0
3    dog5      0
4    dog3      0
5    cat3      1
6    cat2      1
7    cat1      1
8    cat3      1
9   fish1      2
10  fish1      2
11  fish1      2