Я бачу MerseyViking рекомендував квадрадерево . Я збирався запропонувати те саме і щоб пояснити це, ось код і приклад. Код записаний, R
але його слід легко перенести, скажімо, на Python.
Ідея надзвичайно проста: розділіть точки приблизно навпіл у напрямку x, потім рекурсивно розділіть дві половинки вздовж напрямку y, чергуючи напрямки на кожному рівні, поки не буде потрібне більше розщеплення.
Оскільки метою є маскування фактичних точкових місць, корисно ввести деяку випадковість у розколи . Один з швидких простих способів зробити це - розділити на кількісний набір невелику випадкову суму від 50%. Таким чином (a) величини розщеплення малоймовірно збігаються з координатами даних, так що точки однозначно потраплять на квадрати, створені розділенням, і (b) координати точок неможливо буде реконструювати точно з квадрату.
Оскільки наміром є підтримка мінімальної кількості k
вузлів у кожному аркуші квадрату, ми реалізуємо обмежену форму квадратури. Він підтримуватиме (1) кластеризацію точок у групи, що містять між k
та 2 * k
-1 елементами, та (2) відображення квадрантів.
Цей R
код створює дерево вузлів і кінцевих листів, розрізняючи їх за класом. Маркування класу прискорює післяобробку, таку як графічне зображення, показане нижче. Код використовує числові значення для ідентифікаторів. Це працює на дереві до глибини 52 (використовуючи подвійні; якщо використовуються неподписані довгі цілі числа, максимальна глибина - 32). Для глибших дерев (які в будь-якій програмі малоймовірні, оскільки k
буде задіяно принаймні * 2 ^ 52 бали), ідентифікатори повинні бути рядками.
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), а використання оперативної пам'яті - O (n), де m
число клітинок і n
- кількість балів. m
пропорційно n
ділиться на мінімальний бал на комірку,k
. Це корисно для оцінки часу обчислень. Наприклад, якщо потрібно 13 секунд, щоб розділити n = 10 ^ 6 балів у клітинках 50-99 балів (k = 50), 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=".")
Як приклад того, як взаємодіяти з ГІС , давайте випишемо всі квадратичні комірки у вигляді файлу форми багатокутника за допомогою shapefiles
бібліотеки. Код імітує відсічні процедури 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
перейти до субрегіону або розширити відображення у більшому регіоні; цей код за замовчуванням розмір точок.)
Одного лише цього достатньо: просторове приєднання цих багатокутників до початкових точок визначить кластери. Після ідентифікації операції з «підбиття підсумків» бази даних будуть генерувати підсумкову статистику точок всередині кожної комірки.