我理想的问题是"如何访问pandas DataFrameGroupBy.transform中的列?",但是在执行了一些测试(此处显示)之后,我想知道这是否可能。
我想像访问apply
一样访问列名,但改用transform
。
例如,给定以下示例数据:
import numpy as np
import pandas as pd
np.random.seed(123)
numeric_data = np.random.rand(9, 3)
cat_data = [f'grp_{i}' for i in range(1,4)] * 3
df = pd.DataFrame(numeric_data, columns=list('ABC')).assign(D = cat_data)
print(df)
A B C D
0 0.696469 0.286139 0.226851 grp_1
1 0.551315 0.719469 0.423106 grp_2
2 0.980764 0.684830 0.480932 grp_3
3 0.392118 0.343178 0.729050 grp_1
4 0.438572 0.059678 0.398044 grp_2
5 0.737995 0.182492 0.175452 grp_3
6 0.531551 0.531828 0.634401 grp_1
7 0.849432 0.724455 0.611024 grp_2
8 0.722443 0.322959 0.361789 grp_3
如何使用transform
从 A 中减去 B,然后乘以 C?可能吗?
我知道使用apply我可以通过使用lambda或传递用户定义的函数轻松实现这一点,如下所示:
def customFunc(grp):
return (grp['A'] - grp['B']) * grp['C']
df.groupby('D').apply(customFunc)
D
grp_1 0 0.093084
3 0.035679
6 -0.000175
grp_2 1 -0.071147
4 0.150817
7 0.076364
grp_3 2 0.142324
5 0.097464
8 0.144529
dtype: float64
但是,输出值是未排序的(如您在内部索引中看到的那样),因此我不能仅将此输出放在新列中。一种选择是事先使用 apply 对数据帧进行排序,但老实说,我不完全相信它会像预期的那样适用于分组中具有更复杂组的大数据。我会觉得使用 transform 更舒服,否则,我认为按索引将结果合并回 df 会更可靠(假设我们有一个唯一的索引)。
如果我尝试将相同的函数与转换一起使用:
df.groupby('D').transform(customFunc)
然后我得到一个错误:KeyError: 'A'
.
为了检查使用groupby.apply
和groupby.transform
时引擎盖下发生了什么,我做了以下操作:
# Select the target-group
grp = df.groupby('D')
grp.apply(lambda x: type(x))
D
grp_1 (<class 'pandas.core.frame.DataFrame'>, 3)
grp_2 (<class 'pandas.core.frame.DataFrame'>, 3)
grp_3 (<class 'pandas.core.frame.DataFrame'>, 3)
dtype: object
grp.transform(lambda x: type(x))
A B C
0 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
1 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
2 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
3 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
4 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
5 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
6 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
7 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
8 <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0> <property object at 0x7f7dbde619f0>
如您所见,apply
为我们提供组的子数据帧,而我不知道transform
到底提供了什么(这是我第一次面对属性)。我还做了进一步的测试:
# Another trials
grp.transform(lambda x: x.shape) # ValueError
grp.transform(lambda x: x['A']) # KeyError
grp.transform(lambda x: x.loc[0]) # KeyError
grp.transform(lambda x: x.iloc[0]) # works (every value get the first value; similar to 'first')
似乎有了.iloc[]
我可以独立访问每列的值,但我仍然无法弄清楚如何访问transform
中的列(如果可能的话)。
所以,我的最后一个问题:
- 是否可以访问
groupby.transform
中的列名以跨列执行计算? - 如果没有,将输出从
apply
放回数据帧的最佳(可靠)方法是什么?
是否可以访问 groupby.transform 中的列名以跨列执行计算?
不可能,.groupby.transform
单独处理每一列,因此无法像groupby.apply
那样"看到"另一列。
如果使用print
,您可以看到它:
print (df.groupby('D').transform(lambda x: print(x)))
2 0.980764
5 0.737995
8 0.722443
Name: A, dtype: float64
2 0.684830
5 0.182492
8 0.322959
Name: B, dtype: float64
2 0.480932
5 0.175452
8 0.361789
Name: C, dtype: float64
如果没有,将应用输出放回数据帧的最佳(可靠)方法是什么?
如果函数不是聚合值:
def customFunc(grp):
return (grp['A'] - grp['B']) * grp['C']
df['new'] = df.groupby('D').apply(customFunc).rename('new').reset_index(level=0, drop=True)
print (df)
A B C D new
0 0.696469 0.286139 0.226851 grp_1 0.093084
1 0.551315 0.719469 0.423106 grp_2 -0.071147
2 0.980764 0.684830 0.480932 grp_3 0.142324
3 0.392118 0.343178 0.729050 grp_1 0.035679
4 0.438572 0.059678 0.398044 grp_2 0.150817
5 0.737995 0.182492 0.175452 grp_3 0.097464
6 0.531551 0.531828 0.634401 grp_1 -0.000175
7 0.849432 0.724455 0.611024 grp_2 0.076364
8 0.722443 0.322959 0.361789 grp_3 0.144529
所以工作原理相同:
df['new'] = (df['A'] - df['B']) * df['C']
print (df)
A B C D new
0 0.696469 0.286139 0.226851 grp_1 0.093084
1 0.551315 0.719469 0.423106 grp_2 -0.071147
2 0.980764 0.684830 0.480932 grp_3 0.142324
3 0.392118 0.343178 0.729050 grp_1 0.035679
4 0.438572 0.059678 0.398044 grp_2 0.150817
5 0.737995 0.182492 0.175452 grp_3 0.097464
6 0.531551 0.531828 0.634401 grp_1 -0.000175
7 0.849432 0.724455 0.611024 grp_2 0.076364
8 0.722443 0.322959 0.361789 grp_3 0.144529
如果函数聚合值使用DataFrame.join
或Series.map
如果一列用于分组:
def customFunc(grp):
return ((grp['A'] - grp['B']) * grp['C']).mean()
df = df.join(df.groupby('D').apply(customFunc).rename('new'), on='D')
<小时 />def customFunc(grp):
return ((grp['A'] - grp['B']) * grp['C']).mean()
df['new'] = df['D'].map(df.groupby('D').apply(customFunc))
print (df)
A B C D new
0 0.696469 0.286139 0.226851 grp_1 0.042863
1 0.551315 0.719469 0.423106 grp_2 0.052011
2 0.980764 0.684830 0.480932 grp_3 0.128106
3 0.392118 0.343178 0.729050 grp_1 0.042863
4 0.438572 0.059678 0.398044 grp_2 0.052011
5 0.737995 0.182492 0.175452 grp_3 0.128106
6 0.531551 0.531828 0.634401 grp_1 0.042863
7 0.849432 0.724455 0.611024 grp_2 0.052011
8 0.722443 0.322959 0.361789 grp_3 0.128106
或更改功能:
def customFunc(grp):
grp['new'] = ((grp['A'] - grp['B']) * grp['C']).mean()
return grp
df = df.groupby('D').apply(customFunc)
print (df)
A B C D new
0 0.696469 0.286139 0.226851 grp_1 0.042863
1 0.551315 0.719469 0.423106 grp_2 0.052011
2 0.980764 0.684830 0.480932 grp_3 0.128106
3 0.392118 0.343178 0.729050 grp_1 0.042863
4 0.438572 0.059678 0.398044 grp_2 0.052011
5 0.737995 0.182492 0.175452 grp_3 0.128106
6 0.531551 0.531828 0.634401 grp_1 0.042863
7 0.849432 0.724455 0.611024 grp_2 0.052011
8 0.722443 0.322959 0.361789 grp_3 0.128106
只是为了扩展@jezrael的答案:
是否可以访问
groupby.transform
中的列名以跨列执行计算?不可能,
.groupby.transform
单独处理每一列,因此无法像groupby.apply
那样"看到"另一列。
这是因为如何指定transform
(强调我的):
笔记
每个组都被赋予了属性"名称",以防您需要知道您正在处理哪个组。
当前的实现对 f 提出了三个要求:
- f 必须返回一个值,该值要么与输入子帧的形状相同,要么可以广播到输入子帧的形状。 为 例如,如果
f
返回一个标量,它将被广播为具有与输入子帧相同的形状
。- 如果这是数据帧,则 f 必须支持子帧中的逐列应用程序。如果 f 也支持应用于整个 子帧,然后使用从第二个块开始的快速路径。
- f 不得使组发生突变。不支持突变,可能会产生意外结果。
即,它要求函数能够在单个列上工作。但是,如果它适用于整个数据帧,pandas
中途自动切换到应用于整个组(对我来说似乎有点笨拙,但无论如何)。同样,我们可以在转换函数中使用print
看到这一点:
def f(x):
print(x.name, type(x))
return x
In [1]: gb.transform(f)
A <class 'pandas.core.series.Series'>
A <class 'pandas.core.series.Series'>
B <class 'pandas.core.series.Series'>
C <class 'pandas.core.series.Series'>
grp_1 <class 'pandas.core.frame.DataFrame'>
grp_2 <class 'pandas.core.frame.DataFrame'>
grp_3 <class 'pandas.core.frame.DataFrame'>
Out[1]:
A B C
0 0.696469 0.286139 0.226851
1 0.551315 0.719469 0.423106
2 0.980764 0.684830 0.480932
3 0.392118 0.343178 0.729050
4 0.438572 0.059678 0.398044
5 0.737995 0.182492 0.175452
6 0.531551 0.531828 0.634401
7 0.849432 0.724455 0.611024
8 0.722443 0.322959 0.361789
在这里,我们可以看到pandas
在内部做了什么:
- 首先,
A
第一列的"试运行"(不太确定这是在做什么,但可能是看看函数是否兼容或使用什么方法来获得最佳性能); - 然后它
f
第一组(grp_1
)上逐列应用; - 一旦它意识到
f
在整个数据帧上工作(我猜测是通过再次尝试对第一组进行f
,然后与逐列结果进行比较),它就会切换到一次将f
应用于每个组,作为整个数据帧。
事实上,知道了这一点,实际上可以从算法的第二部分开始提取列(一旦它开始应用于整个数据帧):
def f(x):
try:
x["A"]
print(x.name, type(x), "column retrieval succeeded")
except KeyError:
print(x.name, type(x), "column retrieval failed")
return x
In [59]: gb.transform(f)
A <class 'pandas.core.series.Series'> column retrieval failed
A <class 'pandas.core.series.Series'> column retrieval failed
B <class 'pandas.core.series.Series'> column retrieval failed
C <class 'pandas.core.series.Series'> column retrieval failed
grp_1 <class 'pandas.core.frame.DataFrame'> column retrieval succeeded
grp_2 <class 'pandas.core.frame.DataFrame'> column retrieval succeeded
grp_3 <class 'pandas.core.frame.DataFrame'> column retrieval succeeded
Out[59]:
A B C
0 0.696469 0.286139 0.226851
1 0.551315 0.719469 0.423106
2 0.980764 0.684830 0.480932
3 0.392118 0.343178 0.729050
4 0.438572 0.059678 0.398044
5 0.737995 0.182492 0.175452
6 0.531551 0.531828 0.634401
7 0.849432 0.724455 0.611024
8 0.722443 0.322959 0.361789
当然,这在实践中没有用,因为文档中指定的转换函数应该能够处理单个列(以及可选的整个数据帧),因此您不应该引用此函数中的特定列。