2016-11-10 107 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() 
相關問題