2016-11-10 185 views
10

我想将多个列的多个函数应用于groupby对象,这会导致新的pandas.DataFramepandas,将多个列的多个函数应用于groupby对象

我知道该怎么做,在单独的步骤:

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_df之中: user_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,但解决方案对我而言看起来相当难看,考虑到答案已接近四年,现在可能会有更好的方法。

回答

4

我认为你能避免aggapply和而第一多个由mul,然后div和最后使用groupby通过indexaggregatingsum

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

响应对于赏金,我们可以使它更通用,通过使用部分应用程序,从标准库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 

这不是给出的例子超级有用,但也可以是一个普通的例子更为有用。

0

要使用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其中是主要的数据帧,并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 
    

HTH

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可以接受它返回一个数据帧,然后会自动拼接返回dataframes在一起的功能。下面的措辞中有两个小的捕获量。首先注意到传递给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 
0

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

我添加了一个示例数据集并将该操作应用于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 

如果你想保留“用户”为正常列,而不是索引列,使用:

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

的解决方案的另一个固体变化是做什么@MaxU做了与this solutiona similar question和将单个函数包装在Pandas系列中,因此仅需要reset_index()返回数据帧。使用get_stats

def ed(group): 
    return group.elapsed_time * group.num_cores).sum()/86400 

def rd(group): 
    return group.running_time * group.num_cores).sum()/86400 

总结起来讲::

首先,定义功能转换

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

最后:

lasts.groupby('user').apply(get_stats).reset_index() 
相关问题