Pandas: reduza um nível de um índice de coluna multinível?

242

Se eu tenho um índice de coluna multinível:

>>> cols = pd.MultiIndex.from_tuples([("a", "b"), ("a", "c")])
>>> pd.DataFrame([[1,2], [3,4]], columns=cols)
    uma
   --- + -
    b c
- + --- + -
0 1 | 2
1 | 3 4

Como posso descartar o nível "a" desse índice, terminando com:

    b c
- + --- + -
0 1 | 2
1 | 3 4
David Wolever
fonte
3
Seria bom ter um método DataFrame que faça isso para o índice e as colunas. Descartar ou selecionar níveis de índice.
Sören
@ Sören Confira stackoverflow.com/a/56080234/3198568 . droplevelobras pode trabalhar em índices quer multinível ou colunas através do parâmetro axis.
irene

Respostas:

306

Você pode usar 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]
DSM
fonte
55
Provavelmente, é melhor dizer explicitamente qual nível está sendo diminuído. Os níveis são indexados em 0 a partir do topo. >>> df.columns = df.columns.droplevel(0)
Ted Petrou
6
Se o índice que você está tentando soltar estiver no lado esquerdo (linha) e não no lado superior (coluna), você poderá alterar "colunas" para "índice" e usar o mesmo método:>>> df.index = df.index.droplevel(1)
Idodo 28/11
7
Na versão 0.23.4 do Panda, df.columns.droplevel()não está mais disponível.
yoonghm
8
@yoonghm É lá, você provavelmente está apenas chamando-a em colunas que não tem um multi-índice
Matt Harrison
1
Eu tinha três níveis de profundidade e queria descer para o nível intermediário. Descobri que diminuir o nível mais baixo (nível 2) e, em seguida, o nível mais alto (nível 0) funcionou melhor. >>>df.columns = df.columns.droplevel(2) >>>df.columns = df.columns.droplevel(0)
Kyle C
65

Outra maneira de eliminar o índice é usar uma compreensão de lista:

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

   b  c
0  1  2
1  3  4

Essa estratégia também é útil se você deseja combinar os nomes dos dois níveis, como no exemplo abaixo, onde o nível inferior contém dois '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

A queda do nível superior deixaria duas colunas com o índice 'y'. Isso pode ser evitado juntando os nomes à compreensão da lista.

df.columns = ['_'.join(col) for col in df.columns]

    A_x A_y B_y
0   1   2   8
1   3   4   9

Esse é um problema que tive depois de trabalhar em grupo e demorou um pouco para encontrar essa outra pergunta que a resolveu. Eu adaptei essa solução ao caso específico aqui.

hortelã
fonte
2
[col[1] for col in df.columns]é mais diretamente df.columns.get_level_values(1).
Eric O Lebigot
2
Tinha uma necessidade semelhante, em que algumas colunas tinham valores de nível vazios. Utilizou o seguinte:[col[0] if col[1] == '' else col[1] for col in df.columns]
Logan
43

Outra maneira de fazer isso é reatribuir com dfbase em uma seção transversal df, usando o método .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
spacetyper
fonte
1
Isso funciona apenas quando existe um único rótulo para um nível de coluna inteiro.
Ted Petrou #
1
Não funciona quando você deseja largar o segundo nível.
Sören
Esta é uma boa solução se você deseja fatiar e soltar no mesmo nível. Se você quisesse fatia no segundo nível (digamos b) em seguida, solte esse nível e ficar com o primeiro nível ( a), o seguinte iria funcionar:df = df.xs('b', axis=1, level=1, drop_level=True)
Tiffany G. Wilson
27

A partir do Pandas 0.24.0 , agora podemos usar o 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

Isso é muito útil se você deseja manter a cadeia de métodos do DataFrame rolando.

jxc
fonte
Essa é a solução "mais pura", pois um novo DataFrame é retornado em vez de modificado "no lugar".
EliadL
16

Você também pode conseguir isso renomeando as colunas:

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

Isso envolve uma etapa manual, mas pode ser uma opção, especialmente se você renomear seu quadro de dados.

sedeh
fonte
Isso é essencialmente o que a primeira resposta de Mint faz. Agora, também não há necessidade de especificar a lista de nomes (que geralmente é entediante), como é fornecida por você df.columns.get_level_values(1).
Eric O Lebigot
13

Um pequeno truque usando sum com level = 1 (trabalhe quando level = 1 for único)

df.sum(level=1,axis=1)
Out[202]: 
   b  c
0  1  2
1  3  4

Solução mais comum get_level_values

df.columns=df.columns.get_level_values(1)
df
Out[206]: 
   b  c
0  1  2
1  3  4
YOBEN_S
fonte
4

Eu lutei com esse problema, pois não sei por que minha função droplevel () não funciona. Trabalhe em várias e aprenda que 'a' na tabela é o nome das colunas e 'b', 'c' são o índice. Faça assim ajudará

df.columns.name = None
df.reset_index() #make index become label
dhFrank
fonte
1
Isso não reproduz a saída desejada.
Eric O Lebigot
Com base na data em que foi publicado, o nível de queda pode não ter sido incluído na sua versão do Pandas (foi adicionado à versão estável, 24.0, em janeiro de 2019)
LinkBerest