Marco de datos de Pandas: une elementos dentro del rango en función de sus coordenadas geográficas (longitud y latitud)

Obtuve un dataframe que contiene lugares con su latitud y longitud. Imagina por ejemplo ciudades.

df = pd.DataFrame([{'city':"Berlin", 'lat':52.5243700, 'lng':13.4105300}, {'city':"Potsdam", 'lat':52.3988600, 'lng':13.0656600}, {'city':"Hamburg", 'lat':53.5753200, 'lng':10.0153400}]); 

Ahora estoy tratando de poner todas las ciudades en un radio alrededor de otra. Digamos todas las ciudades a una distancia de 500 km de Berlín, 500 km de Hamburgo y así sucesivamente. Lo haría duplicando el dataframe original y uniendo ambos con una función de distancia.

El resultado intermedio sería algo así:

 Berlin --> Potsdam Berlin --> Hamburg Potsdam --> Berlin Potsdam --> Hamburg Hamburg --> Potsdam Hamburg --> Berlin 

Este resultado final después de la agrupación (reducción) debería ser así. Observación: Estaría bien si la lista de valores incluye todas las columnas de la ciudad.

 Berlin --> [Potsdam, Hamburg] Potsdam --> [Berlin, Hamburg] Hamburg --> [Berlin, Potsdam] 

O simplemente el conteo de ciudades a 500 km alrededor de una ciudad.

 Berlin --> 2 Potsdam --> 2 Hamburg --> 2 

Como soy bastante nuevo en Python, apreciaría cualquier punto de partida. Estoy familiarizado con la distancia haversine. Pero no estoy seguro si existen métodos espaciales / espaciales útiles en Scipy o Pandas.

Me alegro si puedes darme un punto de partida. Hasta ahora he intentado seguir este post .

Actualización: la idea original detrás de esta pregunta proviene de la competencia de Kaggle de listado de alquiler Two Sigma Connect . La idea es conseguir que los 100m listados alrededor de otro listado. Lo que a) indica una densidad y, por lo tanto, un área popular yb) si las direcciones son comparables, puede averiguar si hay un cruce y, por lo tanto, un área ruidosa. Por lo tanto, no necesita la relación completa entre elementos, ya que necesita comparar no solo la distancia, sino también la dirección y otros metadatos. PD: no estoy subiendo una solución a Kaggle. Solo quiero aprender

Puedes usar:

 from math import radians, cos, sin, asin, sqrt def haversine(lon1, lat1, lon2, lat2): lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) # haversine formula dlon = lon2 - lon1 dlat = lat2 - lat1 a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 c = 2 * asin(sqrt(a)) r = 6371 # Radius of earth in kilometers. Use 3956 for miles return c * r 

Primero es necesario unir la merge con merge , eliminar la fila con los mismos valores en city_x y city_y mediante la boolean indexing :

 df['tmp'] = 1 df = pd.merge(df,df,on='tmp') df = df[df.city_x != df.city_y] print (df) city_x lat_x lng_x tmp city_y lat_y lng_y 1 Berlin 52.52437 13.41053 1 Potsdam 52.39886 13.06566 2 Berlin 52.52437 13.41053 1 Hamburg 53.57532 10.01534 3 Potsdam 52.39886 13.06566 1 Berlin 52.52437 13.41053 5 Potsdam 52.39886 13.06566 1 Hamburg 53.57532 10.01534 6 Hamburg 53.57532 10.01534 1 Berlin 52.52437 13.41053 7 Hamburg 53.57532 10.01534 1 Potsdam 52.39886 13.06566 

Luego aplique la función haversine :

 df['dist'] = df.apply(lambda row: haversine(row['lng_x'], row['lat_x'], row['lng_y'], row['lat_y']), axis=1) 

Distancia del filtro:

 df = df[df.dist < 500] print (df) city_x lat_x lng_x tmp city_y lat_y lng_y dist 1 Berlin 52.52437 13.41053 1 Potsdam 52.39886 13.06566 27.215704 2 Berlin 52.52437 13.41053 1 Hamburg 53.57532 10.01534 255.223782 3 Potsdam 52.39886 13.06566 1 Berlin 52.52437 13.41053 27.215704 5 Potsdam 52.39886 13.06566 1 Hamburg 53.57532 10.01534 242.464120 6 Hamburg 53.57532 10.01534 1 Berlin 52.52437 13.41053 255.223782 7 Hamburg 53.57532 10.01534 1 Potsdam 52.39886 13.06566 242.464120 

Y por último crear list o obtener size con groupby :

 df1 = df.groupby('city_x')['city_y'].apply(list) print (df1) city_x Berlin [Potsdam, Hamburg] Hamburg [Berlin, Potsdam] Potsdam [Berlin, Hamburg] Name: city_y, dtype: object df2 = df.groupby('city_x')['city_y'].size() print (df2) city_x Berlin 2 Hamburg 2 Potsdam 2 dtype: int64 

También es posible usar numpy haversine solution :

 def haversine_np(lon1, lat1, lon2, lat2): """ 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 return km df['tmp'] = 1 df = pd.merge(df,df,on='tmp') df = df[df.city_x != df.city_y] #print (df) df['dist'] = haversine_np(df['lng_x'],df['lat_x'],df['lng_y'],df['lat_y']) city_x lat_x lng_x tmp city_y lat_y lng_y dist 1 Berlin 52.52437 13.41053 1 Potsdam 52.39886 13.06566 27.198616 2 Berlin 52.52437 13.41053 1 Hamburg 53.57532 10.01534 255.063541 3 Potsdam 52.39886 13.06566 1 Berlin 52.52437 13.41053 27.198616 5 Potsdam 52.39886 13.06566 1 Hamburg 53.57532 10.01534 242.311890 6 Hamburg 53.57532 10.01534 1 Berlin 52.52437 13.41053 255.063541 7 Hamburg 53.57532 10.01534 1 Potsdam 52.39886 13.06566 242.311890 

ACTUALIZACIÓN: Yo sugeriría primero construir un DataFrame a distancia:

 from scipy.spatial.distance import squareform, pdist from itertools import combinations # see definition of "haversine_np()" below x = pd.DataFrame({'dist':pdist(df[['lat','lng']], haversine_np)}, index=pd.MultiIndex.from_tuples(tuple(combinations(df['city'], 2)))) 

Produce eficientemente la distancia de pares DF (sin duplicados):

 In [106]: x Out[106]: dist Berlin Potsdam 27.198616 Hamburg 255.063541 Potsdam Hamburg 242.311890 

Respuesta antigua:

Aquí hay una versión optimizada para bits, que utiliza el método scipy.spatial.distance.pdist :

 from scipy.spatial.distance import squareform, pdist # slightly modified version: of http://stackoverflow.com/a/29546836/2901002 def haversine_np(p1, p2): """ Calculate the great circle distance between two points on the earth (specified in decimal degrees) All args must be of equal length. """ lat1, lon1, lat2, lon2 = np.radians([p1[0], p1[1], p2[0], p2[1]]) 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 return km x = pd.DataFrame(squareform(pdist(df[['lat','lng']], haversine_np)), columns=df.city.unique(), index=df.city.unique()) 

esto nos da:

 In [78]: x Out[78]: Berlin Potsdam Hamburg Berlin 0.000000 27.198616 255.063541 Potsdam 27.198616 0.000000 242.311890 Hamburg 255.063541 242.311890 0.000000 

contemos el número de ciudades donde la distancia es mayor que 30 :

 In [81]: x.groupby(level=0, as_index=False) \ ...: .apply(lambda c: c[c>30].notnull().sum(1)) \ ...: .reset_index(level=0, drop=True) Out[81]: Berlin 1 Hamburg 2 Potsdam 1 dtype: int64