Pandas:如何从多层级列索引中删除一个层级?

396

如果我有一个多级列索引:

>>> cols = pd.MultiIndex.from_tuples([("a", "b"), ("a", "c")])
>>> pd.DataFrame([[1,2], [3,4]], columns=cols)
我怎样才能删除该索引的“a”级别,以便最终得到以下结果:
    b | c
--+---+--
0 | 1 | 2
1 | 3 | 4

5
希望DataFrame有一个方法,能够同时删除或选择索引级别和列级别。 - Soren
1
@Sören 请查看 https://dev59.com/8mEh5IYBdhLWcg3wnEYc#56080234。`droplevel`可以通过参数`axis`在多级索引或列上工作。 - irene
8个回答

471
你可以使用 MultiIndex.droplevel 方法:
>>> cols = pd.MultiIndex.from_tuples([("a", "b"), ("a", "c")])
>>> df = pd.DataFrame([[1,2], [3,4]], columns=cols)
>>> df
   a   
   b  c
0  1  2
1  3  4

[2 rows x 2 columns]
>>> df.columns = df.columns.droplevel()
>>> df
   b  c
0  1  2
1  3  4

[2 rows x 2 columns]

94
最好明确说明要删除哪个级别。级别从顶部开始,索引从0开始。 >>> df.columns = df.columns.droplevel(0) - Ted Petrou
12
如果您要删除的索引在左侧(行)而不是顶部(列),则可以将“columns”更改为“index”,并使用相同的方法: >>> df.index = df.index.droplevel(1) - Idodo
9
在Panda 0.23.4版本中,df.columns.droplevel()功能已不再提供。 - yoonghm
15
它在那里,你可能只是在调用没有多重索引的列。 - Matt Harrison
1
我有三个层级,想要跳到中间层级。我发现最好的方法是先删除最低级别([2]级别),然后再删除最高级别([0]级别)。>>> df.columns = df.columns.droplevel(2) >>> df.columns = df.columns.droplevel(0) - Kyle C

127

截止到Pandas 0.24.0版本,我们现在可以使用DataFrame.droplevel()方法:

cols = pd.MultiIndex.from_tuples([("a", "b"), ("a", "c")])
df = pd.DataFrame([[1,2], [3,4]], columns=cols)

df.droplevel(0, axis=1) 

#   b  c
#0  1  2
#1  3  4

如果您想保持DataFrame方法链的连续性,这将非常有用。


3
这是最“纯净”的解决方案,因为它返回一个新的DataFrame而不是直接对原来的DataFrame进行修改。 - EliadL
5
df.droplevel(0, axis='columns') 更加明确易懂。 - Guy
我会永远来这里,因为我总是忘记设置 axis=1 - igorkf

110

使用列表推导式删除索引的另一种方法:

df.columns = [col[1] for col in df.columns]

   b  c
0  1  2
1  3  4

如果您想要结合两个级别的名称,例如下面的示例中底层包含两个“y”,那么这种策略也很有用:

cols = pd.MultiIndex.from_tuples([("A", "x"), ("A", "y"), ("B", "y")])
df = pd.DataFrame([[1,2, 8 ], [3,4, 9]], columns=cols)

   A     B
   x  y  y
0  1  2  8
1  3  4  9
删除顶层将使具有索引“y”的两列。 通过使用列表推导式加入名称可以避免这种情况。
df.columns = ['_'.join(col) for col in df.columns]

    A_x A_y B_y
0   1   2   8
1   3   4   9

在进行groupby操作之后我遇到了一个问题,花费了一些时间才找到这个问题的解决方案。我将那个解决方案适用于这个具体的案例。


4
[col[1] for col in df.columns] 更直接的写法是 df.columns.get_level_values(1) - Eric O. Lebigot
4
有类似的需求,其中一些列的级别值为空。使用以下代码:[col[0] if col[1] == '' else col[1] for col in df.columns] - Logan
太棒了。我需要一种简单的方法来绑定级别和列。谢谢。 - igorkf

54

另一种方法是基于df的交叉部分重新赋值df,使用.xs方法。

>>> df

    a
    b   c
0   1   2
1   3   4

>>> df = df.xs('a', axis=1, drop_level=True)

    # 'a' : key on which to get cross section
    # axis=1 : get cross section of column
    # drop_level=True : returns cross section without the multilevel index

>>> df

    b   c
0   1   2
1   3   4

2
只有在整个列级别上只有一个标签时,此方法才有效。 - Ted Petrou
1
当你想要删除第二层时,它就无法工作。 - Soren
1
如果您想在同一级别上进行切片和删除操作,那么这是一个不错的解决方案。如果您想在第二个级别(比如 b)上进行切片,然后删除该级别并保留第一个级别(a),则可以使用以下代码:df = df.xs('b', axis=1, level=1, drop_level=True) - Tiffany G. Wilson

20

使用sum的小技巧,level=1时有效(当level=1为全部唯一时有效)

df.sum(level=1,axis=1)
Out[202]: 
   b  c
0  1  2
1  3  4
更常用的解决方案是使用get_level_values
df.columns=df.columns.get_level_values(1)
df
Out[206]: 
   b  c
0  1  2
1  3  4

18

您还可以通过重命名列来实现:

df.columns = ['a', 'b']

这涉及到手动操作,但如果您最终要重新命名数据帧,则可能是一种选项。


这基本上就是Mint的第一个答案所做的。现在,也没有必要指定名称列表(通常很繁琐),因为它已经通过df.columns.get_level_values(1)给出了。 - Eric O. Lebigot

8

我因为不知道我的droplevel()函数为什么不起作用而苦苦挣扎。经过多次尝试,发现您表格中的“a”是列名,“b”、“c”是索引。如果按照这样的方式操作会有所帮助。

df.columns.name = None
df.reset_index() #make index become label

1
这根本无法复制所需的输出。 - Eric O. Lebigot
1
根据发布日期,您使用的 Pandas 版本可能没有包含 drop level 功能(该功能已经在 2019 年 1 月的稳定版 24.0 中添加)。 - LinkBerest

0
new_columns_cdnr = []
for column in list(df.columns):
    new = [x for x in list(column) if not 'unnamed' in x.lower()]
    new_columns_cdnr.append(new[-1])
df.columns = new_columns_cdnr

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