对组对象应用vs转换

Ame*_*ina 142 python pandas

考虑以下数据帧:

     A      B         C         D
0  foo    one  0.162003  0.087469
1  bar    one -1.156319 -1.526272
2  foo    two  0.833892 -1.666304
3  bar  three -2.026673 -0.322057
4  foo    two  0.411452 -0.954371
5  bar    two  0.765878 -0.095968
6  foo    one -0.654890  0.678091
7  foo  three -1.789842 -1.130922
Run Code Online (Sandbox Code Playgroud)

以下命令有效:

> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())
Run Code Online (Sandbox Code Playgroud)

但没有以下工作:

> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)

> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
 TypeError: cannot concatenate a non-NDFrame object
Run Code Online (Sandbox Code Playgroud)

为什么? 文档中的示例似乎表明,调用transform组允许进行逐行操作处理:

# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)
Run Code Online (Sandbox Code Playgroud)

换句话说,我认为变换本质上是一种特定的应用类型(不会聚合的应用).我哪里错了?

作为参考,下面是上面原始数据框的构造:

df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                         'two', 'two', 'one', 'three'],
                   'C' : randn(8), 'D' : randn(8)})
Run Code Online (Sandbox Code Playgroud)

Pri*_*mer 172

因为我觉得同样与.transform操作相混淆,.apply我发现一些答案可以解释这个问题.例如,这个答案非常有用.

到目前为止,我的结果是.transform彼此隔离的Series(列)工作(或处理).这意味着在你的最后两个电话中:

df.groupby('A').transform(lambda x: (x['C'] - x['D']))
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
Run Code Online (Sandbox Code Playgroud)

您要求.transform从两列中获取值,并且"它"实际上不会同时"看到"它们(可以这么说).transform将逐个查看数据框列并返回一系列(或一组系列)'make'的标量重复len(input_column)次数.

所以这个标量应该被用来.transform制作它Series是一个应用于输入的减少函数的结果Series(并且一次只能在一个系列/列上).

考虑这个例子(在您的数据帧上):

zscore = lambda x: (x - x.mean()) / x.std() # Note that it does not reference anything outside of 'x' and for transform 'x' is one column.
df.groupby('A').transform(zscore)
Run Code Online (Sandbox Code Playgroud)

会产生:

       C      D
0  0.989  0.128
1 -0.478  0.489
2  0.889 -0.589
3 -0.671 -1.150
4  0.034 -0.285
5  1.149  0.662
6 -1.404 -0.907
7 -0.509  1.653
Run Code Online (Sandbox Code Playgroud)

这与您一次仅在一列上使用它完全相同:

df.groupby('A')['C'].transform(zscore)
Run Code Online (Sandbox Code Playgroud)

收益:

0    0.989
1   -0.478
2    0.889
3   -0.671
4    0.034
5    1.149
6   -1.404
7   -0.509
Run Code Online (Sandbox Code Playgroud)

请注意,.apply在上一个示例中,(df.groupby('A')['C'].apply(zscore))将以完全相同的方式工作,但如果您尝试在数据帧上使用它,则会失败:

df.groupby('A').apply(zscore)
Run Code Online (Sandbox Code Playgroud)

给出错误:

ValueError: operands could not be broadcast together with shapes (6,) (2,)
Run Code Online (Sandbox Code Playgroud)

那么其他地方.transform有用吗?最简单的情况是尝试将缩减函数的结果分配回原始数据帧.

df['sum_C'] = df.groupby('A')['C'].transform(sum)
df.sort('A') # to clearly see the scalar ('sum') applies to the whole column of the group
Run Code Online (Sandbox Code Playgroud)

收益:

     A      B      C      D  sum_C
1  bar    one  1.998  0.593  3.973
3  bar  three  1.287 -0.639  3.973
5  bar    two  0.687 -1.027  3.973
4  foo    two  0.205  1.274  4.373
2  foo    two  0.128  0.924  4.373
6  foo    one  2.113 -0.516  4.373
7  foo  three  0.657 -1.179  4.373
0  foo    one  1.270  0.201  4.373
Run Code Online (Sandbox Code Playgroud)

尝试用同样.apply会给NaNssum_C.因为.apply会返回减少的Series,它不知道如何广播回来:

df.groupby('A')['C'].apply(sum)
Run Code Online (Sandbox Code Playgroud)

赠送:

A
bar    3.973
foo    4.373
Run Code Online (Sandbox Code Playgroud)

还有一些情况.transform用于过滤数据:

df[df.groupby(['B'])['D'].transform(sum) < -1]

     A      B      C      D
3  bar  three  1.287 -0.639
7  foo  three  0.657 -1.179
Run Code Online (Sandbox Code Playgroud)

我希望这会增加一点清晰度.

  • 我的天啊。区别是如此微妙。 (3认同)
  • .transform()也可以用于填充缺失值。特别是如果您想将组均值或组统计信息广播到该组中的“ NaN”值。不幸的是,熊猫文档对我也没有帮助。 (3认同)

Ted*_*rou 82

apply和之间的两个主要区别transform

transformapplygroupby方法有两个主要区别.

  • apply隐式地将每个组的所有列作为DataFrame传递给自定义函数,同时transform将每个组的每个列作为Series传递给自定义函数
  • 传递给的自定义函数apply可以返回标量,Series或DataFrame(或numpy数组甚至列表).传递给的自定义函数transform必须返回与组相同长度的序列(一维系列,数组或列表).

因此,transform一次只能处理一个系列,并立即apply处理整个DataFrame.

检查自定义功能

它可以帮助检查传递给apply或的自定义函数的输入transform.

例子

让我们创建一些示例数据并检查组,以便您可以看到我在说什么:

df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'], 
                   'a':[4,5,1,3], 'b':[6,10,3,11]})
df
Run Code Online (Sandbox Code Playgroud)

让我们创建一个简单的自定义函数,打印出隐式传递的对象的类型,然后引发错误,以便可以停止执行.

def inspect(x):
    print(type(x))
    raise
Run Code Online (Sandbox Code Playgroud)

现在让我们将此函数传递给groupby applytransform方法,以查看传递给它的对象:

df.groupby('State').apply(inspect)

<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError
Run Code Online (Sandbox Code Playgroud)

如您所见,DataFrame传递给inspect函数.您可能想知道为什么类型DataFrame被打印出两次.熊猫两次跑第一组.它这样做是为了确定是否有快速的方法来完成计算.这是一个您不必担心的细节.

现在,让我们做同样的事情 transform

df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError
Run Code Online (Sandbox Code Playgroud)

它传递了一个系列 - 一个完全不同的Pandas对象.

因此,transform只允许一次使用一个系列.它不可能同时作用于两列.因此,如果我们尝试ab自定义函数内部减去列,我们会收到错误transform.见下文:

def subtract_two(x):
    return x['a'] - x['b']

df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')
Run Code Online (Sandbox Code Playgroud)

我们得到一个KeyError,因为pandas试图找到a不存在的Series索引.您可以使用apply整个DataFrame 完成此操作:

df.groupby('State').apply(subtract_two)

State     
Florida  2   -2
         3   -8
Texas    0   -2
         1   -5
dtype: int64
Run Code Online (Sandbox Code Playgroud)

输出是一个系列,并且保留原始索引时有点混乱,但我们可以访问所有列.


显示传递的pandas对象

它可以帮助更多地在自定义函数中显示整个pandas对象,因此您可以准确地看到您正在操作的内容.您可以使用print我喜欢的语句来使用模块中的display函数,IPython.display以便在jupyter笔记本中以HTML格式输出DataFrame:

from IPython.display import display
def subtract_two(x):
    display(x)
    return x['a'] - x['b']
Run Code Online (Sandbox Code Playgroud)

截图: 在此输入图像描述


变换必须返回与组相同大小的单维序列

另一个区别是transform必须返回与组相同大小的单维序列.在此特定实例中,每个组都有两行,因此transform必须返回两行的序列.如果没有,则会引发错误:

def return_three(x):
    return np.array([1, 2, 3])

df.groupby('State').transform(return_three)
ValueError: transform must return a scalar value for each group
Run Code Online (Sandbox Code Playgroud)

错误消息并不能真正描述问题.您必须返回与组相同长度的序列.所以,这样的函数可以工作:

def rand_group_len(x):
    return np.random.rand(len(x))

df.groupby('State').transform(rand_group_len)

          a         b
0  0.962070  0.151440
1  0.440956  0.782176
2  0.642218  0.483257
3  0.056047  0.238208
Run Code Online (Sandbox Code Playgroud)

返回单个标量对象也适用于 transform

如果从自定义函数返回单个标量,transform则将其用于组中的每个行:

def group_sum(x):
    return x.sum()

df.groupby('State').transform(group_sum)

   a   b
0  9  16
1  9  16
2  4  14
3  4  14
Run Code Online (Sandbox Code Playgroud)

  • `np` 未定义。我想如果您在答案中包含“import numpy as np”,初学者会很感激。 (4认同)
  • 多么美妙的解释啊。您已经详细解释了这一点!谢谢! (2认同)

Che*_*eng 10

我将使用一个非常简单的代码片段来说明不同之处:

test = pd.DataFrame({'id':[1,2,3,1,2,3,1,2,3], 'price':[1,2,3,2,3,1,3,1,2]})
grouping = test.groupby('id')['price']
Run Code Online (Sandbox Code Playgroud)

DataFrame看起来像这样:

    id  price   
0   1   1   
1   2   2   
2   3   3   
3   1   2   
4   2   3   
5   3   1   
6   1   3   
7   2   1   
8   3   2   
Run Code Online (Sandbox Code Playgroud)

该表中有3个客户ID,每个客户进行了3次交易,每次支付1,2,3美元。

现在,我想找到每个客户的最低付款额。有两种方法:

  1. 使用apply

    grouping.min()

回报看起来像这样:

id
1    1
2    1
3    1
Name: price, dtype: int64

pandas.core.series.Series # return type
Int64Index([1, 2, 3], dtype='int64', name='id') #The returned Series' index
# lenght is 3
Run Code Online (Sandbox Code Playgroud)
  1. 使用transform

    分组变换(最小值)

回报看起来像这样:

0    1
1    1
2    1
3    1
4    1
5    1
6    1
7    1
8    1
Name: price, dtype: int64

pandas.core.series.Series # return type
RangeIndex(start=0, stop=9, step=1) # The returned Series' index
# length is 9    
Run Code Online (Sandbox Code Playgroud)

这两个方法都返回一个Series对象,但是第一个的对象length为3,length第二个的对象为9。

如果要回答What is the minimum price paid by each customer,则该apply方法是更适合选择的一种。

如果要回答What is the difference between the amount paid for each transaction vs the minimum payment,则要使用transform,因为:

test['minimum'] = grouping.transform(min) # ceates an extra column filled with minimum payment
test.price - test.minimum # returns the difference for each row
Run Code Online (Sandbox Code Playgroud)

Apply 不能简单地在这里工作,因为它返回的是大小为3的Series,但是原始df的长度为9。您无法轻松地将其集成回原始df。

  • 我认为这是一个很好的答案!感谢您在提出问题四年多后抽出时间做出回答! (4认同)

小智 5

tmp = df.groupby(['A'])['c'].transform('mean')
Run Code Online (Sandbox Code Playgroud)

就好像

tmp1 = df.groupby(['A']).agg({'c':'mean'})
tmp = df['A'].map(tmp1['c'])
Run Code Online (Sandbox Code Playgroud)

或者

tmp1 = df.groupby(['A'])['c'].mean()
tmp = df['A'].map(tmp1)
Run Code Online (Sandbox Code Playgroud)