Volver a la lista

Vectorización: la razón por la que debes abandonar los bucles for

Explicamos por qué las operaciones vectorizadas de NumPy/pandas son decenas o cientos de veces más rápidas que los bucles `for`, con código y benchmarks.

Intermedio
|
10min
|
Verificado (2026-07)
vectorizaciónrendimiento de NumPyeliminación de bucles foroperaciones con arrays
Progreso0/17 (0%)

Vectorization: por qué debes abandonar los bucles for

Al finalizar este tema

Podrás explicar estructuralmente por qué la vectorización es más rápida y desarrollarás el hábito de reemplazar los bucles for por operaciones vectoriales de NumPy/pandas.


Intuición — mismo resultado, diferencia de 100 veces

Dos formas de elevar al cuadrado un millón de números:

python
import numpy as np
import time
data = list(range(1_000_000))
arr = np.array(data)
# Método 1: bucle for
start = time.time()
result_loop = [x ** 2 for x in data]
print(f"Bucle for: {time.time() - start:.3f} segundos")
# Método 2: operación vectorial de NumPy
start = time.time()
result_vec = arr ** 2
print(f"Vectorización: {time.time() - start:.3f} segundos")
text
Bucle for: 0,142 segundos
Vectorización: 0,002 segundos    ← unas 70 veces más rápida

Los resultados son los mismos. El código es más corto. La velocidad es decenas de veces mayor.


¿Por qué existe esta diferencia?

La razón por la que el bucle for de Python es lento es porque en cada iteración, el intérprete de Python interviene:

text
Bucle for (Python):
  Iteración 1: comprobar tipo → crear objeto → operar → guardar objeto
  Iteración 2: comprobar tipo → crear objeto → operar → guardar objeto
  ... (un millón de iteraciones)

Operación vectorial (NumPy):
  Una vez: el código C procesa en bloque datos contiguos en memoria → devuelve el array

NumPy ejecuta los bucles internamente mediante código C/Fortran. No tiene la sobrecarga del intérprete de Python y los datos se almacenan de forma contigua en la memoria, lo que permite un uso eficiente de la caché de la CPU.


Patrón 1 — Operaciones aritméticas

python
import numpy as np
prices = np.array([1000, 2500, 3000, 1500, 4000])
tax_rate = 0.1
# ❌ Bucle for
total_loop = []
for p in prices:
total_loop.append(p * (1 + tax_rate))
# ✅ Vectorización
total_vec = prices * (1 + tax_rate)
print(total_vec) # [1100. 2750. 3300. 1650. 4400.]

prices * (1 + tax_rate) — Realiza una operación en todo el arreglo a la vez.


Patrón 2 — Filtrado por condiciones

python
scores = np.array([85, 42, 91, 67, 55, 78, 93, 38])
# ❌ Bucle for
passed = []
for s in scores:
if s >= 60:
passed.append(s)
# ✅ Indexación booleana
passed = scores[scores >= 60]
print(passed) # [85 91 67 78 93]

scores >= 60 crea un arreglo booleano [True, False, True, True, False, True, True, False], y al usarlo como índice, solo se extraen los elementos que son True.


Patrón 3 — Lo mismo en pandas

python
import pandas as pd
df = pd.DataFrame({
"name": ["A", "B", "C", "D", "E"],
"revenue": [500, 800, 300, 1200, 900],
"cost": [200, 600, 150, 700, 400]
})
# ❌ Bucle for
profits = []
for _, row in df.iterrows():
profits.append(row["revenue"] - row["cost"])
df["profit_loop"] = profits
# ✅ Vectorización
df["profit_vec"] = df["revenue"] - df["cost"]

Recorrer las filas una por una con df.iterrows() es casi siempre un enfoque incorrecto en pandas. Las operaciones a nivel de columna se vectorizan.


Patrón 4 — Asignación condicional de valores

python
# ❌ Bucle for
for i, row in df.iterrows():
if row["profit_vec"] > 500:
df.at[i, "grade"] = "A"
else:
df.at[i, "grade"] = "B"
# ✅ np.where
df["grade"] = np.where(df["profit_vec"] > 500, "A", "B")

np.where(condición, si_verdadero, si_falso) — aplica if/else a todo el array de una sola vez.

Condiciones múltiples más complejas:

python
conditions = [
df["profit_vec"] > 500,
df["profit_vec"] > 200,
df["profit_vec"] > 0
]
choices = ["A", "B", "C"]
df["grade"] = np.select(conditions, choices, default="D")

Cuando no es posible la vectorización

No todas las operaciones pueden ser vectorizadas:

python
# Cálculo acumulativo que depende del valor de la fila anterior
# No puede paralelizarse porque cada fila debe consultar el resultado anterior
result = [data[0]]
for i in range(1, len(data)):
result.append(result[-1] + data[i])

En estos casos, busque funciones especializadas como np.cumsum(); si no existen, utilice un bucle for pero solo dentro del ámbito mínimo necesario.


Reglas de benchmarking

Tamaño de datosBucle forVectorizaciónDiferencia
1,000~0.2ms~0.01ms~20x
100,000~20ms~0.1ms~200x
10,000,000~2s~20ms~100x

Cuanto mayores son los datos, mayor es la ventaja de la vectorización.


Resumen clave

SituaciónUtilizar
Operaciones aritméticasarr * 2, arr + arr2
Filtrado condicionalarr[arr > 0]
Asignación condicionalnp.where(), np.select()
Agregaciónarr.sum(), arr.mean()
Recorrido por filas❌ En lugar de iterrows(), usar operaciones por columna

apply() — un punto intermedio entre la vectorización y los bucles for

Para lógicas complejas que no pueden expresarse mediante operaciones vectoriales, utilice apply():

python
def categorize(row):
if row["revenue"] > 1000 and row["profit_vec"] > 300:
return "Excelente"
elif row["profit_vec"] > 0:
return "Normal"
else:
return "Pérdida"
df["category"] = df.apply(categorize, axis=1)

apply() es más rápido que iterrows(), pero más lento que las operaciones vectoriales puras. Para lógica compleja se recomienda apply(), mientras que para operaciones simples la vectorización es el principio fundamental.


Vectorización de cadenas — Acceso .str

python
names = pd.Series(["Alice Smith", "Bob Jones", "Charlie Brown"])
# ❌ Bucle for
upper_names = []
for name in names:
upper_names.append(name.upper())
# ✅ Vectorización
upper_names = names.str.upper()
first_names = names.str.split(" ").str[0]
has_e = names.str.contains("e", case=False)
print(first_names) # ["Alice", "Bob", "Charlie"]

El accesor .str de pandas vectoriza los métodos de cadena. Realiza la mayoría de las operaciones con cadenas, como split, replace, contains y extract, sin necesidad de bucles for.


Lista de verificación para refactorización de transformaciones

Al encontrar un bucle for en el código existente, verifique en este orden:

  1. ¿Es una operación aritmética/de comparación? → Use directamente los operadores (arr + 1, arr > 0)
  2. ¿Es una bifurcación condicional? → Utilice np.where() o np.select()
  3. ¿Es procesamiento de cadenas? → Use el accesor .str
  4. ¿Es una agregación? → Utilice .sum(), .mean(), .groupby()
  5. ¿Es lógica compleja? → Utilice apply()
  6. ¿Depende de la fila anterior? → Utilice cumsum(), shift() o busque funciones especializadas
  7. Ninguna de las anteriores aplica → Use un bucle for (alcance mínimo)

groupby — Operaciones vectorizadas por grupo

python
df = pd.DataFrame({
"department": ["Ventas", "Desarrollo", "Ventas", "Desarrollo", "Desarrollo"],
"salary": [5000, 6000, 4500, 7000, 5500]
})
# ❌ Media por departamento con un bucle for
departments = df["department"].unique()
for dept in departments:
mask = df["department"] == dept
print(f"{dept}: {df.loc[mask, 'salary'].mean()}")
# ✅ Operación vectorial con groupby
print(df.groupby("department")["salary"].mean())

groupby() aplica operaciones vectoriales por grupo. Está optimizado internamente en C, lo que lo hace más rápido y conciso que los bucles for.


Consideraciones sobre dtype

La velocidad de las operaciones vectoriales también se ve afectada por el dtype:

python
# dtype object, columna con cadenas mezcladas → no vectorizable
mixed = pd.Series([1, "two", 3]) # dtype: object → lento
clean = pd.Series([1, 2, 3]) # dtype: int64 → rápido
# Comprueba el dtype
print(df.dtypes)
# Si una columna numérica se detecta como object, debe convertirse
df["price"] = pd.to_numeric(df["price"], errors="coerce")

Al leer un archivo CSV, si una sola celda de una columna numérica contiene una cadena vacía, el tipo de dato completo se convierte en object, lo que impide realizar operaciones vectoriales. Conviértalo a pd.to_numeric() antes de utilizarlo.

El hábito de verificar si existe un método vectorizado antes de escribir un bucle 'for' puede mejorar significativamente el rendimiento del procesamiento de datos.

💬 Preguntas y comentarios

0 comentarios

Puedes publicar sin iniciar sesión. Los comentarios de invitados no pueden editarse ni eliminarse después.

0/2000

Cargando...