考虑以下数据框架:
columns = ['A', 'B', 'C', 'D']
records = [
['foo', 'one', 0.162003, 0.087469],
['bar', 'one', -1.156319, -1.5262719999999999],
['foo', 'two', 0.833892, -1.666304],
['bar', 'three', -2.026673, -0.32205700000000004],
['foo', 'two', 0.41145200000000004, -0.9543709999999999],
['bar', 'two', 0.765878, -0.095968],
['foo', 'one', -0.65489, 0.678091],
['foo', 'three', -1.789842, -1.130922]
]
df = pd.DataFrame.from_records(records, columns=columns)
"""
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']))
# KeyError or ValueError: could not broadcast input array from shape (5) into shape (5,3)
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
# KeyError or 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)})
当我同样对.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())
您要求.transform从两列中获取值,而“它”实际上并不能同时“看到”它们(可以这么说)。Transform将逐个查看数据帧列并返回一个系列(或一组系列)'made'由重复len(input_column)次的标量组成。
因此,这个应该被.transform用来生成级数的标量是应用于输入级数上的一些约简函数的结果(并且一次只能作用于一个级数/列)。
考虑这个例子(在你的数据框架上):
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尝试同样的方法会在sum_C中给出nan。
因为.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
我希望这能让你更清楚一点。
我将使用一个非常简单的片段来说明两者的区别:
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,每个客户进行3次交易,每次支付1 2 3美元。
现在,我想求出每个顾客的最低付款。有两种方法:
使用应用:
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
使用转换:
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对象,但是第一个的长度是3,第二个的长度是9。
如果你想回答每个客户的最低支付价格是多少,那么应用方法是更合适的选择。
如果你想回答每笔交易支付的金额与最低付款之间的差异是什么,那么你想使用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。
apply和transform之间有两个主要区别
transform方法和apply groupby方法之间有两个主要区别。
输入:
apply隐式地将每个组的所有列作为数据帧传递给自定义函数。
while transform将每个组的每一列作为Series单独传递给自定义函数。
输出:
传递给apply的自定义函数可以返回标量、Series或DataFrame(或numpy数组甚至列表)。
传递给transform的自定义函数必须返回与组相同长度的序列(一维Series、数组或列表)。
因此,transform一次只对一个Series起作用,而apply一次对整个DataFrame起作用。
检查自定义函数
检查传递给应用或转换的自定义函数的输入会有很大帮助。
例子
让我们创建一些样本数据并检查这些组,这样你就可以看到我在说什么:
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方法,看看传递给它的对象是什么:
df.groupby('State').apply(inspect)
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError
如您所见,一个DataFrame被传递到inspect函数中。您可能想知道为什么DataFrame类型被打印了两次。熊猫跑第一组两次。它这样做是为了确定是否有一个快速的方法来完成计算。这是一个你不应该担心的小细节。
现在,让我们对变换做同样的事情
df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError
它被传递一个Series -一个完全不同的Pandas对象。
因此,transform一次只允许处理一个Series。它不可能同时作用于两列。如果我们试着在自定义函数中用b减去a列我们会得到一个变换错误。见下文:
def subtract_two(x):
return x['a'] - x['b']
df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')
当pandas试图查找不存在的Series index a时,我们得到了一个KeyError。你可以用apply完成这个操作,因为它有整个DataFrame:
df.groupby('State').apply(subtract_two)
State
Florida 2 -2
3 -8
Texas 0 -2
1 -5
dtype: int64
输出是一个Series,由于保留了原始索引,因此有点令人困惑,但我们可以访问所有列。
显示传递的pandas对象
它甚至可以在自定义函数中显示整个pandas对象,因此您可以确切地看到您正在操作的对象。你可以使用打印语句,我喜欢使用IPython中的显示函数。显示模块,以便DataFrames很好地输出在一个jupyter笔记本的HTML:
from IPython.display import display
def subtract_two(x):
display(x)
return x['a'] - x['b']
截图:
Transform必须返回与组大小相同的一维序列
另一个区别是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
您可以使用zscore来分析C列和D列中的数据,以寻找异常值,其中zscore是系列-系列。Mean / series.std()。使用apply也创建一个用户定义的函数,用于C和D之间的差异,创建一个新的结果数据框架。Apply使用组结果集。
from scipy.stats import zscore
columns = ['A', 'B', 'C', 'D']
records = [
['foo', 'one', 0.162003, 0.087469],
['bar', 'one', -1.156319, -1.5262719999999999],
['foo', 'two', 0.833892, -1.666304],
['bar', 'three', -2.026673, -0.32205700000000004],
['foo', 'two', 0.41145200000000004, -0.9543709999999999],
['bar', 'two', 0.765878, -0.095968],
['foo', 'one', -0.65489, 0.678091],
['foo', 'three', -1.789842, -1.130922]
]
df = pd.DataFrame.from_records(records, columns=columns)
print(df)
standardize=df.groupby('A')['C','D'].transform(zscore)
print(standardize)
outliersC= (standardize['C'] <-1.1) | (standardize['C']>1.1)
outliersD= (standardize['D'] <-1.1) | (standardize['D']>1.1)
results=df[outliersC | outliersD]
print(results)
#Dataframe results
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
#C and D transformed Z score
C D
0 0.398046 0.801292
1 -0.300518 -1.398845
2 1.121882 -1.251188
3 -1.046514 0.519353
4 0.666781 -0.417997
5 1.347032 0.879491
6 -0.482004 1.492511
7 -1.704704 -0.624618
#filtering using arbitrary ranges -1 and 1 for the z-score
A B C D
1 bar one -1.156319 -1.526272
2 foo two 0.833892 -1.666304
5 bar two 0.765878 -0.095968
6 foo one -0.654890 0.678091
7 foo three -1.789842 -1.130922
>>>>>>>>>>>>> Part 2
splitting = df.groupby('A')
#look at how the data is grouped
for group_name, group in splitting:
print(group_name)
def column_difference(gr):
return gr['C']-gr['D']
grouped=splitting.apply(column_difference)
print(grouped)
A
bar 1 0.369953
3 -1.704616
5 0.861846
foo 0 0.074534
2 2.500196
4 1.365823
6 -1.332981
7 -0.658920
当我同样对.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())
您要求.transform从两列中获取值,而“它”实际上并不能同时“看到”它们(可以这么说)。Transform将逐个查看数据帧列并返回一个系列(或一组系列)'made'由重复len(input_column)次的标量组成。
因此,这个应该被.transform用来生成级数的标量是应用于输入级数上的一些约简函数的结果(并且一次只能作用于一个级数/列)。
考虑这个例子(在你的数据框架上):
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尝试同样的方法会在sum_C中给出nan。
因为.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
我希望这能让你更清楚一点。