【问题标题】:pandas, apply multiple functions of multiple columns to groupby objectpandas,将多列的多个功能应用于groupby对象
【发布时间】:2016-11-10 16:06:47
【问题描述】:

我想将多列的多个函数应用于一个 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 是:

但是我怀疑有更好的方法,例如:

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,但解决方案对我来说看起来相当难看,并且考虑到答案已经将近四年了,现在可能有更好的方法。

【问题讨论】:

    标签: python pandas dataframe group-by


    【解决方案1】:

    解决方案的另一个可靠变体是执行 @MaxU 对 this solutiona similar question 所做的操作,并将各个函数 包装在 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()
    

    【讨论】:

      【解决方案2】:

      我认为您可以避免使用aggapply,而是首先使用mul,然后使用div,最后使用index 使用groupbyaggregating 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   
      

      【讨论】:

        【解决方案3】:

        要通过使用来自同一数据帧的其他列的数据对 groupby 对象使用 agg 方法,您可以执行以下操作:

        1. 定义将Series 作为输入的函数(lambda 函数与否),并使用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,我们通过.loc 方法访问num_cores 列中的数据。

        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
          

        HTH

        【讨论】:

          【解决方案4】:

          作为对赏金的回应,我们可以通过使用标准库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
          

          这对于给定的示例不是很有用,但作为一般示例可能更有用。

          【讨论】:

            【解决方案5】:

            这是一个与“我怀疑有更好的方法”下表达的原始想法非常相似的解决方案。

            我将使用与其他答案相同的测试数据:

            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
            

            【讨论】:

              【解决方案6】:

              这个 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'})
              

              【讨论】:

                猜你喜欢
                • 2017-09-11
                • 1970-01-01
                • 2017-10-29
                • 2018-01-11
                • 1970-01-01
                • 1970-01-01
                • 2018-10-06
                • 1970-01-01
                • 2013-04-23
                相关资源
                最近更新 更多