DBSCAN 用于地理位置数据的聚类
Posted
技术标签:
【中文标题】DBSCAN 用于地理位置数据的聚类【英文标题】:DBSCAN for clustering of geographic location data 【发布时间】:2016-04-07 08:39:54 【问题描述】:我有一个包含纬度和经度对的数据框。
这是我的数据框的样子。
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
我想聚集这些彼此最近的点(200米距离),下面是我的距离矩阵。
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. ]])
然后我在距离矩阵上应用 DBSCAN 聚类算法。
from sklearn.cluster import DBSCAN
db = DBSCAN(eps=2,min_samples=5)
y_db = db.fit_predict(distance_matrix)
我不知道如何选择 eps 和 min_samples 值。它将太远的点聚集在一个簇中。(距离约2公里)是因为它在聚类时计算欧几里得距离吗?请帮忙。
【问题讨论】:
请注意,DBSCAN 不会限制集群中的成对距离。它可传递地加入组半径epsilon,这意味着最大距离没有有用的上限(eps+eps+eps+eps+eps+...每次加入都会将最大值增加eps,所以最大距离为 (numCorePointsInCluster+1)*epsilon)。允许这种情况发生是算法的设计意图。 @Anony-Mousse 是否可以使用可用的 DBSCAN 选项将cluster size
限制为最大值?
没有。当所有东西都连接起来时,根据定义,所有东西都是一个集群。它应该是,根据集群的概念:相似的东西应该在同一个集群中,不管有多少。如果您对控制集群的大小更感兴趣,您可能更喜欢量化方法。
您好,谢谢您的提问,我也很想知道 epsilon 的单位是什么?比如eps=2,是不是代表2km?还是200m?
【参考方案1】:
您可以使用 scikit-learn 的 DBSCAN 对空间经纬度数据进行聚类,而无需预先计算距离矩阵。
db = DBSCAN(eps=2/6371., min_samples=5, algorithm='ball_tree', metric='haversine').fit(np.radians(coordinates))
这来自 clustering spatial data with scikit-learn DBSCAN 上的本教程。特别注意,eps
的值仍然是 2km,但它被 6371 除以将其转换为弧度。另外,请注意 .fit()
采用弧度单位的坐标作为半正弦度量。
【讨论】:
【参考方案2】:DBSCAN 意味着用于原始数据,具有用于加速的空间索引。我知道的唯一加速地理距离的工具是ELKI(Java)——不幸的是,scikit-learn 只支持一些距离,比如欧几里得距离(见sklearn.neighbors.NearestNeighbors
)。
但显然,您可以预先计算成对距离,所以这(还)不是问题。
但是,您没有足够仔细地阅读文档,并且您认为 DBSCAN 使用距离矩阵的假设是错误的:
from sklearn.cluster import DBSCAN
db = DBSCAN(eps=2,min_samples=5)
db.fit_predict(distance_matrix)
在距离矩阵行上使用欧几里得距离,这显然没有任何意义。
请参阅DBSCAN
的文档(已添加重点):
class sklearn.cluster.DBSCAN(eps=0.5, min_samples=5, metric='euclidean', algorithm='auto', leaf_size=30, p=None, random_state=None)
metric:字符串,或可调用
计算特征数组中实例之间的距离时使用的度量。如果 metric 是一个字符串或可调用的,它必须是 metrics.pairwise.calculate_distance 为其 metric 参数允许的选项之一。 如果度量是“预先计算的”,则 X 被假定为距离矩阵并且必须是方阵。 X 可能是稀疏矩阵,在这种情况下,只有“非零”元素可能被视为 DBSCAN 的邻居。
fit_predict
类似:
X : 数组或稀疏 (CSR) 形状矩阵 (n_samples, n_features),或数组 (n_samples, n_samples)
一个特征数组,或样本之间的距离数组if metric='precomputed'。
换句话说,你需要这样做
db = DBSCAN(eps=2, min_samples=5, metric="precomputed")
【讨论】:
这真的很有帮助。我正在开发一个名为在线食品订购应用程序的项目,我必须在其中实时聚集订购位置以进行路线优化。 DBSCAN 是解决这类问题的好方法吗? 我会使用一些知道的东西,例如关于单向街道(或一般街道)。我怀疑聚类有多大帮助,但有特定的路由优化算法。虽然如果您需要它快速,一个简单的贪婪方法可能是要走的路。 感谢您的帮助。 嘿@Anony-Mousse 我意识到你的上述评论,我有一个问题要问你。我有来自一条高速公路和一条关闭高速公路的公交专用道上的车辆 gps 的数据。我只需要使用机动车数据,那么我可以使用 DBSCAN 算法找到哪些车辆是公共汽车,然后删除高速公路数据? 这个答案没有回复原问题:“如何选择 eps & min_samples 值”。此外,“DBSCAN 旨在用于原始数据”不是真的,它取决于应用程序和使用的指标类型【参考方案3】:我不知道您正在使用 haversine
的什么实现,但看起来它以 km 为单位返回结果,所以 eps
应该是 0.2,而不是 2 代表 200 m。
对于min_samples
参数,这取决于您的预期输出。这里有几个例子。我的输出使用基于this answer 的haversine
实现,它提供了一个与您的相似但不完全相同的距离矩阵。
这是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 2 0 -1 1 2 2 0 0 0 -1 -1 -1 1 1 1 -1 -1 1 -1 -1 1]
这会创建三个集群,0, 1
和 2
,并且很多样本不属于至少有 5 个成员的集群,因此没有分配到集群(显示为 -1
)。
使用较小的min_samples
值重试:
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]
这里的大多数样本都在至少一个其他样本的 200m 范围内,因此属于 0
到 7
的八个集群之一。
编辑添加
看起来@Anony-Mousse 是对的,尽管我的结果没有发现任何问题。为了贡献一些东西,这是我用来查看集群的代码:
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()
【讨论】:
是的,我正在使用相同的 hasrsine 实现。当我使用 0.2 时,它仍然会聚集彼此相距太远的点。 当您说它对彼此相距太远的点进行聚类时,您的意思是远离聚类中最近的点还是距离聚类中最远的点? 如何知道集群的边界?我是集群的新手。我想要说明的是,两点之间的距离超过 2 公里,但它仍然包含在一个集群中。 你能举个例子吗?我没有在我的结果中看到这一点。除非您将-1
视为一个集群?
正如您在我的第二个示例中看到的,如果您减少 min_samples
参数,您将获得更多集群,因为最低成员数要求较低,因此未分配的位置将会减少。如果您增加eps
参数,那么您将获得更少的集群和更多的成员。由您决定哪个对您的目的更有用。【参考方案4】:
@eos 给出了我认为的最佳答案——除了利用Haversine distance(在这种情况下最相关的距离度量),它还避免了生成预先计算的距离矩阵的需要。如果您创建一个距离矩阵,那么您需要计算每个点组合的成对距离(尽管利用距离度量是对称的这一事实显然可以节省一点时间)。
如果您只是为 DBSCAN 提供距离测量值并使用ball_tree
算法,则可以避免计算每个可能的距离。这是因为球树算法可以使用三角不等式定理来减少需要检查以找到数据点的最近邻的候选者的数量(这是 DBSCAN 中最大的工作)。
三角不等式定理指出:
|x+y| <= |x| + |y|
...所以如果一个点p
与其邻居n
的距离为x
,另一个点q
与p
的距离为y
,如果x+y
大于我们的最近邻半径,我们知道q
必须离n
太远才被认为是邻居,所以我们不需要计算它的距离。
在scikit-learn documentation 中了解有关球树如何工作的更多信息
【讨论】:
【参考方案5】:您可以通过三种不同的方式将 DBSCAN 与 GPS 数据结合使用。第一个是您可以使用 eps 参数来指定您将考虑创建集群的数据点之间的最大距离,正如您需要考虑距离规模的其他答案中所指定的那样您正在使用的指标选择一个有意义的值。然后您可以使用 min_samples 这可以用作在移动时过滤掉数据点的一种方式。最后,metric 将允许您使用任何您想要的距离。
例如,在我正在进行的一个特定研究项目中,我想从对象的智能手机收集的 GPS 数据位置中提取重要位置。我对主题如何在城市中导航不感兴趣,而且我更愿意处理以米为单位的距离,然后我可以做下一个:
from geopy import distance
def mydist(p1, p2):
return distance.great_circle((p1[0],p1[1],100),(p2[0],p2[1],100)).meters
DBSCAN(eps=50,min_samples=50,n_jobs=-1,metric=mydist)
这里 eps 根据 DBSCAN documentation“两个样本之间的最大距离,一个被认为是在另一个附近。” 而最小样本是“一个点在邻域中被视为核心点的样本数(或总权重)。”基本上,您可以使用 eps 控制集群中数据点的距离,在上面的示例中,我选择了 100 米。 Min samples 只是一种控制密度的方法,在上面的示例中,数据以大约每秒一个样本的速度捕获,因为我对人们何时四处移动不感兴趣,而是对静止位置感兴趣想确保我从同一位置获得至少相当于 60 秒的 GPS 数据。
如果这仍然没有意义,请查看此 DBSCAN animation。
【讨论】:
以上是关于DBSCAN 用于地理位置数据的聚类的主要内容,如果未能解决你的问题,请参考以下文章