Vectorización del cálculo de la distancia de Haversine en Python

Estoy tratando de calcular una matriz de distancias para una larga lista de ubicaciones identificadas por Latitud y Longitud utilizando la fórmula de Haversine que toma dos tuplas de pares de coordenadas para producir la distancia:

def haversine(point1, point2, miles=False): """ Calculate the great-circle distance bewteen two points on the Earth surface. :input: two 2-tuples, containing the latitude and longitude of each point in decimal degrees. Example: haversine((45.7597, 4.8422), (48.8567, 2.3508)) :output: Returns the distance bewteen the two points. The default unit is kilometers. Miles can be returned if the ``miles`` parameter is set to True. """ 

Puedo calcular la distancia entre todos los puntos usando un bucle nested para:

 data.head() id coordinates 0 1 (16.3457688674, 6.30354512503) 1 2 (12.494749307, 28.6263955635) 2 3 (27.794615136, 60.0324947881) 3 4 (44.4269923769, 110.114216113) 4 5 (-69.8540884125, 87.9468778773) 

utilizando una función simple:

 distance = {} def haver_loop(df): for i, point1 in df.iterrows(): distance[i] = [] for j, point2 in df.iterrows(): distance[i].append(haversine(point1.coordinates, point2.coordinates)) return pd.DataFrame.from_dict(distance, orient='index') 

Pero esto toma bastante tiempo dada la complejidad del tiempo, se ejecuta alrededor de los 20 por 500 puntos y tengo una lista mucho más larga. Esto me hace ver la vectorización, y me he topado con numpy.vectorize ( (docs) , pero no numpy.vectorize cómo aplicarlo en este contexto).

np.vectorize() proporcionar su función como un argumento a np.vectorize() , y luego podría usarla como un argumento para pandas.groupby.apply como se ilustra a continuación:

 haver_vec = np.vectorize(haversine, otypes=[np.int16]) distance = df.groupby('id').apply(lambda x: pd.Series(haver_vec(df.coordinates, x.coordinates))) 

Por ejemplo, con datos de muestra de la siguiente manera:

 length = 500 df = pd.DataFrame({'id':np.arange(length), 'coordinates':tuple(zip(np.random.uniform(-90, 90, length), np.random.uniform(-180, 180, length)))}) 

comparar por 500 puntos:

 def haver_vect(data): distance = data.groupby('id').apply(lambda x: pd.Series(haver_vec(data.coordinates, x.coordinates))) return distance %timeit haver_loop(df): 1 loops, best of 3: 35.5 s per loop %timeit haver_vect(df): 1 loops, best of 3: 593 ms per loop 

Desde haversine's function definition , se veía bastante paralelizable . Por lo tanto, al utilizar una de las mejores herramientas de vectorización con NumPy al igual que la broadcasting y al reemplazar las funciones matemáticas con los equivalentes NumPy ufuncs , aquí hay una solución vectorizada:

 # Get data as a Nx2 shaped NumPy array data = np.array(df['coordinates'].tolist()) # Convert to radians data = np.deg2rad(data) # Extract col-1 and 2 as latitudes and longitudes lat = data[:,0] lng = data[:,1] # Elementwise differentiations for lattitudes & longitudes diff_lat = lat[:,None] - lat diff_lng = lng[:,None] - lng # Finally Calculate haversine d = np.sin(diff_lat/2)**2 + np.cos(lat[:,None])*np.cos(lat) * np.sin(diff_lng/2)**2 return 2 * 6371 * np.arcsin(np.sqrt(d)) 

Pruebas de tiempo de ejecución

La otra np.vectorize based solution ha mostrado cierta promesa positiva en cuanto a la mejora del rendimiento con respecto al código original, por lo que esta sección comparará el enfoque basado en la difusión publicado con ese.

Definiciones de funciones –

 def vectotized_based(df): haver_vec = np.vectorize(haversine, otypes=[np.int16]) return df.groupby('id').apply(lambda x: pd.Series(haver_vec(df.coordinates, x.coordinates))) def broadcasting_based(df): data = np.array(df['coordinates'].tolist()) data = np.deg2rad(data) lat = data[:,0] lng = data[:,1] diff_lat = lat[:,None] - lat diff_lng = lng[:,None] - lng d = np.sin(diff_lat/2)**2 + np.cos(lat[:,None])*np.cos(lat) * np.sin(diff_lng/2)**2 return 2 * 6371 * np.arcsin(np.sqrt(d)) 

Tiempos –

 In [123]: # Input ...: length = 500 ...: d1 = np.random.uniform(-90, 90, length) ...: d2 = np.random.uniform(-180, 180, length) ...: coords = tuple(zip(d1, d2)) ...: df = pd.DataFrame({'id':np.arange(length), 'coordinates':coords}) ...: In [124]: %timeit vectotized_based(df) 1 loops, best of 3: 1.12 s per loop In [125]: %timeit broadcasting_based(df) 10 loops, best of 3: 68.7 ms per loop 

Comience por obtener todas las combinaciones usando itertools.product

  results= [(p1,p2,haversine(p1,p2))for p1,p2 in itertools.product(points,repeat=2)] 

Dicho esto, no estoy seguro de qué tan rápido será este aspecto, podría ser un duplicado de Python: acelerando la comparación geográfica