Vedo che MerseyViking ha raccomandato un quadrifoglio . Stavo per suggerire la stessa cosa e per spiegarlo, ecco il codice e un esempio. Il codice è scritto Rma dovrebbe essere facilmente trasferito su Python.
L'idea è straordinariamente semplice: dividere i punti approssimativamente a metà nella direzione x, quindi dividere in modo ricorsivo le due metà lungo la direzione y, alternando le direzioni ad ogni livello, fino a quando non si desidera più una divisione.
Poiché l'intento è quello di nascondere le posizioni dei punti effettivi, è utile introdurre una certa casualità nelle divisioni . Un modo semplice e veloce per farlo è quello di dividere in un insieme quantile una piccola quantità casuale a partire dal 50%. In questo modo (a) è altamente improbabile che i valori di divisione coincidano con le coordinate dei dati, quindi i punti cadranno in modo univoco nei quadranti creati dal partizionamento e (b) le coordinate dei punti saranno impossibili da ricostruire precisamente dal quadrifoglio.
Poiché l'intenzione è quella di mantenere una quantità minima kdi nodi all'interno di ogni foglia di quadrifoglio, implementiamo una forma ristretta di quadrifoglio. Supporterà (1) punti di raggruppamento in gruppi con tra ke 2 * k-1 elementi ciascuno e (2) mappando i quadranti.
Questo Rcodice crea un albero di nodi e foglie terminali, distinguendoli per classe. L'etichettatura di classe accelera la post-elaborazione come la stampa, mostrata di seguito. Il codice utilizza valori numerici per gli ID. Funziona fino a una profondità di 52 nell'albero (usando i doppi; se si usano numeri interi lunghi senza segno, la profondità massima è 32). Per alberi più profondi (che sono altamente improbabili in qualsiasi applicazione, poiché ksarebbero coinvolti almeno * 2 ^ 52 punti), gli ID dovrebbero essere stringhe.
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)
}
Si noti che il design ricorsivo di divisione e conquista di questo algoritmo (e, di conseguenza, della maggior parte degli algoritmi di post-elaborazione) significa che il requisito di tempo è O (m) e l'utilizzo della RAM è O (n) dove mè il numero di celle ed nè il numero di punti. mè proporzionale a ndiviso per i punti minimi per cella,k. Ciò è utile per stimare i tempi di calcolo. Ad esempio, se ci vogliono 13 secondi per partizionare n = 10 ^ 6 punti in celle di 50-99 punti (k = 50), m = 10 ^ 6/50 = 20000. Se invece vuoi partizionare fino a 5-9 punti per cella (k = 5), m è 10 volte più grande, quindi il tempo sale fino a circa 130 secondi. (Poiché il processo di divisione di una serie di coordinate attorno alle loro medie diventa più veloce man mano che le celle si riducono, il tempo effettivo è stato di soli 90 secondi.) Per arrivare fino a k = 1 punto per cella, ci vorrà circa sei volte più a lungo ancora, o nove minuti, e possiamo aspettarci che il codice sia in realtà un po 'più veloce di così.
Prima di andare oltre, generiamo alcuni dati interessanti spaziati in modo irregolare e creiamo il loro quadrifoglio limitato (tempo trascorso 0,29 secondi):

Ecco il codice per produrre questi grafici. Sfrutta Ril polimorfismo: points.quadtreeverrà chiamato ogni volta che la pointsfunzione viene applicata a un quadtreeoggetto, ad esempio. Il potere di questo è evidente nell'estrema semplicità della funzione di colorare i punti in base all'identificatore del cluster:
points.quadtree <- function(q, ...) {
points(q$lower, ...); points(q$upper, ...)
}
points.quadtree.leaf <- function(q, ...) {
points(q$value, col=hsv(q$id), ...)
}
Tracciare la griglia stessa è un po 'più complicato perché richiede un ripetuto ritaglio delle soglie utilizzate per il partizionamento quadrifoglio, ma lo stesso approccio ricorsivo è semplice ed elegante. Utilizzare una variante per costruire rappresentazioni poligonali dei quadranti, se lo si desidera.
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!
Come altro esempio, ho generato 1.000.000 di punti e li ho suddivisi in gruppi di 5-9 ciascuno. Il tempismo è stato di 91,7 secondi.
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=".")

Come esempio di come interagire con un GIS , scriviamo tutte le celle quadrifogli come un file di forma poligonale usando la shapefileslibreria. Il codice emula le routine di ritaglio di lines.quadtree, ma questa volta deve generare descrizioni vettoriali delle celle. Questi vengono emessi come frame di dati da utilizzare con la shapefileslibreria.
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]))
}
I punti stessi possono essere letti direttamente utilizzando read.shpo importando un file di dati di coordinate (x, y).
Esempio di utilizzo:
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)
(Utilizzare qualsiasi estensione desiderata per xylimqui per accedere a una sottoregione o per espandere la mappatura in un'area più ampia; questo codice viene impostato automaticamente sull'estensione dei punti.)
Questo da solo è abbastanza: un'unione spaziale di questi poligoni ai punti originali identificherà i cluster. Una volta identificate, le operazioni di "riepilogo" del database genereranno statistiche riassuntive dei punti all'interno di ciascuna cella.