Python-在组对象上应用vs变换

发布于 2021-02-02 23:14:26

考虑以下数据框:

     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
被浏览
67
1 个回答
  • 面试哥
    面试哥 2021-02-02
    为面试而生,有面试问题,就找面试哥。

    apply和之间的两个主要区别transform

    transformapplygroupby方法之间有两个主要区别。

    • 输入:
    • apply将每个组的所有列作为DataFrame隐式传递给自定义函数。
    • 同时transform将每个组的每一列作为系列分别传递给自定义函数。
    • 输出:
    • 传递给的自定义函数apply可以返回标量,或者返回SeriesDataFrame(或numpy数组,甚至是list)。
    • 传递给的自定义函数transform必须返回与group长度相同的序列(一维Series,数组或列表)。
      因此,transform一次只能处理一个Series,而一次apply可以处理整个DataFrame

    检查自定义功能

    检查传递给applyor的自定义函数的输入可能会很有帮助transform。

    例子
    让我们创建一些示例数据并检查组,以便你可以了解我在说什么:

    import pandas as pd
    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和transformmethod,以查看传递给它的对象:

    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 。你可以通过完整applyDataFrame 来完成此操作:

    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
    


  • 面试哥
    面试哥 2021-02-02
    为面试而生,有面试问题,就找面试哥。


    就像我对.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
    
    

    我希望这可以增加一些清晰度。

知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看