Saltar al contenido
dumaloor.dev_
~/academia
Python

Pandas + Polars: cuando tu DataFrame ya no cabe en RAM

El día que abrí un CSV de 8 millones de filas en Pandas y mi portátil cayó. Cómo Polars me lo agregó en 1,8 segundos sin tocar la RAM, y por qué no uso solo Polars.

4 min de lecturapythonpandaspolarsdata-engineeringperformance

Hubo un punto en el que mis scripts de análisis empezaron a dejar de funcionar. No por bug. Por RAM. Un CSV de ventas de un año entero, 8 millones de filas, abierto con pd.read_csv(...): 6,2 GB de memoria solo para cargarlo. Mi portátil con 16 GB se ahogaba en cuanto intentaba un groupby no trivial. Reiniciar Python cada cinco minutos no es una metodología.

Entró Polars. Y cambió las reglas.

#El problema en concreto

Imagina el caso: necesito el GMV por evento y por día, ordenado por fecha, de un dataset con esta forma:

transaction_id, event_id, ticket_type, price_eur, channel, country, postal_code, created_at, status, ... (18 cols)

8.124.973 filas. CSV de 1,9 GB en disco. Con Pandas estándar:

python10 líneas
import pandas as pd

df = pd.read_csv("ventas_2025.csv")            # 6,2 GB RAM, 28 segundos
out = (
    df[df["status"] == "completed"]
    .groupby([df["created_at"].dt.date, "event_id"])
    .agg(gmv=("price_eur", "sum"), n=("price_eur", "size"))
    .reset_index()
)
# Tiempo total: 41 segundos. RAM pico: 7,4 GB.

41 segundos no es el infierno. El problema es que estoy fundiendo el portátil para una operación que conceptualmente es trivial: SUM por día y evento.

#La misma operación en Polars

python15 líneas
import polars as pl

out = (
    pl.scan_csv("ventas_2025.csv")              # lazy, no carga nada
    .filter(pl.col("status") == "completed")
    .with_columns(pl.col("created_at").str.to_datetime().dt.date().alias("day"))
    .group_by(["day", "event_id"])
    .agg(
        pl.col("price_eur").sum().alias("gmv"),
        pl.len().alias("n"),
    )
    .sort("day")
    .collect()                                  # ahora sí ejecuta
)
# Tiempo total: 1,8 segundos. RAM pico: 480 MB.

23 veces más rápido. 15 veces menos RAM. Sin truco.

Por qué la diferencia

Pandas carga TODO en memoria, en un único hilo. Polars (escrito en Rust sobre Apache Arrow) hace tres cosas a la vez: paraleliza por columnas en todos los núcleos del CPU, evalúa lazy (encadena operaciones y solo ejecuta lo necesario), y usa columnar storage que es más eficiente en cache y compresión.

#Cuándo merece la pena Polars

No siempre. La regla que uso:

Tamaño datasetHerramienta
< 100 MBPandas. La diferencia es invisible y el ecosistema es más maduro.
100 MB – 1 GBPandas si es one-shot; Polars si vas a iterar muchas veces.
1 GB – 50 GBPolars con scan_* (lazy). Aquí es donde brilla.
> 50 GBPolars streaming, o DuckDB sobre el CSV directo, o cargar a Postgres.

Y dos casos en los que no uso Polars aunque encaje el tamaño:

  • Cuando el equipo lo va a tocar y no conoce Polars. El API es similar a Pandas pero no idéntico. Si yo me voy y el siguiente solo sabe Pandas, dejo el código en Pandas.
  • Cuando necesito un ecosistema específico (statsmodels, scikit-learn, geopandas). Estos esperan Pandas. Convierto al final con df.to_pandas().

#El patrón híbrido que uso a diario

Cargar y pre-procesar con Polars (rápido y barato). Pasar a Pandas solo al final, para los pasos que necesiten librerías del ecosistema:

python23 líneas
import polars as pl

# Trabajo pesado en Polars
clean = (
    pl.scan_csv("ventas_2025.csv")
    .filter(pl.col("status") == "completed")
    .with_columns(
        pl.col("created_at").str.to_datetime(),
        pl.col("price_eur").cast(pl.Float64),
    )
    .group_by("event_id")
    .agg(
        gmv = pl.col("price_eur").sum(),
        n   = pl.len(),
        avg = pl.col("price_eur").mean(),
        p95 = pl.col("price_eur").quantile(0.95),
    )
    .collect()
)

# Salida a Pandas solo para lo que requiere el ecosistema
df = clean.to_pandas()
df.plot.scatter(x="n", y="avg")  # matplotlib quiere Pandas

#El truco real: scan_csv y lazy execution

Lo que hace especial a Polars no es solo Rust. Es la separación entre scan_* y collect(). Cuando escribes:

python1 líneas
pl.scan_csv("...").filter(...).group_by(...).agg(...).collect()

Polars no lee el CSV de inmediato. Construye un query plan, lo optimiza (push down de filtros, eliminación de columnas no usadas, paralelización), y solo entonces lee del disco. Si tu agregado solo necesita 3 columnas de 18, Polars solo lee esas 3.

Compáralo con Pandas: pd.read_csv lee TODO siempre, luego tú filtras en memoria.

#Conversión rápida: Pandas → Polars

Si tienes un script Pandas que quieres migrar, esta tabla cubre el 80%:

PandasPolars
pd.read_csv("x.csv")pl.read_csv("x.csv") o pl.scan_csv("x.csv")
df[df["col"] > 5]df.filter(pl.col("col") > 5)
df.groupby("a").agg(...)df.group_by("a").agg(...)
df["new"] = df["a"] + df["b"]df.with_columns((pl.col("a") + pl.col("b")).alias("new"))
df.sort_values("date")df.sort("date")
df.dropna()df.drop_nulls()
df.merge(other, on="id")df.join(other, on="id")

El gotcha más común: Polars no tiene índice como Pandas. Olvídate de set_index. Todo es columnar y sin índices implícitos. En la práctica es liberador.

#Cuándo no me sirve Polars

Honestidad obligatoria: hay casos en los que Polars me ha decepcionado.

  • JSON anidado complejo. Polars puede leer JSON, pero estructuras profundas con arrays de objetos no son su fuerte. Pandas con json_normalize sigue siendo más cómodo.
  • Series temporales con frecuencias raras. Pandas tiene resample("Q"), resample("W-MON"), etc. Polars tiene group_by_dynamic pero con menos azúcar.
  • Plotting directo. Polars no tiene .plot() integrado. O conviertes a Pandas o usas plotnine / matplotlib manualmente.
Receta

Si tu dataframe es < 100 MB: Pandas. Si es > 1 GB: Polars con scan_*. Entre medias: depende. Y para lo que falta ecosistema (statsmodels, geopandas, plotting), .to_pandas() al final y listo.

En el próximo post hago el comparativo directo Python vs R para análisis. Spoiler: con Polars en la mesa, R deja de tener ventaja clara en datasets grandes. Pero sigue ganando para EDA rápido y modelado estadístico.

¿Te ha sido útil?

¿Necesitas algo similar para tu negocio?

Construyo dashboards, pipelines y análisis a medida. Si quieres hablar, escríbeme.

Hablamos