Para analizar datos espaciales, un paso importante suele ser la obtención de una medida de la influencia espacial entre los objetos geográficos. Dicha medida se puede expresar en función de la adyacencia o de ladistancia inversa expresada como una matriz de ponderaciones espaciales. Claro está que esta es una variable latente, dado que la influencia corresponde a un concepto muy complejo que en realidad no se puede medir. Por ejemplo, la influencia espacial entre ciudades puede ser medida de varias maneras: si estan comparten frontera (adyacencia), por su distancia entre centroides o por las longitudes de los bordes compartidos, entre otras.
Con las consideraciones del caso para la medición de la influencia espacial, ¿qué pasa si queremos calcular la matriz de adyacencia de los dos vecinos más cercanos? Veámoslo con un ejemplo.
Para ello, generemos primero un conjunto de datos de seis puntos, similar a lo que trabajamos en el capítulo anterior, y calculemos su matriz de distancias.
# Librerías necesarias
library(tidyverse)
library(ggpubr)
library(raster)
library(ggrepel)
library(spdep)
library(sf)
# Declaramos la función select para que sea tomada de dplyr y no de raster
select <- dplyr::select
# Generación de coordenadas
set.seed(123)
puntos <- tibble(etiqueta = paste("Punto",1:6),
x = rnorm(6, 60, 30), y = rnorm(6, 60, 30))
puntos
## # A tibble: 6 x 3
## etiqueta x y
## <chr> <dbl> <dbl>
## 1 Punto 1 43.2 73.8
## 2 Punto 2 53.1 22.0
## 3 Punto 3 107. 39.4
## 4 Punto 4 62.1 46.6
## 5 Punto 5 63.9 96.7
## 6 Punto 6 111. 70.8
puntos %>%
ggplot(aes(x, y, label=etiqueta))+
geom_point(color="red", size=4)+
geom_text_repel()
distancias <- puntos %>% select(x,y) %>% dist(method = "euclidean") %>% as.matrix()
rownames(distancias) <- puntos$etiqueta
colnames(distancias) <- puntos$etiqueta
distancias
## Punto 1 Punto 2 Punto 3 Punto 4 Punto 5 Punto 6
## Punto 1 0.00000 52.71893 72.30133 33.13642 30.86059 68.33357
## Punto 2 52.71893 0.00000 56.40030 26.18481 75.44895 76.03794
## Punto 3 72.30133 56.40030 0.00000 45.22854 71.59206 31.74843
## Punto 4 33.13642 26.18481 45.22854 0.00000 50.12334 54.93653
## Punto 5 30.86059 75.44895 71.59206 50.12334 0.00000 54.18010
## Punto 6 68.33357 76.03794 31.74843 54.93653 54.18010 0.00000
Una vez que hemos calculado la matriz de distancias, ordenemos cada una de las columnas con el objetivo de obtener los vecinos más cercanos a cada punto (excluyen al mismo punto).
# Ordenamos la matriz
vecinos <- apply(distancias,1,order) %>% t()
vecinos
## [,1] [,2] [,3] [,4] [,5] [,6]
## Punto 1 1 5 4 2 6 3
## Punto 2 2 4 1 3 5 6
## Punto 3 3 6 4 2 5 1
## Punto 4 4 2 1 3 5 6
## Punto 5 5 1 4 6 3 2
## Punto 6 6 3 5 4 1 2
# Excluimos al mismo punto
vecinos <- vecinos[,-1]
vecinos
## [,1] [,2] [,3] [,4] [,5]
## Punto 1 5 4 2 6 3
## Punto 2 4 1 3 5 6
## Punto 3 6 4 2 5 1
## Punto 4 2 1 3 5 6
## Punto 5 1 4 6 3 2
## Punto 6 3 5 4 1 2
Tomemos como ejemplo uno de estas filas, solo para revisar que el cálculo sea el correcto.
vecinos[rownames(vecinos)=="Punto 1",]
## [1] 5 4 2 6 3
Acorde a este resultado, los puntos más cercanos al punto 1 son los puntos 5 y 4. Veámoslo gráficamente:
puntos %>%
mutate(cercano = case_when(etiqueta=="Punto 1"~"centro",
etiqueta %in% c("Punto 5","Punto 4") ~ "vecino",
T~"lejano")) %>%
ggplot(aes(x, y, label=etiqueta, color=cercano))+
geom_point(size=4)+
geom_text_repel()
En esta gráfica se puede notar que en efecto, los puntos 4 y 5 son los más cercanos al punto 1.
Con estos datos creemos entonces la matriz de adyacencia de los 2 vecinos más cercanos para cada punto. Esto lo podemos hacer de la siguiente manera.
# Definimos las posiciones a llenar
vecinos2 <- cbind(rep(1:6, each=2), as.vector(t(vecinos[,1:2])))
# Creamos la matriz de adyacencia
NN2 <- matrix(data=0, nrow = 6,ncol = 6)
rownames(NN2) <- paste("Punto",1:6)
colnames(NN2) <- paste("Punto",1:6)
# Si dos puntos son vecinos los marcamos con 1, de lo contrario con 0
NN2[vecinos2] <- 1
# Poblamos la diagonal con NA
diag(NN2) <- NA
# Visualizamos el resultado
NN2
## Punto 1 Punto 2 Punto 3 Punto 4 Punto 5 Punto 6
## Punto 1 NA 0 0 1 1 0
## Punto 2 1 NA 0 1 0 0
## Punto 3 0 0 NA 1 0 1
## Punto 4 1 1 0 NA 0 0
## Punto 5 1 0 0 1 NA 0
## Punto 6 0 0 1 0 1 NA
Así tenemos una matriz de influencia espacial.
Si la metodología anterior no es suficiente para nuestro análisis, podemos expresar la influencia espacial como un valor continuo y no como un valor binario. Para hacerlo basta con calcular los valores inversos de la matriz de distancias.
pesos <- 1/distancias %>% round(4)
pesos[is.infinite(pesos)] <- NA
pesos
## Punto 1 Punto 2 Punto 3 Punto 4 Punto 5 Punto 6
## Punto 1 NA 0.01896853 0.01383101 0.03017829 0.03240378 0.01463409
## Punto 2 0.01896853 NA 0.01773040 0.03819010 0.01325400 0.01315134
## Punto 3 0.01383101 0.01773040 NA 0.02210995 0.01396802 0.03149765
## Punto 4 0.03017829 0.03819010 0.02210995 NA 0.01995080 0.01820283
## Punto 5 0.03240378 0.01325400 0.01396802 0.01995080 NA 0.01845696
## Punto 6 0.01463409 0.01315134 0.03149765 0.01820283 0.01845696 NA
Cabe notar que lo más usual es usar la matriz de ponderaciones espaciales, la cual suele estar normalizada por filas. Para hacerlo representaremos cada entrada de la matriz como la proporción que representa cada valor con respecto al total en cada fila.
pesos <- pesos/rowSums(pesos, na.rm = T)
pesos
## Punto 1 Punto 2 Punto 3 Punto 4 Punto 5 Punto 6
## Punto 1 NA 0.1724166 0.1257185 0.2743090 0.2945378 0.1330182
## Punto 2 0.1872614 NA 0.1750384 0.3770209 0.1308464 0.1298329
## Punto 3 0.1395141 0.1788474 NA 0.2230241 0.1408961 0.3177183
## Punto 4 0.2346096 0.2968943 0.1718854 NA 0.1550999 0.1415110
## Punto 5 0.3305376 0.1351986 0.1424820 0.2035099 NA 0.1882719
## Punto 6 0.1525292 0.1370747 0.3282959 0.1897258 0.1923745 NA
Para comprobar que hayamos hecho correctamente el cálculo confirmemos la suma por filas (que debe ser igual a 1) y la suma por columnas (que no debe ser igual a 1).
rowSums(pesos, na.rm = T)
## Punto 1 Punto 2 Punto 3 Punto 4 Punto 5 Punto 6
## 1 1 1 1 1 1
colSums(pesos, na.rm = T)
## Punto 1 Punto 2 Punto 3 Punto 4 Punto 5 Punto 6
## 1.0444518 0.9204315 0.9434202 1.2675897 0.9137546 0.9103521
Con estos datos confirmados, veamos ahora como calcular la influencia espacial para polígonos.
Con el fin de calcular la influencia espacial para polígonos utilizaremos la librería spdep. Veamos a continuación como hacerlo sobre los polígonos de Luxemburgo.
luxemburgo <- shapefile(system.file("external/lux.shp", package = "raster"))
luxemburgo
## class : SpatialPolygonsDataFrame
## features : 12
## extent : 5.74414, 6.528252, 49.44781, 50.18162 (xmin, xmax, ymin, ymax)
## crs : +proj=longlat +datum=WGS84 +no_defs
## variables : 5
## names : ID_1, NAME_1, ID_2, NAME_2, AREA
## min values : 1, Diekirch, 1, Capellen, 76
## max values : 3, Luxembourg, 12, Wiltz, 312
Antes de iniciar, veamos el gráfico bidimensional del territorio que vamos a analizar.
luxemburgo %>%
st_as_sf() %>%
ggplot()+
geom_sf()+
coord_sf()
Ahora localizaremos los vecinos de cada polígono. La función que utilizaremos es poly2nb, la cual construye una lista de vecinos para cada región basada en fronteras contiguas (es decir que compartan uno o más puntos limítrofes). Luego transformaremos el resultado de esta función a una matriz a través de la función nb2mat.
# Veamos la data
luxemburgo %>% head()
## ID_1 NAME_1 ID_2 NAME_2 AREA
## 0 1 Diekirch 1 Clervaux 312
## 1 1 Diekirch 2 Diekirch 218
## 2 1 Diekirch 3 Redange 259
## 3 1 Diekirch 4 Vianden 76
## 4 1 Diekirch 5 Wiltz 263
## 5 2 Grevenmacher 6 Echternach 188
# Usamos la función poly2nb para encontrar los vecinos
luxemburgo_vecinos <- poly2nb(luxemburgo, row.names = luxemburgo$ID_2, queen = F)
luxemburgo_vecinos
## Neighbour list object:
## Number of regions: 12
## Number of nonzero links: 46
## Percentage nonzero weights: 31.94444
## Average number of links: 3.833333
Una vez utilizada la función podemos notar que el número promedio de uniones es 3.83, es decir, cada polígono tiene 3.83 vecinos en promedio. Además existen 12 regiones y 46 uniones con un 31.94% de pesos distintos de cero.
Veamos este resultado en forma de matriz:
nb2mat(luxemburgo_vecinos, style = "B", zero.policy = T)
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
## 1 0 1 0 1 1 0 0 0 0 0 0 0
## 2 1 0 1 1 1 1 0 0 0 0 0 1
## 3 0 1 0 0 1 0 0 0 1 0 0 1
## 4 1 1 0 0 0 0 0 0 0 0 0 0
## 5 1 1 1 0 0 0 0 0 0 0 0 0
## 6 0 1 0 0 0 0 0 1 0 0 0 1
## 7 0 0 0 0 0 0 0 1 0 1 1 0
## 12 0 0 0 0 0 1 1 0 0 0 1 1
## 8 0 0 1 0 0 0 0 0 0 1 1 1
## 9 0 0 0 0 0 0 1 0 1 0 1 0
## 10 0 0 0 0 0 0 1 1 1 1 0 1
## 11 0 1 1 0 0 1 0 1 1 0 1 0
## attr(,"call")
## nb2mat(neighbours = luxemburgo_vecinos, style = "B", zero.policy = T)
nb2mat(luxemburgo_vecinos, style = "W", zero.policy = T)
## [,1] [,2] [,3] [,4] [,5] [,6] [,7]
## 1 0.0000000 0.3333333 0.0000000 0.3333333 0.3333333 0.0000000 0.0000000
## 2 0.1666667 0.0000000 0.1666667 0.1666667 0.1666667 0.1666667 0.0000000
## 3 0.0000000 0.2500000 0.0000000 0.0000000 0.2500000 0.0000000 0.0000000
## 4 0.5000000 0.5000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
## 5 0.3333333 0.3333333 0.3333333 0.0000000 0.0000000 0.0000000 0.0000000
## 6 0.0000000 0.3333333 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
## 7 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
## 12 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.2500000 0.2500000
## 8 0.0000000 0.0000000 0.2500000 0.0000000 0.0000000 0.0000000 0.0000000
## 9 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.3333333
## 10 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.2000000
## 11 0.0000000 0.1666667 0.1666667 0.0000000 0.0000000 0.1666667 0.0000000
## [,8] [,9] [,10] [,11] [,12]
## 1 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
## 2 0.0000000 0.0000000 0.0000000 0.0000000 0.1666667
## 3 0.0000000 0.2500000 0.0000000 0.0000000 0.2500000
## 4 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
## 5 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
## 6 0.3333333 0.0000000 0.0000000 0.0000000 0.3333333
## 7 0.3333333 0.0000000 0.3333333 0.3333333 0.0000000
## 12 0.0000000 0.0000000 0.0000000 0.2500000 0.2500000
## 8 0.0000000 0.0000000 0.2500000 0.2500000 0.2500000
## 9 0.0000000 0.3333333 0.0000000 0.3333333 0.0000000
## 10 0.2000000 0.2000000 0.2000000 0.0000000 0.2000000
## 11 0.1666667 0.1666667 0.0000000 0.1666667 0.0000000
## attr(,"call")
## nb2mat(neighbours = luxemburgo_vecinos, style = "W", zero.policy = T)
rowSums(nb2mat(luxemburgo_vecinos, style = "W", zero.policy = T))
## 1 2 3 4 5 6 7 12 8 9 10 11
## 1 1 1 1 1 1 1 1 1 1 1 1
En esta matriz, a través del argumento style se puede especificar si queremos ver la matriz binaria (B) o ponderada (W, S o C).
Si bien esta matriz es informativa, podríamos verla mejor en un gráfico.
luxemburgo_vecinos_sf <- as(object = nb2lines(nb = luxemburgo_vecinos,
coords = coordinates(luxemburgo)),
'sf') %>%
st_set_crs(st_crs(luxemburgo))
## Warning in CRS(proj4string): CRS: projargs should not be NULL; set to NA
influencia_frontera <- luxemburgo %>%
st_as_sf() %>%
ggplot() +
geom_sf() +
geom_sf(data = luxemburgo_vecinos_sf, color="red", size=1) +
coord_sf()+
labs(title = "Frontera")
influencia_frontera
Gráficamente podemos comprobar la conexión de límites entre los distintos territorios existentes.
Ahora revisemos enfoques alternativos para calcular la influencia espacial.
Si lo hacemos basado en distancias:
# Distancias límite en kilómetros
luxemburgo_distancias <- dnearneigh(coordinates(luxemburgo), d1 = 0, d2 = 30, longlat = T)
luxemburgo_distancias
## Neighbour list object:
## Number of regions: 12
## Number of nonzero links: 80
## Percentage nonzero weights: 55.55556
## Average number of links: 6.666667
nb2mat(luxemburgo_distancias, style = "B")
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
## 1 0 1 0 1 1 0 0 0 0 0 0 0
## 2 1 0 1 1 1 1 0 1 1 0 1 1
## 3 0 1 0 1 1 0 0 0 1 0 1 1
## 4 1 1 1 0 1 1 0 1 0 0 0 1
## 5 1 1 1 1 0 0 0 0 0 0 0 1
## 6 0 1 0 1 0 0 1 1 0 0 1 1
## 7 0 0 0 0 0 1 0 1 1 1 1 1
## 8 0 1 0 1 0 1 1 0 1 1 1 1
## 9 0 1 1 0 0 0 1 1 0 1 1 1
## 10 0 0 0 0 0 0 1 1 1 0 1 1
## 11 0 1 1 0 0 1 1 1 1 1 0 1
## 12 0 1 1 1 1 1 1 1 1 1 1 0
## attr(,"call")
## nb2mat(neighbours = luxemburgo_distancias, style = "B")
Gráficamente:
luxemburgo_distancias_sf <- as(object = nb2lines(nb = luxemburgo_distancias,
coords = coordinates(luxemburgo)),
'sf') %>%
st_set_crs(st_crs(luxemburgo))
## Warning in CRS(proj4string): CRS: projargs should not be NULL; set to NA
influencia_distancia <- luxemburgo %>%
st_as_sf() %>%
ggplot() +
geom_sf() +
geom_sf(data = luxemburgo_distancias_sf, color="red", size=1) +
coord_sf() +
labs(title = "Distancia \n(min: 0km, max: 30 km)")
influencia_distancia
Si lo hacemos por los vecinos más cercanos:
# Distancias límite en kilómetros
luxemburgo_vecino1 <- knn2nb(knearneigh(coordinates(luxemburgo), k=1))
luxemburgo_vecino1
## Neighbour list object:
## Number of regions: 12
## Number of nonzero links: 12
## Percentage nonzero weights: 8.333333
## Average number of links: 1
## Non-symmetric neighbours list
nb2mat(luxemburgo_vecino1, style = "B")
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
## 1 0 0 0 0 1 0 0 0 0 0 0 0
## 2 0 0 0 1 0 0 0 0 0 0 0 0
## 3 0 0 0 0 1 0 0 0 0 0 0 0
## 4 0 1 0 0 0 0 0 0 0 0 0 0
## 5 0 0 1 0 0 0 0 0 0 0 0 0
## 6 0 0 0 0 0 0 0 1 0 0 0 0
## 7 0 0 0 0 0 0 0 1 0 0 0 0
## 8 0 0 0 0 0 1 0 0 0 0 0 0
## 9 0 0 0 0 0 0 0 0 0 1 0 0
## 10 0 0 0 0 0 0 0 0 1 0 0 0
## 11 0 0 0 0 0 0 0 0 0 0 0 1
## 12 0 1 0 0 0 0 0 0 0 0 0 0
## attr(,"call")
## nb2mat(neighbours = luxemburgo_vecino1, style = "B")
Gráficamente:
luxemburgo_vecino1_sf <- as(object = nb2lines(nb = luxemburgo_vecino1,
coords = coordinates(luxemburgo)),
'sf') %>%
st_set_crs(st_crs(luxemburgo))
## Warning in CRS(proj4string): CRS: projargs should not be NULL; set to NA
influencia_vecino <- luxemburgo %>%
st_as_sf() %>%
ggplot() +
geom_sf() +
geom_sf(data = luxemburgo_vecino1_sf, color="red", size=1) +
coord_sf()+
labs(title = "K-Vecinos (k=1)")
influencia_vecino
Comparemos los resultados:
ggarrange(influencia_frontera, influencia_distancia, influencia_vecino, nrow = 1)
Como hemos podido ver, las aproximaciones analíticas al concepto de distancias son variados y enriquecerán nuestros análisis según el enfoque que deseemos tomar.
¿Has entendido todos los conceptos? No dudes en hacer preguntas. ¡Nos vemos en el próximo capítulo!