【问题标题】:pandas GroupBy columns with NaN (missing) values具有 NaN(缺失)值的 pandas GroupBy 列
【发布时间】:2013-08-28 01:53:32
【问题描述】:

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

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

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

【问题讨论】:

  • 无法在组中包含(和传播)NaN 非常令人恼火。引用 R 并不令人信服,因为这种行为与许多其他事情不一致。无论如何,虚拟黑客也很糟糕。但是,如果有 NaN,组的大小(包括 NaN)和计数(忽略 NaN)会有所不同。 dfgrouped = df.groupby(['b']).a.agg(['sum','size','count']) dfgrouped['sum'][dfgrouped['size']!=dfgrouped['count ']] = 无
  • 你能总结一下你具体想要达到的目标吗?即我们看到了一个输出,但是“期望的”输出是什么?
  • 使用 pandas 1.1,您很快就能在 groupby() 中指定 dropna=False 以获得您想要的结果。 More info
  • 请注意,在撰写本文时,有一个错误导致 dropna=False 因 MultiIndex 分组而失败。有一些未解决的问题在他们的 github 上提到了这一点,不幸的是,修复它的动力并不大。

标签: python pandas group-by pandas-groupby nan


【解决方案1】:

熊猫 >= 1.1

从 pandas 1.1 开始,您可以更好地控制这种行为,NA values are now allowed in the grouper 使用 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

【讨论】:

  • 希望这个答案能逐渐登上顶峰。这是正确的方法。
  • 这对我不起作用。 kroscek_jupyter_metabase = fromdb_1474_detail.groupby(groupby, dropna = False)[col_to_count].count() 返回TypeError: groupby() got an unexpected keyword argument 'dropna'
  • @Cignitor 请运行 print(pd.__version__) 并告诉我它的内容。
  • 不幸的是,MultiIndex 分组失败了。到目前为止,我见过的最直接的解决方法(虽然很丑陋)似乎是在分组之前替换 NaN 值。
  • 我像 None 一样在同一个组中!
【解决方案2】:

这是mentioned in the Missing Data section of the docs

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(参见 this github issue - 它使用相同的占位符 hack)。

但是,如 another answer 中所述,“从 pandas 1.1 开始,您可以更好地控制此行为,现在使用 dropna=False 在 grouper 中允许使用 NA 值

【讨论】:

  • 这是我之前想到的一个合乎逻辑但有趣的解决方案,Pandas 从空字段生成 NaN 字段,我们必须将它们改回来。这就是我正在考虑寻找其他解决方案的原因,例如运行 SQL 服务器并从那里查询表(看起来有点太复杂),或者尽管有 Pandas 还是寻找另一个库,或者使用我自己的(我想要摆脱)。谢谢
  • @GyulaSámuelKarli 对我来说这似乎是一个小错误(请参阅上面的错误报告),我的解决方案是一种解决方法。我觉得你注销了整个图书馆很奇怪。
  • 我不想写下 Pandas 只是寻找最适合我要求的工具。
  • 看看我下面的答案,我相信我找到了一个非常好的(更干净,可能更快)的解决方案。 stackoverflow.com/a/43375020/408853
  • 不,这与 R 不一致。 df %>% group_by 也会给出 NA 摘要并带有警告,可以通过将分组列传递给 fct_explicit_na 然后创建(缺失)级别来避免这种情况.
【解决方案3】:

古老的话题,如果有人仍然对此感到困惑——另一种解决方法是在分组之前通过 .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

【讨论】:

  • @K3---rnc:查看对您链接的评论 - 您链接中帖子的作者做错了。
  • @Thomas,是的,就像上面的例子一样。如果您可以使示例安全(并且微不足道),请进行编辑。
  • suma 在这里是字符串连接,而不是数字总和。这仅“有效”,因为“b”由不同的条目组成。你需要'a'是数字,'b'是字符串
【解决方案4】:

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

【讨论】:

  • 那是因为您将整个 DF 转换为 str,而不仅仅是 b
  • 请注意,这已在上述答案中得到修复。
  • 我认为新的解决方案更好,但仍然不安全。考虑一种情况,其中“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()
【解决方案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]

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

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

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

【讨论】:

  • 这是一般情况下的最佳解决方案,但在我知道可以使用无效字符串/数字的情况下,我可能会选择下面安迪·海登的回答......我希望 pandas 尽快解决此问题。
【解决方案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 的行为。很抱歉将其添加为不同的答案,我没有足够的声誉来发表评论。)

【讨论】:

  • 还有df['b'].fillna(-1)
【解决方案7】:

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

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

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

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

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

【讨论】:

  • 这对我很有帮助,但它回答的问题与原来的问题略有不同。 IIUC,您的解决方案在求和中传播 NaN,但“b”列中的 NaN 项仍会作为行删除。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-20
相关资源
最近更新 更多