1. Influencia espacial

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.

1.1. Vecinos más cercanos

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.

1.2. Matriz de pesos

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.

1.3. 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!