Seleccionando columnas de pandas MultiIndex

Tengo DataFrame con columnas MultiIndex que tienen este aspecto:

# sample data col = pd.MultiIndex.from_arrays([['one', 'one', 'one', 'two', 'two', 'two'], ['a', 'b', 'c', 'a', 'b', 'c']]) data = pd.DataFrame(np.random.randn(4, 6), columns=col) data 

Data de muestra

¿Cuál es la forma correcta y sencilla de seleccionar solo columnas específicas (por ejemplo, ['a', 'c'] , no un rango) desde el segundo nivel?

Actualmente lo estoy haciendo así:

 import itertools tuples = [i for i in itertools.product(['one', 'two'], ['a', 'c'])] new_index = pd.MultiIndex.from_tuples(tuples) print(new_index) data.reindex_axis(new_index, axis=1) 

Resultado Esperado

Sin embargo, no parece una buena solución, ya que tengo que eliminar las itertools , crear otro MultiIndex a mano y luego reindexar (y mi código real es aún más desordenado, ya que las listas de columnas no son tan fáciles de obtener). Estoy bastante seguro de que tiene que haber alguna forma ix o xs de hacer esto, pero todo lo que probé resultó en errores.

No es genial, pero tal vez:

 >>> data one two abcabc 0 -0.927134 -1.204302 0.711426 0.854065 -0.608661 1.140052 1 -0.690745 0.517359 -0.631856 0.178464 -0.312543 -0.418541 2 1.086432 0.194193 0.808235 -0.418109 1.055057 1.886883 3 -0.373822 -0.012812 1.329105 1.774723 -2.229428 -0.617690 >>> data.loc[:,data.columns.get_level_values(1).isin({"a", "c"})] one two acac 0 -0.927134 0.711426 0.854065 1.140052 1 -0.690745 -0.631856 0.178464 -0.418541 2 1.086432 0.808235 -0.418109 1.886883 3 -0.373822 1.329105 1.774723 -0.617690 

¿trabajaría?

Creo que hay una forma mucho mejor (ahora), por lo que me molesto en sacar esta pregunta (que fue el resultado principal de Google) de las sombras:

 data.select(lambda x: x[1] in ['a', 'b'], axis=1) 

da su salida esperada en una rápida y limpia de una sola línea:

  one two abab 0 -0.341326 0.374504 0.534559 0.429019 1 0.272518 0.116542 -0.085850 -0.330562 2 1.982431 -0.420668 -0.444052 1.049747 3 0.162984 -0.898307 1.762208 -0.101360 

Es principalmente autoexplicativo, el [1] refiere al nivel.

Puedes usar cualquiera, loc o ix voy a mostrar un ejemplo con loc :

 data.loc[:, [('one', 'a'), ('one', 'c'), ('two', 'a'), ('two', 'c')]] 

Cuando tiene un DataFrame MultiIndexed, y quiere filtrar solo algunas de las columnas, debe pasar una lista de tuplas que coincidan con esas columnas. Así que el enfoque de itertools estaba bastante bien, pero no es necesario crear un nuevo MultiIndex:

 data.loc[:, list(itertools.product(['one', 'two'], ['a', 'c']))] 

Para seleccionar todas las columnas denominadas 'a' y 'c' en el segundo nivel de su indexador de columnas, puede usar los segmentadores de datos:

 >>> data.loc[:, (slice(None), ('a', 'c'))] one two acac 0 -0.983172 -2.495022 -0.967064 0.124740 1 0.282661 -0.729463 -0.864767 1.716009 2 0.942445 1.276769 -0.595756 -0.973924 3 2.182908 -0.267660 0.281916 -0.587835 

Aquí puedes leer más sobre los rebanadores.

Un riff un poco más fácil, en mi opinión, sobre la respuesta de Marc P. usando slice :

 import pandas as pd col = pd.MultiIndex.from_arrays([['one', 'one', 'one', 'two', 'two', 'two'], ['a', 'b', 'c', 'a', 'b', 'c']]) data = pd.DataFrame(np.random.randn(4, 6), columns=col) data.loc[:, pd.IndexSlice[:, ['a', 'c']]] one two acac 0 -1.731008 0.718260 -1.088025 -1.489936 1 -0.681189 1.055909 1.825839 0.149438 2 -1.674623 0.769062 1.857317 0.756074 3 0.408313 1.291998 0.833145 -0.471879 

A partir de los pandas 0.21 o menos, .select está en desuso a favor de .loc .

v0.23 + Respuesta.
El uso de pd.IndexSlice hace que loc sea ​​una opción más preferible para ix y select .

Puede encontrar más información sobre la segmentación y el filtrado de MultiIndexes en ¿Cómo puedo segmentar o filtrar los niveles de MultiIndex DataFrame? .


DataFrame.loc con pd.IndexSlice

 # Setup col = pd.MultiIndex.from_arrays([['one', 'one', 'one', 'two', 'two', 'two'], ['a', 'b', 'c', 'a', 'b', 'c']]) data = pd.DataFrame('x', index=range(4), columns=col) data one two abcabc 0 xxxxxx 1 xxxxxx 2 xxxxxx 3 xxxxxx 

 data.loc[:, pd.IndexSlice[:, ['a', 'c']]] one two acac 0 xxxx 1 xxxx 2 xxxx 3 xxxx 

Alternativamente, puede elegir un parámetro de axis para loc de forma explícita desde qué eje está indexando:

 data.loc(axis=1)[pd.IndexSlice[:, ['a', 'c']]] one two acac 0 xxxx 1 xxxx 2 xxxx 3 xxxx 

MultiIndex.get_level_values
Llamar a data.columns.get_level_values para filtrar con loc es otra opción:

 data.loc[:, data.columns.get_level_values(1).isin(['a', 'c'])] one two acac 0 xxxx 1 xxxx 2 xxxx 3 xxxx 

Esto naturalmente puede permitir el filtrado de cualquier expresión condicional en un solo nivel. Aquí hay un ejemplo aleatorio con filtrado lexicográfico:

 data.loc[:, data.columns.get_level_values(1) > 'b'] one two cc 0 xx 1 xx 2 xx 3 xx