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, 12,并且很多样本不属于至少有 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 范围内,因此属于 07 的八个集群之一。

编辑添加

看起来@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,另一个点qp 的距离为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 用于地理位置数据的聚类的主要内容,如果未能解决你的问题,请参考以下文章

空间聚类算法简述

DBSCAN 算法

聚类算法--DBSCAN

基于密度的聚类方法

DBSCAN 按位置和密度对数据进行聚类

R中DBSCAN的聚类中心平均值?