【问题标题】:Fill categorical NaN values based upon proportion within groupby根据 groupby 中的比例填充分类 NaN 值
【发布时间】:2020-09-24 16:52:41
【问题描述】:

我正在处理一个完全由分类特征组成的数据集。

一列只有缺失值:8124 中的 2480 NaN。

我可以根据现有分类值的百分比成功填充 NaN 值:

print(df['stalk-root'].value_counts(normalize=True), '\n')

产量:

b    0.669029
e    0.198441
c    0.098512
r    0.034018

然后我使用这些百分比来填充缺失值:

# https://stackoverflow.com/questions/38934140/fill-missing-values-by-a-ratio-of-other-values-in-pandas
df['stalk-root'] = df['stalk-root'].fillna(pd.Series(np.random.choice(['b', 'e', 'c', 'r'],
                                                     p=[0.669029, 0.198441, 0.098512, 0.034018], size=len(df))))

效果很好。

但是,如果我按“类”列分组,我很好奇 df['stalk-root'] 列的 value_counts 是什么样的。

print(df.groupby('class')['stalk-root'].value_counts(normalize=True), '\n')

产量:

e      b             0.550459
       e             0.247706
       c             0.146789
       r             0.055046
p      b             0.860853
       e             0.118738
       c             0.020408

这是一个很大的不同。足够大,我现在想将我的 NaN 填充程序修改为第一个 groupby 类,然后按百分比填充,如上。

我以前用数字列和 mean() 做过这个,但不同的是我是手动的 根据 value_counts(normalize=True) 的结果填充 np.random.choice() 中的百分比。

我不知道怎么说:groupby 类,运行 ['stalk-root'].value_counts(normalize=True),然后像我一样将这些值输入 fillna(np.random.choice()上面做了。

我将有两组完全不同的填充值,而“r”只出现在其中一组中。

一个将是(对于“e”类):

np.random.choice(['b', 'e', 'c', 'r'],
                   p=[0.550459, 0.247706, 0.146789, 0.055046], size=len(df)

另一个(对于“p”类)将是:

np.random.choice(['b', 'e', 'c'],
                   p=[0.860853, 0.118738, 0.020408], size=len(df)

我遇到的第二个问题是 size=len(df)。这必须是每个分组的大小(我假设),并且它们的大小不同。

data file from kaggle

【问题讨论】:

  • 可以提供样品吗?说df[['class', 'stalk-root']].head(50).to_dict()
  • @RichieV,我必须编辑我的问题以适合您要求的输出。见上文。
  • 我正在使用来自 Kaggle 的蘑菇.csv 数据集。
  • 那个数据集中没有NaNs...你下载后修改了吗?
  • df.info() 数据集中没有空值。 RangeIndex: 8124 entries, 0 to 8123 & 所有列都有8124 non-null

标签: python-3.x pandas pandas-groupby


【解决方案1】:

这是groupby的解决方案

was_null = df['stalk-root'].isna()

for _, gdf in df.groupby('class')['stalk-root']:
    vc = gdf.value_counts(normalize=True)
    df.loc[gdf.loc[gdf.isna()].index, 'stalk-root'] = (
        np.random.choice(vc.index, gdf.isna().sum(), p=vc)
    )

验证输出

# old distribution
print(df[was_null].groupby('class')['stalk-root'].value_counts(normalize=True))

class  stalk-root
e      b             0.561111
       e             0.236111
       c             0.140278
       r             0.062500
p      b             0.865341
       e             0.117045
       c             0.017614
Name: stalk-root, dtype: float64


# new distribution
print(df.groupby('class')['stalk-root'].value_counts(normalize=True))

class  stalk-root
e      b             0.552281
       e             0.245722
       c             0.145675
       r             0.056321
p      b             0.862870
       e             0.117978
       c             0.019152
Name: stalk-root, dtype: float64

【讨论】:

    【解决方案2】:

    也许我理解错了,但你能不能这样做:

    class_e = pd.Series(np.random.choice(['b', 'e', 'c', 'r'],
                       p=[0.550459, 0.247706, 0.146789, 0.055046], size=len(df)))
    class_p = pd.Series(np.random.choice(['b', 'e', 'c'],
                       p=[0.860853, 0.118738, 0.020408], size=len(df))
    
    df.loc[df['class'] == e, 'stalk-root'] = df['stalk-root'].fillna(class_e)
    df.loc[df['class'] == p, 'stalk-root'] = df['stalk-root'].fillna(class_p)
    

    【讨论】:

    • e_size = df[df['class'] == e, :].size。如果我使用e_size = len(df[df['class'] == 'e']),则错误已解决,但结果不正确。您最初的解决方案似乎是正确的。它必须循环遍历整个 df,因为受影响的行可能在任何地方。起初我没有意识到这一点。如果您修改后的解决方案包含在 groupby() 对象中,那么我认为它是正确的。
    • 我什至可以通过一次注释掉 df.loc[df['class'] == e, 'stalk-root'] 行来准确查看您的第一个解决方案的每个部分填充了多少行。
    • 对不起,我不太明白。如果您希望在 groupby 中使用 @RichieV 的解决方案,应该接受 :)
    • 对我来说没关系。您最初的解决方案运行良好。删除您的编辑以将初始解决方案放回原处。
    猜你喜欢
    • 2017-06-29
    • 1970-01-01
    • 2015-10-05
    • 1970-01-01
    • 1970-01-01
    • 2014-11-05
    • 2021-01-24
    • 1970-01-01
    • 2019-05-10
    相关资源
    最近更新 更多