
Pandas y polares: ¿es hora de migrar? definitivamente tal vez 🤔
¿Dónde estamos? 🗺️
Si es un científico de datos, un ingeniero de aprendizaje automático o participa de alguna otra manera en un proyecto basado en datos, es muy probable que haya utilizado pandas para limpiar, desinfectar, filtrar y preparar los datos que se utilizarán como entrada para los métodos, modelos o algoritmos elegidos. En este primer paso, que normalmente ocupa una fracción no trivial del cronograma total del proyecto, no estás ejecutando instrucciones que requieran un uso intensivo de la CPU, pero la ejecución no es tan rápida como te gustaría. Además, si el conjunto de datos en el que está trabajando es grande, las limitaciones de hardware (como la memoria) son inminentes. Estas limitaciones son el resultado de dos cosas: Python es, en general, impresionante (pero tal vez la gestión de la concurrencia de subprocesos no sea la mejor opción) y Pandas tiene una ejecución ansiosa (esto significa que una instrucción se está leyendo y ejecutando en este momento; hablaremos de esto más adelante). ¡Aquí es donde entran en juego los polos! Construido en Rust, es mucho más rápido que los viejos Pandas. A diferencia de Python, a Rust se le da muy bien la concurrencia y, con la ventaja de que la ejecución lenta es la ejecución lenta, se pueden aplicar muchas optimizaciones antes de la ejecución.
¡Ha llegado una nueva alternativa! 🆕
Para ver qué hay detrás del capó, experimentamos con ambas bibliotecas y exploramos los tiempos necesarios para sus operaciones en el conjunto de datos de anuncios de AirBnB para NYC 🗽. En nuestro escenario de prueba, la mejora al trabajar con Pandas fue de alrededor de un factor de 5 🤯 y puede ser incluso mayor según el contexto (los datos involucrados), el tamaño y los volúmenes. Recreamos una fuente de datos normal, preprocesamos la asignación de un proyecto, cargamos el conjunto en la memoria, aplicamos filtros, eliminamos las columnas no utilizadas del marco, realizamos algunas operaciones matemáticas, lo unimos con otros conjuntos de datos relacionados y terminamos ordenando el resultado final, comparando las API de la biblioteca en cada caso (el repositorio se puede encontrar y descargar). aquí). En las secciones siguientes, puede ver cómo se realiza el tiempo de ejecución por operación.
Empecemos 💪
Revisaremos un conjunto de operaciones con estas especificaciones de configuración y conjuntos de datos:
- Memoria del sistema: 8 GB
- Procesador: Mac M1 (MacBook Pro)
- Pitón == 3,9,13
- Pandas = 1,43
- Polares = 0,16,11
- listados (41533, 75)
- opiniones (108372, 6)
Leer CSV
Lo primero que hay que hacer para tener los datos disponibles es cargarlos en la memoria como una variable, esto se hace mediante Pandas: pd.read_csv (file_pathname, low_memory=false) Polares (ansiosos): polars.read_csv (nombre_archivo_marco de datos) Polares (perezosos): polars.scan_csv (dataframe_filename, ignore_errors=true) y los tiempos para recuperarlos son: Archivo fuente Polars (s) Pandas (s) Ansioso y perezoso listados 0.20 2.00e-03 1.02 reseñas 6.34e-01 3.18e-04 4.11 Aquí podemos ver que en ambos marcos de datos, Polars es mucho más rápido en cualquiera de sus sabores.
Filtrar (por valor de columnas)
Luego, pasamos a filtrar los datos con algunos valores fijos. Concretamente, decidimos centrarnos en algunos vecinos de Manhattan ('East Village', 'Greenpoint', 'Upper East Side', 'Lower East Side', 'Upper West Side'), anfitriones sobresalientes -superanfitriones- y alquileres que son para todo el lugar. El código que se ejecuta en cada caso es: Pandas filtered_pd = listings_dataset_pd [(listings_dataset_pd ['neighbourhood_cleansed'] .isin (desirable_neighbor boors)) & (listings_dataset_pd ['host_is_superhost'] == 't') & (listings_dataset_pd ['room_type'] == 'Todo el hogar')] Polares (ansiosos): filtered_pl_eager = listings_dataset_pl_eager.filter ((polars.col ('neighbourhood_cleansed') .is_in (desirable_neighbor boors)) & (polars.col ('host_is_superhost') == 't') & (polars.col ('room_type') == 'Todo el casa/apt')) Polares (perezosos): filtered_pl_lazy = (listings_dataset_pl_lazy .filter (polars.col ('neighbourhood_cleansed') .is_in (desirable_neighbor boors)) .filter (polars.col ('host_is_superhost') == 't') .filter (polars.col ('room_type') == 'Todo el casa/apt')) Nota: compruebe que la sintaxis no sea exactamente la misma, pero lo suficientemente similar como para que se entienda fácilmente. Los tiempos obtenidos para cada abordaje son los que se muestran a continuación: Archivo fuente Polars (s) Pandas (s) Ansioso y perezoso listados 7.60e-05 1.9e-02 2.8e-02 Una vez más, Polars era considerablemente mejor a la hora de filtrar las columnas no deseadas. Recuerde que nuestro alcance es corto y que estamos evitando grandes números, simplemente comparando magnitudes.
Ordenar
La clasificación es quizás una característica más deseada por los científicos de datos que por los ingenieros de aprendizaje automático o los ingenieros de datos, en lo que respecta a la captura de algunos valores atípicos o información. Además, clasificamos los datos con: Pandas: sorted_pd = filreted_pd.sort_values (by= [nombre_columna]) Polares (ansiosos): sorted_pl_eager = filtered_pl_eager.sort ([nombre_columna]) Polares (perezosos): sorted_pl_lazy filtered_pl_lazy.sort ([column_name]) y los tiempos recuperados son: Conjunto de datos Polars (s) Pandas (s) Ansioso y perezoso listados 4.00e-03 1.79e-05 8.80e-04
Eliminar (eliminar) columnas
Otra manipulación que queremos intentar es eliminar, debido a la inmensidad de los datos que se cargan en nuestra canalización, los cuadernos o los oyentes de eventos. Elegimos algunas columnas que no son esenciales, como ('source', 'name', 'description', 'picture_url', 'host_location', 'host_about'). De esta forma, de ahora en adelante, los conjuntos de datos cargados son más ligeros. Los códigos para hacerlo son: Pandas sorted_pd.drop (columns = columns_to_remove, inplace=true) Nota: usar inplace siempre que sea posible, es un poco más eficiente con respecto a la memoria. Polares (ansiosos) para la columna de not_wanted_columns: sorted_pl_eager.drop_in_place (column) Polares (perezosos) sorted_pl_lazy = sorted_pl_lazy.drop (columns) Los tiempos de cada ejecución fueron: Conjuntos de datos Polars (s) Pandas (s) Ansioso y perezoso listados 2.67e-04 2.81e-05 2.21e-03
Únete
Con el fin de crear un conjunto de datos más rico, buscamos unir los dos que se están asignando en memoria, listados y reseñas. De una columna en común 'listing_id', que necesita un poco de preprocesamiento en los listados, tenemos que extraer el identificador de 'listing_url' que se logra con mapa, lambda y un poco de expresión regular -consulta el código fuente para más detalles-. Además, hasta ahora hemos ido acumulando operaciones por el camino lento posponiendo su ejecución real, de hecho son tipos de datos diferentes, polos.Lazy Frame no es lo mismo que polos.Marco de datos (y no es lo mismo que pandas.DataFrame -lo que puede resultar un poco confuso al principio-). Este enfoque le da lo mejor de Polars, al optimizar las transformaciones sobre un conjunto de datos, que la proyección es un grupo de pasos ordenados que se fusionan si es posible para obtener un mejor rendimiento. Este es el nuestro: [caption id="attachment_2284" aligncenter» width="1024"]

Gráfico que muestra las transformaciones optimizadas en un conjunto de datos [/caption] Transformación proyectada para cada conjunto de datos al aplicar transformaciones apiladas, con una lista a la izquierda y las reseñas a la derecha. Estos gráficos se generaron con el método show_graph (). En la imagen anterior, puedes ver que la ventaja del enfoque perezoso se obtiene si terminas con unos pocos bloques, en los que las operaciones se combinan entre sí, como es el caso de las listas, pero no de las reseñas. Los códigos para hacerlo son: Pandas sorted_pd.join (reseñas, on = joining_column_name, lsuffix='_left', rsuffix='_right') Polares sorted_pd.join (reviews, on = joining_column_name, lsuffix='_left', rsuffix='_right') Los tiempos de cada ejecución son: Conjunto de datos
Polares (s)
Pandas (s) Ansioso y perezoso
ambos
5.50e-02 9.36e-01 3.40e-02 Por primera vez en esta exploración, los pandas vencieron a Polars. Esto ocurre debido a los pasos previos necesarios para unirse, por ejemplo, para una ejecución lenta recoger (como se llama en la biblioteca) las operaciones de datos, ¡todas!
Envoltura
Este paso es «obligatorio» si la adopción de Polars va a ser gradual (lo cual recomendamos; hablaremos de esto más adelante). Los tipos de datos de Polars y Pandas se pueden convertir entre sí, por lo que aquí haremos una pequeña conversión de Polars a Pandas, para no interferir con otras tareas que ya están en fase de producción o aplicación avanzada. ¡Basta con usar un comando de una sola línea! El código para resumir la variable es: migrated_dataset = joined_pl.to_pandas () El cambio de tipo de datos implica el siguiente tiempo: Conjunto de datos Polares (s) - Ansioso conjunto unido 3.88e-02
Tiempos generales de ejecución ⌚
Al resumir todos los tiempos involucrados en todas las operaciones (filtrar, ordenar, eliminar, unir y empaquetar), podemos ver dos cosas claras: la primera se refiere a la no gran diferencia o ganancia entre una ejecución lenta y una ejecución ansiosa, pero no hay que olvidar que nuestro alcance era pequeño y si la transformación de datos es bastante mayor, esta distancia aumentará. El segundo es el impresionante factor de mejora, podemos obtener el mismo resultado x5 veces más rápido! Polares (s) Pandas (s) 🥉 Perezoso 🥇 Ansioso 🥈 0.93 0.95 5.19 Además, si lo validamos desde una perspectiva más granular, caso por caso, obtenemos: Operación | LibraryPolars EagerPolars LazyPandas Lea CSV 0.83 2.32e-03 5.13 2.º 1.º 3.º
Filtrar
7.60e-05 1.90e-02 2.80e-02 1.º 2.º 3.º Ordenar 4.00e-03 1.79e-05 8.80e-04 3rd 1st 2nd Eliminar col 2.67e-04 2.81e-05 2.21e-03 2.21e-03 2.1er 3rd
Únete
5.50e-02 9.36e-01 3.40e-02 1st 3rd 2nd Wrap 3.88e-02 - - Descargo de responsabilidad: esta operación no se puede comparar debido a que la ejecuta una de las alternativas. Al asignar puntos en función de la clasificación de cada una de las tareas anteriores (3 para la primera, 2 para la segunda y 1 para la tercera), obtenemos 11 puntos para Polars Eager, 12 para Polars Lazy y 7 para Pandas, lo que también refleja las ventajas en términos de tiempo de ejecución de trabajar con cualquiera de los sabores de Polars en lugar de con Pandas.
¿Cómo debemos proceder? 🤔
Como pudimos ver en esta exploración, Polars parece una gran alternativa para ahorrar (mucho) tiempo en las tareas del proyecto debido a su implementación y las optimizaciones disponibles. Pero, ¿es seguro trasladar todos los proyectos, oleoductos, cuadernos o activos actuales que utilizan pandas a Polars? No parece una buena idea hacerlo tan de repente. Sin embargo, con un enfoque iterativo, trabajar con polos en nuevos proyectos (quizás los más pequeños al principio) utilizando el método de envoltura permitirá al equipo ganar terreno en la biblioteca y quedar satisfecho con su impacto, parece una inversión que vale la pena. Si está interesado en una comparación más exhaustiva, puede consultar esto sitio github donde se comparan muchas herramientas (todas ellas geniales).
Una pequeña ventaja
No dudes en explorar otros blogs en los que hemos estado trabajando sobre dato y aire en general.
