pandas, применять несколько функций из нескольких столбцов для группировки объекта

Я хочу применить несколько функций из нескольких столбцов к объекту 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_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}) 

Однако это не работает, потому что agg() работает на pandas.Series .

Я нашел этот вопрос и ответ , но решения выглядят довольно уродливыми для меня, и, учитывая, что ответ почти четырехлетний, может быть, сейчас лучше.

6 Solutions collect form web for “pandas, применять несколько функций из нескольких столбцов для группировки объекта”

Я думаю, вы можете избежать agg или apply и, скорее, сначала несколько mul , затем div и last use groupby по index с 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 

Еще одна твердая вариация решения заключается в том, чтобы сделать то, что @MaxU сделал с этим решением для аналогичного вопроса, и обернуть отдельные функции в серии Pandas , поэтому для reset_index() требуется только 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() 

В ответ на щедрость мы можем сделать его более общим, используя частичное приложение, из стандартных функций 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 

Это не очень полезно для приведенного примера, но может быть более полезным в качестве общего примера.

Вот решение, которое очень напоминает оригинальную идею, выраженную в «Я подозреваю, что есть лучший способ».

Я буду использовать те же данные тестирования, что и другие ответы:

 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 

Второй заключается в том, что возвращаемый dataframe имеет иерархический индекс (этот столбец нулей), который может быть сплющен, как показано ниже:

 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 

Эта функция 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'}) 

Чтобы использовать метод groupby объекта 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. Группировка и совокупность:

     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 

НТН

  • Панды: медленное преобразование даты
  • Поворот DataFrame в Pandas для вывода в CSV
  • читать csv-файл и возвращать data.frame в Python
  • Показывать не ascii (японский) персонажей в легенде сюжета панд
  • Эффективно проверьте, присутствует ли значение в любом из заданных диапазонов
  • Pandon Pandas - определить, будут ли значения в столбце 0 повторяться в каждом последующем столбце
  • В пандах могу ли я глубоко скопировать DataFrame, включая его индекс и столбец?
  • Как прочитать файл Parquet в Pandas DataFrame?
  • Подсвечник Matplotlib (внутридневной) - один большой блок
  • Как фильтровать фрейм данных дат в определенный месяц / день?
  • как применить функцию к нескольким столбцам в кадре данных pandas за один раз
  • Python - лучший язык программирования в мире.