如何在Pandas中对数据框进行排序,而不改变分组?

3

我正在尝试使用Pandas进行分组并应用排序,类似下面的示例:

1

目前我已经创建了各个框架以获取小计。不确定如何继续操作以正确排序而不会借助黑科技。

示例数据框来自此前的问题。

df = pd.DataFrame({
'admin0': ['cntry1', 'cntry1', 'cntry1', 'cntry1', 'cntry1', 'cntry1', 'cntry2', 'cntry2', 'cntry2', 'cntry2', 'cntry2'], 
'admin1': ['state1', 'state1', 'state1', 'state2', 'state2', 'state2', 'state3', 'state3', 'state3', 'state3', 'state4'], 
'admin2': ['city1', 'city1', 'city2', 'city3', 'city4', 'city4', 'city5', 'city6', 'city6', 'city6', 'city7'], 
'windspeed': [60, 90, 60, 60, 60, 90, 60, 60, 90, 120, 60], 
'population': [700, 210, 100, 70, 180, 370, 890, 120, 420, 360, 740]
})
g1 = df.groupby(['admin0', 'admin1',  'admin2']).sum()
g2 = g1.groupby(level=[0, 1]).sum()
g2.index = pd.MultiIndex.from_arrays([g2.index.get_level_values(0), g2.index.get_level_values(1), len(g2.index)*['']])
g3 = g1.groupby(level=0).sum()
g3.index = pd.MultiIndex.from_arrays([g3.index.get_level_values(0), len(g3.index)*[''], len(g3.index)*['']])
g = pd.concat([g1, g2, g3])

现在g的状态是:
                         windspeed  population
admin0 admin1 admin2                       
cntry1 state1 city1         150         910
              city2          60         100
       state2 city3          60          70
              city4         150         550
cntry2 state3 city5          60         890
              city6         270         900
       state4 city7          60         740
cntry1 state1               210        1010
       state2               210         620
cntry2 state3               330        1790
       state4                60         740
cntry1                      420        1630
cntry2                      390        2530

我现在希望可以进行排序,而不改变如gif所示的分组方式。
当按风速升序分组时,预期响应结果为:
                      windspeed  population
admin0 admin1 admin2
cntry2                      390        2530
       state4                60         740
              city7          60         740
       state3               330        1790
              city5          60         890
              city6         270         900
cntry1                      420        1630
       state1               210        1010
              city2          60         100
              city1         150         910
       state2               210         620
              city3          60          70
              city4         150         550

g.sort_values('windspeed', ascending=False)?人口也一样吗? - piterbarg
5个回答

0

编辑:在澄清问题后,修改答案,原始回答如下

要在多重索引中对值进行排序,只需在排序调用中包含多重索引级别即可。排序按顺序进行,因此它首先按第一个键进行排序,然后在其中按第二个键进行排序,依此类推。

>>> g.sort_values(['admin0', 'admin1', 'admin2', 'windspeed'])
                      windspeed  population
admin0 admin1 admin2                       
cntry1                      420        1630
       state1               210        1010
              city1         150         910
              city2          60         100
       state2               210         620
              city3          60          70
              city4         150         550
cntry2                      390        2530
       state3               330        1790
              city5          60         890
              city6         270         900
       state4                60         740
              city7          60         740

然而,在您的示例中,ascending=False 看起来是这样的,并不完全符合您的要求,因为使用空的字符串 '' 创建了多级索引,其中 'country' 和 'state' 级别的度量标准是相同的。
>>> g.sort_values(['admin0', 'admin1', 'admin2', 'windspeed'], ascending=False)
                      windspeed  population
admin0 admin1 admin2                       
cntry2 state4 city7          60         740
                             60         740
       state3 city6         270         900
              city5          60         890
                            330        1790
                            390        2530
cntry1 state2 city4         150         550
              city3          60          70
                            210         620
       state1 city2          60         100
              city1         150         910
                            210        1010
                            420        1630

这可能有点令人困惑,但这是因为pandas打印多级索引的方式--当一个多级索引的级别重复时,它不会再次打印。但由于级别值设置为'',所以你无法判断它们是否只是没有重复,还是标签实际上是''。你可以通过使用一些在statecity之后出现的字符来获得你想要的精确输出,例如如果我在MultiIndex.from_arrays调用中用'z'替换''

g.sort_values(['admin0', 'admin1', 'admin2', 'windspeed'], ascending=False)
                      windspeed  population
admin0 admin1 admin2                       
cntry2 z      z             390        2530
       state4 z              60         740
              city7          60         740
       state3 z             330        1790
              city6         270         900
              city5          60         890
cntry1 z      z             420        1630
       state2 z             210         620
              city4         150         550
              city3          60          70
       state1 z             210        1010
              city2          60         100
              city1         150         910

或者您可以使用有序分类索引,其中明确指定''应该是最后一个标签(而不是第一个)。


原始答案

我觉得我可能误解了你的问题,这不是你想要的吗?

>>> g.sort_values(by="windspeed")
                      windspeed  population
admin0 admin1 admin2                       
cntry1 state1 city2          60         100
       state2 city3          60          70
cntry2 state3 city5          60         890
       state4 city7          60         740
                             60         740
cntry1 state1 city1         150         910
       state2 city4         150         550
       state1               210        1010
       state2               210         620
cntry2 state3 city6         270         900
                            330        1790
                            390        2530
cntry1                      420        1630

>>> g.sort_values(by=["windspeed","population"])
                      windspeed  population
admin0 admin1 admin2                       
cntry1 state2 city3          60          70
       state1 city2          60         100
cntry2 state4 city7          60         740
                             60         740
       state3 city5          60         890
cntry1 state2 city4         150         550
       state1 city1         150         910
       state2               210         620
       state1               210        1010
cntry2 state3 city6         270         900
                            330        1790
                            390        2530
cntry1                      420        1630
>>> 

是的,排序不应影响分组。分组本身应该在一起。在您提供的响应中,这些组是混合在一起的。 - doomer
好的,@doomer,我现在明白问题了,已经编辑我的答案来回答它。 - sneakers-the-rat
如果我们在排序中包含索引的所有级别...结果将与对索引进行排序相同,而值列不会产生影响。正如您在结果中看到的那样,状态4(总共60)出现在状态3(330)之前,而城市5(60)出现在城市6(270)之后。因此,没有关于风速的排序。我已经发布了一个使用Python完成的答案。您可以比较结果..理想情况下,索引中的空白值不应产生任何影响。因为它们代表子组的总和,并始终显示在组开始的节点上。 - doomer
啊哈,我终于明白你的意思了——在这种情况下,我的答案是数据格式不正确,因为你同时在同一列中进行了几个级别的比较——把苹果和橙子进行比较。我建议重新格式化数据,添加一个“类型”列,以指定每种情况下“风速”表示什么(城市、州或国家计数),因为空标签的隐含差异并不能满足你的需求。我认为你在这种情况下混淆了“模型”和“视图”——你不需要按照显示形式计算数据。 - sneakers-the-rat
但如果你的答案对你有效,那太好了! - sneakers-the-rat
显示剩余2条评论

0

使用 sort_index(level=0)

                      windspeed  population
admin0 admin1 admin2                       
cntry1                      420        1630
       state1               210        1010
              city1         150         910
              city2          60         100
       state2               210         620
              city3          60          70
              city4         150         550
cntry2                      390        2530
       state3               330        1790
              city5          60         890
              city6         270         900
       state4                60         740
              city7          60         740

这是默认的pandas排序,其中索引被排序(在本例中按字母顺序)。一旦我对风速或人口进行排序,它就会破坏原有的顺序。 - doomer

0

'windspeed'升序排序的例子,从您的数据框g继续:

levels = ['admin0', 'admin1',  'admin2']
g.groupby(levels[:-1], group_keys = False).apply(lambda x: x.sort_values(by = 'windspeed', ascending=True))

基本上,你需要像之前一样执行groupby操作,然后在除了最后一个级别以外的所有级别上再次进行groupby操作,并根据所需列进行排序。


嗨,谢谢回复。我不确定它是否符合我的要求。 - doomer
我编辑了答案,不需要单独列出一列,你可以直接将其传递给排序。也许这更符合你的需求。 - Always Right Never Left
抱歉..我仍在学习如何用代码/图像适当地回答问题。正如您在上面的gif中所看到的,除了最终叶节点之外,我仍需要对组/子组级别的总计进行排序。 - doomer
哦,我刚刚看了一下输入的数据框,错过了你执行的额外分组,抱歉。我编辑了答案,使其从你的数据框“g”开始,请检查这是否是你想要的。 - Always Right Never Left
没关系,排序似乎不影响总数,我现在看到问题了。猜测需要对双重分组技巧进行一些解决方法,只是现在想不出来。 - Always Right Never Left
这正是我的问题。我使用的方法是引入一个新列,其中包含每个级别的条目元组。然后我最终对这些小数据框进行排序,然后分配新值,再次进行排序。对于大型数据集来说,这非常耗时。 - doomer

0

你需要在排序中包含前三列

g1.sort_values(by=['admin0', 'admin1',  'admin2','windspeed','population'], ascending = False)

这相当于pandas默认排序,没有考虑实际列,即windspeedpopulation。小排列应该在保留顺序的情况下排除最后一级。即g1.sort_values(by=['admin0', 'admin1', 'windspeed','population'], ascending = False), g2.sort_values(by=['admin0', 'windspeed','population'], ascending = False)等等来产生影响。仍然没有简单的方法来合并这些小型数据框。 - doomer

0
这是我的hack解决方案。它很丑陋,由于在Python层中进行了过多的计算,肯定不好。希望pandas/numpy/scipy专家能够指出问题,并提供比这更好的解决方案。
import pandas as pd
import numpy as np

def compute_glevels(mini_dfs, rp_len):

    level_vals = [{} for _ in range(rp_len)]
    for i, sdf in enumerate(reversed(mini_dfs)):
        idx = sdf.index
        g_levels = [[[] for _ in range(rp_len)] for _ in range(len(idx))]
        for j, entry in enumerate(sdf.index):
            for l, lv in enumerate(entry):
                if lv:
                    if l > i:
                        print('i', i,  'j', j, 'l', l, 'level_vals', level_vals)
                        raise Exception
                    else:
                        v = level_vals[l].setdefault(entry[:l+1], len(level_vals[l]) + 1)
                        g_levels[j][l] = v
                else:
                    g_levels[j][l] = 0
        sdf['g_lvl'] = g_levels


def run(df, row_pivots=None, aggregates=None, sort=None):

    rp_len = len(row_pivots)
    by = list(row_pivots[:-1])
    asc = [True] * (rp_len - 1)
    for entry in sort:
        by.append(entry[0])
        asc.append(entry[1] == 'asc')
    grouper = df.groupby(list(row_pivots), sort=False).agg(aggregates)
    grouper.sort_values(by=by, ascending=asc, inplace=True)
    sdfs = [grouper]

    for i in range(1, rp_len):
        if i < rp_len - 1:
            by = list(row_pivots[:-(i+1)])
            asc = [True] * len(by)
        else:
            by = []
            asc = []
        for entry in sort:
            by.append(entry[0])
            asc.append(entry[1] == 'asc')
        levels = list(range(rp_len - i))
        sdf = sdfs[i-1].groupby(level=levels, sort=False).agg(aggregates)
        sdf.sort_values(by=by, ascending=asc, inplace=True)
        idx_len = len(sdf.index)
        sdf.index = pd.MultiIndex.from_arrays([sdf.index.get_level_values(l) if l < (rp_len - i) else [''] * idx_len for l in range(rp_len)])
        sdfs.append(sdf)

    compute_glevels(sdfs, rp_len)
    total_dict = sdfs[-1].agg(aggregates).to_dict()
    total_dict['g_lvl'] = [0, 0, 0]
    total = pd.DataFrame([total_dict], columns=sdfs[0].columns, index=pd.MultiIndex.from_tuples([[''] * rp_len], names=row_pivots))
    sdfs.append(total)
    g = pd.concat(sdfs).sort_values('g_lvl')
    print(g)
    return g

if __name__ == '__main__':

    df = pd.DataFrame({
        'admin0': ['cntry1', 'cntry1', 'cntry1', 'cntry1', 'cntry1', 'cntry1', 'cntry2', 'cntry2', 'cntry2', 'cntry2','cntry2'],
        'admin1': ['state1', 'state1', 'state1', 'state2', 'state2', 'state2', 'state3', 'state3', 'state3', 'state3','state4'],
        'admin2': ['city1', 'city1', 'city2', 'city3', 'city4', 'city4', 'city5', 'city6', 'city6', 'city6', 'city7'],
        'windspeed': [60, 90, 60, 60, 60, 90, 60, 60, 90, 120, 60],
        'population': [700, 210, 100, 70, 180, 370, 890, 120, 420, 360, 740]
    })
    g1 = run(df, row_pivots=('admin0', 'admin1', 'admin2'), aggregates={'windspeed': np.sum, 'population': np.sum}, sort=(('windspeed', 'asc'), ))
    g2 = run(df, row_pivots=('admin0', 'admin1', 'admin2'), aggregates={'windspeed': np.sum, 'population': np.sum}, sort=(('windspeed', 'asc'), ('population', 'asc')))
    print(g1.equals(g2))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接