Criação de matriz de distância usando nparray com pdist e squareform
-
24-12-2019 - |
Pergunta
Estou tentando agrupar usando DBSCAN (implementação do scikit learn) e dados de localização.Meus dados estão no formato de array np, mas para usar DBSCAN com a fórmula Haversine preciso criar uma matriz de distância.Estou recebendo o seguinte erro quando tento fazer isso (um erro de 'módulo' que não pode ser chamado). Pelo que li online, este é um erro de importação, mas tenho certeza de que não é o meu caso.Criei minha própria fórmula de distância haversine, mas tenho certeza de que o erro não está nisso.
Estes são meus dados de entrada, um array 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 ]]
E esta é a linha de código que está com erro.
distance_matrix = sp.spatial.distance.squareform(sp.spatial.distance.pdist
(ResultArray,(lambda u,v: haversine(u,v))))
Esta é a mensagem de erro:
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
Eu importo scipy como sp.(importar scipy como sp)
Solução
Consulte a resposta do @TommasoF.Esta resposta está errada: pdist
permite escolher uma função de distância personalizada.Excluirei a resposta quando ela não for mais escolhida como a resposta correta.
Simplesmente scipy
de pdist
não permite passar em uma função de distância personalizada.Como você pode ler no documentos, você tem algumas opções, mas a distância ao redor não está na lista de métricas suportadas.
(Matlab pdist
suporta a opção, veja aqui)
você precisa fazer o cálculo "manualmente", ou seja,com loops, algo assim 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. ]]
Apenas para referência, uma implementação em Python de Haverside pode ser encontrada aqui.
Outras dicas
Com o Scipy você pode definir uma função de distância personalizada conforme sugerido pela documentação neste link e relatado aqui por conveniência:
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()))
Aqui eu relato minha versão do código inspirada no código deste link:
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
E ligando da seguinte maneira:
D = spatial.distance.pdist(A, lambda u, v: getDistanceByHaversine(u,v))
Na minha implementação a matriz A tem como primeira coluna os valores de longitude e como segunda coluna os valores de latitude expressos em graus decimais.
Agora você pode agrupar dados espaciais de latitude-longitude com o DBSCAN do scikit-learn e a métrica haversine sem pré-calcular uma matriz de distância usando o scipy.
db = DBSCAN(eps=2/6371., min_samples=5, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates))
Isso vem deste tutorial em agrupando dados espaciais com scikit-learn DBSCAN.Em particular, observe que o eps
o valor é 2 km dividido por 6371 (raio da Terra em km) para convertê-lo em radianos.Além disso, observe que .fit()
assume coordenadas em unidades radianas para a métrica Haversine.