对组对象应用vs变换

考虑以下数据框:

ABCD 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) 

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

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

 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会逐个查看dataframe列,然后返回一个len(input_column)次重复的标量序列(或一组序列)。

所以这个标量,应该被.transform用来制作Series是由于在inputSeries (一次只能在一个系列/列上)应用一些缩小function。

考虑这个例子(在你的数据框上):

 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) 

会产生:

  CD 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 

请注意,在最后一个示例( 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 

收益:

  ABCD 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相同的将.apply NaNs 。 因为.apply会返回一个缩小的Series ,它不知道如何播放:

 df.groupby('A')['C'].apply(sum) 

赠送:

 A bar 3.973 foo 4.373 

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

 df[df.groupby(['B'])['D'].transform(sum) < -1] ABCD 3 bar three 1.287 -0.639 7 foo three 0.657 -1.179 

我希望这增加了一点清晰。

applytransform之间的两个主要区别

transformapply groupby方法有两个主要区别。

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

因此,一次只能transform一个系列的作品,并且一次apply整个DataFrame。

检查自定义function

它可以帮助您检查传递给applytransform自定义函数的input。

例子

我们来创build一些示例数据并检查这些组,以便查看我在说什么:

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

让我们创build一个简单的自定义函数,输出隐式传递的对象的types,然后引发一个错误,以便停止执行。

 def inspect(x): print(type(x)) raise 

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

 df.groupby('State').apply(inspect) <class 'pandas.core.frame.DataFrame'> <class 'pandas.core.frame.DataFrame'> RuntimeError 

正如你所看到的,一个DataFrame被传入inspect函数。 您可能想知道为什么DataFrametypes打印出来了两次。 pandas跑了第一组两次。 它是这样做的,以确定是否有一个快速的方法来完成计算或不。 这是一个小的细节,你不应该担心。

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

 df.groupby('State').transform(inspect) <class 'pandas.core.series.Series'> <class 'pandas.core.series.Series'> RuntimeError 

它通过了一个系列 – 一个完全不同的pandas物体。

所以, transform只允许一次使用一个系列。 它不可能同时在两列上行动。 所以,如果我们尝试从自定义函数内部的b减去a列,我们将得到一个transform错误。 见下文:

 def subtract_two(x): return x['a'] - x['b'] df.groupby('State').transform(subtract_two) KeyError: ('a', 'occurred at index a') 

当pandas试图find不存在的系列索引a ,我们得到一个KeyError。 您可以通过apply完成此操作,因为它具有整个DataFrame:

 df.groupby('State').apply(subtract_two) State Florida 2 -2 3 -8 Texas 0 -2 1 -5 dtype: int64 

输出是一个系列,有点混乱,因为保留了原始索引,但我们可以访问所有列。


显示传递的pandas对象

它可以帮助更多的人在自定义函数中显示整个pandas对象,所以你可以看到你正在使用的是什么。 您可以使用print语句,我喜欢使用IPython.display模块中的displayfunction,以便在jupyter笔记本中将数据框良好地输出到HTML中:

 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 

错误消息不是真正描述的问题。 您必须返回与组相同长度的序列。 所以,像这样的function将工作:

 def rand_group_len(x): return np.random.rand(len(x)) df.groupby('State').transform(rand_group_len) ab 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) ab 0 9 16 1 9 16 2 4 14 3 4 14