我看到MerseyViking建议使用四叉树。我将提出相同的建议,并且为了解释它,这是代码和示例。该代码是用书面语言编写的,R
但应该易于移植到Python。
这个想法非常简单:在x方向上将点分成大约一半,然后在y方向上递归地将两半分开,在每个级别上交替方向,直到不再需要拆分为止。
因为目的是掩盖实际的点位置,所以在分割中引入一些随机性很有用。一种快速简单的方法是在分位数集上将少量随机数从50%分开。以这种方式(a)分裂值极不可能与数据坐标重合,因此点将唯一地落入由分区创建的象限中,并且(b)点坐标将无法从四叉树精确地重建。
因为目的是k
在每个四叉树叶子中维持最少数量的节点,所以我们实现了四叉树的受限形式。它将支持(1)将聚类点分为组,每个组之间有k
2 * k
-1个元素,以及(2)映射象限。
这段R
代码创建了节点和末端叶子的树,并按类对其进行区分。类标签加快了后处理(例如绘图)的速度,如下所示。该代码使用数字值作为ID。这在树中的深度达到52(使用双精度;如果使用无符号长整数,则最大深度为32)。对于较深的树(在任何应用程序中都不太可能,因为至少k
涉及* 2 ^ 52点),所以id必须是字符串。
quadtree <- function(xy, k=1) {
d = dim(xy)[2]
quad <- function(xy, i, id=1) {
if (length(xy) < 2*k*d) {
rv = list(id=id, value=xy)
class(rv) <- "quadtree.leaf"
}
else {
q0 <- (1 + runif(1,min=-1/2,max=1/2)/dim(xy)[1])/2 # Random quantile near the median
x0 <- quantile(xy[,i], q0)
j <- i %% d + 1 # (Works for octrees, too...)
rv <- list(index=i, threshold=x0,
lower=quad(xy[xy[,i] <= x0, ], j, id*2),
upper=quad(xy[xy[,i] > x0, ], j, id*2+1))
class(rv) <- "quadtree"
}
return(rv)
}
quad(xy, 1)
}
注意,该算法的递归分而治之设计(和,因此,大部分的后处理算法)的装置,该时间要求是O(m)和RAM使用是O(n),其中m
是的数单元格和n
是点数。 m
与n
除以每个单元格的最小点成比例,k
。这对于估计计算时间很有用。例如,如果将n = 10 ^ 6点划分为50-99点(k = 50)的单元需要13秒,则m = 10 ^ 6/50 =20000。如果您想将其划分为5-9每个像元的点数(k = 5),m大10倍,因此计时时间增加到大约130秒。(因为随着像元变小,在它们的中心附近分解一组坐标的过程变得更快,所以实际时间仅为90秒。)要一直到每个像元k = 1点,将需要大约六倍的时间。还是九分钟,我们可以预期代码实际上会比这快一点。
在继续之前,让我们生成一些有趣的不规则空间数据并创建其受限的四叉树(经过0.29秒的时间):
这是生成这些图的代码。它利用了R
多态性:例如,points.quadtree
只要将points
函数应用于quadtree
对象,就会被调用。该功能的强大之处在于,可以根据聚类标识符为这些点着色,从而极大地简化了功能:
points.quadtree <- function(q, ...) {
points(q$lower, ...); points(q$upper, ...)
}
points.quadtree.leaf <- function(q, ...) {
points(q$value, col=hsv(q$id), ...)
}
绘制网格本身有点棘手,因为它需要重复裁剪用于四叉树划分的阈值,但是相同的递归方法既简单又优雅。如果需要,可以使用变体构造象限的多边形表示。
lines.quadtree <- function(q, xylim, ...) {
i <- q$index
j <- 3 - q$index
clip <- function(xylim.clip, i, upper) {
if (upper) xylim.clip[1, i] <- max(q$threshold, xylim.clip[1,i]) else
xylim.clip[2,i] <- min(q$threshold, xylim.clip[2,i])
xylim.clip
}
if(q$threshold > xylim[1,i]) lines(q$lower, clip(xylim, i, FALSE), ...)
if(q$threshold < xylim[2,i]) lines(q$upper, clip(xylim, i, TRUE), ...)
xlim <- xylim[, j]
xy <- cbind(c(q$threshold, q$threshold), xlim)
lines(xy[, order(i:j)], ...)
}
lines.quadtree.leaf <- function(q, xylim, ...) {} # Nothing to do at leaves!
再举一个例子,我生成了1,000,000个点,并将它们分为5-9个组。时间是91.7秒。
n <- 25000 # Points per cluster
n.centers <- 40 # Number of cluster centers
sd <- 1/2 # Standard deviation of each cluster
set.seed(17)
centers <- matrix(runif(n.centers*2, min=c(-90, 30), max=c(-75, 40)), ncol=2, byrow=TRUE)
xy <- matrix(apply(centers, 1, function(x) rnorm(n*2, mean=x, sd=sd)), ncol=2, byrow=TRUE)
k <- 5
system.time(qt <- quadtree(xy, k))
#
# Set up to map the full extent of the quadtree.
#
xylim <- cbind(x=c(min(xy[,1]), max(xy[,1])), y=c(min(xy[,2]), max(xy[,2])))
plot(xylim, type="n", xlab="x", ylab="y", main="Quadtree")
#
# This is all the code needed for the plot!
#
lines(qt, xylim, col="Gray")
points(qt, pch=".")
作为如何与GIS交互的示例,让我们使用该shapefiles
库将所有四叉树单元写为多边形shapefile 。该代码模拟的裁剪例程lines.quadtree
,但是这一次它必须生成单元的向量描述。这些作为数据帧输出,供shapefiles
库使用。
cell <- function(q, xylim, ...) {
if (class(q)=="quadtree") f <- cell.quadtree else f <- cell.quadtree.leaf
f(q, xylim, ...)
}
cell.quadtree <- function(q, xylim, ...) {
i <- q$index
j <- 3 - q$index
clip <- function(xylim.clip, i, upper) {
if (upper) xylim.clip[1, i] <- max(q$threshold, xylim.clip[1,i]) else
xylim.clip[2,i] <- min(q$threshold, xylim.clip[2,i])
xylim.clip
}
d <- data.frame(id=NULL, x=NULL, y=NULL)
if(q$threshold > xylim[1,i]) d <- cell(q$lower, clip(xylim, i, FALSE), ...)
if(q$threshold < xylim[2,i]) d <- rbind(d, cell(q$upper, clip(xylim, i, TRUE), ...))
d
}
cell.quadtree.leaf <- function(q, xylim) {
data.frame(id = q$id,
x = c(xylim[1,1], xylim[2,1], xylim[2,1], xylim[1,1], xylim[1,1]),
y = c(xylim[1,2], xylim[1,2], xylim[2,2], xylim[2,2], xylim[1,2]))
}
点本身可以使用read.shp
或通过导入(x,y)坐标的数据文件直接读取。
使用示例:
qt <- quadtree(xy, k)
xylim <- cbind(x=c(min(xy[,1]), max(xy[,1])), y=c(min(xy[,2]), max(xy[,2])))
polys <- cell(qt, xylim)
polys.attr <- data.frame(id=unique(polys$id))
library(shapefiles)
polys.shapefile <- convert.to.shapefile(polys, polys.attr, "id", 5)
write.shapefile(polys.shapefile, "f:/temp/quadtree", arcgis=TRUE)
(在xylim
此处使用任何所需的范围可以进入子区域或将映射扩展到更大的区域;此代码默认为点的范围。)
仅此一项就足够了:这些多边形到原始点的空间连接将识别出聚类。一旦确定,数据库“摘要”操作将生成每个像元内各个点的摘要统计信息。