根据列属性对多重索引级别排序

1 投票
1 回答
537 浏览
提问于 2025-04-18 00:04

假设我有一个叫做 df 的多重索引数据框:

                   C         D  E
A    B                           
bar  one    0.934232  0.518263  0
     three  0.079759  0.192417  2
flux six    1.484391 -0.607172  2
     three -1.816136 -0.660524  1
foo  five  -0.695819 -0.406685  0
     one   -0.589729 -0.974765  1
     two    0.640990  0.319567  0
     two    0.485979 -2.127268  1

我想要根据每个值在列中的属性来对第一层 A 进行排序,也就是说:

  • 根据 E 的最后一个值,从大到小排序
  • 根据 D 的最小值,从小到大排序

我该怎么做呢?

最终的 df 应该保持第一层的“连续性”(也就是说,所有 A 下的项目应该仍然在同一个 A 下面,B 也是如此,等等)。

如果有帮助的话,这里是生成像上面那样随机 df 的代码:

from numpy.random import randn as randn
from numpy.random import randint as randint

def create_random_multi_index():
  df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                            'foo', 'flux', 'foo', 'flux'],  
                     'B' : ['one', 'one', 'two', 'three',
                            'two', 'six', 'five', 'three'],
                     'C' : randn(8), 'D' : randn(8), 'E': randint(0,3, size=(8,))})
  df.set_index(['A', 'B'], inplace=True)
  df.sort_index(inplace=True)
  return df


df = create_random_multi_index()

更新:

我尝试了:

e0 = df.groupby(level=0, as_index=False).E.max().E
d0 = df.groupby(level=0, as_index=False).D.last().D
new = df.iloc[pd.concat([e0, d0], 1).sort(['E', 'D'], ascending=[True, False]).index]

但我得到了:

                   C         D  E
A    B                           
flux six    1.484391 -0.607172  2
bar  one    0.934232  0.518263  0
     three  0.079759  0.192417  2

[3 rows x 3 columns]

这不对(缺少了一个完整的第一层条目)。

1 个回答

2

一个有效的技巧是直接替换多重索引的层级,然后排序,最后再把它们放回去:

In [11]: levels = df.index.levels

In [12]: e0 = -df.groupby(level=0).E.median()

In [13]: d1 = df.groupby(level=1).D.min()

In [14]: df.index.levels = [e0, d1]

In [15]: df = df.sort_index()

In [16]: df.index.levels = levels

这之所以有效,是因为你对每一列都有聚合操作。

另外一种可能更稳妥的方法是使用transform,然后用sort,并传入一个升序的列表

In [21]: e0 = df.groupby(level=0, as_index=False).transform("median").E

In [22]: d0 = df.groupby(level=0, as_index=False).transform("min").D

In [23]: to_sort = pd.concat([e0, d0], 1).reset_index(drop=True)

In [24]: to_sort
Out[24]: 
   E         D
0  2  0.278293
1  2 -0.548683
2  2  0.723572
3  0 -0.160737
4  1  1.174394
5  0 -0.304647
6  0 -0.916528
7  1 -0.350992

In [25]: to_sort.sort(['E', 'D'], ascending=[True, False])
Out[25]: 
   E         D
3  0 -0.160737
5  0 -0.304647
6  0 -0.916528
4  1  1.174394
7  1 -0.350992
2  2  0.723572
0  2  0.278293
1  2 -0.548683

然后用这个结果来重新索引:

In [26]: df.iloc[to_sort.sort(['E', 'D'], ascending=[True, False]).index]
Out[26]:
                   C         D  E
A    B                           
flux three  0.479158 -0.160737  0
foo  one    0.598025 -0.304647  0
     two    0.073532 -0.916528  0
     five   0.866019  1.174394  1
     two    1.259768 -0.350992  1
flux six    2.380352  0.723572  2
bar  one   -0.443605  0.278293  2
     three  0.506341 -0.548683  2

撰写回答