DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一个比较有代表性的基于密度的聚类算法。与划分和层次聚类方法不同,它将簇定义为密度相连的点的最大集合,能够把具有足够高密度的区域划分为簇,并可在噪声的空间数据库中发现任意形状的聚类。
DBSCAN中的几个定义:
Ε邻域:给定对象半径为Ε内的区域称为该对象的Ε邻域;
核心对象:如果给定对象Ε领域内的样本点数大于等于MinPts,则称该对象为核心对象;
直接密度可达:对于样本集合D,如果样本点q在p的Ε领域内,并且p为核心对象,那么对象q从对象p直接密度可达。
密度可达:对于样本集合D,给定一串样本点p1,p2….pn,p= p1,q= pn,假如对象pi从pi-1直接密度可达,那么对象q从对象p密度可达。
密度相连:存在样本集合D中的一点o,如果对象o到对象p和对象q都是密度可达的,那么p和q密度相联。
可以发现,密度可达是直接密度可达的传递闭包,并且这种关系是非对称的。密度相连是对称关系。DBSCAN目的是找到密度相连对象的最大集合。
Eg: 假设半径Ε=3,MinPts=3,点p的E领域中有点{m,p,p1,p2,o}, 点m的E领域中有点{m,q,p,m1,m2},点q的E领域中有点{q,m},点o的E领域中有点{o,p,s},点s的E领域中有点{o,s,s1}.
那么核心对象有p,m,o,s(q不是核心对象,因为它对应的E领域中点数量等于2,小于MinPts=3);
点m从点p直接密度可达,因为m在p的E领域内,并且p为核心对象;
点q从点p密度可达,因为点q从点m直接密度可达,并且点m从点p直接密度可达;
点q到点s密度相连,因为点q从点p密度可达,并且s从点p密度可达。
DBSCAN算法描述:
输入: 包含n个对象的数据库,半径e,最少数目MinPts;
输出:所有生成的簇,达到密度要求。
(1)Repeat
(2)从数据库中抽出一个未处理的点;
(3)IF抽出的点是核心点 THEN 找出所有从该点密度可达的对象,形成一个簇;
(4)ELSE 抽出的点是边缘点(非核心对象),跳出本次循环,寻找下一个点;
(5)UNTIL 所有的点都被处理。
DBSCAN对用户定义的参数很敏感,细微的不同都可能导致差别很大的结果,而参数的选择无规律可循,只能靠经验确定。
好处
1. 与K-means方法相比,DBSCAN不需要事先知道要形成的簇类的数量。
2. 与K-means方法相比,DBSCAN可以发现任意形状的簇类。
3. 同时,DBSCAN能够识别出噪声点。
4.DBSCAN对于数据库中样本的顺序不敏感,即Pattern的输入顺序对结果的影响不大。但是,对于处于簇类之间边界样本,可能会根据哪个簇类优先被探测到而其归属有所摆动。
缺点
1. DBScan不能很好反映高维数据。
2. DBScan不能很好反映数据集以变化的密度。
代码
# coding=utf-8
from numpy import *
import matplotlib.pyplot as plt
from matplotlib.pyplot import *
from collections import defaultdict
import random
class DbScan(object):
def show(self,data,color=None):
if not color:
color='green'
group=self.createDataSet()
fig = plt.figure(1)
axes = fig.add_subplot(111)
axes.scatter(group[:, 0], group[:, 1], s=40, c='red')
axes.scatter(data[:, 0], data[:, 1], s=50, c=color)
plt.show()
def createDataSet(self):
group = [[1.0, 1.1], [1.0, 1.0],
[0, 0], [0, 0.1],
[2, 1.0], [2.1, 0.9],
[0.3, 0.0], [1.1, 0.9],
[2.2, 1.0], [2.1, 0.8],
[3.3, 3.5], [2.1, 0.9],
[2, 1.0], [2.1, 0.9],
[3.5, 3.4], [3.6, 3.5]
]
return group
def dist(self,p1, p2):
return ((p1[0]-p2[0])**2+ (p1[1]-p2[1])**2)**(0.5)
def db_scan(self):
all_points = self.createDataSet()
E = 0.3
minPts = 2
# find out the core points
other_points = []
core_points = [] # 核心点集合
plotted_points = [] # 使用到的点 非噪声点
for point in all_points:
point.append(0) # 在点的后面加上第三维度类别,初始类别为 0
total = 0
for otherPoint in all_points:
distance = self.dist(otherPoint, point) # 遍历其他点并计算距离
if distance <= E:
total += 1 # 计算当前点的e领域内点的个数
if total > minPts:
core_points.append(point) # 是核心点,添加到列表core
plotted_points.append(point) # 将核心点添加到列表 plotted
else:
other_points.append(point) # 不是核心点,添加到其他点
# find border points
border_points = []
for core in core_points: # 遍历核心点
for other in other_points: # 遍历非核心点
if self.dist(core, other) <= E:
border_points.append(other) # 添加到非噪声点集合
plotted_points.append(other) # 添加到非噪声点集合
# implement the algorithm
cluster_label = 0
for point in core_points: # 遍历核心点
if point[2] == 0: # 核心点所属类别为0
cluster_label += 1
point[2] = cluster_label # 每遍历一个核心点,类别栏就加1
for point2 in plotted_points: # 遍历非噪声点
distance = self.dist(point2, point)
if point2[2] == 0 and distance <= E: # 非噪声点的类别为0 并且与核心点的距离小于e
point2[2] = point[2] # 将核心点的类别赋值给非噪声点
# print point, point2
# after the points are asssigned correnponding labels, we group them
cluster_list = defaultdict(lambda: [[], []]) # 定义一个字典,默认值是包含两个列表的列表
for point in plotted_points:
cluster_list[point[2]][0].append(point[0]) # 类别为键,值得第一个列表是非噪声点的x
cluster_list[point[2]][1].append(point[1]) # 类别为键,值得第一个列表是非噪声点的y
markers = ['+', '*', '.', 'd', '^', 'v', '>', '<', 'p']
#
# plotting the clusters
i = 0
print cluster_list
for value in cluster_list:
cluster = cluster_list[value]
plot(cluster[0], cluster[1], markers[i])
i = i % 10 + 1
# plot the noise points as well
noise_points = []
for point in all_points:
if not point in core_points and not point in border_points:
noise_points.append(point)
noisex = []
noisey = []
for point in noise_points:
noisex.append(point[0])
noisey.append(point[1])
plot(noisex, noisey, "x")
# /#
# title(str(len(cluster_list)) + " clusters created with E =" + str(E) + " Min Points=" + str(
# minPts) + " total points=" + str(len(all_points)) + " noise Points = " + str(len(noise_points)))
axis((-1, 5, -1, 5))
show()
def start(self):
self.db_scan()
p=DbScan()
p.start()
dbscan 在2维上效果要优于kmeans,不过各有利弊,一个是靠数据密度做输入,一个是靠分类类别做输入