Come posso generare una griglia irregolare contenente minimo n punti?


20

Dato un grande campione (~ 1 milione) di punti distribuiti in modo non uniforme - è possibile generare una griglia irregolare (in termini di dimensioni, ma potrebbe anche essere di forma irregolare se ciò è possibile?) Che conterrà un numero minimo specificato di n punti?

Per me è meno importante se le "celle" generate di tale griglia contengono esattamente n numero di punti o almeno n punti.

Sono a conoscenza di soluzioni come genvecgrid in ArcGIS o Crea Grid Layer in QGIS / mmgis, tuttavia creeranno tutte griglie regolari che si tradurranno in un output con celle vuote (problema più piccolo - potrei semplicemente scartarle) o celle con il conteggio dei punti meno di n (problema più grande dato che avrei bisogno di una soluzione per aggregare quelle celle, probabilmente usando alcuni strumenti da qui ?).

Ho cercato su Google senza risultati e sono aperto a soluzioni commerciali (ArcGIS ed estensioni) o gratuite (Python, PostGIS, R).


1
Quanto deve essere "regolare" la griglia? Mi chiedo se puoi fare un po 'di clustering gerarchico e poi tagliare il dendrogramma per soddisfare le tue esigenze (anche se questo probabilmente allunga quella che sarebbe definita una normale configurazione spaziale). La documentazione di CrimeStat contiene alcuni buoni esempi di questo tipo di clustering .
Andy W

5
Potresti spiegare esattamente cosa intendi per "griglia irregolare"? Sembra un ossimoro :-). Più precisamente, quale sarebbe lo scopo di questo esercizio? Si noti inoltre che sono probabilmente necessari criteri o vincoli aggiuntivi: dopo tutto, se si disegna un quadrato intorno a 1 milione di punti, potrebbe essere considerato parte di una griglia e ne contenga più di n . Probabilmente non ti importerebbe questa soluzione banale, però: ma perché no, esattamente?
whuber

@AndyW Grazie. Buona idea e vale la pena esplorare. Dai un'occhiata. Le dimensioni e la forma della "griglia" sono di secondaria importanza per me - la priorità (a causa della privacy dei dati) è quella di "nascondere" n funzioni dietro uno
radek

@whuber Grazie pure. Sono d'accordo - ma non ero sicuro di come avrei potuto nominare tale partizionamento. Come accennato in precedenza, la mia motivazione principale è la riservatezza dei dati. Avendo cinque punti (che non posso mostrare sulla mappa finale) mi piacerebbe rappresentarli per area che li copre; e ottieni media / mediana / ecc. valore per quello. Sono d'accordo che sarebbe possibile disegnare un rettangolo o uno scafo convesso che li rappresenti tutti - sarebbe la massima protezione della privacy dei dati, immagino? ;] Tuttavia - sarebbe più utile rappresentarlo con il contorno delle forme, diciamo 10 caratteristiche. Quindi - posso ancora conservare il modello spaziale.
Radek,

1
IMO, data la tua descrizione, utilizzerei un qualche tipo di interpolazione e visualizzerei una mappa raster (forse una larghezza di banda adattativa della dimensione della tua N minima sarebbe sufficiente per rendere più fluidi i dati). Per quanto riguarda CrimeStat, i file più grandi che ho usato erano circa 100.000 casi credo (e il clustering richiederebbe sicuramente tempo). È probabile che tu possa fare una pre-generalizzazione dei tuoi dati per rappresentarli come meno casi e ottenere comunque risultati desiderabili per quello che vuoi. È un programma davvero semplice, suggerirei di prenderti solo pochi minuti per provarlo e vederlo di persona.
Andy W

Risposte:


26

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):

quadtree

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=".")

inserisci qui la descrizione dell'immagine


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.


Wow! Fantastico.
Ci proverò

4
Risposta migliore @whuber! +1
MerseyViking

1
(1) Puoi leggere gli shapefile direttamente con ( tra l'altro ) il shapefilespacchetto oppure puoi esportare (x, y) coordinate nel testo ASCII e leggerle con read.table. (2) Raccomando di scrivere qtin due modi: primo, come file di forma punto in xycui i idcampi sono inclusi come identificatori di cluster; in secondo luogo, dove i segmenti di linea tracciati lines.quadtreevengono scritti come un file di forma polilinea (o dove un'elaborazione analoga scrive le celle come un file di forma poligonale). Questo è semplice come modificare lines.quadtree.leafl'output xylimcome un rettangolo. (Vedi le modifiche.)
whuber

1
@whubber Grazie mille per un aggiornamento. Tutto ha funzionato senza intoppi. Meritato +50, anche se ora penso che meriti +500!
Radek,

1
Sospetto che gli ID calcolati non fossero univoci per qualche motivo. Apportare queste modifiche nella definizione di quad: (1) inizializzare id=1; (2) cambia id/2in id*2nella lower=riga; (3) apporta una modifica simile a id*2+1nella upper=riga. (Modificherò la mia risposta per riflettere ciò.) Ciò dovrebbe anche occuparsi del calcolo dell'area: a seconda del GIS, tutte le aree saranno positive o tutte negative. Se sono tutti negativi, inverti gli elenchi per xe yin cell.quadtree.leaf.
whuber

6

Verifica se questo algoritmo fornisce abbastanza anonimato per il tuo campione di dati:

  1. inizia con una griglia regolare
  2. se il poligono ha una soglia inferiore alla soglia, fondersi con la spirale alternata vicina (E, S, W, N) in senso orario.
  3. se il poligono ha una soglia inferiore alla soglia, vai a 2, altrimenti vai al poligono successivo

Ad esempio, se la soglia minima è 3:

algoritmo


1
Il diavolo è nei dettagli: sembra che questo approccio (o quasi qualsiasi algoritmo di cluster agglomerato) minacci di lasciare piccoli punti "orfani" sparsi in tutto il luogo, che quindi non possono essere elaborati. Non sto dicendo che questo approccio sia impossibile, ma manterrei un sano scetticismo in assenza di un algoritmo reale e di un esempio della sua applicazione a un set di dati realistico.
whuber

In effetti questo approccio potrebbe essere problematico. Un'applicazione di questo metodo a cui stavo pensando utilizza i punti come rappresentazioni di edifici residenziali. Penso che questo metodo funzionerebbe bene in aree più densamente popolate. Tuttavia, ci sarebbero ancora casi in cui ci sono letteralmente uno o due edifici "nel mezzo del nulla" e ci vorrebbero molte iterazioni e porterebbero aree molto grandi a raggiungere finalmente la soglia minima.
Radek,

5

Analogamente all'interessante soluzione di Paulo, che ne dici di usare un algoritmo di suddivisione dei quad tree?

Imposta la profondità a cui desideri che quadtree vada. Potresti anche avere un numero minimo o massimo di punti per cella, quindi alcuni nodi sarebbero più profondi / più piccoli di altri.

Suddividi il tuo mondo, scartando i nodi vuoti. Risciacquare e ripetere fino a quando i criteri non sono soddisfatti.


Grazie. Quale software consiglieresti per questo?
Radek,

1
In linea di principio questa è una buona idea. Ma come sorgerebbero i nodi vuoti se non permettessi mai meno di un numero minimo positivo di punti per cella? (Esistono molti tipi di quadricre, quindi la possibilità di nodi vuoti indica che ne hai in mente uno che non è adattato ai dati, il che solleva preoccupazioni sulla sua utilità per l'attività prevista.)
whuber

1
Lo immagino così: immagino che un nodo abbia più della soglia massima di punti in esso, ma sono raggruppati verso la parte superiore sinistra del nodo. Il nodo verrà suddiviso, ma il nodo figlio in basso a destra sarà vuoto, quindi può essere eliminato.
MerseyViking,

1
Vedo cosa stai facendo (+1). Il trucco è quello di suddividere in un punto determinato dalle coordinate (come la loro mediana), garantendo così nessuna cella vuota. Altrimenti, il quadrifoglio è determinato principalmente dallo spazio occupato dai punti e non dai punti stessi; il tuo approccio diventa quindi un modo efficace per realizzare l'idea generica proposta da @Paulo.
whuber

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.