: cuando comencé a usar Pandas, escribía bucles como este todo el tiempo:
para i en rango (len (df)): si df.loc[i, “sales”] > 1000: df.loc[i, “tier”] = “alto” más: df.loc[i, “tier”] = “bajo”
Funcionó. Y pensé: “Oye, está bien, ¿verdad?”
Resulta… no tanto.
No me di cuenta en ese momento, pero bucles como este son una trampa clásica para principiantes. Hacen que Pandas haga mucho más trabajo del necesario e introducen un modelo mental que te mantiene pensando fila por fila en lugar de columna por columna.
Una vez que comencé a pensar en columnas, las cosas cambiaron. El código se hizo más corto. La ejecución se hizo más rápida. Y de repente, Pandas sintió que en realidad estaba diseñado para ayudarme, no para frenarme.
Para mostrar esto, usemos un pequeño conjunto de datos al que haremos referencia en todo momento:
importar pandas como pd df = pd.DataFrame({ “producto”: [“A”, “B”, “C”, “D”, “E”]”ventas”: [500, 1200, 800, 2000, 300]
})
Producción:
ventas de productos 0 A 500 1 B 1200 2 C 800 3 D 2000 4 E 300
Nuestro objetivo es simple: etiquetar cada fila como alta si las ventas son superiores a 1000; de lo contrario, como baja.
Déjame mostrarte cómo lo hice al principio y por qué hay una manera mejor.
El enfoque de bucle con el que comencé
Aquí está el bucle que utilicé cuando estaba aprendiendo:
para i en rango (len (df)): si df.loc[i, “sales”] > 1000: df.loc[i, “tier”] = “alto” más: df.loc[i, “tier”] = impresión “baja” (df)
Produce este resultado:
Nivel de ventas de productos 0 A 500 bajo 1 B 1200 alto 2 C 800 bajo 3 D 2000 alto 4 E 300 bajo
Y sí, funciona. Pero esto es lo que aprendí de la manera más difícil:
Pandas realiza una pequeña operación para cada fila, en lugar de manejar eficientemente toda la columna a la vez.
Este enfoque no escala: lo que se siente bien con 5 filas se ralentiza con 50.000 filas.
Más importante aún, te mantiene pensando como un principiante, fila por fila, en lugar de como un usuario profesional de Pandas.
Sincronización del ciclo (el momento en que me di cuenta de que era lento)
Cuando ejecuté mi bucle por primera vez en este pequeño conjunto de datos, pensé: “No hay problema, es lo suficientemente rápido”. Pero luego me pregunté… ¿y si tuviera un conjunto de datos más grande?
Entonces lo probé:
importar pandas como tiempo de importación pd # Crear un conjunto de datos más grande df_big = pd.DataFrame({ “producto”: [“A”, “B”, “C”, “D”, “E”] * 100_000, “ventas”: [500, 1200, 800, 2000, 300] * 100_000 }) # Tiempo de inicio del ciclo = time.time() para i en el rango(len(df_big)): if df_big.loc[i, “sales”] > 1000: df_big.loc[i, “tier”] = “alto” más: df_big.loc[i, “tier”] = “bajo” fin = time.time() print(“Tiempo de bucle:”, fin – inicio)
Esto es lo que obtuve:
Tiempo de bucle: 129.27328729629517
Eso son 129 segundos.
Más de dos minutos solo para etiquetar filas como “altas” o “bajas”.
Ese fue el momento en que hice clic para mí. El código no era sólo “un poco ineficiente”. Básicamente se trataba de utilizar Pandas de forma incorrecta.
E imagine esto ejecutándose dentro de una canalización de datos, en una actualización del panel, en millones de filas todos los días.
¿Por qué es tan lento?
El bucle obliga a Pandas a:
Acceder a cada fila individualmente Ejecutar lógica a nivel de Python para cada iteración Actualizar el DataFrame una celda a la vez
En otras palabras, convierte un motor de columnas altamente optimizado en un procesador de listas Python glorificado.
Y Pandas no está diseñado para eso.
La solución de una sola línea (y el momento en que hizo clic)
Después de ver 129 segundos, supe que tenía que haber una manera mejor.
Entonces, en lugar de recorrer las filas, intenté expresar la regla a nivel de columna:
“Si las ventas > 1000, etiquételo como alto. De lo contrario, etiquételo como bajo”.
Eso es todo. Esa es la regla.
Aquí está la versión vectorizada:
importar numpy como np importar hora inicio = time.time() df_big[“tier”] = np.donde(df_big[“sales”] > 1000, “alto”, “bajo”) fin = tiempo.tiempo() print(“Tiempo vectorizado:”, fin – inicio)
¿Y el resultado?
Tiempo vectorizado: 0,08
Deja que eso se asimile.
Versión en bucle: 129 segundos
Versión vectorizada: 0,08 segundos
Eso es más de 1.600 veces más rápido.
¿Qué acaba de pasar?
La diferencia clave es esta:
El bucle procesó el DataFrame fila por fila. La versión vectorizada procesó toda la columna de ventas en una operación optimizada.
Cuando escribes:
df_grande[“sales”] > 1000
Pandas no verifica los valores uno por uno en Python. Realiza la comparación en un nivel inferior (a través de NumPy), en código compilado, en toda la matriz.
Luego np.where() aplica las etiquetas en una sola pasada eficiente.
Aquí está el cambio sutil pero poderoso:
En lugar de preguntar:
“¿Qué debo hacer con esta fila?”
Preguntas:
“¿Qué regla se aplica a esta columna?”
Esa es la línea entre los Pandas principiantes y los Pandas profesionales.
En este punto, pensé que había “subido de nivel”. Luego descubrí que podía hacerlo aún más sencillo.
Y luego descubrí la indexación booleana
Después de cronometrar la versión vectorizada, me sentí muy orgulloso. Pero luego me di cuenta de otra cosa.
Ni siquiera necesito np.where() para esto.
Volvamos a nuestro pequeño conjunto de datos:
df = pd.DataFrame({ “producto”: [“A”, “B”, “C”, “D”, “E”]”ventas”: [500, 1200, 800, 2000, 300]
})
Nuestro objetivo sigue siendo el mismo:
Etiquete cada fila como alta si las ventas son > 1000; de lo contrario, como baja.
Con np.where() escribimos:
df[“tier”] = np.donde(df[“sales”] > 1000, “alto”, “bajo”)
Es más limpio y más rápido. Mucho mejor que un bucle.
Pero aquí está la parte que realmente cambió mi forma de pensar sobre Pandas:
Esta línea de aquí…
df[“sales”] > 1000
…ya devuelve algo increíblemente útil.
Veámoslo:
Producción:
0 Falso 1 Verdadero 2 Falso 3 Verdadero 4 Falso Nombre: ventas, tipo d: bool
Esa es una serie booleana.
Pandas simplemente evaluó la condición de toda la columna a la vez.
Sin bucle. No si. Sin lógica fila por fila.
Produjo una máscara completa de valores Verdadero/Falso de una sola vez.
La indexación booleana parece una superpotencia
Ahora aquí es donde se pone interesante.
Puedes usar esa máscara booleana directamente para filtrar filas:
df[df[“sales”] > 1000]
Y Pandas te ofrece instantáneamente:
Incluso podemos construir la columna de nivel usando indexación booleana directamente:
df[“tier”] = “bajo” df.loc[df[“sales”] > 1000, “nivel”]= “alto”
Básicamente estoy diciendo:
Supongamos que todo es “bajo”. Anule solo las filas donde las ventas > 1000.
Eso es todo.
Y de repente, no estoy pensando:
“Para cada fila, verifique el valor…”
Estoy pensando:
“Comience con un valor predeterminado. Luego aplique una regla a un subconjunto”.
Ese cambio es sutil, pero lo cambia todo.
Una vez que me sentí cómodo con las máscaras booleanas, comencé a preguntarme:
¿Qué sucede cuando la lógica no es tan clara como “mayor que 1000”? ¿Qué pasa si necesito reglas personalizadas?
Ahí es donde descubrí aplicar(). Y al principio parecía lo mejor de ambos mundos.
¿No es apply() lo suficientemente bueno?
Seré honesto. Después de que dejé de escribir bucles, pensé que lo tenía todo resuelto. Porque existía esta función mágica que parecía solucionarlo todo:
aplicar().
Se sentía como el término medio perfecto entre bucles desordenados y una vectorización aterradora.
Entonces, naturalmente, comencé a escribir cosas como esta:
df[“tier”] = gl[“sales”].apply( lambda x: “alto” si x > 1000 en caso contrario “bajo”)
¿Y a primera vista?
Esto luce genial.
Sin bucle for Sin indexación manual Fácil de leer
Se siente como una solución profesional.
Pero esto es lo que no entendí en ese momento:
apply() todavía ejecuta código Python para cada fila.
Simplemente oculta el bucle.
Cuando usas:
df[“sales”].aplicar(lambda x: …)
Pandas sigue siendo:
Tomar cada valor Pasarlo a una función de Python Devolver el resultado Repetir eso para cada fila
Es más limpio que un bucle for, sí. ¿Pero en cuanto a rendimiento? Está mucho más cerca de un bucle que de una verdadera vectorización.
Eso fue una especie de llamada de atención para mí. Me di cuenta de que estaba reemplazando los bucles visibles por invisibles.
Entonces, ¿cuándo debería utilizar apply()?
Si la lógica se puede expresar con operaciones vectorizadas → hazlo. Si se puede expresar con máscaras booleanas → hazlo. Si es absolutamente necesario una lógica Python personalizada → entonces use apply().
En otras palabras:
Vectorizar primero. Busque aplicar () solo cuando sea necesario.
No porque apply() sea malo. Pero debido a que Pandas es más rápido y limpio cuando piensas en columnas, no en funciones por filas.
Conclusión
Mirando hacia atrás, el mayor error que cometí no fue escribir bucles. Se suponía que si el código funcionaba, era lo suficientemente bueno.
Pandas no te castiga inmediatamente por pensar en filas. Pero a medida que sus conjuntos de datos crecen, sus canalizaciones escalan y su código termina en paneles y flujos de trabajo de producción, la diferencia se vuelve obvia.
El pensamiento fila por fila no escala. Los bucles ocultos de Python no escalan. Las reglas a nivel de columna sí lo hacen.
Esa es la verdadera línea entre el uso de Pandas para principiantes y profesionales.
Entonces, en resumen:
Deja de preguntar qué hacer con cada fila. Empiece a preguntar qué regla se aplica a toda la columna.
Una vez que realice ese cambio, su código se volverá más rápido, más limpio, más fácil de revisar y de mantener. Y empiezas a detectar patrones ineficientes al instante, incluido el tuyo.