J'ai une trame de données avec des paires de latitude et de longitude.
Voici à quoi ressemble mon dataframe.
order_lat order_long
0 19.111841 72.910729
1 19.111342 72.908387
2 19.111342 72.908387
3 19.137815 72.914085
4 19.119677 72.905081
5 19.119677 72.905081
6 19.119677 72.905081
7 19.120217 72.907121
8 19.120217 72.907121
9 19.119677 72.905081
10 19.119677 72.905081
11 19.119677 72.905081
12 19.111860 72.911346
13 19.111860 72.911346
14 19.119677 72.905081
15 19.119677 72.905081
16 19.119677 72.905081
17 19.137815 72.914085
18 19.115380 72.909144
19 19.115380 72.909144
20 19.116168 72.909573
21 19.119677 72.905081
22 19.137815 72.914085
23 19.137815 72.914085
24 19.112955 72.910102
25 19.112955 72.910102
26 19.112955 72.910102
27 19.119677 72.905081
28 19.119677 72.905081
29 19.115380 72.909144
30 19.119677 72.905081
31 19.119677 72.905081
32 19.119677 72.905081
33 19.119677 72.905081
34 19.119677 72.905081
35 19.111860 72.911346
36 19.111841 72.910729
37 19.131674 72.918510
38 19.119677 72.905081
39 19.111860 72.911346
40 19.111860 72.911346
41 19.111841 72.910729
42 19.111841 72.910729
43 19.111841 72.910729
44 19.115380 72.909144
45 19.116625 72.909185
46 19.115671 72.908985
47 19.119677 72.905081
48 19.119677 72.905081
49 19.119677 72.905081
50 19.116183 72.909646
51 19.113827 72.893833
52 19.119677 72.905081
53 19.114100 72.894985
54 19.107491 72.901760
55 19.119677 72.905081
Je veux regrouper ces points qui sont les plus proches les uns des autres (distance de 200 mètres) en suivant ma matrice de distance.
from scipy.spatial.distance import pdist, squareform
distance_matrix = squareform(pdist(X, (lambda u,v: haversine(u,v))))
array([[ 0. , 0.2522482 , 0.2522482 , ..., 1.67313071,
1.05925366, 1.05420922],
[ 0.2522482 , 0. , 0. , ..., 1.44111548,
0.81742536, 0.98978355],
[ 0.2522482 , 0. , 0. , ..., 1.44111548,
0.81742536, 0.98978355],
...,
[ 1.67313071, 1.44111548, 1.44111548, ..., 0. ,
1.02310118, 1.22871515],
[ 1.05925366, 0.81742536, 0.81742536, ..., 1.02310118,
0. , 1.39923529],
[ 1.05420922, 0.98978355, 0.98978355, ..., 1.22871515,
1.39923529, 0. ]])
Ensuite, j'applique l'algorithme de clustering DBSCAN sur la matrice de distance.
from sklearn.cluster import DBSCAN
db = DBSCAN(eps=2,min_samples=5)
y_db = db.fit_predict(distance_matrix)
Je ne sais pas comment choisir la valeur eps & min_samples. Il regroupe les points qui sont bien trop loin, en un seul cluster (environ 2 km de distance) Est-ce parce qu'il calcule la distance euclidienne en se regroupant? veuillez aider.
DBSCAN est signifié à utiliser sur les données brutes, avec un index spatial pour l'accélération. Le seul outil que je connaisse avec l'accélération pour les distances géographiques est ELKI (Java) - scikit-learn ne le supporte malheureusement que pour quelques distances comme la distance euclidienne (voir sklearn.neighbors.NearestNeighbors
). Mais apparemment, vous pouvez essayer de précalculer les distances par paire, donc ce n'est pas (encore) un problème.
Cependant, vous n'avez pas lu la documentation assez attentivement, et votre supposition que DBSCAN utilise une matrice de distance est fausse:
from sklearn.cluster import DBSCAN
db = DBSCAN(eps=2,min_samples=5)
db.fit_predict(distance_matrix)
utilise la distance euclidienne sur les lignes de la matrice de distance , ce qui n'a évidemment aucun sens.
Voir la documentation de DBSCAN
(soulignement ajouté):
classe sklearn.cluster.DBSCAN (eps = 0,5, min_samples = 5, metric = 'euclidean' , algorithm = 'auto', leaf_size = 30, p = Aucun, random_state = Aucun)
métrique : chaîne ou appelable
Métrique à utiliser lors du calcul de la distance entre les instances dans un tableau d'entités. Si métrique est une chaîne ou appelable, elle doit être l'une des options autorisées par metrics.pairwise.calculate_distance pour son paramètre métrique. Si la métrique est "précalculée", X est supposé être une matrice de distance et doit être carré. X peut être une matrice clairsemée, auquel cas seulement " des éléments différents de zéro peuvent être considérés comme des voisins pour DBSCAN.
similaire pour fit_predict
:
[~ # ~] x [~ # ~] : matrice ou matrice clairsemée (CSR) de forme (n_échantillons, n_fonctionnalités), ou matrice de forme ( n_échantillons, n_échantillons)
Un tableau d'entités ou un tableau de distances entre les échantillons si métrique = `` précalculé ''.
En d'autres termes, vous devez faire
db = DBSCAN(eps=2, min_samples=5, metric="precomputed")
Vous pouvez regrouper des données spatiales latitude-longitude avec DBSCAN de scikit-learn sans précalculer une matrice de distance.
db = DBSCAN(eps=2/6371., min_samples=5, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates))
Cela vient de ce tutoriel sur clustering des données spatiales avec scikit-learn DBSCAN. En particulier, notez que la valeur eps
est toujours de 2 km, mais elle est divisée par 6371 pour la convertir en radians. Notez également que .fit()
prend les coordonnées en radians pour la métrique haversine.
Je ne sais pas quelle implémentation de haversine
vous utilisez, mais il semble qu'elle renvoie des résultats en km, donc eps
devrait être 0,2, pas 2 pour 200 m.
Pour le paramètre min_samples
, Cela dépend de la sortie attendue. Voici quelques exemples. Mes sorties utilisent une implémentation de haversine
basée sur cette réponse qui donne une matrice de distance similaire, mais pas identique à la vôtre.
C'est avec db = DBSCAN(eps=0.2, min_samples=5)
[0 -1 -1 -1 1 1 1 -1 -1 1 1 1 2 2 1 1 1 -1 -1 -1 -1 1 -1 -1 -1 -1 -1 1 1 -1 1 1 1 1 1 1 2 0 -1 1 2 2 0 0 0 -1 -1 -1 1 1 1 -1 -1 1 -1 -1 1]
Cela crée trois clusters, 0, 1
Et 2
, Et de nombreux échantillons ne tombent pas dans un cluster avec au moins 5 membres et ne sont donc pas affectés à un cluster (indiqué comme -1
).
Réessayez avec une valeur min_samples
Plus petite:
db = DBSCAN(eps=0.2, min_samples=2)
[0 1 1 2 3 3 3 4 4 3 3 3 5 5 3 3 3 2 6 6 7 3 2 2 8 8 8 3 3 6 3 3 3 3 3 5 0 -1 3 5 5 0 0 0 6 -1 - 1 3 3 3 7 -1 3 -1 -1 3]
Ici, la plupart des échantillons se trouvent à moins de 200 m d'au moins un autre échantillon et tombent donc dans l'un des huit groupes 0
À 7
.
Modifié pour ajouter
Il semble que @ Anony-Mousse a raison, même si je n'ai rien vu de mal dans mes résultats. Pour contribuer à quelque chose, voici le code que j'utilisais pour voir les clusters:
from math import radians, cos, sin, asin, sqrt
from scipy.spatial.distance import pdist, squareform
from sklearn.cluster import DBSCAN
import matplotlib.pyplot as plt
import pandas as pd
def haversine(lonlat1, lonlat2):
"""
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
"""
# convert decimal degrees to radians
lat1, lon1 = lonlat1
lat2, lon2 = lonlat2
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
X = pd.read_csv('dbscan_test.csv')
distance_matrix = squareform(pdist(X, (lambda u,v: haversine(u,v))))
db = DBSCAN(eps=0.2, min_samples=2, metric='precomputed') # using "precomputed" as recommended by @Anony-Mousse
y_db = db.fit_predict(distance_matrix)
X['cluster'] = y_db
plt.scatter(X['lat'], X['lng'], c=X['cluster'])
plt.show()