Polars es una librería de análisis de datos en Python diseñada para manipular grandes volúmenes de datos de forma eficiente. Escrita en Rust y basada en Apache Arrow, ofrece un rendimiento superior a Pandas al aprovechar la concurrencia y el formato de memoria independientemente del lenguaje. Su enfoque de lazy execution y la optimización de consultas la hacen ideal para operaciones complejas y grandes volúmenes de datos.
¡Comenzamos!
La teoría nos dice que polars está diseñado para ser unas 5 veces más eficiente computacionalmente que Pandas. Para comprobar esta afirmación, hemos puesto a prueba ambas librerías lanzando 50 ejecuciones y realizando distintos tipos de transformaciones sobre el mismo dataset. En las siguientes figuras hemos representado las distribuciones de los tiempos de ejecución en las operaciones de: lectura de un archivo csv, ordenación de un dataframe, agrupación y combinación de varias expresiones.
Como podemos observar, los resultados reflejan que, efectivamente, Polars consigue un rendimiento significativamente mejor que Pandas. Llegando a ser casi 5 veces más rápido que Pandas en algunas transformaciones. A continuación, analizaremos los motivos que hacen que polar sea tan eficiente:
Polars Está Escrito En Rust
Polars está escrito en Rust, un lenguaje de bajo nivel rápido, mientras que Pandas se basa en NumPy. La capacidad de Rust para la concurrencia segura permite que Polars utilice todos los núcleos (cores) de la máquina, lo que resulta en un rendimiento mucho más rápido en comparación con Pandas, que solo utiliza un núcleo. Esto hace que Polars sea ideal para consultas complejas y grandes conjuntos de datos.
Polars Se Basa En Apache Arrow
Otro factor que contribuye a la mejora significativa del rendimiento de Polars es Apache Arrow, un formato de memoria independiente del lenguaje. Fue creado para abordar problemas de escalabilidad en Pandas y es el backend de Pandas 2.0. Apache Arrow proporciona interoperabilidad entre bibliotecas, lo que acelera el rendimiento al evitar la conversión de datos entre pasos del flujo de trabajo. Además, Arrow es más eficiente en memoria y admite una amplia gama de tipos de datos, incluidos datetime, booleanos y columnas complejas. Además, utiliza un almacenamiento de datos columnar, lo que facilita el paralelismo y la recuperación de datos.
Optimización De Consultas
Otro pilar fundamental del rendimiento de Polars es cómo evalúa el código. Por defecto, Pandas utiliza una ejecución eager, realizando las operaciones en el orden en que las has escrito. En cambio, Polars tiene la capacidad de realizar tanto una ejecución eager como una ejecución lazy, donde un optimizador de consultas evalúa todas las operaciones requeridas y traza la forma más eficiente de ejecutar el código. Esto puede incluir, entre otras cosas, reescribir el orden de ejecución de las operaciones o eliminar cálculos redundantes.
Sencillez De La API
Como último motivo, Polars tiene una API extremadamente expresiva, lo que significa que prácticamente cualquier operación que se desee realizar puede ser expresada como un método de Polars. En contraste, las operaciones más complejas en Pandas a menudo necesitan ser ejecutadas mediante el método apply a través de una expresión lambda. Dicho método apply, conlleva que se recorran todas las filas del DataFrame, ejecutando secuencialmente la operación en cada una. Poder utilizar métodos incorporados te permite trabajar a nivel de columna y aprovechar otra forma de paralelismo llamada SIMD (Single Instruction, Multiple Data).
Primeros Pasos Con Polars
A continuación, vamos a explicar brevemente cómo se trabaja con Polars, mostrando las funciones básicas y algunas ideas de posibles operaciones. Para ello, vamos a trabajar con el conocido conjunto de datos de California Housing Price, de Keras.
Tal y como comentamos anteriormente, Polars trabaja con expresiones. Estas ofrecen una estructura modular que nos permite combinar conceptos simples en consultas más complejas. Las cuatros expresiones básicas de Polars son:
- select
- filter
- with_columns
- group_by
Expresión Select
La expresión select nos permite seleccionar las columnas que queramos. Para trabajar con esta expresión simplemente necesitamos dos cosas:
1. El DataFrame sobre el que queramos trabajar.
2. Los nombres de las columnas a seleccionar.
Por ejemplo, sobre el dataset anterior, si quisiéramos obtener la columna de longitud, haríamos la siguiente consulta:
Cabe destacar que los nombres de las columnas siempre deben pasarse para cualquier consulta en un objeto del tipo Polars.col. Es decir, si quisiéramos sacar tanto la longitud como la latitud, la consulta debería ser la siguiente:
Expresión Filter
La expresión filter nos permite obtener un subconjunto del DataFrame original, a través de una condición. Para utilizarla, necesitamos 2 cosas:
1. El DataFrame sobre el que queramos trabajar.
2. Los filtros que queremos aplicar.
Por ejemplo, si sobre el dataset quisiéramos seleccionar aquellas filas cuya casa tenga un valor superior a los 200.000 dólares, la consulta debería ser la siguiente:
Polars cuenta también con operadores de filtrado básicos que nos permiten simplificar las consultas. Podemos consultar la lista completa de operadores disponibles en la documentación oficial. Por ejemplo, el operador is_between nos permite consultar las filas de las casas cuya edad mediana de las personas que viven en ella esté entre los 30 y 40 años.
Como se puede suponer, los filtros se pueden combinar para crear consultas más complejas.
Expresión with columns
La expresión with_columns nos permite añadir columnas a nuestro dataset en base a operaciones. Por ejemplo, si quisiéramos calcular cuantas habitaciones hay por persona en una determinada zona, nuestra expresión tendría la siguiente forma:
Como se puede ver, para añadir una columna el parámetro que le pasamos a la expresión with_columns tiene dos partes:
(Operación).alias(Nombre de la columna resultado)
Al igual que ocurre con las anteriores expresiones, con la expresión with_columns podemos calcular también varias columnas a la misma vez. Si, por ejemplo, quisiéramos hacer el cálculo anterior y además calcular cuantas habitaciones para dormir hay por persona, la expresión tendría la siguiente forma:
Expresión group by
La expresión group_by nos permite crear nuevos DataFrames mediante cálculos por grupos. Por ejemplo, en nuestro conjunto de datos contamos con una variable llamada ocean_proximity, la cual es una tira de caracteres que nos indica la proximidad al mar. Esta variable tiene sólo 5 valores posibles, es decir, se trata de una variable categórica. Si quisiéramos calcular por ejemplo los valores medios para cada variable, agrupando por zona, nuestro código sería el siguiente:
Como se observa, si combinamos la operación de group_by con el uso de una métrica. Obtenemos el valor para todas las variables del conjunto de datos.
Sin embargo, si lo que buscamos es obtener diversas métricas, como por ejemplo el mínimo, máximo y media, debemos hacerlo a nivel de variable y mediante el uso del operador agg.
Combinación De Expresiones
Ahora que ya conocemos las expresiones básicas de Polars, cabe destacar que estas expresiones pueden combinarse entre ellas, mediante concatenación de puntos, para realizar consultas más complejas. Por ejemplo, podría darse el caso de que creemos nuevas columnas utilizando la expresión with_columns, pero nuestro dataset tenga demasiadas columnas y no podamos comprobar los cálculos a simple vista. En este caso, una solución sería combinar la expresión with_columns con una expresión select como se puede ver en la siguiente captura:
Otra posible consulta, sería agrupar las muestras por su etiqueta de proximidad al mar y obtener aquellas zonas cuya edad media sea superior a 30 años. En este caso, nuestra consulta tendría la siguiente forma:
Traducción De Expresiones Pandas A Polars
Ahora que ya conocemos las expresiones de Polars y cómo emplearlas, desarrollaremos un pequeño diccionario de cómo realizar algunas de las consultas más tradicionales de Pandas en Polars.
Selección De Columnas
Selección De Filas Por Índice
Para seleccionar filas por índice tenemos el inconveniente de que en Polars los DataFrames no poseen índice. Sin embargo, crearse una columna auxiliar que realice la función del índice para después trabajar con ella es muy sencillo, el código tendría la siguiente forma:
df.insert_column(pos,pl.Series(nombre,valores))
pos: posición en la que queremos insertar la columna (0 si queremos que esté al principio)
nombre: nombre que le queremos dar a la columna
valores: valores que toman las observaciones
De esta forma, crear nuestro índice sería tan sencillo como:
df.insert_column(0,pl.Series(“índice”,range(df.shape[0])))
Selección De Filas Por Filtro
En este caso, utilizamos la expresión filter que hemos visto en Polars.
Creación De Columnas
Unión De DataFrames
Ordenar DataFrames
Más Funciones Interesantes
Para escoger las n filas con menor valor en una variable (en este caso 5):
Para rellenar los valores faltantes de una tabla con un determinado valor (en este caso 0):
Para calcular la media móvil de una serie temporal usando n periodos (en este caso 3):
Para eliminar las filas con valores faltantes:
Para quedarnos con la/s fila/s que contengan el valor máximo para una determinada variable:
Conclusión
En conclusión, en este post hemos mostrado y explicado la mejora de rendimiento que presenta la librería Polars sobre Pandas. Dicha mejora, plantea la posibilidad de que, en un futuro, la mayoría de los proyectos de análisis de datos se basen en esta librería. De hecho, librerías ampliamente extendidas como scikit-learn ya empiezan a contemplar Polars en sus funciones. Todo ello, parece indicar que Polars acabará convirtiéndose en una de las librerías de referencia para el procesado de datos con Python, generando la necesidad en todos los profesionales de aprender a trabajar con ella.
Como primeros pasos en dicho aprendizaje, en este artículo hemos realizado una breve introducción a Polars, qué caracteriza a la librería y ejemplificado su uso a través de expresiones básicas.
Por último, hemos tratado de comparar algunas expresiones frecuentes que se utilizan en tareas de manipulación de datos en Pandas y Polars, creando un breve “diccionario” para principiantes de Polars.