2016-11-10 163 views
10

複数のカラムの複数の関数をgroupbyオブジェクトに適用して、新しいpandas.DataFrameという結果にしたいと考えています。pandas、groupbyオブジェクトに複数のカラムの複数の関数を適用する

私は別々のステップでそれを行う方法を知っている:

user_dfビーイングになり
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

、のように:

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歳に近いと考えれば、今はもっと良い方法があるかもしれません。

答えて

4

は、私はあなたがaggregatingsumindexによってaggまたはapplyと、むしろ最初の複数mulことで、その後、divと最後の使用groupbyを避けることができると思う:対応して

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

あなたが次のことを行うことができ、同じデータフレームの他の列からのデータを使用してgroupbyオブジェクト上aggメソッドを使用するには:

  1. は、入力として取るあなたの機能(lambda機能かどうか)を定義しますSeriesを使用し、df.loc[series.index, col]構文を使用して他の列からデータを取得します。この例では:lastsはメインデータフレームがある、と私たちは列に.loc方法にnum_coresおかげでデータにアクセス

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

  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は、データフレームを返した後、自動的に一緒に返されたデータフレームをステッチする関数を受け入れることができます。下の文言には2つの小さなキャッチがあります。最初に、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_です。

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がa similar questionthis solutionでやった行うことで、 Pandasシリーズに個々の関数をラップし、データフレームを返すためには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)}) 

lasts.groupby('user').apply(get_stats).reset_index() 
関連する問題