pandas groupby transform自定义函数

11

能否使用自定义函数进行分组转换?

data = {
        'a':['a1','a2','a3','a4','a5'],
        'b':['b1','b1','b2','b2','b1'],
        'c':[55,44.2,33.3,-66.5,0],
        'd':[10,100,1000,10000,100000],
        }

import pandas as pd
df = pd.DataFrame.from_dict(data)

df['e'] = df.groupby(['b'])['c'].transform(sum) #this works as expected
print (df)
#    a   b     c       d     e
#0  a1  b1  55.0      10  99.2
#1  a2  b1  44.2     100  99.2
#2  a3  b2  33.3    1000 -33.2
#3  a4  b2 -66.5   10000 -33.2
#4  a5  b1   0.0  100000  99.2

def custom_calc(x, y):
    return (x * y)

#obviously wrong code here
df['e'] = df.groupby(['b'])['c'].transform(custom_calc(df['c'], df['d'])) 
从上面的例子中可以看出,我想探索能否将自定义函数传递到.transform()中。
我知道.apply()存在,但我想弄清楚是否可以仅使用.transform()
更重要的是,我想了解如何制定一个正确的函数,以便将其传递到.transform()中以正确应用。
P.S. 目前,我知道像'count'sum'sum'等默认函数可以正常工作。

1
请将以下有关编程的内容从英语翻译成中文。只返回已翻译的文本:相关:https://dev59.com/zV4c5IYBdhLWcg3wzs6Q#47143056 - Dani Mesejo
1
我不理解这个操作。您是想要 cd 的总乘积还是每行的乘积?提供期望的输出将非常有帮助。 - user3483203
1
.transform 最容易作用于单个序列。如果您想使用需要多个序列作为输入的函数进行转换,虽然可以完成,但这相当麻烦,并且通常可以通过其他避免转换的方式来完成(例如使用 map 函数)。 - ALollz
@ALollz 你说得对。但我的意图是为了简化代码,而不是优化这个问题。因此,我想知道是否有一个模板可以创建可以传递到.transform()的函数。 - ycx
@user3483203 提供的代码是一个示例。它可以是任何类型的函数计算。问题的真正要点在粗体字中。我想知道是否存在用于创建此类函数以传递到.transform()的模板,以便简化代码。 - ycx
我认为混淆的部分在于您提供的函数并不是一个很好的.groupby.transform候选项。由于您的函数返回一个Series或数组,因此实际上没有什么需要转换的,因为通常使用它来将标量组值广播回原始DataFrame中的所有其他成员。 - ALollz
1个回答

16

我喜欢用创建一个小的自定义函数,并打印出传递的参数及其类型的方式来查看正在发生的情况。然后,你可以看到你需要处理什么。

def f(x):
    print(type(x))
    print('\n')
    print(x)
    print(x.index)
    return df.loc[x.index,'d']*x

df['f'] = df.groupby('b')['c'].transform(f)
print(df)

#Output from print statements in function
<class 'pandas.core.series.Series'>


0    55.0
1    44.2
4     0.0
Name: b1, dtype: float64
Int64Index([0, 1, 4], dtype='int64')
<class 'pandas.core.series.Series'>


2    33.3
3   -66.5
Name: b2, dtype: float64
Int64Index([2, 3], dtype='int64')
#End output from print statements in custom function

    a   b     c       d     e         f
0  a1  b1  55.0      10  99.2     550.0
1  a2  b1  44.2     100  99.2    4420.0
2  a3  b2  33.3    1000 -33.2   33300.0
3  a4  b2 -66.5   10000 -33.2 -665000.0
4  a5  b1   0.0  100000  99.2       0.0

在这里,我正在对列'c'进行转换,但我在自定义函数中对数据框对象进行了"外部"调用以获取'd'。

您也可以像这样传递要用作参数的"external":

def f(x, col):
    return df.loc[x.index, col]*x

df['g'] = df.groupby('b')['c'].transform(f, col='d')

print(df)

输出:

    a   b     c       d     e         f         g
0  a1  b1  55.0      10  99.2     550.0     550.0
1  a2  b1  44.2     100  99.2    4420.0    4420.0
2  a3  b2  33.3    1000 -33.2   33300.0   33300.0
3  a4  b2 -66.5   10000 -33.2 -665000.0 -665000.0
4  a5  b1   0.0  100000  99.2       0.0       0.0

在你的 .transform(f, col='d') 中,默认假定传递的第一个参数将始终是作为 'c'中所述的Series,传递到 f(x) 中吗?那么 sum'count' 究竟是如何工作的呢?因为这些默认函数不包括 x,或者默认情况下是 sum 真正的含义是 sum(x),而 'count' 真正的含义是 'count(x)' 吗?这是否是根据您的代码推断出来的情况?在这种情况下,.transform() 如何能够评估 'count(x)' 时,它处于字符串格式?非常感谢您详细的回答! - ycx
1
很棒的回答,Scott。特别是在第二部分中返回1个更多的列时。我喜欢你在第二部分中使用df索引的方法。我已经寻找类似的东西两天了,现在终于找到了 :) - rishi jain
1
我不理解 f 函数中的 'df',为什么它在那里?它没有在任何地方定义过。 - Dinh Quang Tuan
1
“df”在问题中被定义,并且在主程序中的函数外部进行定义。即使没有传递该变量,该变量在函数中可用。 - Scott Boston
@ScottBoston:非常感谢。您能解释一下函数中的变量“x”以及在“transform”时如何使用它吗?我对“return df.loc[x.index, col]*x”感到困惑。 - Dinh Quang Tuan
我们可以看到在这个解决方案的顶部,当您执行 df.groupby('b')['c'].transform(f) 时,会将pd.Series传递给自定义函数f。因此,在此示例中,x是由“b”定义的两部分中的列'c'的pd.Series。因此,引用原始数据框,我们可以使用loc获取由“col”定义的列中的值,并乘以每个索引处的pd.Series中的值。 - Scott Boston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接