pandas GroupBy列的NaN(缺失)值。

问题描述 投票:139回答: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个回答
127
投票

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

自动排除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

话说回来,这感觉是非常糟糕的黑客行为......也许应该有一个选项,在groupby中包含NaN(见 这个github问题 - 其中使用了同样的占位符)。)


39
投票

古老的话题,如果有人还在纠结这个问题--另一个变通方法是在分组前通过.astype(str)转换为字符串。这样就可以节省NaN's。

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

13
投票

pandas >= 1.1

从pandas 1.1开始,你可以更好地控制这种行为。现在在grouper中允许使用NA值。 使用 dropna=False:

# 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

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作为字符串连接,而不是将其作为数字添加。


6
投票

安迪-海登的解决方案有一个小问题--它不能用了(现在?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的行为是这样的。抱歉,我没有足够的声誉来评论,所以把它作为一个不同的答案添加进来。)


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')]))

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


4
投票

我已经回答过这个问题了,但不知什么原因,答案被转换成了评论。 尽管如此,这是最有效的解决方案。

不能将NaNs纳入(和传播)到组中是相当麻烦的。引用R并不能让人信服,因为这种行为与其他很多事情都不一致。总之,假人黑客也是相当糟糕的。然而,如果有NaNs,一个组的大小(包括NaNs)和计数(忽略NaNs)会有所不同。

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

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

当这些不同的时候,你可以将该组的聚合函数的结果值设回None。

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