Avant de parler de parallélisation, vous pouvez travailler sur l'optimisation de votre boucle. La première façon serait d'itérer sur les données au lieu des valeurs incrémentales sur la longueur et ensuite accéder aux données à chaque fois :
#exemple jouet
np.random.seed(1)
size_nbi = 20
size_Final = 100
nbi = pd.DataFrame({'LONG_017':np.random.random(size=size_nbi)/100+73,
'LAT_016':np.random.random(size=size_nbi)/100+73,})
Final = pd.DataFrame({'X':np.random.random(size=size_Final)/100+73,
'Y':np.random.random(size=size_Final)/100+73,
'CUM_OCC':np.random.randint(size_Final,size=size_Final)})
En utilisant votre méthode, vous obtenez environ 75 ms pour ces tailles de dataframes :
%%timeit
for i in range (len(nbi.LONG_017)):
StoredOCC = []
for j in range (len(Final.X)):
r = haversine(nbi.LONG_017[i], nbi.LAT_016[i], Final.X[j], Final.Y[j])
if (r < 0.03048):
SWw = Final.CUM_OCC[j]
StoredOCC.append(SWw)
if len(StoredOCC) != 0:
nbi.loc[i, 'ADTT_02_2019'] = np.max(StoredOCC)
# 75.6 ms ± 4.05 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Maintenant, si vous changez légèrement les boucles, itérer sur les données elles-mêmes et utiliser une liste pour le résultat puis mettre le résultat comme une colonne en dehors des boucles, vous pouvez descendre à 5ms donc 15 fois plus rapide, juste en l'optimisant (et vous pouvez trouver une meilleure optimisation) :
%%timeit
res_ADIT = []
for lon1, lat1 in zip (nbi.LONG_017.to_numpy(),
nbi.LAT_016.to_numpy()):
StoredOCC = []
for lon2, lat2, SWw in zip(Final.X.to_numpy(),
Final.Y.to_numpy(),
Final.CUM_OCC.to_numpy()):
r = haversine(lon1, lat1, lon2, lat2)
if (r < 0.03048):
StoredOCC.append(SWw)
if len(StoredOCC) != 0:
res_ADIT.append(np.max(StoredOCC))
else:
res_ADIT.append(np.nan)
nbi['ADIT_v2'] = res_ADIT
# 5.23 ms ± 305 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Maintenant, vous pouvez aller encore plus loin et utiliser numpy
et la vectorisation de votre deuxième boucle, et également passer directement les valeurs en radians au lieu de devoir les map
à chaque boucle :
# liste vide pour le résultat
res_ADIT = []
# travailler avec des tableaux en radians
arr_lon2 = np.radians(Final.X.to_numpy())
arr_lat2 = np.radians(Final.Y.to_numpy())
arr_OCC = Final.CUM_OCC.to_numpy()
for lon1, lat1 in zip (np.radians(nbi.LONG_017), #pass directly the radians
np.radians(nbi.LAT_016):
# faire toutes les soustractions de manière vectorielle
arr_dlon = arr_lon2 - lon1
arr_dlat = arr_lat2 - lat1
# pareil ici en utilisant des fonctions numpy
arr_dist = np.sin(arr_dlat/2)**2 + np.cos(lat1) * np.cos(arr_lat2) * np.sin(arr_dlon/2)**2
arr_dist = 2 * np.arcsin(np.sqrt(arr_dist))
arr_dist *= 6372.8
# extraire les valeurs de CUM_OCC qui répondent au critère
r = arr_OCC[arr_dist<0.03048]
# vérifier qu'il y a au moins un élément
if r.size>0:
res_ADIT.append(max(r))
else :
res_ADIT.append(np.nan)
nbi['AUDIT_np'] = res_ADIT
Si vous faites un timeit
de ceci, vous descendez à 819 µs ± 15.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
donc environ 90 fois plus rapide que votre solution initiale pour la petite taille des dataframes et tous les résultats sont les mêmes
print(nbi)
LONG_017 LAT_016 ADTT_02_2019 ADIT_v2 AUDIT_np
0 73.004170 73.008007 NaN NaN NaN
1 73.007203 73.009683 30.0 30.0 30.0
2 73.000001 73.003134 14.0 14.0 14.0
3 73.003023 73.006923 82.0 82.0 82.0
4 73.001468 73.008764 NaN NaN NaN
5 73.000923 73.008946 NaN NaN NaN
6 73.001863 73.000850 NaN NaN NaN
7 73.003456 73.000391 NaN NaN NaN
8 73.003968 73.001698 21.0 21.0 21.0
9 73.005388 73.008781 NaN NaN NaN
10 73.004192 73.000983 93.0 93.0 93.0
11 73.006852 73.004211 NaN NaN NaN
Vous pouvez jouer un peu avec le code et augmenter la taille de chaque dataframe jouet, et vous verrez comment en augmentant surtout la taille des données Final, le gain devient intéressant par exemple avec size_nbi = 20
et size_Final = 1000
, vous obtenez un gain de 400 fois plus rapide avec la solution vectorisée. et ainsi de suite. Pour votre taille complète de données (3K * 6M), vous avez encore besoin d'un peu de temps et sur mon ordinateur cela prendrait environ 25 minutes selon mon estimation alors que votre solution est en centaines d'heures. Maintenant, si cela n'est pas suffisant, vous pouvez penser à la parallélisation ou également utiliser numba