pandas:GroupBy .pipe() 与 .apply()

pandas: GroupBy .pipe() vs .apply()

pandas documentation 关于 GroupBy 对象的新 .pipe() 方法的示例中,接受相同 lambda 的 .apply() 方法会 return 相同的结果。

In [195]: import numpy as np

In [196]: n = 1000

In [197]: df = pd.DataFrame({'Store': np.random.choice(['Store_1', 'Store_2'], n),
   .....:                    'Product': np.random.choice(['Product_1', 'Product_2', 'Product_3'], n),
   .....:                    'Revenue': (np.random.random(n)*50+10).round(2),
   .....:                    'Quantity': np.random.randint(1, 10, size=n)})

In [199]: (df.groupby(['Store', 'Product'])
   .....:    .pipe(lambda grp: grp.Revenue.sum()/grp.Quantity.sum())
   .....:    .unstack().round(2))

Out[199]: 
Product  Product_1  Product_2  Product_3
Store                                   
Store_1       6.93       6.82       7.15
Store_2       6.69       6.64       6.77

我可以看到 pipe 功能与 DataFrame 对象的 apply 有何不同,但不是 GroupBy 对象。有没有人解释或举例说明 pipe 可以为 GroupBy 使用 apply 可以做什么?

pipe 的作用是允许您传递可调用对象,并期望调用 pipe 的对象是传递给可调用对象的对象。

对于 apply,我们假设调用 apply 的对象具有子组件,每个子组件都将传递给传递给 apply 的可调用对象。在 groupby 的上下文中,子组件是称为 groupby 的数据帧的切片,其中每个切片本身就是一个数据帧。这类似于一个系列 groupby.

groupby 上下文中使用 pipe 可以执行的操作之间的主要区别在于,您可以调用 groupby 对象的整个范围。对于apply,你只知道local slice。

设置
考虑 df

df = pd.DataFrame(dict(
    A=list('XXXXYYYYYY'),
    B=range(10)
))

   A  B
0  X  0
1  X  1
2  X  2
3  X  3
4  Y  4
5  Y  5
6  Y  6
7  Y  7
8  Y  8
9  Y  9

示例 1
使整个 'B' 列的总和为 1,而每个子组的总和为相同的数量。这要求计算知道存在多少组。这是我们不能用 apply 做的事情,因为 apply 不知道有多少组存在。

s = df.groupby('A').B.pipe(lambda g: df.B / g.transform('sum') / g.ngroups)
s

0    0.000000
1    0.083333
2    0.166667
3    0.250000
4    0.051282
5    0.064103
6    0.076923
7    0.089744
8    0.102564
9    0.115385
Name: B, dtype: float64

注:

s.sum()

0.99999999999999989

并且:

s.groupby(df.A).sum()

A
X    0.5
Y    0.5
Name: B, dtype: float64

示例 2
从另一组的值中减去一组的平均值。同样,这不能用 apply 完成,因为 apply 不知道其他组。

df.groupby('A').B.pipe(
    lambda g: (
        g.get_group('X') - g.get_group('Y').mean()
    ).append(
        g.get_group('Y') - g.get_group('X').mean()
    )
)

0   -6.5
1   -5.5
2   -4.5
3   -3.5
4    2.5
5    3.5
6    4.5
7    5.5
8    6.5
9    7.5
Name: B, dtype: float64
print(df.groupby(['A'])['B'].apply(lambda l: l/l.sum()/df.A.nunique()))