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 R
ma 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 k
di nodi all'interno di ogni foglia di quadrifoglio, implementiamo una forma ristretta di quadrifoglio. Supporterà (1) punti di raggruppamento in gruppi con tra k
e 2 * k
-1 elementi ciascuno e (2) mappando i quadranti.
Questo R
codice 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é k
sarebbero 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 n
diviso 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 R
il polimorfismo: points.quadtree
verrà chiamato ogni volta che la points
funzione viene applicata a un quadtree
oggetto, 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 shapefiles
libreria. 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 shapefiles
libreria.
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.shp
o 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 xylim
qui 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.