Nota
Esta publicación se estructurará de la siguiente manera:
- Las preguntas planteadas en el OP serán abordadas, una por una.
- Para cada pregunta, se demostrarán uno o más métodos aplicables para resolver este problema y obtener el resultado esperado.
Las notas s (como esta) se incluirán 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 revisando los documentos y descubriendo varias características oscuras, y desde 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, 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 adelante, los Cuatro Modismos) que volveremos a visitar con frecuencia.
DataFrame.loc
- Una solución general para la selección por etiqueta (+ pd.IndexSlice
para aplicaciones más complejas que involucran cortes)
DataFrame.xs
- Extraiga una sección transversal particular de una Serie / Marco de datos.
DataFrame.query
- Especifique las operaciones de segmentación y / o filtrado dinámicamente (es decir, como una expresión que se evalúa dinámicamente. Es más aplicable a algunos escenarios que a otros. Consulte también esta sección de los documentos para consultar en MultiIndexes.
Indizació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 observar 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 dada. Es muy importante comprender que no todas las expresiones idiomáticas funcionarán igual de bien (si es que lo hacen) en todas las circunstancias. Si un idioma no se ha enumerado como una posible solución a un problema a continuación, eso significa que el idioma no puede aplicarse a ese problema de manera efectiva.
Pregunta 1
¿Cómo selecciono las filas que tienen "a" en el nivel "uno"?
col
one two
a t 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 consigues
TypeError: Expected tuple, got str
Eso significa que está utilizando una versión anterior de pandas. ¡Considere 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 argumentos levels
y axis
(aquí se pueden suponer valores predeterminados razonables).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Aquí, el drop_level=False
argumento es necesario para evitar que xs
caiga el nivel "uno" en el resultado (el nivel en el que dividimos).
Otra opción más aquí es usar query
:
df.query("one == 'a'")
Si el índice no tiene un nombre, deberá cambiar la 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 dejar caer el nivel "uno" en la salida?
col
two
t 0
u 1
v 2
w 3
Esto se puede hacer fácilmente usando
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 drop_level
argumento (se supone que es True
por defecto).
Nota
Puede observar que un DataFrame filtrado 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
a t 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'])
Puede deshacerse 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
a t 0
b t 4
t 8
d t 12
Intuitivamente, querrás algo que involucre slice()
:
df.loc[(slice(None), 't'), :]
¡Simplemente funciona! ™ Pero es torpe. Podemos facilitar una sintaxis de corte más natural usando la pd.IndexSlice
API aquí.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
Esto es mucho, mucho más limpio.
Nota
¿Por qué se :
requiere el corte final a través de las columnas? Esto se debe a que loc
puede usarse para seleccionar y cortar a lo largo de ambos ejes ( axis=0
o
axis=1
). Sin dejar explícitamente claro en qué eje se va a realizar el corte, la operación se vuelve ambigua. Vea el gran cuadro rojo en la documentación sobre el corte .
Si desea eliminar cualquier sombra de ambigüedad, loc
acepta un axis
parámetro:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Sin el axis
parámetro (es decir, simplemente haciendo df.loc[pd.IndexSlice[:, 't']]
), se supone que el corte se encuentra en las columnas, y se KeyError
generará una en esta circunstancia.
Esto está documentado en rebanadoras . Para el propósito de esta publicación, sin embargo, 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
, puedes 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 para el mismo efecto.
Pregunta 2
¿Cómo puedo seleccionar filas correspondientes a los elementos "b" y "d" en el nivel "uno"?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
Usando loc, esto se hace de manera similar al especificar una lista.
df.loc[['b', 'd']]
Para resolver el problema anterior de seleccionar "b" y "d", también puede usar 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 convencionalmente Python. 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 expresión dinámica 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
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Con loc
, esto es posible solo en conjunción con pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
El primer colon :
en pd.IndexSlice[:, ['t', 'w']]
significa cortar en el primer nivel. A medida que aumenta la profundidad del nivel que se está consultando, necesitará especificar más sectores, uno por nivel en cada segmento. Sin embargo, no necesitará especificar más niveles más allá 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
y 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 df
? Específicamente, ¿cómo recupero la sección transversal de ('c', 'u')
, dada por
col
one two
c u 9
Utilice loc
especificando una tupla de claves:
df.loc[('c', 'u'), :]
O,
df.loc[pd.IndexSlice[('c', 'u')]]
Nota:
en este punto, puede encontrarse con un PerformanceWarning
aspecto similar a este:
PerformanceWarning: indexing past lexsort depth may impact performance.
Esto solo significa que su índice no está ordenado. pandas depende del índice que se está ordenando (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 hacer múltiples consultas de este tipo en conjunto:
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
oFalse
consecuencia. Puede llamar a esta función para determinar si se requiere un paso de clasificación adicional o no.
Con xs
, esto nuevamente es simplemente pasar una tupla única 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 múltiples niveles, get_level_values
todavía se puede usar, 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
c u 9
a w 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 y niveles transversales:
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 RECOMIENDA! Pero es posible.
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
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Esto es realmente muy difícil de hacer loc
mientras se garantiza 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 pd.concat
para manejar cada etiqueta por separado:
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
Pero notará que una de las filas está duplicada. Esto se debe a que esa fila satisfizo ambas condiciones de corte, por lo que apareció dos veces. En cambio, deberás 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), entonces esto no los retendrá. Usar con extrema precaución .
Con query
, esto es estúpidamente simple:
df.query("one == 'a' or two == 't'")
Con get_level_values
esto, 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 subniveles "w".
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Este es un caso especial que he agregado 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, cortar problemas como este requerirá 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 guardar algo de escritura, reconocerá que hay un patrón para cortar "a", "b" y sus subniveles, por lo que podemos separar la tarea de división 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 la construcción de máscaras condicionales generales para el filtrado.
Pregunta extra
¿Qué pasa si necesito cortar una MultiIndex
columna ?
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 A B C D
two e f g h e f g h e f g h e f g h
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.
Para cortar con loc
, use
df3.loc[:, ....] # Notice how we slice across the index with `:`.
o,
df3.loc[:, pd.IndexSlice[...]]
Para usar xs
según corresponda, simplemente pase un argumento axis=1
.
Puede acceder a los valores de nivel de columna directamente usando df.columns.get_level_values
. Luego deberá hacer algo como
df.loc[:, {condition}]
Donde {condition}
representa alguna condición construida usando columns.get_level_values
.
Para usar query
, su única opción es transponer, consultar en el índice y volver a transponer:
df3.T.query(...).T
No recomendado, use una de las otras 3 opciones.
level
argumento paraIndex.isin
!