问题:在组对象上应用vs变换
考虑以下数据帧:
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
以下命令起作用:
> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())
但以下任何一项均无效:
> 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
为什么? 文档上的示例似乎建议通过调用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)
换句话说,我认为转换本质上是一种特定的应用类型(不聚合)。我哪里错了?
供参考,以下是上面原始数据帧的构造:
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)})
回答 0
apply
和之间的两个主要区别transform
transform
和apply
groupby方法之间有两个主要区别。
- 输入:
apply
将每个组的所有列作为DataFrame隐式传递给自定义函数。- 同时
transform
将每个组的每一列作为系列分别传递给自定义函数。
- 输出:
- 传递给的自定义函数
apply
可以返回标量,或者返回Series或DataFrame(或numpy数组,甚至是list)。 - 传递给的自定义函数
transform
必须返回与group长度相同的序列(一维Series,数组或列表)。
- 传递给的自定义函数
因此,transform
一次只能处理一个Series,而一次apply
可以处理整个DataFrame。
检查自定义功能
检查传递给apply
or的自定义函数的输入可能会很有帮助transform
。
例子
让我们创建一些示例数据并检查组,以便您可以了解我在说什么:
import pandas as pd
import numpy as np
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'],
'a':[4,5,1,3], 'b':[6,10,3,11]})
State a b
0 Texas 4 6
1 Texas 5 10
2 Florida 1 3
3 Florida 3 11
让我们创建一个简单的自定义函数,该函数打印出隐式传递的对象的类型,然后引发一个错误,以便可以停止执行。
def inspect(x):
print(type(x))
raise
现在,让我们将此函数传递给groupby apply
和transform
method,以查看传递给它的对象:
df.groupby('State').apply(inspect)
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError
如您所见,DataFrame被传递到inspect
函数中。您可能想知道为什么将DataFrame类型打印两次。熊猫两次参加第一组比赛。这样做是为了确定是否存在快速完成计算的方法。这是您不应该担心的次要细节。
现在,让我们用 transform
df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError
它传递了一个Series-一个完全不同的Pandas对象。
因此,一次transform
只能使用一个系列。它并非不可能同时作用于两根色谱柱。因此,如果尝试a
从b
自定义函数中减去column ,则会出现错误transform
。见下文:
def subtract_two(x):
return x['a'] - x['b']
df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')
当熊猫试图找到a
不存在的Series索引时,我们得到一个KeyError 。您可以通过完整apply
的DataFrame 来完成此操作:
df.groupby('State').apply(subtract_two)
State
Florida 2 -2
3 -8
Texas 0 -2
1 -5
dtype: int64
输出是一个Series,并且保留了原始索引,因此有些混乱,但是我们可以访问所有列。
显示传递的熊猫对象
它可以在自定义函数中显示整个pandas对象,从而提供更多帮助,因此您可以确切地看到所使用的对象。您可以使用print
我喜欢使用模块中的display
函数的语句,IPython.display
以便在Jupyter笔记本中以HTML形式很好地输出DataFrame:
from IPython.display import display
def subtract_two(x):
display(x)
return x['a'] - x['b']
变换必须返回与组大小相同的一维序列
另一个区别是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
该错误消息并不能真正说明问题。您必须返回与组长度相同的序列。因此,这样的功能将起作用:
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
返回单个标量对象也适用于 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
回答 1
就像我对.transform
操作vs 感到困惑一样,.apply
我找到了一些答案,这使我对该问题有所了解。例如,此答案非常有帮助。
到目前为止,我的建议是彼此隔离地.transform
处理(或处理)Series
(列)。这意味着在最后两个呼叫中:
df.groupby('A').transform(lambda x: (x['C'] - x['D']))
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
您要求.transform
从两列中获取值,而“它”实际上并没有同时“看到”它们(可以这么说)。transform
将逐一查看数据框列,然后返回一系列“(由一系列)标量组成的”(或一组系列),这些标量被重复了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)
将生成:
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
这与您一次只在一列上使用它完全相同:
df.groupby('A')['C'].transform(zscore)
生成:
0 0.989
1 -0.478
2 0.889
3 -0.671
4 0.034
5 1.149
6 -1.404
7 -0.509
请注意,.apply
在上一个示例(df.groupby('A')['C'].apply(zscore)
)中,它的工作方式完全相同,但是如果您尝试在数据帧上使用它,它将失败:
df.groupby('A').apply(zscore)
给出错误:
ValueError: operands could not be broadcast together with shapes (6,) (2,)
那么还有什么.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
生成:
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
尝试用同样.apply
会给NaNs
在sum_C
。因为.apply
会返回reduce Series
,所以它不知道如何广播回去:
df.groupby('A')['C'].apply(sum)
给予:
A
bar 3.973
foo 4.373
在某些情况下,什么时候.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
我希望这可以增加一些清晰度。
回答 2
我将使用一个非常简单的代码片段来说明不同之处:
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']
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
该表中有3个客户ID,每个客户进行三笔交易,每次支付1,2,3美元。
现在,我想找到每个客户的最低付款额。有两种方法:
使用
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
使用
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
这两个方法都返回一个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
Apply
不能简单地在这里工作,因为它返回的是大小为3的Series,但是原始df的长度为9。您无法轻松地将其集成回原始df。
回答 3
tmp = df.groupby(['A'])['c'].transform('mean')
就好像
tmp1 = df.groupby(['A']).agg({'c':'mean'})
tmp = df['A'].map(tmp1['c'])
要么
tmp1 = df.groupby(['A'])['c'].mean()
tmp = df['A'].map(tmp1)