pandas GroupBy列的NaN(缺失)值

我有一个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目标值的行。(我想包括这些行!)

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

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

292655 次浏览

这是在文档的缺失数据部分中提到过:

“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(见这个github问题 -使用相同的占位符黑客)。

然而,正如另一个答案, "从熊猫1.1你可以更好地控制这种行为,NA值现在允许在石斑鱼使用dropna=False"

古老的话题,如果有人仍然在这个问题上绊倒-另一个解决方案是在分组之前通过.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

我不能给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作为字符串连接起来,而不是作为数字相加。

安迪·海登的解决方案有一个小问题——它不起作用了(不再起作用了?),因为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的行为。很抱歉补充一个不同的答案,我没有足够的声誉来评论。)

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

不能在组中包含(和传播)nan是相当严重的。引用R是没有说服力的,因为这种行为与许多其他事情不一致。不管怎样,虚拟黑客也很糟糕。但是,如果组中存在nan,则组的大小(包括nan)和计数(忽略nan)会有所不同。

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


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

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

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

一个比较简单的解决方法是使用pd.drop_duplicate()来创建值组合的唯一索引,每个值组合都有自己的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')]))

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

熊猫>= 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