Pour tous ceux qui cherchent à appliquer tqdm sur leur code d'application de pandas parallèle personnalisé.
(J'ai essayé certaines des bibliothèques de parallélisation au fil des ans, mais je n'ai jamais trouvé de solution de parallélisation à 100%, principalement pour la fonction apply, et je devais toujours revenir pour mon code "manuel".)
df_multi_core - c'est celui que vous appelez. Il accepte:
- Votre objet df
- Le nom de la fonction que vous souhaitez appeler
- Le sous-ensemble de colonnes sur lequel la fonction peut être exécutée (aide à réduire le temps / la mémoire)
- Le nombre de travaux à exécuter en parallèle (-1 ou omis pour tous les cœurs)
- Tout autre kwargs accepté par la fonction df (comme "axis")
_df_split - il s'agit d'une fonction d'assistance interne qui doit être positionnée globalement sur le module en cours d'exécution (Pool.map est "dépendant du placement"), sinon je la localiserais en interne ..
voici le code de mon résumé (j'ajouterai plus de tests de fonction pandas ici):
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
Ci-dessous est un code de test pour une application parallélisée avec 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))
Dans la sortie, vous pouvez voir 1 barre de progression pour l'exécution sans parallélisation et des barres de progression par cœur lors de l'exécution avec parallélisation. Il y a un léger accrochage et parfois le reste des cœurs apparaît à la fois, mais même dans ce cas, je pense que c'est utile car vous obtenez les statistiques de progression par cœur (it / sec et total des enregistrements, par exemple)
Merci @abcdaa pour cette superbe bibliothèque!