Pandas: ¿eliminar un nivel de un índice de columna de varios niveles?

243

Si tengo un índice de columna de varios niveles:

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

¿Cómo puedo eliminar el nivel "a" de ese índice, por lo que termino con:

    b | C
- + --- + -
0 | 1 | 2
1 | 3 | 4 4
David Wolever
fuente
3
Sería bueno tener un método DataFrame que lo haga tanto para el índice como para las columnas. Ya sea de soltar o seleccionar niveles de índice.
Sören
@ Sören Echa un vistazo a stackoverflow.com/a/56080234/3198568 . droplevelworks puede funcionar en índices o columnas multinivel a través del parámetro axis.
Irene

Respuestas:

307

Puedes 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
fuente
55
Probablemente sea mejor decir explícitamente qué nivel se está eliminando. Los niveles están indexados en 0 comenzando desde arriba. >>> df.columns = df.columns.droplevel(0)
Ted Petrou
66
Si el índice que intenta colocar está en el lado izquierdo (fila) y no en el lado superior (columna), puede cambiar "columnas" a "índice" y utilizar el mismo método:>>> df.index = df.index.droplevel(1)
Idodo
77
En Panda versión 0.23.4, df.columns.droplevel()ya no está disponible.
yoonghm
8
@yoonghm Está ahí, probablemente solo lo esté llamando en columnas que no tienen un índice múltiple
Matt Harrison
1
Tenía tres niveles de profundidad y quería bajar al nivel medio. Descubrí que dejar caer el más bajo (nivel [2]) y luego el más alto (nivel [0]) funcionó mejor. >>>df.columns = df.columns.droplevel(2) >>>df.columns = df.columns.droplevel(0)
Kyle C
65

Otra forma de eliminar el índice es usar una lista de comprensión:

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

   b  c
0  1  2
1  3  4

Esta estrategia también es útil si desea combinar los nombres de ambos niveles, como en el ejemplo a continuación, donde el nivel inferior contiene dos '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

Dejar caer el nivel superior dejaría dos columnas con el índice 'y'. Eso puede evitarse uniendo los nombres con la comprensión de la lista.

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

    A_x A_y B_y
0   1   2   8
1   3   4   9

Ese es un problema que tuve después de hacer un grupo y me tomó un tiempo encontrar esta otra pregunta que lo resolvió. Adapté esa solución al caso específico aquí.

menta
fuente
2
[col[1] for col in df.columns]Es más directamente df.columns.get_level_values(1).
Eric O Lebigot
2
Tenía una necesidad similar en la que algunas columnas tenían valores de nivel vacíos. Usó lo siguiente:[col[0] if col[1] == '' else col[1] for col in df.columns]
Logan
43

Otra forma de hacerlo es reasignar en dffunción de una sección transversal de df, utilizando el 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
fuente
1
Esto solo funciona cuando hay una sola etiqueta para un nivel de columna completo.
Ted Petrou
1
No funciona cuando quieres soltar el segundo nivel.
Sören
Esta es una buena solución si desea cortar y soltar para el mismo nivel. Si quisiera cortar en el segundo nivel (digamos b), luego baje ese nivel y quede con el primer nivel ( a), lo siguiente funcionaría:df = df.xs('b', axis=1, level=1, drop_level=True)
Tiffany G. Wilson
27

A partir de Pandas 0.24.0 , ahora podemos usar 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

Esto es muy útil si desea mantener su cadena de método DataFrame funcionando.

jxc
fuente
Esta es la solución "más pura" en que se devuelve un nuevo DataFrame en lugar de modificarlo "en su lugar".
EliadL
16

También puede lograrlo cambiando el nombre de las columnas:

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

Esto implica un paso manual, pero podría ser una opción, especialmente si eventualmente cambiaría el nombre de su marco de datos.

sedeh
fuente
Esto es esencialmente lo que hace la primera respuesta de Mint. Ahora, tampoco es necesario especificar la lista de nombres (que generalmente es tediosa), ya que se la da usted df.columns.get_level_values(1).
Eric O Lebigot
13

Un pequeño truco sum con level = 1 (funciona cuando level = 1 es único)

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

Solución más común get_level_values

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

He tenido problemas con este problema porque no sé por qué mi función droplevel () no funciona. Trabaja en varios y aprende que 'a' en tu tabla es el nombre de las columnas y 'b', 'c' son el índice. Hacer esto ayudará

df.columns.name = None
df.reset_index() #make index become label
dhFrank
fuente
1
Esto no reproduce la salida deseada en absoluto.
Eric O Lebigot
Según la fecha de publicación, es posible que el nivel de caída no se haya incluido en su versión de Pandas (se agregó a la versión estable, 24.0, en enero de 2019)
LinkBerest