My ideal question would be "How to access columns in pandas DataFrameGroupBy.transform?", but after performing some tests (herein showed), I wonder if that's even possible.
I would like to access column names just like I can do with apply, but using transform instead.
For example, given this sample data:
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
How can I use transform to subtract B from A, then multiply by C? Is it possible?
I know that with apply I can easily achieve that by using lambda or passing a user-defined function, like this:
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
The output values, though, it's unsorted (as you can see in the internal index) so that I can't just place this output as it is in a new column. An option would be sorting the dataframe before-hand using apply, but to be honest I'm not fully confident it would work as expected for large data with more complex groups in groupby. I would feel more comfortable using transform, otherwise, I think it's more reliable to merge the result back to the df by index (supposing we have a unique index).
If I try to use the same function with transform:
df.groupby('D').transform(customFunc)
then I get an error: KeyError: 'A'.
To inspect what's going on under the hood when using groupby.apply and groupby.transform, I did the following:
# 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>
As you can see, apply provide us sub-dataframes as groups, while I don't know exactly what transform does provide (it's the first time I've faced a property). I also have done further tests:
# 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')
It seems that with .iloc[] I can access the values for each column independently, but I still couldn't figure how to access the columns within transform (if it's even possible).
So, my final questions:
- Is it possible to access column names in
groupby.transformto perform calculation across columns? - If not, what's the best (reliable) way to place the output from
applyback to the dataframe?