Creación de matriz de distancia usando nparray con pdist y squareform
-
24-12-2019 - |
Pregunta
Estoy intentando agrupar usando DBSCAN (implementación de scikit learn) y datos de ubicación.Mis datos están en formato de matriz np, pero para usar DBSCAN con la fórmula de Haversine necesito crear una matriz de distancia.Recibo el siguiente error cuando intento hacer esto (un error de 'módulo' no invocable). Por lo que he leído en línea, este es un error de importación, pero estoy bastante seguro de que ese no es mi caso.He creado mi propia fórmula de distancia haversine, pero estoy seguro de que el error no se debe a esto.
Estos son mis datos de entrada, una matriz np (ResultArray).
[[ 53.3252628 -6.2644198 ]
[ 53.3287395 -6.2646543 ]
[ 53.33321202 -6.24785807]
[ 53.3261015 -6.2598324 ]
[ 53.325291 -6.2644105 ]
[ 53.3281323 -6.2661467 ]
[ 53.3253074 -6.2644483 ]
[ 53.3388147 -6.2338417 ]
[ 53.3381102 -6.2343826 ]
[ 53.3253074 -6.2644483 ]
[ 53.3228188 -6.2625379 ]
[ 53.3253074 -6.2644483 ]]
Y esta es la línea de código que genera errores.
distance_matrix = sp.spatial.distance.squareform(sp.spatial.distance.pdist
(ResultArray,(lambda u,v: haversine(u,v))))
Este es el mensaje de error:
File "Location.py", line 48, in <module>
distance_matrix = sp.spatial.distance.squareform(sp.spatial.distance.pdist
(ResArray,(lambda u,v: haversine(u,v))))
File "/usr/lib/python2.7/dist-packages/scipy/spatial/distance.py", line 1118, in pdist
dm[k] = dfun(X[i], X[j])
File "Location.py", line 48, in <lambda>
distance_matrix = sp.spatial.distance.squareform(sp.spatial.distance.pdist
(ResArray,(lambda u,v: haversine(u,v))))
TypeError: 'module' object is not callable
Importo scipy como sp.(importar scipy como sp)
Solución
Consulte la respuesta de @TommasoF.Esta respuesta es incorrecta: pdist
permite elegir una función de distancia personalizada.Eliminaré la respuesta una vez que ya no sea elegida como la respuesta correcta.
Simplemente scipy
's pdist
no permite pasar en una función de distancia personalizada.Como puedes leer en el documentos, tiene algunas opciones, pero la distancia lateral no está dentro de la lista de métricas admitidas.
(Matlab pdist
Sin embargo, admite la opción, consulte aquí)
es necesario hacer el cálculo "manualmente", es decircon bucles, algo como esto funcionará:
from numpy import array,zeros
def haversine(lon1, lat1, lon2, lat2):
""" See the link below for a possible implementation """
pass
#example input (your's, truncated)
ResultArray = array([[ 53.3252628, -6.2644198 ],
[ 53.3287395 , -6.2646543 ],
[ 53.33321202 , -6.24785807],
[ 53.3253074 , -6.2644483 ]])
N = ResultArray.shape[0]
distance_matrix = zeros((N, N))
for i in xrange(N):
for j in xrange(N):
lati, loni = ResultArray[i]
latj, lonj = ResultArray[j]
distance_matrix[i, j] = haversine(loni, lati, lonj, latj)
distance_matrix[j, i] = distance_matrix[i, j]
print distance_matrix
[[ 0. 0.38666203 1.41010971 0.00530489]
[ 0.38666203 0. 1.22043364 0.38163748]
[ 1.41010971 1.22043364 0. 1.40848782]
[ 0.00530489 0.38163748 1.40848782 0. ]]
Sólo como referencia, se puede encontrar una implementación en Python de Haverside. aquí.
Otros consejos
Con Scipy puede definir una función de distancia personalizada según lo sugerido por la documentación en este enlace y reportado aquí por conveniencia:
Y = pdist(X, f)
Computes the distance between all pairs of vectors in X using the user supplied 2-arity function f. For example, Euclidean distance between the vectors could be computed as follows:
dm = pdist(X, lambda u, v: np.sqrt(((u-v)**2).sum()))
Aquí denuncia mi versión del código inspirado en el código de este enlace :
from numpy import sin,cos,arctan2,sqrt,pi # import from numpy
# earth's mean radius = 6,371km
EARTHRADIUS = 6371.0
def getDistanceByHaversine(loc1, loc2):
'''Haversine formula - give coordinates as a 2D numpy array of
(lat_denter link description hereecimal,lon_decimal) pairs'''
#
# "unpack" our numpy array, this extracts column wise arrays
lat1 = loc1[1]
lon1 = loc1[0]
lat2 = loc2[1]
lon2 = loc2[0]
#
# convert to radians ##### Completely identical
lon1 = lon1 * pi / 180.0
lon2 = lon2 * pi / 180.0
lat1 = lat1 * pi / 180.0
lat2 = lat2 * pi / 180.0
#
# haversine formula #### Same, but atan2 named arctan2 in numpy
dlon = lon2 - lon1
dlat = lat2 - lat1
a = (sin(dlat/2))**2 + cos(lat1) * cos(lat2) * (sin(dlon/2.0))**2
c = 2.0 * arctan2(sqrt(a), sqrt(1.0-a))
km = EARTHRADIUS * c
return km
y llamando de la siguiente manera:
D = spatial.distance.pdist(A, lambda u, v: getDistanceByHaversine(u,v))
En mi implementación, la matriz A tiene la primera columna los valores de longitud y como la segunda columna los valores de latitud expresados en grados decimales.
Ahora puede agrupar datos espaciales de latitud y longitud con DBSCAN de scikit-learn y la métrica haversine sin precalcular una matriz de distancia usando scipy.
db = DBSCAN(eps=2/6371., min_samples=5, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates))
Esto viene de este tutorial en agrupación de datos espaciales con scikit-learn DBSCAN.En particular, observe que el eps
El valor es 2 km dividido por 6371 (radio de la Tierra en km) para convertirlo a radianes.Además, observe que .fit()
toma coordenadas en unidades de radianes para la métrica haversine.