Para cualquiera que esté buscando aplicar tqdm en su código personalizado de aplicación de pandas paralelo.
(Probé algunas de las bibliotecas para la paralelización a lo largo de los años, pero nunca encontré una solución de paralelización al 100%, principalmente para la función de aplicación, y siempre tuve que volver por mi código "manual").
df_multi_core : este es el que usted llama. Acepta:
- Tu objeto df
- El nombre de la función que le gustaría llamar
- El subconjunto de columnas sobre el que se puede realizar la función (ayuda a reducir el tiempo / memoria)
- El número de trabajos para ejecutar en paralelo (-1 u omitir para todos los núcleos)
- Cualquier otro kwargs que acepte la función de df (como "axis")
_df_split : esta es una función auxiliar interna que debe colocarse globalmente en el módulo en ejecución (Pool.map es "dependiente de la ubicación"), de lo contrario la ubicaría internamente.
Aquí está el código de mi esencia ( agregaré más pruebas de función de pandas allí):
import pandas as pd
import numpy as np
import multiprocessing
from functools import partial
def _df_split(tup_arg, **kwargs):
split_ind, df_split, df_f_name = tup_arg
return (split_ind, getattr(df_split, df_f_name)(**kwargs))
def df_multi_core(df, df_f_name, subset=None, njobs=-1, **kwargs):
if njobs == -1:
njobs = multiprocessing.cpu_count()
pool = multiprocessing.Pool(processes=njobs)
try:
splits = np.array_split(df[subset], njobs)
except ValueError:
splits = np.array_split(df, njobs)
pool_data = [(split_ind, df_split, df_f_name) for split_ind, df_split in enumerate(splits)]
results = pool.map(partial(_df_split, **kwargs), pool_data)
pool.close()
pool.join()
results = sorted(results, key=lambda x:x[0])
results = pd.concat([split[1] for split in results])
return results
A continuación se muestra un código de prueba para una aplicación paralela con tqdm "progress_apply".
from time import time
from tqdm import tqdm
tqdm.pandas()
if __name__ == '__main__':
sep = '-' * 50
# tqdm progress_apply test
def apply_f(row):
return row['c1'] + 0.1
N = 1000000
np.random.seed(0)
df = pd.DataFrame({'c1': np.arange(N), 'c2': np.arange(N)})
print('testing pandas apply on {}\n{}'.format(df.shape, sep))
t1 = time()
res = df.progress_apply(apply_f, axis=1)
t2 = time()
print('result random sample\n{}'.format(res.sample(n=3, random_state=0)))
print('time for native implementation {}\n{}'.format(round(t2 - t1, 2), sep))
t3 = time()
# res = df_multi_core(df=df, df_f_name='apply', subset=['c1'], njobs=-1, func=apply_f, axis=1)
res = df_multi_core(df=df, df_f_name='progress_apply', subset=['c1'], njobs=-1, func=apply_f, axis=1)
t4 = time()
print('result random sample\n{}'.format(res.sample(n=3, random_state=0)))
print('time for multi core implementation {}\n{}'.format(round(t4 - t3, 2), sep))
En la salida puede ver 1 barra de progreso para ejecutar sin paralelización y barras de progreso por núcleo cuando se ejecuta con paralelización. Hay una ligera interrupción y, a veces, el resto de los núcleos aparecen a la vez, pero incluso entonces creo que es útil ya que obtienes las estadísticas de progreso por núcleo (it / sec y registros totales, por ejemplo)
¡Gracias @abcdaa por esta gran biblioteca!