pandas groupby 中的 NaN 项目后跟包括类别列预期行为的聚合吗?

Are NaN items in pandas groupby followed by aggregation including category column expected behavior?

我聚合了一个包含类别列的 pandas DataFrame。输出包含几个我没有预料到且不理解的 NaN。示例代码:

import pandas as pd

d = pd.DataFrame({'a': [1, 1, 2, 2], 'b': [1, 1, 2, 2], 'c': [1, 2, 3, 4]})
d.astype({'b': 'category'}).groupby('a').agg({'b':'first', 'c':['mean', 'max']})

我得到以下输出:

      b    c     
  first mean  max
0     1  NaN  NaN
1     2  1.5  2.0
2   NaN  3.5  4.0

我本应得到以下输出:

      b    c    
  first mean max
a               
1     1  1.5   2
2     2  3.5   4

有人可以解释 NaN 吗?

以下两种代码变体,一种没有类别列,一种没有 c 列的多个聚合,给出了预期的输出。

d.groupby('a').agg({'b':'first', 'c':['mean', 'max']})
d.astype({'b': 'category'}).groupby('a').agg({'b':'first', 'c':'mean'})

我正在使用 pandas 0.25.2 和 python 3.7.4。

我解释的出发点是有(和比较行为) 两个个数据帧:

  • d - 原始 DataFrame,
  • d2 = d.astype({'b': 'category'}) - 兄弟 DataFrame, b 列更改为 分类.

创建这两个 DataFrame 的分组:

gr = d.groupby('a')
gr2 = d2.groupby('a')

目前它们看起来完全相同,例如如果你 运行:

for key, grp in gr:
    print(f'\nGroup: {key}\n{grp}')

gr2 的相同代码,您将得到 完全 相同的结果。

但是当你尝试 聚合 b 列。

当你 运行 gr.b.agg('first')(在原始 DataFrame 上)时,你将得到:

a
1    1
2    2
Name: b, dtype: int64

结果是 系列 并且正如 d 内容所预期的那样:

  • 对于 a == 1 的行组,b 的第一个值是 1,
  • 对于 a == 2 的行组,b 的第一个值是 2.

但是如果你 运行 gr2.b.agg('first') (在兄弟 DataFrame 上),你将得到:

[1, 2]
Categories (2, int64): [1, 2]

结果为pandas.core.arrays.categorical.Categorical类型 更重要的是,与分组键没有关系

相反,类似的分组键是连续数字 (从 0 开始)。

你可以确认一下,运行宁gr2.agg({'b':'first'}) 你会得到:

   b
0  1
1  2

这就是您问题的根源。 当你 运行:

gr2.agg({'b':'first', 'c':['mean', 'max']})

合并分类和"normal"列,然后:

  • 以上人工分组键产生自 b,
  • 的聚合
  • actual 分组键结合 c.

对于(人工)分组键0:

  • b / first 的值为 1(见上文),
  • for c / meanc / max 没有值,所以 结果包含 2 NaNs.

用于分组键1:

  • for artificial key, b / first 的值为 2,
  • 对于 true 键,c / meanc / max 的值是 1.52.0,
  • Pandas 将它们全部放在 相同的 行中。

用于分组键2:

  • 没有"artificial"具有此值的分组键, 所以在 b / first 列中有 NaN,
  • 对于c / meanc / max对应的值, 所以它们被打印在那里。

结论:这种情况下,转换不是一个好主意 分类类型的任何列。

仅使用 原始 DataFrame,具有 "original"(非分类)类型 你会得到你预期的结果。

从 09:14:38Z

开始编辑以下评论

你在 post 和 提到的评论。

您的 post 包含写入:

I get the following output:

      b    c     
  first mean  max
0     1  NaN  NaN
1     2  1.5  2.0
2   NaN  3.5  4.0

所以打印输出确实包含NaN值。

但是在你写的评论中 我没有得到任何 NaN

为了验证这个矛盾,我又做了一次测试:

d = pd.DataFrame({'a': [1, 1, 2, 2], 'b': [1, 1, 2, 2], 'c': [1, 2, 3, 4]})
d2 = d.astype({'b': 'category'})
d2.groupby('a').agg({'b':'first', 'c':['mean', 'max']})

并得到:

      b    c     
  first mean  max
0     1  NaN  NaN
1     2  1.5  2.0
2   NaN  3.5  4.0

就像您的 post NaN 值一样。

我认为 bug, but possible solution is use function with Series.iat for first value of group, because Series.first 工作方式不同:

Convenience method for subsetting initial periods of time series data based on a date offset.

def first(x):
    return x.iat[0]

d = pd.DataFrame({'a': [1, 1, 2, 2], 'b': [1, 1, 2, 2], 'c': [1, 2, 3, 4]})
d = d.astype({'b': 'category'}).groupby('a').agg({'b':first, 'c':['mean', 'max']})
print(d)
      b    c    
  first mean max
a               
1     1  1.5   2
2     2  3.5   4

如果使用 lambda 函数,则为 MultiIndex 的第二级获取 <lambda> 字符串:

d = d.astype({'b': 'category'}).groupby('a').agg({'b':lambda x: x.iat[0], 'c':['mean', 'max']})
print(d)
         b    c    
  <lambda> mean max
a                  
1        1  1.5   2
2        2  3.5   4