我想将多列的多个函数应用于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'))
但我怀疑有更好的方法,例如:
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,但解决方案看起来相当丑陋,考虑到答案已接近四年,现在可能有更好的方法。
答案 0 :(得分:5)
我认为您可以避免agg
或apply
,而是首先按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 solution到a 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
方法,您可以执行以下操作:
定义您的函数(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
列中的数据。
使用这些函数创建一个字典,并为新创建的列创建名称。键是应用每个函数的列的名称,值是另一个字典,其中键是函数的名称,值是函数。
my_func = {"elapsed_time" : {"elapsed_day" : ed},
"running_time" : {"running_days" : rd}}
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
如果要删除旧列名:
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
答案 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'})