De Pandas a Polars: Escribiendo Código de Manipulación de Datos Más Rápido y Expresivo en Python
Contexto del Problema
Si has trabajado con datos en Python, es casi seguro que has usado Pandas. Es la librería de facto, una herramienta increíblemente flexible y potente que ha dominado el ecosistema de la ciencia de datos durante años. Sin embargo, a medida que los datasets crecen y las operaciones se vuelven más complejas, es posible que hayas notado ciertas limitaciones: el consumo de memoria puede dispararse y el rendimiento puede decaer, especialmente en tareas que involucran grandes volúmenes de datos. Esto se debe en gran parte a que Pandas opera principalmente en un solo hilo (single-threaded) y su evaluación es "ansiosa" (eager), lo que significa que cada operación se ejecuta de inmediato.
Aquí es donde entra Polars, una librería de DataFrames reimplementada desde cero en Rust, diseñada para el alto rendimiento y el procesamiento eficiente de datos. Polars aprovecha al máximo los procesadores multinúcleo modernos y utiliza un sistema de evaluación "perezosa" (lazy) para optimizar las consultas completas antes de ejecutarlas. Para un desarrollador junior o mid, aprender Polars no es solo añadir otra herramienta a tu arsenal; es adoptar un paradigma que te permitirá construir pipelines de datos más rápidos, eficientes en memoria y escalables.
Conceptos Clave
Para entender por qué Polars es tan rápido, debemos comprender tres conceptos fundamentales que lo diferencian de Pandas.
- Motor de Consultas en Rust: El núcleo de Polars está escrito en Rust, un lenguaje conocido por su rendimiento a nivel de C/C++ y su seguridad en el manejo de memoria. Esto permite a Polars ejecutar operaciones a una velocidad increíble y paralelizar tareas de forma segura y automática, aprovechando todos los núcleos de tu CPU.
- Evaluación Perezosa (Lazy Evaluation): A diferencia de Pandas, que ejecuta cada línea de código al instante (eager), Polars tiene un modo "lazy". En este modo, cuando escribes una operación, Polars no la ejecuta. En su lugar, construye un plan de consulta lógico. Solo cuando explícitamente pides el resultado (usando
.collect()), Polars revisa todo el plan, lo optimiza y luego lo ejecuta de la manera más eficiente posible. Esto permite optimizaciones como la eliminación de pasos innecesarios o la aplicación de filtros directamente en la fuente de datos para reducir la carga de memoria. - Expresiones (Expressions): Las expresiones son el corazón de la API de Polars. En lugar de aplicar funciones a un DataFrame, defines transformaciones sobre las columnas usando
pl.col(),pl.sum(), etc. Estas expresiones son las que se registran en el plan de consulta del modo lazy. Permiten un código más declarativo y legible, y lo más importante, son la clave para que Polars pueda paralelizar y optimizar tus transformaciones.
Implementación Paso a Paso
La mejor manera de aprender es viendo el código en acción. Comparemos las operaciones más comunes en Pandas y Polars.
1. Instalación y Lectura de Datos
Primero, instala Polars. Se recomienda incluir dependencias extra para leer CSV (usando pyarrow) y para interactuar con Pandas si es necesario.
# Instalar Polars con las dependencias recomendadas
!pip install polars[pandas,pyarrow]
Ahora, vamos a leer un archivo CSV. La sintaxis es muy similar.
import polars as pl
# En Polars, la lectura de CSV es significativamente más rápida
df_polars = pl.read_csv("mi_dataset.csv")
# Para comparación, así sería en Pandas
# import pandas as pd
# df_pandas = pd.read_csv("mi_dataset.csv")
print(df_polars.head())
Una de las primeras ventajas que notarás es la velocidad de lectura. Polars es consistentemente más rápido cargando datos.
2. Selección y Filtrado de Datos
La selección y el filtrado en Polars se realizan a través de expresiones claras y explícitas.
# --- Selección de columnas ---
# Polars: usa el método select()
selected_cols = df_polars.select([
"columna_a",
"columna_b"
])
# Pandas: usa el operador [] con una lista de columnas
# selected_cols_pd = df_pandas[["columna_a", "columna_b"]]
# --- Filtrado de filas ---
# Polars: usa el método filter() con una expresión
filtered_rows = df_polars.filter(
pl.col("columna_a") > 50
)
# Pandas: usa indexación booleana
# filtered_rows_pd = df_pandas[df_pandas["columna_a"] > 50]
El uso de pl.col() es idiomático en Polars y es lo que permite que el motor de consultas entienda y optimice la operación.
3. Creación y Modificación de Columnas
Para añadir o modificar columnas, Polars utiliza with_columns(). Esto fomenta un estilo de programación más funcional y encadenable.
# Polars: usa with_columns() para crear una o más columnas nuevas
df_con_nuevas_columnas = df_polars.with_columns([
(pl.col("columna_a") * 2).alias("columna_a_doble"),
(pl.col("columna_a") + pl.col("columna_b")).alias("suma_a_b")
])
# Pandas: asignación directa
# df_pandas["columna_a_doble"] = df_pandas["columna_a"] * 2
# df_pandas["suma_a_b"] = df_pandas["columna_a"] + df_pandas["columna_b"]
El método .alias() es crucial para nombrar la nueva columna resultante de una expresión.
4. Agregaciones y Agrupaciones (Group By)
Las operaciones group_by son un punto fuerte de Polars, mostrando una sintaxis limpia y un rendimiento superior.
# Polars: encadenamiento de group_by() y agg()
agregado_polars = df_polars.group_by("categoria").agg([
pl.sum("valor").alias("valor_total"),
pl.mean("valor").alias("valor_promedio"),
pl.count().alias("conteo_items") # pl.count() cuenta las filas del grupo
])
# Pandas: sintaxis similar pero a menudo más verbosa para múltiples agregaciones
# agregado_pandas = df_pandas.groupby("categoria").agg(
# valor_total=("valor", "sum"),
# valor_promedio=("valor", "mean"),
# conteo_items=("categoria", "count")
# )
La API de Polars para agregaciones es muy expresiva, permitiendo anidar expresiones complejas dentro de .agg().
Mini Proyecto / Aplicación Sencilla
Vamos a aplicar lo aprendido en un mini proyecto. Analizaremos un dataset de transacciones para encontrar la hora del día con el mayor volumen de ventas promedio.
Objetivo: Calcular el total de ventas por hora y luego encontrar la hora con el promedio más alto de ventas totales a lo largo de los días.
Primero, generemos un dataset de ejemplo.
import polars as pl
import numpy as np
import pandas as pd # Usado solo para generar el rango de fechas
# Generar datos de ejemplo
num_rows = 1_000_000
dates = pd.to_datetime(pd.date_range(start='2025-01-01', end='2025-12-31', freq='10S'))
data = {
'timestamp': np.random.choice(dates, num_rows),
'product_id': np.random.randint(1, 100, num_rows),
'sale_amount': np.random.uniform(5.0, 500.0, num_rows).round(2)
}
df_ventas = pl.DataFrame(data)
print(df_ventas.head())
Ahora, implementemos la lógica de análisis usando el modo lazy de Polars para un rendimiento óptimo.
# Implementación con Polars (Modo Lazy)
# 1. Iniciar el modo lazy con .lazy()
analisis_lazy = df_ventas.lazy()\
.with_columns([
# 2. Extraer la hora del timestamp
pl.col("timestamp").dt.hour().alias("hora_del_dia"),
# Extraer la fecha para agrupar por día/hora
pl.col("timestamp").dt.date().alias("fecha")
])\
.group_by(["fecha", "hora_del_dia"]).agg([
# 3. Calcular el total de ventas por hora para cada día
pl.sum("sale_amount").alias("ventas_totales_por_hora")
])\
.group_by("hora_del_dia").agg([
# 4. Calcular el promedio de esas ventas totales por hora
pl.mean("ventas_totales_por_hora").alias("promedio_ventas_hora")
])\
.sort("promedio_ventas_hora", descending=True) # 5. Ordenar para encontrar la mejor hora
# 6. Ejecutar el plan de consulta optimizado
resultado_final = analisis_lazy.collect()
print(resultado_final)
Snippet de Ejecución
Para ejecutar este código, simplemente guarda el script como analisis_ventas.py y ejecútalo desde tu terminal. El resultado mostrará un DataFrame ordenado, con la hora del día de mayor venta promedio en la primera fila.
python analisis_ventas.py
Este ejemplo demuestra el poder del encadenamiento de métodos y la evaluación perezosa. Polars no calcula los resultados intermedios; en su lugar, optimiza toda la cadena de operaciones y la ejecuta una sola vez, lo que es mucho más eficiente.
Errores Comunes y Depuración
- Pensar en "Modo Pandas": El error más común es intentar iterar sobre filas o usar funciones
applycon lógica de Python. Esto anula casi todas las ventajas de rendimiento de Polars. La solución es pensar en términos de expresiones de columna. - Olvidar
.collect(): Cuando trabajas en modo lazy (iniciado con.lazy()opl.scan_csv()), tus operaciones devuelven un objetoLazyFrame, que es solo un plan. Si olvidas llamar a.collect()al final, no obtendrás un DataFrame con resultados. - Confusión de Contextos: Una expresión como
pl.col("A").sum()se comporta de manera diferente en un contextoselect(calcula la suma de toda la columna) que en un contextogroup_by(calcula la suma para cada grupo). Entender el contexto es clave. - Errores de Tipos de Datos: Polars es más estricto con los tipos de datos que Pandas. Un error común es intentar una operación de string en una columna numérica. Usa
df.schemapara verificar los tipos y el método.cast()para convertirlos explícitamente.
Aprendizaje Futuro / Próximos Pasos
Dominar los fundamentos de Polars abre la puerta a técnicas más avanzadas para el manejo de datos a gran escala:
- Streaming con
scan_*: Para datasets que no caben en la memoria RAM, Polars puede procesarlos en modo streaming. Usandopl.scan_csv()opl.scan_parquet(), Polars procesa el archivo en fragmentos (chunks) sin cargarlo todo a la vez. - Funciones de Ventana (Window Functions): Explora las funciones de ventana con la expresión
.over(). Permiten realizar cálculos sobre un subconjunto de filas relacionadas con la fila actual, como calcular una media móvil o un ranking. - Integración con el Ecosistema: Aprende a convertir DataFrames de Polars a y desde otros formatos como NumPy arrays, Pandas DataFrames o Apache Arrow tables. Esto es vital para integrar Polars en flujos de trabajo existentes con librerías como Scikit-learn, Matplotlib o PyTorch.
- Optimización de Consultas: Utiliza
lazy_frame.describe_optimized_plan()para ver cómo Polars reordena y optimiza tu consulta. Esto te ayudará a entender mejor su funcionamiento interno y a escribir código aún más eficiente.
Polars está en desarrollo activo, por lo que siempre es una buena idea consultar la documentación oficial para las últimas características y mejores prácticas. Adoptar Polars puede acelerar drásticamente tus flujos de trabajo de datos y es una habilidad muy valiosa en el panorama actual de la ingeniería y ciencia de datos.