使用pandas groupby + apply后恢复标准的单索引数据框

时间:2019-03-01 03:38:43

标签: python pandas apply pandas-groupby multi-index

我想对Python数据框中的每个组应用自定义归约函数。该功能通过执行合并组中几列的操作,将组减少为一行。

我已经这样实现了:

import pandas as pd
import numpy as np

df = pd.DataFrame(data={
  "afac": np.random.random(size=1000),
  "bfac": np.random.random(size=1000),
  "class":np.random.randint(low=0,high=5,size=1000)
})

def f(group):
  total_area = group['afac'].sum()
  per_area   = (group['afac']/total_area).values
  per_pop    = group['bfac'].values
  return pd.DataFrame(data={'per_apop': [np.sum(per_area*per_pop)]})

aggdf = df.groupby('class').apply(f)

我的输入数据框df如下:

>>> df
         afac      bfac  class
0    0.689969  0.992403      0
1    0.688756  0.728763      1
2    0.086045  0.499061      1
3    0.078453  0.198435      2
4    0.621589  0.812233      4

但是我的代码给出了这个多索引数据框:

>>> aggdf
         per_apop
class            
0     0  0.553292
1     0  0.503112
2     0  0.444281
3     0  0.517646
4     0  0.503290

我尝试了各种方法来恢复“正常”数据帧,但似乎没有任何作用。

>>> aggdf.reset_index()
   class  level_1  per_apop
0      0        0  0.553292
1      1        0  0.503112
2      2        0  0.444281
3      3        0  0.517646
4      4        0  0.503290

>>> aggdf.unstack().reset_index()
  class  per_apop
                0
0     0  0.553292
1     1  0.503112
2     2  0.444281
3     3  0.517646
4     4  0.503290

如何执行此操作并在之后获得正常的数据帧?

更新:输出数据框应具有classper_apop的列。理想情况下,函数f可以返回多列,也可能返回多行。也许使用

return pd.DataFrame(data={'per_apop': [np.sum(per_area*per_pop),2], 'sue':[1,3]})

3 个答案:

答案 0 :(得分:1)

使自定义函数返回Series

def f(group):
  total_area = group['afac'].sum()
  per_area   = (group['afac']/total_area).values
  per_pop    = group['bfac'].values
  return pd.Series(data={'per_apop': np.sum(per_area*per_pop)})
df.groupby('class').apply(f).reset_index()

   class  per_apop
0      0  0.508332
1      1  0.505593
2      2  0.488117
3      3  0.481572
4      4  0.500401

答案 1 :(得分:1)

您可以使用reset_index选择要重置的级别以及是否要保留索引。在您的情况下,您最终得到了一个具有2个级别的多索引:class和一个未命名的索引。 reset_index允许您重置整个索引(默认)或仅重置所需的级别。在以下示例中, last 级别(-1)被拉出索引。通过同时使用drop=True,它会被删除,而不是作为一列添加到数据框中。

aggdf.reset_index(level=-1, drop=True)

       per_apop
class
0      0.476184
1      0.476254
2      0.509735
3      0.502444
4      0.525287

编辑:

要将索引的class级别推回到数据帧,只需再次调用.reset_index()。丑陋,但行得通。

aggdf.reset_index(level=-1, drop=True).reset_index()

   class  per_apop
0      0  0.515733
1      1  0.497349
2      2  0.527063
3      3  0.515476
4      4  0.494530

或者,您也可以重置索引,然后删除多余的列。

aggdf.reset_index().drop('level_1', axis=1)


   class  per_apop
0      0  0.515733
1      1  0.497349
2      2  0.527063
3      3  0.515476
4      4  0.494530

答案 2 :(得分:0)

虽然你有一个很好的答案,但一个建议:
在第一组中测试 funcdf.groupby(...).apply( func ),如下所示:

agroupby = df.groupby(...)  
for key, groupdf in agroupby:  # an iterator -> (key, groupdf) ... pairs
    break  # get the first pair
print( "\n-- first groupdf: len %d  type %s \n%s" % (
        len(groupdf), type(groupdf), groupdf ))  # DataFrame
test = myfunc( groupdf )
    # groupdf .col [col] [[col ...]] .set_index .resample ... as usual