为什么分组操作的行为会有所不同

4
当使用pandas groupby functions并在groupby之后操作输出时,我注意到一些函数在返回索引和如何操作索引方面的行为不同。
假设我们有一个包含以下信息的数据框:
    Name   Type  ID
0  Book1  ebook   1
1  Book2  paper   2
2  Book3  paper   3
3  Book1  ebook   1
4  Book2  paper   2

如果我们这样做
df.groupby(["Name", "Type"]).sum()  

我们得到一个DataFrame

             ID
Name  Type     
Book1 ebook   2
Book2 paper   4
Book3 paper   3

这个MultiIndex包含了在groupby中使用的列:

MultiIndex([('Book1', 'ebook'),
            ('Book2', 'paper'),
            ('Book3', 'paper')],
           names=['Name', 'Type'])

有一列称为ID

但是,如果我应用size()函数,结果将是一个Series

Name   Type 
Book1  ebook    2
Book2  paper    2
Book3  paper    1
dtype: int64

最后,如果我执行 pct_change(),我们只会得到结果DataFrame的一列:
    ID
0   NaN
1   NaN
2   NaN
3   0.0
4   0.0

TL;DR。我想知道为什么有些函数返回一个Series,而其他一些函数返回一个DataFrame,因为这让我在处理同一个DataFrame中的不同操作时感到困惑。

2个回答

3

来自文档

大小:

Returns
Series
Number of rows in each group.
对于 sum,由于您没有传递给它要求聚合的列,因此它将返回未经分组键处理的数据框。
df.groupby(["Name", "Type"])['ID'].sum()  # return Series

diffpct_change 这样的函数不是聚合函数,它们将返回与原始数据帧相同的索引值,而对于 countmeansum 这些聚合函数,则会以值和 groupby 键作为索引进行返回。


我明白了,但是缺乏标准的原因是什么呢?如果操作返回相同的结构,那不是更容易吗? - Gabriel Ziegler
1
@GabrielZiegler 这取决于函数的类型,例如 diff 函数将返回每一行,但 sum 函数会将整个 groupby 值视为一个输出。 - BENY

2
输出结果不同是因为 聚合方式(aggregation)不同,而聚合方式主要控制返回的内容。可以将其看作等效于数组。数据相同,但一个“聚合”返回单个标量值,另一个则返回与输入大小相同的数组。
import numpy as np
np.array([1,2,3]).sum()
#6

np.array([1,2,3]).cumsum()
#array([1, 3, 6], dtype=int32)

同样的事情也适用于DataFrameGroupBy对象的聚合。 groupby的第一部分只是从DataFrame创建到组的映射。由于这并没有真正做任何事情,因此没有理由认为使用不同操作的相同groupby需要返回相同类型的输出(参见上文)。
gp = df.groupby(["Name", "Type"])
# Haven't done any aggregations yet...

这里另一个重要的部分是我们有一个DataFrameGroupBy对象。还有SeriesGroupBy对象,它们之间的区别会影响返回结果。

gp
#<pandas.core.groupby.generic.DataFrameGroupBy object>

当你进行聚合操作时会发生什么?

使用DataFrameGroupBy进行聚合(如sum)并将其压缩为每个组的单个值时,返回的将是一个DataFrame,其中索引是唯一的分组键。返回的是一个DataFrame,因为我们提供了一个DataFrameGroupBy对象。DataFrames可以有多列,如果还有另一个数字列,那么它也会被聚合,需要输出DataFrame。

gp.sum()
#             ID
#Name  Type     
#Book1 ebook   2
#Book2 paper   4
#Book3 paper   3

另一方面,如果您使用SeriesGroupBy对象(使用[]选择单个列),则会返回一个Series,其中包含唯一组键的索引。

df.groupby(["Name", "Type"])['ID'].sum()
|------- SeriesGroupBy ----------|

#Name   Type 
#Book1  ebook    2
#Book2  paper    4
#Book3  paper    3
#Name: ID, dtype: int64

对于返回数组的聚合(如cumsum、pct_change),DataFrameGroupBy将返回一个DataFrame,而SeriesGroupBy将返回一个Series。但是索引不再是唯一的组键。这是因为这样做没有意义;通常您想在组内进行计算,然后将结果分配回原始的DataFrame。因此,返回值的索引类似于您提供给聚合的原始DataFrame。由于pandas处理所有对齐操作,因此创建这些列非常简单。
df['ID_pct_change'] = gp.pct_change()

#    Name   Type  ID  ID_pct_change
#0  Book1  ebook   1            NaN  
#1  Book2  paper   2            NaN   
#2  Book3  paper   3            NaN   
#3  Book1  ebook   1            0.0  # Calculated from row 0 and aligned.
#4  Book2  paper   2            0.0

但是 size 怎么办?它有点奇怪。一个组的 size 是一个标量。无论这个组有多少列或这些列中是否有缺失值,将其发送到 DataFrameGroupBy 或 SeriesGroupBy 对象都是无关紧要的。因此,pandas 总会返回一个 Series。再次强调,作为一种分组级别的聚合,返回一个标量,因此对于唯一的组键进行索引化是有意义的。

gp.size()
#Name   Type 
#Book1  ebook    2
#Book2  paper    2
#Book3  paper    1
#dtype: int64

为了完整起见,虽然像sum这样的聚合函数返回单个标量值,但将这些值带回原始DataFrame中该组的每一行通常是有用的。然而,正常的.sum返回具有不同索引的结果,因此它们不会对齐。您可以将值与唯一键合并,但是pandas提供了转换这些聚合的能力。由于这里的意图是将其带回原始DataFrame,因此Series/DataFrame的索引与原始输入相同。

gp.transform('sum')
#   ID
#0   2    # Row 0 is Book1 ebook which has a group sum of 2
#1   4
#2   3
#3   2    # Row 3 is also Book1 ebook which has a group sum of 2
#4   4

1
非常感谢您如此专注和教学性的回复。 - Gabriel Ziegler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接