具有 NaN(缺失)值的 pandas GroupBy 列

问题描述 投票:0回答:7

我有一个 DataFrame,在我希望分组的列中有许多缺失值:

import pandas as pd
import numpy as np
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})

In [4]: df.groupby('b').groups
Out[4]: {'4': [0], '6': [2]}

看到 Pandas 删除了目标值为 NaN 的行。 (我想包括这些行!)

因为我需要很多这样的操作(很多列都有缺失值),并且使用比中位数更复杂的函数(通常是随机森林),所以我想避免编写太复杂的代码。

有什么建议吗?我应该为此编写一个函数还是有一个简单的解决方案?

python pandas group-by pandas-groupby nan
7个回答
301
投票

熊猫 >= 1.1

从 pandas 1.1 开始,您可以更好地控制这种行为,NA 值现在允许在石斑鱼中使用dropna=False


pd.__version__ # '1.1.0.dev0+2004.g8d10bfb6f' # Example from the docs df a b c 0 1 2.0 3 1 1 NaN 4 2 2 1.0 3 3 1 2.0 2 # without NA (the default) df.groupby('b').sum() a c b 1.0 2 3 2.0 2 5

# with NA df.groupby('b', dropna=False).sum() a c b 1.0 2 3 2.0 2 5 NaN 1 4
    

175
投票
这是

在文档的缺失数据部分中提到的

GroupBy 中的 NA 组被自动排除。此行为与 R

一致

一种解决方法是在执行 groupby 之前使用占位符(例如 -1):

In [11]: df.fillna(-1)
Out[11]: 
   a   b
0  1   4
1  2  -1
2  3   6

In [12]: df.fillna(-1).groupby('b').sum()
Out[12]: 
    a
b    
-1  2
4   1
6   3

就是说,这感觉很糟糕……也许应该有一个选项将 NaN 包含在 groupby 中(参见this github issue——它使用相同的占位符 hack)。

但是,如另一个答案中所述,“从 pandas 1.1 开始,您可以更好地控制这种行为,NA 值现在允许在石斑鱼中使用 dropna=False


51
投票

古老的话题,如果有人仍然被这个问题绊倒——另一个解决方法是在分组之前通过 .astype(str) 转换为字符串。这将保存 NaN 的。

df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()
    a
b   
4   1
6   3
nan 2

13
投票

我无法对 M. Kiewisch 添加评论,因为我没有足够的声誉点数(只有 41,但需要超过 50 点才能评论)。

无论如何,只想指出 M. Kiewisch 解决方案不能按原样工作,可能需要更多调整。考虑例如

>>> df = pd.DataFrame({'a': [1, 2, 3, 5], 'b': [4, np.NaN, 6, 4]})
>>> df
   a    b
0  1  4.0
1  2  NaN
2  3  6.0
3  5  4.0
>>> df.groupby(['b']).sum()
     a
b
4.0  6
6.0  3
>>> df.astype(str).groupby(['b']).sum()
      a
b
4.0  15
6.0   3
nan   2

表明对于组b=4.0,对应的值为15而不是6。这里只是将1和5拼接成字符串,而不是将其作为数字相加。


7
投票

到目前为止提供的所有答案都会导致潜在的危险行为,因为您很可能选择了一个实际上是数据集一部分的虚拟值。随着您创建具有许多属性的组,这种情况越来越有可能发生。简而言之,该方法并不总能很好地概括。

一个不那么棘手的解决方案是使用 pd.drop_duplicates() 创建一个唯一的值组合索引,每个索引都有自己的 ID,然后根据该 ID 进行分组。它更冗长但确实完成了工作:

def safe_groupby(df, group_cols, agg_dict):
    # set name of group col to unique value
    group_id = 'group_id'
    while group_id in df.columns:
        group_id += 'x'
    # get final order of columns
    agg_col_order = (group_cols + list(agg_dict.keys()))
    # create unique index of grouped values
    group_idx = df[group_cols].drop_duplicates()
    group_idx[group_id] = np.arange(group_idx.shape[0])
    # merge unique index on dataframe
    df = df.merge(group_idx, on=group_cols)
    # group dataframe on group id and aggregate values
    df_agg = df.groupby(group_id, as_index=True)\
               .agg(agg_dict)
    # merge grouped value index to results of aggregation
    df_agg = group_idx.set_index(group_id).join(df_agg)
    # rename index
    df_agg.index.name = None
    # return reordered columns
    return df_agg[agg_col_order]

请注意,您现在可以简单地执行以下操作:

data_block = [np.tile([None, 'A'], 3),
              np.repeat(['B', 'C'], 3),
              [1] * (2 * 3)]

col_names = ['col_a', 'col_b', 'value']

test_df = pd.DataFrame(data_block, index=col_names).T

grouped_df = safe_groupby(test_df, ['col_a', 'col_b'],
                          OrderedDict([('value', 'sum')]))

这将返回成功的结果,而不必担心覆盖被误认为虚拟值的真实数据。


6
投票

Andy Hayden 的解决方案的一个小问题 - 它不起作用(不再?)因为

np.nan == np.nan
产生
False
,所以
replace
函数实际上没有做任何事情。

对我有用的是:

df['b'] = df['b'].apply(lambda x: x if not np.isnan(x) else -1)

(至少那是 Pandas 0.19.2 的行为。很抱歉将其添加为不同的答案,我没有足够的声誉来发表评论。)


5
投票

我已经回答了这个问题,但由于某种原因,答案被转换为评论。尽管如此,这是最有效的解决方案:

无法在组中包含(和传播)NaN 非常令人恼火。引用 R 是没有说服力的,因为这种行为与许多其他事情不一致。无论如何,虚拟黑客也很糟糕。但是,如果存在 NaN,则组的大小(包括 NaN)和计数(忽略 NaN)将不同。

dfgrouped = df.groupby(['b']).a.agg(['sum','size','count'])

dfgrouped['sum'][dfgrouped['size']!=dfgrouped['count']] = None

当这些不同时,您可以为该组的聚合函数的结果将值设置回无。

© www.soinside.com 2019 - 2024. All rights reserved.