pandas,将多列的多个函数应用于groupby对象

时间:2016-11-10 16:06:47

标签: python pandas dataframe group-by

我想将多列的多个函数应用于groupby对象,从而产生新的pandas.DataFrame

我知道如何分开执行:

by_user = lasts.groupby('user')
elapsed_days = by_user.apply(lambda x: (x.elapsed_time * x.num_cores).sum() / 86400)
running_days = by_user.apply(lambda x: (x.running_time * x.num_cores).sum() / 86400)
user_df = elapsed_days.to_frame('elapsed_days').join(running_days.to_frame('running_days'))

这会导致user_dfuser_df

但我怀疑有更好的方法,例如:

by_user.agg({'elapsed_days': lambda x: (x.elapsed_time * x.num_cores).sum() / 86400, 
             'running_days': lambda x: (x.running_time * x.num_cores).sum() / 86400})

但是,这不起作用,因为AFAIK agg()适用于pandas.Series

我确实找到this question and answer,但解决方案看起来相当丑陋,考虑到答案已接近四年,现在可能有更好的方法。

6 个答案:

答案 0 :(得分:5)

我认为您可以避免aggapply,而是首先按mul,然后div,最后使用groupby index使用aggregating sum

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]})

print (lasts)
   elapsed_time  num_cores  running_time user
0         40000          7         30000    a
1         50000          8         20000    s
2         60000          9         30000    d
3         90000          4         15000    d
by_user = lasts.groupby('user')
elapsed_days = by_user.apply(lambda x: (x.elapsed_time * x.num_cores).sum() / 86400)
print (elapsed_days)
running_days = by_user.apply(lambda x: (x.running_time * x.num_cores).sum() / 86400)
user_df = elapsed_days.to_frame('elapsed_days').join(running_days.to_frame('running_days'))
print (user_df)
      elapsed_days  running_days
user                            
a         3.240741      2.430556
d        10.416667      3.819444
s         4.629630      1.851852
lasts = lasts.set_index('user')
print (lasts[['elapsed_time','running_time']].mul(lasts['num_cores'], axis=0)
                                             .div(86400)
                                             .groupby(level=0)
                                             .sum())
      elapsed_time  running_time
user                            
a         3.240741      2.430556
d        10.416667      3.819444
s         4.629630      1.851852   

答案 1 :(得分:5)

解决方案的另一个重要变体是执行@MaxU对this solutiona similar question所做的操作,并将单个函数包装在Pandas系列中,因此只需要{ {1}}返回一个数据帧。

首先,定义转换函数:

reset_index()

使用def ed(group): return group.elapsed_time * group.num_cores).sum() / 86400 def rd(group): return group.running_time * group.num_cores).sum() / 86400

将它们整合到一个系列中
get_stats

最后:

def get_stats(group):
    return pd.Series({'elapsed_days': ed(group),
                      'running_days':rd(group)})

答案 2 :(得分:2)

要使用同一数据框的其他列中的数据对agg对象使用groupby方法,您可以执行以下操作:

  1. 定义您的函数(lambda函数与否),将Series作为输入,并使用df.loc[series.index, col]语法从其他列获取数据。通过这个例子:

    ed = lambda x: (x * lasts.loc[x.index, "num_cores"]).sum() / 86400. 
    rd = lambda x: (x * lasts.loc[x.index, "num_cores"]).sum() / 86400.
    

    其中lasts是主要的DataFrame,我们通过num_cores方法访问.loc列中的数据。

  2. 使用这些函数创建一个字典,并为新创建的列创建名称。键是应用每个函数的列的名称,值是另一个字典,其中键是函数的名称,值是函数。

    my_func = {"elapsed_time" : {"elapsed_day" : ed},
               "running_time" : {"running_days" : rd}}
    
  3. Groupby和聚合:

    user_df = lasts.groupby("user").agg(my_func)
    user_df
         elapsed_time running_time
          elapsed_day running_days
    user                          
    a        3.240741     2.430556
    d       10.416667     3.819444
    s        4.629630     1.851852
    
  4. 如果要删除旧列名:

     user_df.columns = user_df.columns.droplevel(0)
     user_df
          elapsed_day  running_days
    user                           
    a        3.240741      2.430556
    d       10.416667      3.819444
    s        4.629630      1.851852
    
  5. HTH

答案 3 :(得分:1)

为了回应赏金,我们可以通过使用标准库functools.partial函数中的部分应用程序来使其更加通用。

import functools
import pandas as pd

#same data as other answer:
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]})

#define the desired lambda as a function:
def myfunc(column, df, cores):
    return (column * df.ix[column.index][cores]).sum()/86400

#use the partial to define the function with a given column and df:
mynewfunc = functools.partial(myfunc, df = lasts, cores = 'num_cores')

#agg by the partial function
lasts.groupby('user').agg({'elapsed_time':mynewfunc, 'running_time':mynewfunc})

这给了我们:

    running_time    elapsed_time
user        
a   2.430556    3.240741
d   3.819444    10.416667
s   1.851852    4.629630

这对于给出的示例并不是非常有用,但作为一般示例可能更有用。

答案 4 :(得分:0)

这是一个与“我怀疑有更好的方法”所表达的原始观点非常相似的解决方案。

我将使用与其他答案相同的测试数据:

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.apply可以接受一个返回数据帧的函数,然后自动将返回的数据帧拼接在一起。下面的措辞中有两个小渔获物。第一个注意到传递给DataFrame的值实际上是单元素列表而不仅仅是数字。

def aggfunc(group):
    """ This function mirrors the OP's idea. Note the values below are lists """
    return pd.DataFrame({'elapsed_days': [(group.elapsed_time * group.num_cores).sum() / 86400], 
                         'running_days': [(group.running_time * group.num_cores).sum() / 86400]})

user_df = lasts.groupby('user').apply(aggfunc)

结果:

        elapsed_days  running_days
user                              
a    0      3.240741      2.430556
d    0     10.416667      3.819444
s    0      4.629630      1.851852

第二个是返回的数据帧有一个分层索引(该列为零),可以展平,如下所示:

user_df.index = user_df.index.levels[0]

结果:

      elapsed_days  running_days
user                            
a         3.240741      2.430556
d        10.416667      3.819444
s         4.629630      1.851852

答案 5 :(得分:0)

这个聚合函数可能就是你要找的东西。

我添加了一个示例数据集,并将操作应用于lasts的副本,我将其命名为lasts_

import pandas as pd

lasts = pd.DataFrame({'user'        :['james','james','james','john','john'],
                      'elapsed_time':[ 200000, 400000, 300000,800000,900000],
                      'running_time':[ 100000, 100000, 200000,600000,700000],
                      'num_cores'   :[      4,      4,      4,     8,     8] })

# create temporary df to add columns to, without modifying original dataframe
lasts_ = pd.Series.to_frame(lasts.loc[:,'user'])  # using 'user' column to initialize copy of new dataframe.  to_frame gives dataframe instead of series so more columns can be added below
lasts_['elapsed_days'] = lasts.loc[:,'elapsed_time'] * lasts.loc[:,'num_cores'] / 86400
lasts_['running_days'] = lasts.loc[:,'running_time'] * lasts.loc[:,'num_cores'] / 86400

# aggregate
by_user = lasts_.groupby('user').agg({'elapsed_days': 'sum', 
                                      'running_days': 'sum' })

# by_user:
# user  elapsed_days        running_days
# james 41.66666666666667   18.51851851851852
# john  157.4074074074074   120.37037037037037

如果要将'user'保留为普通列而不是索引列,请使用:

by_user = lasts_.groupby('user', as_index=False).agg({'elapsed_days': 'sum', 
                                                      'running_days': 'sum'})