Ofrezco un R
solución que se codifica en un ligeramente no-R
manera de ilustrar cómo podría ser abordado en otras plataformas.
La preocupación en R
(así como de otras plataformas, especialmente aquellos que están a favor de un estilo de programación funcional) es que la actualización constante de una gran cantidad puede ser muy caro. En su lugar, a continuación, este algoritmo mantiene su propia estructura de datos privados en el que (a) todas las células que han sido llenados hasta el momento están en la lista y (b) todas las células que están disponibles para ser elegido (alrededor del perímetro de las células llenas) están en la lista. Aunque la manipulación de esta estructura de datos es menos eficiente que directamente la indización en una matriz, manteniendo los datos modificados en un tamaño pequeño, es probable que tome mucho menos tiempo de cálculo. (No se ha hecho un esfuerzo para optimizarlo para R
,. Pre-asignación de los vectores de estado debe ahorrar algo de tiempo de ejecución, si usted prefiere seguir trabajando en R
.)
El código está comentado y deben ser fáciles de leer. Para hacer que el algoritmo sea lo más completo posible, que no hace uso de add-ons, excepto en la final a la trama el resultado. La única parte difícil es que para la eficiencia y la simplicidad prefiere índice en las mallas 2D mediante el uso de 1D índices. Una conversión que sucede en la neighbors
función de las necesidades de la 2D de indexación con el fin de averiguar lo que el acceso de los vecinos de una célula puede ser y luego los convierte en la 1D índice. Esta conversión es estándar, así que no voy a comentar más, excepto para señalar que en otras plataformas GIS puede que desee invertir los roles de la columna y la fila de los índices. (En R
, la fila de los índices de cambio antes de los índices de las columnas.)
Para ilustrar, este código tiene una cuadrícula x
que representa a la tierra y a un río-como característica de puntos inaccesibles, se inicia en un lugar específico (5, 21) en esa red (cerca de la parte inferior curva del río) y se expande al azar para cubrir los 250 puntos. Total de temporización es de 0,03 segundos. (Cuando el tamaño de la matriz es mayor por un factor de 10.000 a 3000 filas por 5000 columnas, el momento en que sube sólo a 0.09 segundos-un factor de sólo 3--demostración de la escalabilidad de este algoritmo.) En lugar de simplemente generar una cuadrícula de 0, 1, y 2, salidas de la secuencia con la que las nuevas células fueron asignados. En la figura las primeras células son de color verde, que se graduó en la medallas de oro en el salmón colores.
Debe ser evidente que un niño de ocho punto vecindario de cada celda está siendo utilizado. Para otros barrios, simplemente modificar el nbrhood
valor cerca del principio de la expand
: es una lista de índice de compensaciones en relación a cualquier célula dada. Por ejemplo, una "D4" barrio podría ser especificado como matrix(c(-1,0, 1,0, 0,-1, 0,1), nrow=2)
.
También es evidente que este método de propagación tiene sus problemas: se deja orificios detrás. Si eso no es lo que era la intención, hay varias formas de solucionar este problema. Por ejemplo, mantener la disposición de las células en una cola, de modo que las primeras células que se encuentran son también los más antiguos se llenó. Algunos aleatorización puede aplicarse, pero la disposición de las células dejará de ser elegido con uniforme (igual) de probabilidades. Otra, más complicada, sería para seleccionar celdas disponibles con probabilidades que dependen de la cantidad de lleno a los vecinos que tienen. Una vez que una célula se convierte rodeado, usted puede hacer su probabilidad de selección tan alto que algunos de los agujeros quedaría sin cubrir.
Voy a terminar comentando que este no es un autómata celular (CA), que no procedería de la célula por célula, sino de la actualización de bloques enteros de células en cada generación. La diferencia es sutil: con la CA, las probabilidades de selección de las células no sería uniforme.
#
# Expand a patch randomly within indicator array `x` (1=unoccupied) by
# `n.size` cells beginning at index `start`.
#
expand <- function(x, n.size, start) {
if (x[start] != 1) stop("Attempting to begin on an unoccupied cell")
n.rows <- dim(x)[1]
n.cols <- dim(x)[2]
nbrhood <- matrix(c(-1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1), nrow=2)
#
# Adjoin one more random cell and update `state`, which records
# (1) the immediately available cells and (2) already occupied cells.
#
grow <- function(state) {
#
# Find all available neighbors that lie within the extent of `x` and
# are unoccupied.
#
neighbors <- function(i) {
n <- c((i-1)%%n.rows+1, floor((i-1)/n.rows+1)) + nbrhood
n <- n[, n[1,] >= 1 & n[2,] >= 1 & n[1,] <= n.rows & n[2,] <= n.cols,
drop=FALSE] # Remain inside the extent of `x`.
n <- n[1,] + (n[2,]-1)*n.rows # Convert to *vector* indexes into `x`.
n <- n[x[n]==1] # Stick to valid cells in `x`.
n <- setdiff(n, state$occupied)# Remove any occupied cells.
return (n)
}
#
# Select one available cell uniformly at random.
# Return an updated state.
#
j <- ceiling(runif(1) * length(state$available))
i <- state$available[j]
return(list(index=i,
available = union(state$available[-j], neighbors(i)),
occupied = c(state$occupied, i)))
}
#
# Initialize the state.
# (If `start` is missing, choose a value at random.)
#
if(missing(start)) {
indexes <- 1:(n.rows * n.cols)
indexes <- indexes[x[indexes]==1]
start <- sample(indexes, 1)
}
if(length(start)==2) start <- start[1] + (start[2]-1)*n.rows
state <- list(available=start, occupied=c())
#
# Grow for as long as possible and as long as needed.
#
i <- 1
indices <- c(NA, n.size)
while(length(state$available) > 0 && i <= n.size) {
state <- grow(state)
indices[i] <- state$index
i <- i+1
}
#
# Return a grid of generation numbers from 1, 2, ... through n.size.
#
indices <- indices[!is.na(indices)]
y <- matrix(NA, n.rows, n.cols)
y[indices] <- 1:length(indices)
return(y)
}
#
# Create an interesting grid `x`.
#
n.rows <- 3000
n.cols <- 5000
x <- matrix(1, n.rows, n.cols)
ij <- sapply(1:n.cols, function(i)
c(ceiling(n.rows * 0.5 * (1 + exp(-0.5*i/n.cols) * sin(8*i/n.cols))), i))
x[t(ij)] <- 0; x[t(ij - c(1,0))] <- 0; x[t(ij + c(1,0))] <- 0
#
# Expand around a specified location in a random but reproducible way.
#
set.seed(17)
system.time(y <- expand(x, 250, matrix(c(5, 21), 1)))
#
# Plot `y` over `x`.
#
library(raster)
plot(raster(x[n.rows:1,], xmx=n.cols, ymx=n.rows), col=c("#2020a0", "#f0f0f0"))
plot(raster(y[n.rows:1,] , xmx=n.cols, ymx=n.rows),
col=terrain.colors(255), alpha=.8, add=TRUE)
Con ligeras modificaciones podemos recorrer expand
crear varios grupos. Es conveniente diferenciar los grupos de un identificador, que aquí se va a ejecutar 2, 3, ..., etc.
En primer lugar, el cambio expand
a devolver (a) NA
en la primera línea si no es un error, y (b) los valores en indices
en lugar de una matriz y
. (No pierdas el tiempo en la creación de una nueva matriz y
con cada llamada.) Con este cambio, el bucle es fácil: elija un arranque aleatorio, intenta ampliar su alrededor, se acumulan los índices clúster en indices
en caso de éxito, y repetir hasta que esté hecho. Una parte clave del bucle es limitar el número de iteraciones en el caso de que muchos de los clústeres contiguos no se encuentra: esto se hace con count.max
.
Aquí está un ejemplo en 60 centros de cluster son elegidos de manera uniforme al azar.
size.clusters <- 250
n.clusters <- 60
count.max <- 200
set.seed(17)
system.time({
n <- n.rows * n.cols
cells.left <- 1:n
cells.left[x!=1] <- -1 # Indicates occupancy of cells
i <- 0
indices <- c()
ids <- c()
while(i < n.clusters && length(cells.left) >= size.clusters && count.max > 0) {
count.max <- count.max-1
xy <- sample(cells.left[cells.left > 0], 1)
cluster <- expand(x, size.clusters, xy)
if (!is.na(cluster[1]) && length(cluster)==size.clusters) {
i <- i+1
ids <- c(ids, rep(i, size.clusters))
indices <- c(indices, cluster)
cells.left[indices] <- -1
}
}
y <- matrix(NA, n.rows, n.cols)
y[indices] <- ids
})
cat(paste(i, "cluster(s) created.", sep=" "))
Aquí está el resultado cuando se aplica a un 310 por 500 rejilla (de hecho lo suficientemente pequeña y gruesa para los grupos a ser evidente). Se tarda dos segundos en ejecutarse; en un 3100 de 5000 cuadrícula (100 veces más grande) lleva más tiempo (24 segundos) pero el momento es de escala razonablemente bien. (En otras plataformas, tales como C++, el momento en que debiera depender del tamaño de la cuadrícula.)
0 votos
Además de R, ¿qué otros lenguajes de programación/software le interesa utilizar para este análisis?
0 votos
También me gustaría utilizar ArcGIS o QGIS. Por desgracia, no estoy muy familiarizado con Python. GDAL a través de un terminal bash es también una posibilidad.