Mejorar el rendimiento de Pandas con swifter

Publicado el 12 octubre 2020 por Daniel Rodríguez @analyticslane

A la hora de trabajar con datos en rendimiento es un factor clave. No es lo mismo procesar un conjunto de datos en un minuto que en un segundo. A pesar de que pandas ofrece un rendimiento razonable en la mayoría de las situaciones, no es así con conjunto de datos muy grandes. Por lo existe soluciones para mejorar su rendimiento como Modin o Cython que ya hemos visto en este blog. Otro parapete que se puede utilizar para mejorar el rendimiento de Pandas es swifter. Un paquete que nos permite aplicar de forma eficiente cualquier función a una serie o DataFrame de Pandas.

Instalación de swifter

El proceso de instalación de swifter es sencillo como la mayoría de paquete de Python, pudiéndose usar para ello pip. Aunque, dado las características de swifter, es aconsejable actualizar previamente Pandas para estar seguro de que se tenga una versión compatible. Para lo que es necesario escribir en la terminal:

pip install -U pandas
pip install swifter

Alternativamente, si estamos trabajando con Conda se puede usa el comando para instalar el paquete:

conda install -c conda-forge swifter

Mejora del rendimiento

Para evaluar el rendimiento de swifter podemos crear un DataFrame aleatorio con 20 millones de registros.

import numpy as np
import pandas as pd
import swifter

df = pd.DataFrame(np.random.randint(-100, 100,
                                    size=(20000000, 2)),
                  columns=list('AB'))

Ahora podemos ver lo que le cuesta elevar al cuadrado una de las columnas a Pandas.

%time df['z'] = df['A'].apply(lambda t: t ** 2)
CPU times: user 11.1 s, sys: 949 ms, total: 12 s
Wall time: 12.8 s

El rendimiento que hemos obtenido no está mal, ha tardado unos 11 segundos. Pero si usamos swifter para aplicar la función podemos ver que el rendimiento es mucho mayor.

%time df['z'] = df['A'].swifter.apply(lambda t: t ** 2)
CPU times: user 149 ms, sys: 103 ms, total: 252 ms
Wall time: 256 ms

Ahora solo ha tardado solamente unas centésimas de milisegundos. !Una mejora de dos órdenes de magnitud¡ Esto es así porque swifter utiliza una versión vectorizada de la función, mejorando considerablemente el rendimiento.

Funciones no vectorizadas

El problema de swifter es que no siempre se obtiene una mejora de este tipo. Si la función que se desea aplicar no se puede vectorizar, el rendimiento puede ser menor que Pandas. Para esto podemos probar con una versión de la función valor absoluto aplicada al DataFrame anterior.

def abs(x):
    if x > 0:
        return x
    else:
        return -x
    
%time df['z'] = df['A'].apply(lambda t: abs(t))
CPU times: user 7.58 s, sys: 680 ms, total: 8.26 s
Wall time: 8.47 s

En este caso, la función ha tardado unos 8 segundos. Veamos lo que pasa al usar swifter

%time df['z'] = df['A'].swifter.apply(lambda t: abs(t))
CPU times: user 1.31 s, sys: 954 ms, total: 2.26 s
Wall time: 15.7 s

Vemos que el tiempo es un poco mayor, hemos pasado a 15 segundos. Esto es así porque no existe una versión vectorizada de la función y en este caso intenta paralizar el trabajo, sin obtener una mejora.

Vectorizar la función con np.where()

El problema que hemos visto en el caso anterior se puede solucionar vectorizando la tarea con la función np.where(). Lo que nos permite volver a mejorar el tiempo de ejecución.

def abs(x):
    return np.where(x > 0, x, -x)
    
%time df['z'] = df['A'].swifter.apply(lambda t: abs(t))
CPU times: user 239 ms, sys: 176 ms, total: 415 ms
Wall time: 451 ms

Conclusiones

En esta entrada se ha visto una librería interesante para mejorar el rendimiento de las operaciones realizadas con Pandas. Aunque no es una librería mágica. Ya que es necesario implementar la operación de forma vectorial para evitar que el rendimiento sea pero que con la librería estándar. Aun así, en las situaciones en las que funciona bien es una excelente opción para mejorar el rendimiento de nuestros códigos.

Imagen de John Howard en Pixabay