: Cuando comencé a usar Pandas, escribía bucles como este todo el tiempo:

for i in range(len(df)):
if df.loc[i, "sales"] > 1000:
df.loc[i, "tier"] = "high"
else:
df.loc[i, "tier"] = "low"

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 trabaje mucho más de lo necesario e introducen un modelo mental que te mantiene pensando fila por fila en lugar de columna por columna.

Tan pronto como comencé a pensar columnaslas cosas han cambiado. El código se hizo más corto. La ejecución se ha vuelto más rápida. Y de repente, Pandas sintió que estaba construido para ayúdameno me hagas llegar tarde.

Para mostrar esto, usaremos un pequeño conjunto de datos a los que haremos referencia a lo largo del texto:

import pandas as pd
df = pd.DataFrame({
"product": ["A", "B", "C", "D", "E"],
"sales": [500, 1200, 800, 2000, 300]
})

Salida:

product sales
0 A 500
1 B 1200
2 C 800
3 D 2000
4 E 300

Nuestro objetivo es simple: etiquetar cada línea como high si las ventas son más de 1000 en caso contrario low.

Déjame mostrarte cómo lo hice inicialmentey por qué hay una mejor manera.

El enfoque Loop con el que comencé

Aquí está el bucle que utilicé cuando estaba aprendiendo:

for i in range(len(df)):
if df.loc[i, "sales"] > 1000:
df.loc[i, "tier"] = "high"
else:
df.loc[i, "tier"] = "low"
print(df)

Produce este resultado:

product sales tier
0 A 500 low
1 B 1200 high
2 C 800 low
3 D 2000 high
4 E 300 low

Y sí, funciona. Pero esto es lo que aprendí de la manera más difícil:
Los pandas están haciendo una pequeña operación para cada líneaen lugar de manipular eficientemente toda la columna a la vez.

Este enfoque no es escalable: lo que se ve bien con 5 líneas se ralentiza con 50.000 líneas.

Lo más importante es que te hace pensar como un principiante, línea por línea, en lugar de como un usuario profesional de Pandas.

Timing the Loop (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 intenté:

import pandas as pd
import time
# Make a bigger dataset
df_big = pd.DataFrame({
"product": ["A", "B", "C", "D", "E"] * 100_000,
"sales": [500, 1200, 800, 2000, 300] * 100_000
})

# Time the loop
start = time.time()
for i in range(len(df_big)):
if df_big.loc[i, "sales"] > 1000:
df_big.loc[i, "tier"] = "high"
else:
df_big.loc[i, "tier"] = "low"
end = time.time()
print("Loop time:", end - start)

Esto es lo que obtuve:

Loop time: 129.27328729629517

Eso es todo 129 segundos.

En dos minutos sólo para etiquetar las líneas como "high" o "low".

Ese fue el momento en que hice clic para mí. El código no era sólo “un poco ineficiente”. Básicamente estaba usando Pandas de manera 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:

  • Accede a cada línea individualmente
  • Ejecute lógica a nivel de Python para cada iteración
  • Actualice 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 no es para eso que se creó Pandas.

La corrección de una línea (y el momento en que hizo clic)

despues de ver 129 segundosSabía que tenía que haber una manera mejor.
Entonces, en lugar de seguir las líneas, traté de expresar la regla en nivel de columna:

“Si las ventas > 1000, etiquételo como alto. De lo contrario, etiquételo como bajo”.

Y eso. Esa es la regla.

Aquí está la versión vectorizada:

import numpy as np
import time

start = time.time()
df_big["tier"] = np.where(df_big["sales"] > 1000, "high", "low")
end = time.time()
print("Vectorized time:", end - start)

¿Y el resultado?

Vectorized time: 0.08

Deja que eso se asimile.

Versión en bucle: 129 segundos
Versión vectorizada: 0,08 segundos

el termino 1.600 veces más rápido.

¿Qué acaba de pasar?

La principal diferencia es esta:

El bucle procesó el DataFrame. línea por línea. La versión vectorizada procesó todo sales columna en una operación optimizada.

Cuando escribes:

df_big["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.

Entonces 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 línea?”

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 hacer todo 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 tuve otra conclusión.

ni siquiera necesito np.where() para este.

Volvamos a nuestro pequeño conjunto de datos:

df = pd.DataFrame({
"product": ["A", "B", "C", "D", "E"],
"sales": [500, 1200, 800, 2000, 300]
})

Nuestro objetivo sigue siendo el mismo:

Etiqueta cada línea high si ventas > 1000, en caso contrario low.

Con np.where() escribimos:

df["tier"] = np.where(df["sales"] > 1000, "high", "low")

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 aquí…

df["sales"] > 1000

…ya devuelve algo increíblemente útil.

Vamos a ver:

Salida:

0 False
1 True
2 False
3 True
4 False
Name: sales, dtype: bool

Esta es una serie booleana.

Pandas simplemente evaluó la condición de toda la columna a la vez.

Sin bucles. No if. Sin lógica línea por línea.

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 esta máscara booleana directamente para filtrar filas:

df[df["sales"] > 1000]

Y Pandas ofrece instantáneamente:

Incluso podemos construir el tier columna usando indexación booleana directamente:

df["tier"] = "low"
df.loc[df["sales"] > 1000, "tier"] = "high"

Básicamente estoy diciendo:

  • Supongamos que todo es "low".
  • Reemplace solo aquellas filas donde las ventas sean > 1000.

Y eso.

Y de repente, no estoy pensando:

“Para cada línea, verifique el valor…”

Estoy pensando:

“Comience con un patrón. Luego aplique una regla a un subconjunto”.

Este 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?

Fue entonces cuando descubrí apply(). Y al principio parecía lo mejor de ambos mundos.

No lo es apply() ¿Lo suficientemente bueno?

Seré honesto. Una vez 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:
apply().

Parecía el compromiso perfecto entre bucles confusos y una vectorización aterradora.

Entonces, naturalmente, comencé a escribir cosas como esta:

df["tier"] = df["sales"].apply(
lambda x: "high" if x > 1000 else "low"
)

¿Y a primera vista?

Esto luce genial.

  • No for enlace
  • Sin indexación manual
  • fácil de leer

Este sentimientos como solución profesional.

Pero esto es lo que no entendí en ese momento:

apply() todavía está ejecutando el código Python para cada línea.
Simplemente oculta el bucle.

Cuando usas:

df["sales"].apply(lambda x: ...)

Pandas sigue siendo:

  • tomando cada valor
  • Pasando a una función de Python
  • Devolviendo el resultado
  • Repitiendo esto para cada línea.

Es más limpio que un for corbata, sí. ¿Pero en términos de rendimiento? Está mucho más cerca de un bucle que la verdadera vectorización.

Esto 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ías usar apply()?

  • Si la lógica se puede expresar con operaciones vectorizadas → hazlo.
  • Si se puede expresar con máscaras booleanas → hazlo.
  • Si la lógica Python personalizada es absolutamente necesaria → entonces use apply().
    En otras palabras:

Vectorizar primero. para alcanzar apply()only cuando lo necesites.
No porque apply() es malo. Pero porque Pandas es más rápido y limpio cuando piensas en columnas, no en funciones de fila.

Conclusión

Mirando hacia atrás, el mayor error que cometí fue no escribir bucles. Se suponía que si el código funcionaba, sería suficientemente bueno.

Los pandas no te castigan inmediatamente por pensar en pelear. Pero a medida que sus conjuntos de datos crecen, sus canalizaciones crecen y su código termina en paneles y flujos de trabajo de producción, la diferencia se vuelve obvia.

  • El pensamiento línea por línea no es escalable.
  • Los bucles ocultos de Python no son escalables.
  • Las reglas a nivel de columna sí lo hacen.

Esta es la verdadera línea entre el uso principiante y profesional de Pandas.

Entonces, en resumen:

Deja de preguntar qué hacer con cada línea. Comience preguntando qué regla se aplica a toda la columna.

Una vez que realice este cambio, su código será más rápido, más limpio, más fácil de revisar y de mantener. Y al instante empiezas a detectar patrones ineficientes, incluido el tuyo.

Fuente