具有NaN(缺失)值的groupby列

Gyu*_*rli 119 grouping nan pandas

我有一个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]}
Run Code Online (Sandbox Code Playgroud)

看到Pandas已经删除了具有NaN目标值的行.(我想要包含这些行!)

由于我需要很多这样的操作(许多cols都缺少值),并且使用比中位数(通常是随机森林)更复杂的函数,我想避免编写太复杂的代码片段.

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

cs9*_*s95 159

熊猫 >= 1.1

从 pandas 1.1 开始,您可以更好地控制这种行为,现在在 grouper 中允许使用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
Run Code Online (Sandbox Code Playgroud)
# with NA
df.groupby('b', dropna=False).sum()

     a  c
b        
1.0  2  3
2.0  2  5
NaN  1  4
Run Code Online (Sandbox Code Playgroud)

  • 希望这个答案能够逐步上升到顶峰。这是正确的做法。 (17认同)
  • 不幸的是,多索引分组失败了。到目前为止,我见过的最直接的解决方法虽然很难看,但似乎是在分组之前替换 NaN 值。 (3认同)

And*_*den 106

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

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
Run Code Online (Sandbox Code Playgroud)

也就是说,这感觉非常糟糕...也许应该有一个选项在groupby中包含NaN(参见这个github问题 - 它使用相同的占位符hack).

  • 不,这与 R 不一致。 df %>% group_by 也会给出 NA 摘要,并带有警告,可以通过将分组列传递给 fct_explicit_na 来避免,然后创建(缺少)级别。 (4认同)
  • 这是一个合乎逻辑的但是我之前想到的一种有趣的解决方案,Pandas从空的那些中制作NaN字段,我们必须将它们改回来.这就是我正在考虑寻找其他解决方案的原因,比如运行SQL服务器并从那里查询表(看起来有点太复杂),或者看看Pandas的其他库,或者使用我自己的(我想要的)摆脱).谢谢 (3认同)

小智 28

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

in:
Run Code Online (Sandbox Code Playgroud)

__CODE__ __CODE__

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

  • 这里“a”的“sum”是字符串连接,而不是数字和。这只是“有效”,因为“b”由不同的条目组成。您需要“a”为数字,“b”为字符串 (3认同)

Kam*_*chi 7

我无法向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
Run Code Online (Sandbox Code Playgroud)

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

  • 那是因为你将整个DF转换为str,而不仅仅是`b`列 (10认同)

Tue*_*hek 6

安迪海登解决方案的一个小问题 - 它不起作用(不再?)因为np.nan == np.nan产量False,所以replace功能实际上并没有做任何事情.

对我有用的是:

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

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

  • 还有`df ['b'].fillna(-1)`. (9认同)

小智 5

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

无法在组中包含(和传播) NaN 是相当令人恼火的。引用 R 并不令人信服,因为这种行为与许多其他事情不一致。不管怎样,虚拟黑客也很糟糕。但是,如果存在 NaN,则组的大小(包括 NaN)和计数(忽略 NaN)将会不同。

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

dfgrouped['sum'][dfgrouped['size']!=dfgrouped['count']] = None
Run Code Online (Sandbox Code Playgroud)

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


Gra*_*eth 5

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

一个不太麻烦的解决方法是使用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]
Run Code Online (Sandbox Code Playgroud)

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

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')]))
Run Code Online (Sandbox Code Playgroud)

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