将多个函数同时应用于Pandas groupby对象

4

这个问题的变体已经被问过了(请参见这个问题),但我还没有找到一个好的解决方案,似乎是Pandas中groupby的常见用例。

假设我有一个名为lasts的数据框,并按user进行分组:

lasts = pd.DataFrame({'user':['a','s','d','d'],
                   'elapsed_time':[40000,50000,60000,90000],
                   'running_time':[30000,20000,30000,15000],
                   'num_cores':[7,8,9,4]})

我有一些函数想要应用到groupby_obj上(这些函数的作用并不重要,只需要知道它们需要数据框中的多列):

def custom_func(group):
    return group.running_time.median() - group.num_cores.mean()

def custom_func2(group):
    return max(group.elapsed_time) -min(group.running_time) 

我可以将这些函数分别应用于数据框,并将结果数据框合并,但是这似乎效率低下、缺乏优雅的解决方案,我想象中应该有一种单行解决方案。

虽然我还没有找到这种方法,但是此博客文章(在页面底部搜索“Create a function to get the stats of a group”)建议将这些函数包装成一个字典形式的函数:

def get_stats(group):
    return {'custom_column_1': custom_func(group), 'custom_column_2':custom_func2(group)}

然而,当我运行代码groupby_obj.apply(get_stats)时,我得到了一个字典结果的列,而不是原来的列:

user
a    {'custom_column_1': 29993.0, 'custom_column_2'...
d    {'custom_column_1': 22493.5, 'custom_column_2'...
s    {'custom_column_1': 19992.0, 'custom_column_2'...
dtype: object

实际上,我想使用一行代码获得类似于这个数据帧的东西:

user custom_column_1    custom_column_2
a    29993.0                10000
d    22493.5                75000
s    19992.0                30000

改进此工作流程的建议?
2个回答

5
考虑以下方法:
funcs = {
  'running_time': {'rt_med':'median', 'rt_min':'min'},
  'num_cores': {'nc_avg':'mean'},
  'elapsed_time': {'et_max':'max'}
}

x = lasts.groupby('user').agg(funcs)
x.columns = x.columns.droplevel(0)

formulas = """
custom_column_1 = rt_med - nc_avg
custom_column_2 = et_max - rt_min

"""

res = x.eval(formulas, inplace=False).drop(x.columns, 1).reset_index()

结果:

In [145]: res
Out[145]:
  user  custom_column_1  custom_column_2
0    a          29993.0            10000
1    d          22493.5            75000
2    s          19992.0            30000

解释(逐步):

In [146]: x = lasts.groupby('user').agg(funcs)

In [147]: x
Out[147]:
     running_time        num_cores elapsed_time
           rt_med rt_min    nc_avg       et_max
user
a           30000  30000       7.0        40000
d           22500  15000       6.5        90000
s           20000  20000       8.0        50000

In [148]: x.columns = x.columns.droplevel(0)

In [149]: x
Out[149]:
      rt_med  rt_min  nc_avg  et_max
user
a      30000   30000     7.0   40000
d      22500   15000     6.5   90000
s      20000   20000     8.0   50000

In [150]: x.eval(formulas, inplace=False)
Out[150]:
      rt_med  rt_min  nc_avg  et_max  custom_column_1  custom_column_2
user
a      30000   30000     7.0   40000          29993.0            10000
d      22500   15000     6.5   90000          22493.5            75000
s      20000   20000     8.0   50000          19992.0            30000

In [151]: x.eval(formulas, inplace=False).drop(x.columns, 1)
Out[151]:
      custom_column_1  custom_column_2
user
a             29993.0            10000
d             22493.5            75000
s             19992.0            30000

In [152]: x.eval(formulas, inplace=False).drop(x.columns, 1).reset_index()
Out[152]:
  user  custom_column_1  custom_column_2
0    a          29993.0            10000
1    d          22493.5            75000
2    s          19992.0            30000

3
如果你稍微修改一下get_stats函数:
def get_stats(group):
    return pd.Series({'custom_column_1': custom_func(group),
                      'custom_column_2':custom_func2(group)})

现在你可以简单地这样做:
In [202]: lasts.groupby('user').apply(get_stats).reset_index()
Out[202]:
  user  custom_column_1  custom_column_2
0    a          29993.0          10000.0
1    d          22493.5          75000.0
2    s          19992.0          30000.0

另一种(略丑陋)的方法是使用您的功能(未更改):

In [188]: pd.DataFrame(lasts.groupby('user')
                            .apply(get_stats).to_dict()) \
            .T \
            .rename_axis('user') \
            .reset_index()
Out[188]:
  user  custom_column_1  custom_column_2
0    a          29993.0          10000.0
1    d          22493.5          75000.0
2    s          19992.0          30000.0

1
在我看来,将字典包装在Series中是你提出的三个解决方案中最好的解决方案。谢谢。 - zthomas.nc
@zthomas.nc,欢迎您 :) 如果您认为某个答案解决了您的问题,请考虑接受该答案。 - MaxU - stand with Ukraine

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