考虑以下数据帧:
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
会给NaNs
在sum_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)
我希望这会增加一点清晰度.
Ted*_*rou 82
apply
和之间的两个主要区别transform
transform
和apply
groupby方法有两个主要区别.
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 apply
和transform
方法,以查看传递给它的对象:
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
只允许一次使用一个系列.它不可能同时作用于两列.因此,如果我们尝试a
从b
自定义函数内部减去列,我们会收到错误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对象,因此您可以准确地看到您正在操作的内容.您可以使用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)
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美元。
现在,我想找到每个客户的最低付款额。有两种方法:
使用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)
使用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。
小智 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)