La idea
Un filtro de pandas no es un bucle ni una búsqueda. Son dos pasos. Primero, una comparación sobre una columna produce una máscara: una columna de True y False, un valor por fila. Luego los corchetes conservan las filas donde la máscara dice True.
Figura 1
orders[orders['channel'] == 'marketplace'], paso a paso
| index | order_id | channel | shipping_fee |
|---|---|---|---|
| 2 | 1003 | marketplace | 4.99 |
| 4 | 1005 | web | 0.00 |
| 5 | 1006 | marketplace | 4.99 |
| 6 | 1007 | marketplace | 7.65 |
| 8 | 1009 | app | 0.00 |
| 9 | 1010 | web | 6.95 |
| index | is_match |
|---|---|
| 2 | true |
| 4 | false |
| 5 | true |
| 6 | true |
| 8 | false |
| 9 | false |
| index | order_id | channel | shipping_fee |
|---|---|---|---|
| 2 | 1003 | marketplace | 4.99 |
| 5 | 1006 | marketplace | 4.99 |
| 6 | 1007 | marketplace | 7.65 |
- Seis pedidos de
orders. Solo queremos los de marketplace. orders['channel'] == 'marketplace'es una comparación sobre una columna entera. No elige filas. Devuelve una columna nueva con un True o False por fila: la máscara.orders[mask]conserva las filas donde la máscara es True y descarta el resto. Sobreviven tres filas.- Mira el
indexdel resultado: 2, 5, 6. Las filas conservan las etiquetas que tenían enorders, así que el resultado no empieza en 0.
Ese es todo el mecanismo. orders[orders['channel'] == 'marketplace'] se lee raro la primera vez porque orders aparece dos veces. El de dentro construye la máscara, el de fuera la aplica. Cuando ves la máscara como una cosa en sí misma, puedes ponerle nombre, imprimirla, contarla y combinarla con otras.
Por qué existe
En Python a secas escribirías un bucle con un if y un append. Funciona, y son cinco líneas para una pregunta de una línea. La máscara hace toda la tabla de una vez, como las operaciones vectorizadas de los Fundamentos: la comparación se ejecuta para cada fila en código compilado, y el resultado encaja con la tabla porque lleva el mismo índice.
También convierte el filtrado en algo que se puede componer. Una condición es un valor. Puedes guardarla en una variable, combinar dos con &, invertir una con ~ o promediarla. orders['shipping_fee'] > 0 tiene una media, y esa media es la proporción de pedidos que pagaron envío, porque True cuenta como 1 y False como 0. Ese truco responde todas las preguntas de "qué porcentaje de..." del desafío final.
Cómo funciona por dentro
Comparación y luego selección
==, !=, >, >=, < y <= comparan una columna con un solo valor o con otra columna de la misma longitud, y devuelven una máscara. isin(['web', 'app']) es la máscara para "es uno de estos", between(10, 50) para "dentro de un rango, extremos incluidos", y str.contains('lamp') para texto. Los valores que faltan comparan como False, así que NaN > 5 es False y la fila queda fuera. Una comparación nunca cambia la tabla: solo los corchetes crean una nueva.
Combinar máscaras
and, or y not de Python funcionan con valores True/False sueltos, no con columnas. Para máscaras usa &, | y ~, y pon cada condición entre paréntesis.
Figura 2
Dos máscaras, combinadas con & y |
| order_id | is_marketplace | fee_over_5 | both | either |
|---|---|---|---|---|
| 1003 | true | false | false | true |
| 1005 | false | false | false | false |
| 1006 | true | false | false | true |
| 1007 | true | true | true | true |
| 1009 | false | false | false | false |
| 1010 | false | true | false | true |
- Dos preguntas sobre los mismos seis pedidos: ¿es de marketplace? y ¿el envío cuesta más de $5? Cada una es su propia columna de True y False.
a & bes True solo donde ambas son True. Solo el pedido 1007 es de marketplace y tiene un envío mayor a $5.a | bes True donde al menos una es True. Eso conserva 1003, 1006, 1007 y 1010. Las mismas dos máscaras dan resultados distintos según el operador.
& es "y": todas las condiciones deben cumplirse, así que cada una estrecha el resultado. | es "o": basta con una, así que cada una lo ensancha. Un "o" que querías como "y" es el error clásico: (status == 'cancelled') | (status == 'refunded') son los pedidos perdidos, mientras que & entre ellas no devuelve nada, porque ningún pedido tiene dos estados a la vez. Para "uno de estos valores", isin es más claro que una cadena de |.
Figura 3
Por qué cada condición necesita sus propios paréntesis
-
(orders['channel'] == 'web')una máscara -
&combinar -
(orders['shipping_fee'] > 5)una máscara -
orders[ ... ]conserva las filas True
- En Python,
&tiene más prioridad que==y>. Sin paréntesis,orders['channel'] == 'web' & orders['shipping_fee'] > 5intenta hacer primero'web' & orders['shipping_fee'], y falla. - Los paréntesis obligan a que cada comparación se ejecute primero, y cada una produce una máscara.
- Ahora
&combina dos máscaras de la misma longitud, fila a fila. - Los corchetes conservan las filas donde la máscara combinada es True. Una sola regla, sin importar cuántas condiciones haya.
Etiquetas frente a posiciones
Cada fila tiene una etiqueta (el índice) y una posición (0, 1, 2 desde arriba). .loc selecciona por etiqueta e .iloc por posición. En una tabla recién cargada son los mismos números, y por eso la gente los confunde. Tras un filtro o una ordenación se separan: la primera fila del resultado tiene posición 0 y puede llevar la etiqueta 2. .loc también acepta una máscara y nombres de columna a la vez, orders.loc[mask, ['order_id', 'channel']], y a diferencia de iloc, sus slices incluyen la etiqueta final. Cuando quieras etiquetas nuevas 0, 1, 2, usa reset_index(drop=True).
Ordenar
sort_values('shipping_fee', ascending=False) reordena las filas por una columna, o por varias: sort_values(['channel', 'shipping_fee'], ascending=[True, False]). Las columnas posteriores desempatan las anteriores, y eso hace que un "top 10" sea igual en cada ejecución. Las etiquetas viajan con las filas, así que una tabla ordenada tiene un índice en desorden hasta que lo reinicias. head(10) tras la ordenación toma los diez primeros, y nlargest es un atajo para eso.
Constrúyelo por piezas y comprueba la cuenta
Un filtro largo es más fácil de creer cuando lo armas con máscaras con nombre: is_web = orders['channel'] == 'web', paid_ship = orders['shipping_fee'] > 0, y luego orders[is_web & paid_ship]. Cada nombre dice qué significa, y puedes probar una a una. Dos comprobaciones rápidas atrapan la mayoría de los errores. mask.sum() cuenta las filas que coinciden, porque cada True vale 1, y len(orders[mask]) debería coincidir. Si un filtro devuelve cero filas, mira las piezas por separado: una suele estar escrita contra un valor que no existe, como 'Web' en vez de 'web'.
Primero filtra, luego pregunta
Filtrar rara vez es la respuesta. Es el paso previo a una. "Cesta media de los pedidos web completados" es un filtro seguido de una media. "Qué pedidos de marketplace pagaron el envío más alto" es un filtro seguido de una ordenación y head. Mantenerlos separados, máscara primero y pregunta después, te deja revisar la tabla filtrada a ojo antes de calcular el número que reportas. Si la tabla filtrada se ve mal, el número saldrá mal por muy limpia que esté la última línea.
Errores comunes
"Filtrar cambia el DataFrame"
No lo cambia. orders[mask] devuelve un DataFrame nuevo y deja orders como estaba. Si quieres conservar la tabla filtrada, asígnala: marketplace = orders[mask]. Esto también explica por qué un filtro que ejecutaste hace una hora "no hizo nada" a tu original. Miraste un resultado y nunca lo guardaste.
"Puedo escribir una condición con and / or"
orders['a'] > 1 and orders['b'] < 5 lanza "the truth value of a Series is ambiguous", porque Python pregunta "¿esta columna entera es True?" y una columna tiene muchos valores. Usa & y | con paréntesis. El texto del error suena profundo y la causa es solo el operador.
"La primera fila de un resultado es la fila 0"
Solo en una tabla recién cargada. Después de un filtro es la primera fila que sobrevivió, con su etiqueta original, así que result.loc[0] puede fallar con un KeyError mientras result.iloc[0] funciona. Si solo necesitas las filas y no las etiquetas viejas, llama a reset_index(drop=True) y los dos vuelven a coincidir.
Ponte a prueba
Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.
No del todo. Aquí ambos cuentan desde 0, pero loc toma la etiqueta de una fila e iloc toma su posición.Prueba otra respuesta.
Correcto. iloc es por posición en lo que tengas ahora, loc es por etiqueta. Un filtro conserva las etiquetas originales, así que el primer pedido de marketplace mantiene su etiqueta 2 y no hay ninguna fila con etiqueta 0.
No del todo. Ambos funcionan en cualquier DataFrame. La diferencia es el número que les das: una etiqueta o una posición.Prueba otra respuesta.
No del todo. Filtrar no reinicia el índice. Para eso hay que llamar a reset_index(drop=True).Prueba otra respuesta.
Lección hecha: ya está marcada en el mapa.