考虑以下数据帧:
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
为什么? The example on the documentation似乎建议在一个组上调用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)})
当我感觉与.transform
操作与.apply
相似时,我发现了一些答案,对这个问题有所了解。例如,This answer非常有帮助。
到目前为止,我的结果是.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
将逐个查看数据框列,并返回一系列(或一组系列)'scledrs'重复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
将返回减少的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
我希望这会增加一点清晰度。
apply
and transform
transform
和apply
组之间存在两个主要差异。
apply
隐式地将每个组的所有列作为DataFrame传递给自定义函数,而transform
将每个组的每个列作为Series传递给自定义函数apply
的自定义函数可以返回标量,系列或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
让我们创建一个简单的自定义函数,打印出隐式传递的对象的类型,然后引发错误,以便可以停止执行。
def inspect(x):
print(type(x))
raise
现在让我们将此函数传递给groupby apply
和transform
方法,以查看传递给它的对象:
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
它传递了一个系列 - 一个完全不同的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')
我们得到一个KeyError,因为pandas试图找到不存在的Series索引a
。您可以使用apply
完成此操作,因为它具有整个DataFrame:
df.groupby('State').apply(subtract_two)
State
Florida 2 -2
3 -8
Texas 0 -2
1 -5
dtype: int64
输出是一个系列,并且保留原始索引时有点混乱,但我们可以访问所有列。
它可以帮助更多地在自定义函数中显示整个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
我将使用一个非常简单的片段来说明差异:
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
:
grouping.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,但原始df的长度为9.你不能轻易地将它整合回原来的df。
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)