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


147

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

由于我需要许多这样的操作(许多col具有缺失的值),并且使用的函数比中位数(通常是随机森林)更复杂,因此我想避免编写过于复杂的代码。

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


1
@PhillipCloud我已经编辑了这个问题,只包含了与杰夫的开放大熊猫增强相关的问题,实际上非常好。
安迪·海登

1
不能在组中包含(和传播)NaN的情况更加严重。引用R不能令人信服,因为此行为与许多其他情况不一致。无论如何,虚拟黑客也很糟糕。但是,如果存在NaN,则组的大小(包括NaN)和计数(忽略NaN)将有所不同。dfgrouped = df.groupby(['b'])。a.agg(['sum','size','count'])dfgrouped ['sum'] [dfgrouped ['size']!= dfgrouped ['count “]] =无
布赖恩Preslopsky

您能否总结一下您要具体实现的目标?即,我们看到一个输出,但是“所需”输出是什么?
大约

1
随着熊猫1.1,你很快就可以指定dropna=Falsegroupby()得到你想要的结果。更多信息
cs95

Answers:


130

这是在文档的丢失数据部分中提到

GroupBy中的NA组被自动排除。例如,此行为与R一致。

一种解决方法是在进行分组方式之前使用占位符(例如-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

就是说,这感觉很糟糕……也许应该有一个选项可以在groupby中包含NaN(请参阅此github问题 -使用相同的占位符hack)。


4
这是一个逻辑但有趣的解决方案,我之前已经想到过,Pandas从空字段中提取NaN字段,我们必须将其改回。这就是我正在考虑寻找其他解决方案的原因,例如运行SQL Server并从那里查询表(看起来有点太复杂),或者尽管有Pandas却寻找另一个库,或者使用我自己的(我想要的)摆脱)。Thx
GyulaSámuelKarli

@GyulaSámuelKarli对我来说,这似乎是个小错误(请参见上面的错误报告),我的解决方案是一种解决方法。我觉得很奇怪,您注销了整个库。
安迪·海登

1
我不想写下熊猫只是寻找最适合我的要求的工具。
2013年

1
在下面查看我的答案,我相信我已经找到了一个很好的(更干净,并且可能更快)的解决方案。stackoverflow.com/a/43375020/408853
大约

4
不,这与R不一致。df%>%group_by也会给NA摘要提供警告,可以通过将分组列传递到fct_explicit_na来避免,然后创建一个(Missing)级别来避免。
破坏护理

40

古老的话题,如果有人仍然迷迷糊糊-另一个解决方法是在分组之前通过.astype(str)转换为字符串。这样可以节省NaN。

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

@ K3 --- rnc:看到链接的注释-链接中帖子的作者做错了什么。
汤玛斯(Thomas)

@Thomas,是的,与上面的示例完全一样。如果可以使示例安全(并且不重要),请进行编辑。
K3 --- rnc

suma是字符串连接在这里,而不是一个数字之和。这只是“有效”,因为“ b”由不同的条目组成。您需要'a'为数字,'b'为字符串
BallpointBen

28

大熊猫> = 1.1

从pandas 1.1开始,您将可以更好地控制此行为,现在可以使用dropna=False以下方法在石斑鱼中使用NA值

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

您可以使用以下命令安装v1.1的预发行版本:

pip install https://github.com/pandas-dev/pandas/releases/download/v1.1.0rc0/pandas-1.1.0rc0.tar.gz

4
希望这个答案能逐步达到顶峰。这是正确的方法。
kdbanman

我不认为1.1已经发布。检查了conda和pip以及仍然有1.0.4的版本
sammywemmy

1
@sammywemmy是的,目前只能在开发环境中运行。在旧SO帖子中引入新功能时,我希望抢先一步。;-)
cs95

9

我无法为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连接为字符串,而不是将它们加为数字。


12
那是因为您将整个DF转换为str,而不是仅转换为b
Korem '17

请注意,此问题已在上述答案中得到修复。
Shaido-恢复莫妮卡

1
我认为新的解决方案更好,但仍然不安全。考虑一种情况,其中“ b”列中的一项与字符串化的np.NaN相同。然后将这些东西结合在一起。df = pd.DataFrame({'a':[1,2,3,5,6],'b':['foo',np.NaN,'bar','foo','nan']}) ; df ['b'] = df ['b']。astype(str); df.groupby(['b'])。sum()
Kamaraju Kusumanchi,

6

安迪·海登(Andy Hayden)解决方案的一个小问题–由于np.nan == np.nanyields False,它不起作用了(不再吗?),因此该replace函数实际上没有任何作用。

对我有用的是:

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

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


12
也有df['b'].fillna(-1)
K3 --- rnc

6

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

不太麻烦的解决方法是使用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')]))

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


这是一般情况下的最佳解决方案,但是在我知道我可以使用无效的字符串/数字的情况下,我可能会选择下面的Andy Hayden的答案...我希望熊猫能尽快解决此问题。
莎拉·梅瑟

4

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

无法在群组中包含(和传播)NaN的情况更加严重。引用R不能令人信服,因为此行为与许多其他情况不一致。无论如何,虚拟黑客也很糟糕。但是,如果存在NaN,则组的大小(包括NaN)和计数(忽略NaN)将有所不同。

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

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

如果它们不同,则可以将该组的聚合函数的结果的值设置回None。


1
这对我很有帮助,但它回答的问题与原始问题略有不同。IIUC,您的解决方案会传播求和的NaN,但“ b”列中的NaN项仍会作为行删除。
Andrew

0

在Anaconda中安装了Pandas 1.1

我无法评论cs95的答案,但他帮助我解决了该问题。

我尝试安装Pandas 1.1,但使用他的代码失败,因此我用google搜索并能够安装。

我首先以管理员身份运行anaconda提示符,然后粘贴以下代码:

pip install pandas==1.1.0rc0

之后包括使用 dropna = False

链接:https//libraries.io/pypi/pandas

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.