2 votes

Accélérer une boucle for imbriquée à travers deux DataFrames Pandas

J'ai une latitude et une longitude stockées dans un dataframe pandas ( df ) avec des points de remplissage comme NaN para stop_id, stoplat, stoplon et dans un autre cadre de données areadf qui contient plus de lats/lons et un identifiant arbitraire ; c'est l'information qui doit être introduite dans le fichier df .

J'essaye de connecter les deux pour que les colonnes d'arrêt en df contient des informations sur l'arrêt le plus proche de ce point de latitude et de longitude, ou bien laissez-le tel quel. NaN s'il n'y a pas d'arrêt dans un rayon R du point.

Pour l'instant, mon code est le suivant, mais il prend beaucoup de temps (>40 minutes pour ce que j'exécute actuellement, avant de changer la zone en un df et d'utiliser itertuples ; je ne suis pas sûr de l'ampleur de la différence que cela fera ?) car il y a des milliers de points de lat/lon et d'arrêts pour chaque ensemble de données, ce qui est un problème parce que je dois exécuter cela sur plusieurs fichiers. Je suis à la recherche de suggestions pour accélérer le processus. J'ai déjà apporté quelques améliorations mineures (par exemple, le passage à un cadre de données, l'utilisation d'itertuples au lieu d'iterrows, la définition de lats et de lons en dehors de la boucle pour éviter d'avoir à les récupérer de df à chaque boucle) mais je suis à court d'idées pour accélérer le processus. getDistance utilise la formule Haversine telle que définie pour obtenir la distance entre le panneau d'arrêt et le point lat,lon donné.

import pandas as pd
from math import cos, asin, sqrt

R=5
lats = df['lat']
lons = df['lon']
for stop in areadf.itertuples():
    for index in df.index:
        if getDistance(lats[index],lons[index],
                       stop[1],stop[2]) < R:
            df.at[index,'stop_id'] = stop[0] # id
            df.at[index,'stoplat'] = stop[1] # lat
            df.at[index,'stoplon'] = stop[2] # lon

def getDistance(lat1,lon1,lat2,lon2):
    p = 0.017453292519943295     #Pi/180
    a = (0.5 - cos((lat2 - lat1) * p)/2 + cos(lat1 * p) * 
         cos(lat2 * p) * (1 - cos((lon2 - lon1) * p)) / 2)
    return 12742 * asin(sqrt(a)) * 100

Les données de l'échantillon :

df
lat        lon         stop_id    stoplat    stoplon
43.657676  -79.380146  NaN        NaN        NaN
43.694324  -79.334555  NaN        NaN        NaN

areadf
stop_id    stoplat    stoplon
0          43.657675  -79.380145
1          45.435143  -90.543253

Désiré :

df
lat        lon         stop_id    stoplat    stoplon
43.657676  -79.380146  0          43.657675  -79.380145
43.694324  -79.334555  NaN        NaN        NaN

1voto

DJK Points 4148

Une façon de faire serait d'utiliser la fonction haversine de numpy de aquí Il suffit de le modifier légèrement pour pouvoir tenir compte du rayon que vous souhaitez.

Il suffit d'itérer dans votre df avec appliquer et trouver la valeur la plus proche dans un rayon donné

def haversine_np(lon1, lat1, lon2, lat2,R):
    """
    Calculate the great circle distance between two points
    on the earth (specified in decimal degrees)
    All args must be of equal length.    
    """
    lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = np.sin(dlat/2.0)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2.0)**2
    c = 2 * np.arcsin(np.sqrt(a))
    km = 6367 * c
    if km.min() <= R:
        return km.argmin()
    else:
        return -1

df['dex'] = df[['lat','lon']].apply(lambda row: haversine_np(row[1],row[0],areadf.stoplon.values,areadf.stoplat.values,1),axis=1)

Ensuite, fusionnez les deux cadres de données.

df.merge(areadf,how='left',left_on='dex',right_index=True).drop('dex',axis=1)

         lat        lon  stop_id    stoplat    stoplon
0  43.657676 -79.380146      0.0  43.657675 -79.380145
1  43.694324 -79.334555      NaN        NaN        NaN

NOTE : Si vous choisissez de suivre cette méthode, vous devez être sûr que les index des deux dataframes sont réinitialisés ou qu'ils sont ordonnés séquentiellement de 0 à la longueur totale de df. Assurez-vous donc de réinitialiser les index avant d'exécuter cette méthode.

df.reset_index(drop=True,inplace=True)
areadf.reset_index(drop=True,inplace=True)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X