pandas 2.0 llegó en abril de 2023 y fue la actualización más significativa de la librería en años. No añadió mil funciones nuevas; cambió cosas de fondo que afectan a cómo funciona la librería por dentro y, en algunos casos, a cómo tienes que escribir tu código.
El cambio más visible en el día a día es Copy-on-Write, pero antes de llegar ahí, vale la pena repasar el otro gran añadido: el soporte para Apache Arrow como backend. Hasta pandas 2.0, los datos se almacenaban internamente usando arrays de NumPy. Con Arrow disponible como alternativa, pandas puede representar columnas de forma más eficiente en memoria, especialmente con tipos de datos como strings o nulos en enteros.
Para usarlo basta con indicarlo al leer los datos:
df = pd.read_csv("datos.csv", dtype_backend="pyarrow")
No es obligatorio, pero si trabajas con datasets grandes y notas que la memoria se dispara, merece la pena probarlo.
Otro cambio relevante son los nullable integer types. Antes, si una columna entera tenía algún valor nulo, pandas la convertía automáticamente a float64 porque NumPy no soporta enteros nulos. Con los nuevos tipos como pd.Int64Dtype() puedes tener enteros con NaN sin ese hack:
df["columna"] = df["columna"].astype(pd.Int64Dtype())
Copy-on-Write: el cambio que más código rompe
Copy-on-Write (CoW) es el cambio de comportamiento más importante de pandas 2.x y el que más confusión genera al migrar código antiguo.
El problema clásico de pandas era este: si tomabas un slice de un DataFrame y lo modificabas, a veces modificabas el original y a veces no. El famoso SettingWithCopyWarning era la señal de que algo podía salir mal. Era un comportamiento impredecible que causaba bugs difíciles de rastrear.
Con CoW, la regla es simple: cualquier operación que modifique datos siempre crea una copia. Nunca modifica el objeto original. Esto hace el comportamiento predecible, aunque implica que ciertos patrones de código que antes "funcionaban" (aunque por las razones equivocadas) ahora no modifican lo que crees:
df2 = df[df["precio"] > 100]
df2["descuento"] = 0.1 # Con CoW, esto NO modifica df
En pandas 2.0 CoW es opt-in. Para activarlo antes de que se convierta en el comportamiento por defecto:
pd.options.mode.copy_on_write = True
La recomendación es activarlo ya en proyectos nuevos y ajustar el código que dependa del comportamiento anterior.
Operaciones básicas en pandas
Para quien llegue nuevo o quiera tener una referencia rápida, estas son las operaciones más habituales:
# Cargar y explorar
df = pd.read_csv("datos.csv")
df.head()
df.info()
df.describe()
# Filtrar
df[df["precio"] > 100]
# Agrupar y agregar
df.groupby("categoria")["ventas"].sum()
# Joins
df.merge(otro_df, on="id", how="left")
Nada nuevo aquí; pandas sigue siendo la misma API de siempre en lo que respecta a estas operaciones.
Qué es Polars y por qué es más rápido
Polars es una librería de DataFrames escrita en Rust que usa Apache Arrow como formato de columnas interno. Eso ya da pistas de por qué es rápida: Rust elimina la sobrecarga del intérprete de Python en las operaciones más costosas, y Arrow permite trabajar con los datos de forma muy eficiente en memoria.
Dos características definen cómo funciona Polars:
- Multi-hilo por defecto. Polars usa todos los cores disponibles sin que tengas que configurar nada. Una operación de groupby sobre un dataset grande aprovecha automáticamente todos los procesadores.
- Evaluación lazy. En lugar de ejecutar cada operación según la escribes, Polars construye un plan de ejecución y lo optimiza antes de hacer nada. Solo ejecuta cuando llamas a
.collect().
Una cosa que puede sorprender al llegar de pandas es que Polars no tiene índice. No hay ese df.index al que se recurre en pandas. Al principio parece una limitación, pero en la práctica elimina una fuente habitual de confusión, sobre todo cuando los índices se desalinean tras ciertos filtros o merges.
Operaciones básicas en Polars
La API de Polars tiene su propia sintaxis, distinta de pandas. Aquí están los equivalentes más habituales:
# Carga eager (igual que pandas)
df = pl.read_csv("datos.csv")
# Carga lazy (recomendada para datasets grandes)
lf = pl.scan_csv("datos.csv")
# Filtrar
df.filter(pl.col("precio") > 100)
# Agrupar y agregar
df.group_by("categoria").agg(pl.col("ventas").sum())
# Join
df.join(otro_df, on="id", how="left")
# Añadir columna calculada
df.with_columns(
(pl.col("precio") * 1.21).alias("precio_iva")
)
La evaluación lazy se ve bien aquí: puedes encadenar filtros, selecciones y transformaciones, y Polars decide el orden más eficiente de ejecución antes de tocar los datos:
resultado = (
pl.scan_csv("datos.csv")
.filter(pl.col("precio") > 100)
.select(["id", "categoria", "precio"])
.collect()
)
Benchmarks: cuándo la diferencia importa de verdad
La pregunta que se repite es cuánto más rápido es Polars. La respuesta honesta es que depende del tamaño del dataset y de la operación.
- Datasets pequeños (menos de 100K filas). La diferencia no se nota en la práctica. Ambas librerías son suficientemente rápidas y el tiempo de ejecución no va a ser el cuello de botella.
- Datasets medianos y grandes (cientos de miles o millones de filas). Aquí Polars se distancia. En operaciones como groupby, joins o transformaciones de columnas, la diferencia puede ser de 2x a 10x según el caso.
- Lectura de archivos grandes.
pl.scan_csv("archivo.csv").filter(...).collect()usa menos memoria quepd.read_csv()porque Polars puede aplicar los filtros durante la lectura, sin cargar todo en RAM primero.
Para pipelines de transformación complejos con varios pasos, la evaluación lazy de Polars elimina operaciones intermedias innecesarias. pandas ejecuta cada paso por separado y crea DataFrames temporales en cada uno.
Apache Arrow: lo que tienen en común
Tanto pandas 2.x con backend Arrow como Polars usan Apache Arrow internamente. Eso tiene una consecuencia práctica muy útil: puedes convertir entre los dos sin copiar datos en memoria.
# De pandas a Polars
df_polars = pl.from_pandas(df_pandas)
# De Polars a pandas
df_pandas = df_polars.to_pandas()
# Guardar en Parquet (funciona con ambos)
df_polars.write_parquet("archivo.parquet")
df_pandas.to_parquet("archivo.parquet")
Parquet se ha convertido en el formato estándar para persistir datos tabulares en columnas: comprime bien, lee rápido y tanto pandas como Polars lo leen sin problemas.
Cuándo usar pandas y cuándo Polars en 2026
No hay una respuesta universal. La elección depende del contexto:
- Usa pandas cuando el dataset cabe cómodo en memoria y el resto de tu código (sklearn, matplotlib, scipy) espera DataFrames de pandas. La integración con el resto del ecosistema de Python para ML sigue siendo el punto fuerte de pandas.
- Usa Polars cuando el dataset es grande, quieres el máximo rendimiento en el ETL o estás construyendo un pipeline de datos desde cero sin dependencias previas de pandas.
- Mezcla los dos si tiene sentido: usa Polars para el procesamiento pesado y convierte a pandas justo antes de pasarle los datos a una librería de ML que lo requiera. La conversión es barata si ambos ya usan Arrow.
Para proyectos nuevos donde no hay dependencias heredadas, Polars es la opción natural para el procesamiento de datos. Para proyectos existentes con mucho código de pandas, la migración tiene coste y hay que valorar si la ganancia de rendimiento lo justifica.
Si quieres profundizar en cómo usar Python para preparar datos antes de entrenar modelos, te puede interesar Python para ML: del procesamiento de datos al entrenamiento. Y si el consumo de memoria te da problemas con datasets grandes, echa un vistazo a Python y memoria: optimizar el uso de RAM en procesamiento de datos.
Imagen: Pexels / AlphaTradeZone
