Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

367
Views
Seleccionar filas en pandas MultiIndex DataFrame

¿Cuáles son las formas más comunes de pandas para seleccionar/filtrar filas de un marco de datos cuyo índice es un índice múltiple ?

  • Rebanado basado en un solo valor/etiqueta
  • Rebanado basado en múltiples etiquetas de uno o más niveles
  • Filtrado en condiciones y expresiones booleanas
  • Qué métodos son aplicables en qué circunstancias

Suposiciones para simplificar:

  1. el marco de datos de entrada no tiene claves de índice duplicadas
  2. el marco de datos de entrada a continuación solo tiene dos niveles. (La mayoría de las soluciones que se muestran aquí se generalizan a N niveles)

Ejemplo de entrada:

 mux = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), list('tuvwtuvwtuvwtuvw') ], names=['one', 'two']) df = pd.DataFrame({'col': np.arange(len(mux))}, mux) col one two at 0 u 1 v 2 w 3 bt 4 u 5 v 6 w 7 t 8 cu 9 v 10 dw 11 t 12 u 13 v 14 w 15

Pregunta 1: Selección de un solo artículo

¿Cómo selecciono las filas que tienen "a" en el nivel "uno"?

 col one two at 0 u 1 v 2 w 3

Además, ¿cómo podría eliminar el nivel "uno" en la salida?

 col two t 0 u 1 v 2 w 3

Pregunta 1b
¿Cómo divido todas las filas con el valor "t" en el nivel "dos"?

 col one two at 0 bt 4 t 8 dt 12

Pregunta 2: Selección de valores múltiples en un nivel

¿Cómo puedo seleccionar las filas correspondientes a los elementos "b" y "d" en el nivel "uno"?

 col one two bt 4 u 5 v 6 w 7 t 8 dw 11 t 12 u 13 v 14 w 15

Pregunta 2b
¿Cómo obtendría todos los valores correspondientes a "t" y "w" en el nivel "dos"?

 col one two at 0 w 3 bt 4 w 7 t 8 dw 11 t 12 w 15

Pregunta 3: Rebanar una sola sección transversal (x, y)

¿Cómo recupero una sección transversal, es decir, una sola fila que tiene valores específicos para el índice de df ? Específicamente, ¿cómo recupero la sección transversal de ('c', 'u') , dada por

 col one two cu 9

Pregunta 4: Rebanar varias secciones transversales [(a, b), (c, d), ...]

¿Cómo selecciono las dos filas correspondientes a ('c', 'u') y ('a', 'w') ?

 col one two cu 9 aw 3

Pregunta 5: Un artículo cortado por nivel

¿Cómo puedo recuperar todas las filas correspondientes a "a" en el nivel "uno" o "t" en el nivel "dos"?

 col one two at 0 u 1 v 2 w 3 bt 4 t 8 dt 12

Pregunta 6: Corte arbitrario

¿Cómo puedo cortar secciones transversales específicas? Para "a" y "b", me gustaría seleccionar todas las filas con subniveles "u" y "v", y para "d", me gustaría seleccionar filas con subnivel "w".

 col one two au 1 v 2 bu 5 v 6 dw 11 w 15

La pregunta 7 utilizará una configuración única que consiste en un nivel numérico:

 np.random.seed(0) mux2 = pd.MultiIndex.from_arrays([ list('aaaabbbbbccddddd'), np.random.choice(10, size=16) ], names=['one', 'two']) df2 = pd.DataFrame({'col': np.arange(len(mux2))}, mux2) col one two a 5 0 0 1 3 2 3 3 b 7 4 9 5 3 6 5 7 2 8 c 4 9 7 10 d 6 11 8 12 8 13 1 14 6 15

Pregunta 7: Filtrado por desigualdad numérica en niveles individuales del multiíndice

¿Cómo obtengo todas las filas donde los valores en el nivel "dos" son mayores que 5?

 col one two b 7 4 9 5 c 7 10 d 6 11 8 12 8 13 6 15

Nota: Esta publicación no explicará cómo crear MultiIndexes, cómo realizar operaciones de asignación en ellos o cualquier discusión relacionada con el rendimiento (estos son temas separados para otro momento).

over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

MultiÍndice / Indexación Avanzada

Nota
Este puesto se estructurará de la siguiente manera:

  1. Las preguntas planteadas en el OP serán abordadas, una por una.
  2. Para cada pregunta, se demostrarán uno o más métodos aplicables para resolver este problema y obtener el resultado esperado.

Se incluirán notas (muy parecidas a esta) para los lectores interesados en aprender sobre funcionalidades adicionales, detalles de implementación y otra información superficial sobre el tema en cuestión. Estas notas han sido compiladas a través de la exploración de los documentos y el descubrimiento de varias características oscuras, y de mi propia experiencia (ciertamente limitada).

Todos los ejemplos de código se han creado y probado en pandas v0.23.4, python3.7 . Si algo no está claro o es incorrecto en cuanto a los hechos, o si no encontró una solución aplicable a su caso de uso, no dude en sugerir una edición, solicitar una aclaración en los comentarios o abrir una nueva pregunta,... según corresponda. .

Aquí hay una introducción a algunos modismos comunes (en lo sucesivo, los cuatro modismos) que volveremos a visitar con frecuencia.

  1. DataFrame.loc : una solución general para la selección por etiqueta (+ pd.IndexSlice para aplicaciones más complejas que involucran cortes)

  2. DataFrame.xs : extrae una sección transversal particular de una serie/marco de datos.

  3. DataFrame.query : especifique las operaciones de división y/o filtrado de forma dinámica (es decir, como una expresión que se evalúa de forma dinámica). Es más aplicable a algunos escenarios que a otros. Consulte también esta sección de los documentos para realizar consultas en MultiIndexes.

  4. Indexación booleana con una máscara generada usando MultiIndex.get_level_values (a menudo junto con Index.isin , especialmente cuando se filtra con múltiples valores). Esto también es bastante útil en algunas circunstancias.

Será beneficioso analizar los diversos problemas de corte y filtrado en términos de los cuatro modismos para obtener una mejor comprensión de lo que se puede aplicar a una situación determinada. Es muy importante comprender que no todos los modismos funcionarán igual de bien (si es que lo hacen) en todas las circunstancias. Si una expresión idiomática no se ha enumerado como una posible solución a un problema a continuación, eso significa que la expresión idiomática no se puede aplicar a ese problema de manera efectiva.


Pregunta 1

¿Cómo selecciono las filas que tienen "a" en el nivel "uno"?

 col one two at 0 u 1 v 2 w 3

Puede usar loc , como una solución de propósito general aplicable a la mayoría de las situaciones:

 df.loc[['a']]

En este punto, si obtiene

 TypeError: Expected tuple, got str

Eso significa que estás usando una versión anterior de pandas. Considere la posibilidad de actualizar! De lo contrario, use df.loc[('a', slice(None)), :] .

Alternativamente, puede usar xs aquí, ya que estamos extrayendo una sola sección transversal. Tenga en cuenta los levels y los argumentos del axis (aquí se pueden asumir valores predeterminados razonables).

 df.xs('a', level=0, axis=0, drop_level=False) # df.xs('a', drop_level=False)

Aquí, se necesita el argumento drop_level=False para evitar que xs deje caer el nivel "uno" en el resultado (el nivel en el que cortamos).

Otra opción aquí es usar query :

 df.query("one == 'a'")

Si el índice no tuviera un nombre, necesitaría cambiar su cadena de consulta para que sea "ilevel_0 == 'a'" .

Finalmente, usando get_level_values :

 df[df.index.get_level_values('one') == 'a'] # If your levels are unnamed, or if you need to select by position (not label), # df[df.index.get_level_values(0) == 'a']

Además, ¿cómo podría eliminar el nivel "uno" en la salida?

 col two t 0 u 1 v 2 w 3

Esto se puede hacer fácilmente usando cualquiera

 df.loc['a'] # Notice the single string argument instead the list.

O,

 df.xs('a', level=0, axis=0, drop_level=True) # df.xs('a')

Tenga en cuenta que podemos omitir el argumento drop_level (se supone que es True de forma predeterminada).

Nota
Puede notar que un DataFrame filtrado aún puede tener todos los niveles, incluso si no se muestran al imprimir el DataFrame. Por ejemplo,

 v = df.loc[['a']] print(v) col one two at 0 u 1 v 2 w 3 print(v.index) MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']], labels=[[0, 0, 0, 0], [0, 1, 2, 3]], names=['one', 'two'])

Puedes deshacerte de estos niveles usando MultiIndex.remove_unused_levels :

 v.index = v.index.remove_unused_levels()
 print(v.index) MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']], labels=[[0, 0, 0, 0], [0, 1, 2, 3]], names=['one', 'two'])

Pregunta 1b

¿Cómo divido todas las filas con el valor "t" en el nivel "dos"?

 col one two at 0 bt 4 t 8 dt 12

Intuitivamente, querrías algo relacionado con slice() :

 df.loc[(slice(None), 't'), :]

¡Simplemente funciona!™ Pero es torpe. Podemos facilitar una sintaxis de corte más natural usando la API pd.IndexSlice aquí.

 idx = pd.IndexSlice df.loc[idx[:, 't'], :]

Esto es mucho, mucho más limpio.

Nota
¿Por qué se requiere el segmento final : a través de las columnas? Esto se debe a que loc se puede usar para seleccionar y cortar a lo largo de ambos ejes ( axis=0 o axis=1 ). Sin aclarar explícitamente en qué eje se debe realizar el corte, la operación se vuelve ambigua. Consulte el cuadro rojo grande en la documentación sobre rebanado .

Si desea eliminar cualquier matiz de ambigüedad, loc acepta un parámetro de axis :

 df.loc(axis=0)[pd.IndexSlice[:, 't']]

Sin el parámetro del axis (es decir, simplemente haciendo df.loc[pd.IndexSlice[:, 't']] ), se supone que el corte está en las columnas y se generará un KeyError en esta circunstancia.

Esto está documentado ensegmentaciones . Sin embargo, a los efectos de esta publicación, especificaremos explícitamente todos los ejes.

Con xs , es

 df.xs('t', axis=0, level=1, drop_level=False)

Con query , es

 df.query("two == 't'") # Or, if the first level has no name, # df.query("ilevel_1 == 't'")

Y finalmente, con get_level_values , puede hacer

 df[df.index.get_level_values('two') == 't'] # Or, to perform selection by position/integer, # df[df.index.get_level_values(1) == 't']

Todo con el mismo efecto.


Pregunta 2

¿Cómo puedo seleccionar las filas correspondientes a los elementos "b" y "d" en el nivel "uno"?

 col one two bt 4 u 5 v 6 w 7 t 8 dw 11 t 12 u 13 v 14 w 15

Usando loc, esto se hace de manera similar especificando una lista.

 df.loc[['b', 'd']]

Para resolver el problema anterior de seleccionar "b" y "d", también puede usar la query :

 items = ['b', 'd'] df.query("one in @items") # df.query("one == @items", parser='pandas') # df.query("one in ['b', 'd']") # df.query("one == ['b', 'd']", parser='pandas')

Nota
Sí, el analizador predeterminado es 'pandas' , pero es importante resaltar que esta sintaxis no es Python convencional. El analizador de Pandas genera un árbol de análisis ligeramente diferente de la expresión. Esto se hace para que algunas operaciones sean más intuitivas de especificar. Para obtener más información, lea mi publicación sobre Evaluación de expresiones dinámicas en pandas usando pd.eval() .

Y, con get_level_values + Index.isin :

 df[df.index.get_level_values("one").isin(['b', 'd'])]

Pregunta 2b

¿Cómo obtendría todos los valores correspondientes a "t" y "w" en el nivel "dos"?

 col one two at 0 w 3 bt 4 w 7 t 8 dw 11 t 12 w 15

Con loc , esto solo es posible junto con pd.IndexSlice .

 df.loc[pd.IndexSlice[:, ['t', 'w']], :]

Los primeros dos puntos : en pd.IndexSlice[:, ['t', 'w']] significa cortar el primer nivel. A medida que aumenta la profundidad del nivel consultado, deberá especificar más sectores, uno por cada nivel que se está dividiendo. Sin embargo, no necesitará especificar más niveles además del que se está cortando.

Con query , esto es

 items = ['t', 'w'] df.query("two in @items") # df.query("two == @items", parser='pandas') # df.query("two in ['t', 'w']") # df.query("two == ['t', 'w']", parser='pandas')

Con get_level_values e Index.isin (similar al anterior):

 df[df.index.get_level_values('two').isin(['t', 'w'])]

Pregunta 3

¿Cómo recupero una sección transversal, es decir, una sola fila que tiene valores específicos para el índice de df ? Específicamente, ¿cómo recupero la sección transversal de ('c', 'u') , dada por

 col one two cu 9

Use loc especificando una tupla de claves:

 df.loc[('c', 'u'), :]

O,

 df.loc[pd.IndexSlice[('c', 'u')]]

Nota
En este punto, es posible que se encuentre con una advertencia de PerformanceWarning similar a esta:

 PerformanceWarning: indexing past lexsort depth may impact performance.

Esto solo significa que su índice no está ordenado. pandas depende del índice que se ordene (en este caso, lexicográficamente, ya que estamos tratando con valores de cadena) para una búsqueda y recuperación óptimas. Una solución rápida sería ordenar su DataFrame por adelantado usando DataFrame.sort_index . Esto es especialmente deseable desde el punto de vista del rendimiento si planea realizar varias consultas de este tipo en tándem:

 df_sort = df.sort_index() df_sort.loc[('c', 'u')]

También puede usar MultiIndex.is_lexsorted() para verificar si el índice está ordenado o no. Esta función devuelve True o False según corresponda. Puede llamar a esta función para determinar si se requiere o no un paso de clasificación adicional.

Con xs , esto es simplemente pasar una sola tupla como primer argumento, con todos los demás argumentos establecidos en sus valores predeterminados apropiados:

 df.xs(('c', 'u'))

Con query , las cosas se vuelven un poco torpes:

 df.query("one == 'c' and two == 'u'")

Ahora puede ver que esto va a ser relativamente difícil de generalizar. Pero todavía está bien para este problema en particular.

Con accesos que abarcan varios niveles, todavía se puede usar get_level_values , pero no se recomienda:

 m1 = (df.index.get_level_values('one') == 'c') m2 = (df.index.get_level_values('two') == 'u') df[m1 & m2]

Pregunta 4

¿Cómo selecciono las dos filas correspondientes a ('c', 'u') y ('a', 'w') ?

 col one two cu 9 aw 3

Con loc , esto sigue siendo tan simple como:

 df.loc[[('c', 'u'), ('a', 'w')]] # df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]

Con query , deberá generar dinámicamente una cadena de consulta iterando sobre sus secciones transversales y niveles:

 cses = [('c', 'u'), ('a', 'w')] levels = ['one', 'two'] # This is a useful check to make in advance. assert all(len(levels) == len(cs) for cs in cses) query = '(' + ') or ('.join([ ' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)]) for cs in cses ]) + ')' print(query) # ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w')) df.query(query)

100% NO RECOMENDADO! Pero es posible.

¿Qué pasa si tengo varios niveles?
Una opción en este escenario sería usar droplevel para eliminar los niveles que no está verificando, luego usar isin para probar la membresía y luego el índice booleano en el resultado final.

 df[df.index.droplevel(unused_level).isin([('c', 'u'), ('a', 'w')])]

Pregunta 5

¿Cómo puedo recuperar todas las filas correspondientes a "a" en el nivel "uno" o "t" en el nivel "dos"?

 col one two at 0 u 1 v 2 w 3 bt 4 t 8 dt 12

En realidad, esto es muy difícil de hacer con loc mientras se asegura la corrección y se mantiene la claridad del código. df.loc[pd.IndexSlice['a', 't']] es incorrecto, se interpreta como df.loc[pd.IndexSlice[('a', 't')]] (es decir, seleccionar una sección transversal ). Puede pensar en una solución con pd.concat para manejar cada etiqueta por separado:

 pd.concat([ df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:] ]) col one two at 0 u 1 v 2 w 3 t 0 # Does this look right to you? No, it isn't! bt 4 t 8 dt 12

Pero notará que una de las filas está duplicada. Esto se debe a que esa fila satisfizo ambas condiciones de corte y, por lo tanto, apareció dos veces. En su lugar, tendrá que hacer

 v = pd.concat([ df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:] ]) v[~v.index.duplicated()]

Pero si su DataFrame contiene inherentemente índices duplicados (que desea), esto no los retendrá. Utilizar con extrema precaución .

Con query , esto es estúpidamente simple:

 df.query("one == 'a' or two == 't'")

Con get_level_values , esto sigue siendo simple, pero no tan elegante:

 m1 = (df.index.get_level_values('one') == 'a') m2 = (df.index.get_level_values('two') == 't') df[m1 | m2]

Pregunta 6

¿Cómo puedo cortar secciones transversales específicas? Para "a" y "b", me gustaría seleccionar todas las filas con subniveles "u" y "v", y para "d", me gustaría seleccionar filas con subnivel "w".

 col one two au 1 v 2 bu 5 v 6 dw 11 w 15

Este es un caso especial que agregué para ayudar a comprender la aplicabilidad de los Cuatro modismos: este es un caso en el que ninguno de ellos funcionará de manera efectiva, ya que el corte es muy específico y no sigue ningún patrón real.

Por lo general, los problemas de rebanado como este requerirán pasar explícitamente una lista de claves a loc . Una forma de hacerlo es con:

 keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')] df.loc[keys, :]

Si desea ahorrar algo de escritura, reconocerá que existe un patrón para dividir "a", "b" y sus subniveles, por lo que podemos separar la tarea de dividir en dos partes y concat el resultado:

 pd.concat([ df.loc[(('a', 'b'), ('u', 'v')), :], df.loc[('d', 'w'), :] ], axis=0)

La especificación de corte para "a" y "b" es un poco más limpia (('a', 'b'), ('u', 'v')) porque los mismos subniveles que se indexan son los mismos para cada nivel.


Pregunta 7

¿Cómo obtengo todas las filas donde los valores en el nivel "dos" son mayores que 5?

 col one two b 7 4 9 5 c 7 10 d 6 11 8 12 8 13 6 15

Esto se puede hacer usando query ,

 df2.query("two > 5")

Y get_level_values .

 df2[df2.index.get_level_values('two') > 5]

Nota
Similar a este ejemplo, podemos filtrar en función de cualquier condición arbitraria utilizando estas construcciones. En general, es útil recordar que loc y xs son específicamente para la indexación basada en etiquetas, mientras que query y get_level_values son útiles para crear máscaras condicionales generales para el filtrado.


Pregunta extra

¿Qué sucede si necesito dividir una columna MultiIndex ?

En realidad, la mayoría de las soluciones aquí también son aplicables a las columnas, con cambios menores. Considerar:

 np.random.seed(0) mux3 = pd.MultiIndex.from_product([ list('ABCD'), list('efgh') ], names=['one','two']) df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3) print(df3) one ABCD two efghefghefghefgh 0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6 1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3 2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7

Estos son los siguientes cambios que deberá realizar en los cuatro modismos para que funcionen con columnas.

  1. Para cortar con loc , use

     df3.loc[:, ....] # Notice how we slice across the index with `:`.

    o,

     df3.loc[:, pd.IndexSlice[...]]
  2. Para usar xs según corresponda, simplemente pase un argumento axis=1 .

  3. Puede acceder a los valores de nivel de columna directamente mediante df.columns.get_level_values . A continuación, tendrá que hacer algo como

     df.loc[:, {condition}]

    Donde {condition} representa alguna condición creada usando columns.get_level_values .

  4. Para usar query , su única opción es transponer, consultar en el índice y transponer nuevamente:

     df3.T.query(...).T

    No recomendado, use una de las otras 3 opciones.

over 4 years ago · Santiago Trujillo Report

0

Recientemente me encontré con un caso de uso en el que tenía un marco de datos de índice múltiple de más de 3 niveles en el que no podía hacer que ninguna de las soluciones anteriores produjera los resultados que estaba buscando. Es muy posible que las soluciones anteriores funcionen para mi caso de uso, y probé varias, sin embargo, no pude hacer que funcionaran con el tiempo que tenía disponible.

Estoy lejos de ser un experto, pero encontré una solución que no figuraba en las respuestas completas anteriores. No ofrezco ninguna garantía de que las soluciones sean óptimas.

Esta es una forma diferente de obtener un resultado ligeramente diferente a la Pregunta #6 anterior. (y probablemente otras preguntas también)

Específicamente estaba buscando:

  1. Una forma de elegir más de dos valores de un nivel del índice y un solo valor de otro nivel del índice, y
  2. Una forma de dejar los valores de índice de la operación anterior en la salida del marco de datos.

Como una llave inglesa en los engranajes (sin embargo, totalmente reparable):

  1. Los índices no tenían nombre.

En el marco de datos del juguete a continuación:

 index = pd.MultiIndex.from_product([['a','b'], ['stock1','stock2','stock3'], ['price','volume','velocity']]) df = pd.DataFrame([1,2,3,4,5,6,7,8,9, 10,11,12,13,14,15,16,17,18], index) 0 a stock1 price 1 volume 2 velocity 3 stock2 price 4 volume 5 velocity 6 stock3 price 7 volume 8 velocity 9 b stock1 price 10 volume 11 velocity 12 stock2 price 13 volume 14 velocity 15 stock3 price 16 volume 17 velocity 18

Usando los siguientes trabajos, por supuesto:

 df.xs(('stock1', 'velocity'), level=(1,2)) 0 a 3 b 12

Pero quería un resultado diferente, así que mi método para obtener ese resultado fue:

 df.iloc[df.index.isin(['stock1'], level=1) & df.index.isin(['velocity'], level=2)] 0 a stock1 velocity 3 b stock1 velocity 12

Y si quisiera más de dos valores de un nivel y un solo valor (o más de 2) de otro nivel:

 df.iloc[df.index.isin(['stock1','stock3'], level=1) & df.index.isin(['velocity'], level=2)] 0 a stock1 velocity 3 stock3 velocity 9 b stock1 velocity 12 stock3 velocity 18

El método anterior es probablemente un poco torpe, sin embargo, descubrí que satisfacía mis necesidades y, como beneficio adicional, era más fácil de entender y leer para mí.

over 4 years ago · Santiago Trujillo Report

0

Esto parece un gran caso para dfsql

 df.sql(<SQL select statement>)

https://github.com/mindsdb/dfsql

Un artículo completo al respecto aquí:

https://medium.com/riselab/why-every-data-scientist-using-pandas-needs-modin-bringing-sql-to-dataframes-3b216b29a7c0

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!