El presente documento tiene como objetivo mostar el proceso que se ha seguido para determinar en qué zonas de la España peninsular se concentra un nivel mayor de NO2 y en que partes la presencia de este gas contaminante es reducido. Para ello, utilizaremos una base de datos que contiene los valores de esta sustancia recogidos en estaciones de medición situadas en diferentes puntos del país. Nos ayudaremos del mapa de España a partir del cuál crearemos una maya que nos permitirá interpolar los valores para obtener los niveles de concentración correspondientes a todas las zonas de la Península y dar respuesta a la cuestión que queremos resolver.
El proceso se ha hecho para el año 2018. Para la obtención de los mapas que se corresponden con los años 2017, 2016, 2015 y 2014 se ha seguido el mismo proceso.
librerias <- c("tidyverse",
"dplyr",
"readxl",
"data.table",
"knitr",
"tables",
"kableExtra",
"sp",
"sf",
"gstat",
"osmdata",
"ggmap",
"stars",
"gstat",
"mapview",
"gridExtra"
)
for (pkg in librerias)
{
if (!(pkg %in% installed.packages()))
install.packages(pkg, repos = "http://cran.r-project.org")
library(pkg, character.only = TRUE)
}
Los datos con los que vamos a trabajar han sido obtenidos de la European Environmental Agency (EEA). Los estados miembros europeos proporcionan medidas de la calidad del aire a esta agencia. En este caso, nos interesa conocer el nivel de concentración de NO2 en el aire en la Península Ibérica en el año 2018 .(Link para descargar los datos:https://www.eea.europa.eu/data-and-maps/data/air-pollutant-concentrations-at-station/air-pollutant-concentrations-2018/air-pollutant-concentrations-2018-compared )
Estos datos han sido tratados antes de ser cargados en R. Se han seleccionado las observaciones que se corresponden con España y se han eliminado los datos atribuibles a las provincias de Ceuta, Melilla, Islas Baleares e Islas Canarias.
setwd("C:/Users/sanch/OneDrive/Escritorio/BIA/2021-2022/Datos_Espaciales_y_Espacio_Temporales/trabajo_final")
ES_NO2 <- read_excel("C:/Users/sanch/OneDrive/Escritorio/BIA/2021-2022/Datos_Espaciales_y_Espacio_Temporales/trabajo_final/NO2_por_anyos.xlsx",
sheet = "2018")
También eliminamos los NA y los datos que puedan estar duplicados. Este último paso es importante ya que al no hacerlo, los datos no eran del todo correctos y, además de tardar una importante cantidad de tiempo en ejecutar los diferentes trozos de código, no era posible llevar a cabo la interpolación con el Kriging Ordinario. Esto se debe a que los datos están medidos en diferentes momentos del tiempo por lo que hay valores duplicados
ES_NO2<- ES_NO2%>%
filter(!is.na(ES_NO2$AirPollutionLevel) == TRUE )
ES_NO2 <- ES_NO2 %>% distinct(Longitude, .keep_all = TRUE)
Para identidicar valores atípicos visualmente, representamos un diagrama de caja y bigotes (boxplot). Para ello hacemos uso de la función boxplot:
boxplot(ES_NO2$AirPollutionLevel, # Datos
horizontal = FALSE, # Horizontal o vertical
lwd = 2, # Lines width
col = "cadetblue3", # Color
xlab = "", # Etiqueta eje X
ylab = "", # Etiqueta eje Y
main = "Boxplot valores NO2", # Título
notch = F, # Añade intervalos de confianza para la mediana
border = "black", # Color del borde del boxplot
outpch = 25, # Símbolo para los outliers
outbg = "red", # Color de los datos atípicos
whiskcol = "black", # Color de los bigotes
whisklty = 2, # Tipo de línea para los bigotes
lty = 1) # Tipo de línea (caja y mediana)
Podemos apreciar la existencia de valores atipicos representados en color rojo. A continuación procedemos a eliminarlos y volver a hacer la representación para comprobar que ya no existen.
Vemos que se consideran valores outliers aquellos que son mayores que 44, por lo que, con la aydua de nuevo de la función filter, eliminamos dichos valores.
ES_NO2<- ES_NO2%>%
filter(!AirPollutionLevel > 44 )
boxplot(ES_NO2$AirPollutionLevel, # Datos
horizontal = FALSE, # Horizontal o vertical
lwd = 2, # Lines width
col = "cadetblue3", # Color
xlab = "", # Etiqueta eje X
ylab = "", # Etiqueta eje Y
main = "Boxplot valores NO2", # Título
notch = F, # Añade intervalos de confianza para la mediana
border = "black", # Color del borde del boxplot
outpch = 25, # Símbolo para los outliers
outbg = "red", # Color de los datos atípicos
whiskcol = "black", # Color de los bigotes
whisklty = 2, # Tipo de línea para los bigotes
lty = 1) # Tipo de línea (caja y mediana)
Ya no existen valores atípicos en nuestra base de datos. Veamos como queda el dataset de trabajo mostrando las 10 primeras líneas:
kbl(head(ES_NO2,10)) %>%
kable_styling(c("striped","bordered"))
| …1 | Country | AirQualityStationEoICode | AQStationName | AirPollutant | AirPollutionLevel | UnitOfAirpollutionLevel | AirQualityStationType | AirQualityStationArea | Longitude | Latitude | Altitude |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Coruña | Spain | ES1960A | A CABANA | NO2 | 7.301683 | ug/m3 | Industrial | suburban | -8.252200 | 43.4916 | 33 |
| Zaragoza | Spain | ES0588A | ABANTO | NO2 | 17.935069 | ug/m3 | Industrial | suburban | -3.074140 | 43.3205 | 136 |
| Alava | Spain | ES1544A | AGURAIN | NO2 | 12.072474 | ug/m3 | Background | suburban | -2.393700 | 42.8490 | 594 |
| Alicante | Spain | ES1968A | ALACANT - RABASSA | NO2 | 14.089008 | ug/m3 | Industrial | suburban | -0.513889 | 38.3511 | 20 |
| Alicante/Alacant | Spain | ES1635A | ALACANT-EL PLÁ | NO2 | 21.510279 | ug/m3 | Traffic | urban | -0.471944 | 38.3594 | 45 |
| Alicante/Alacant | Spain | ES1915A | ALACANT-FLORIDA-BABEL | NO2 | 23.783469 | ug/m3 | Background | urban | -0.506667 | 38.3403 | 55 |
| Zaragoza | Spain | ES1418A | ALAGÓN | NO2 | 16.934297 | ug/m3 | Traffic | suburban | -1.143330 | 41.7628 | 235 |
| Albacete | Spain | ES1535A | ALBACETE | NO2 | 13.538145 | ug/m3 | Background | suburban | -1.852100 | 38.9793 | 686 |
| Valencia | Spain | ES1911A | ALBALAT DELS TARONGERS | NO2 | 7.267711 | ug/m3 | Industrial | suburban | -0.336667 | 39.7053 | 320 |
| Sevilla | Spain | ES1640A | ALCALÁ DE GUADAIRA | NO2 | 17.138363 | ug/m3 | Background | urban | -5.833720 | 37.3425 | 68 |
Vemos la distribución de los valores que toma la variable de interés:
hist(ES_NO2$AirPollutionLevel,
col= "cadetblue3",
main="Histograma de NO2",
xlab = "NO2 (ug/m3)",
ylab = "Frecuencia")
La gran mayoría de los valores de NO2 se concentran entre el 5 y el 15. Creamos una nueva variable en la base de datos que contendrá el logaritmo neperiano de esta variable para que su distribución se asemeje a una normal y de esta manera evitar futuros problemas a la hora de llevar a cabo la interpolación. Asimismo, eliminamos aquellos valores que puedan ser negativos, ya no solo para no incurrur en dificultades sino porque no tiene mucho sentido que el nivel de concenctración de NO2 en el aire sea negativo, como poco, debería ser 0.
ES_NO2$VALORLOG = log(ES_NO2$AirPollutionLevel)
hist(ES_NO2$VALORLOG,
col= "cadetblue3",
main="Histograma de NO2 transformada con log Ln",
xlab = "NO2 (ug/m3)",
ylab = "Frecuencia")
ES_NO2<- ES_NO2%>%
filter(!VALORLOG < 0 )
hist(ES_NO2$VALORLOG,
col= "cadetblue3",
main="Histograma de NO2 transformada con log Ln",
xlab = "NO2 (ug/m3)",
ylab = "Frecuencia")
Convertimos nuestra base de datos en un objeto espacial. Utilizamos para ello el sistema de coordenadas de referencia “EPSG:4258” (https://epsg.io/4258).
Nuestra base de datos utilza el sistema de coordenadas de referencia “EPSG:4979” que es el que tiene un área de uso mundial, por eso le indicamos que tome ese crs a la hora de convertir la base de datos en un objeto espacial y posteriormente lo transformamos.
library(sf)
crs <- st_crs("EPSG:4258")
no2.sf <- st_as_sf(ES_NO2, coords = c("Longitude", "Latitude"), crs = "EPSG:4979") %>%
st_transform(crs)
Si representamos los puntos de datos podemos apreciar el mapa de España:
plot(no2.sf$geometry,asp=1,pch=1, col="cadetblue4")
Una vez tenemos los datos, vamos a utilizar el mapa de España agrupando por las diferentes Comunidades Autónomas. Dicho mapa nos resultará útil para la creación del grid a partir del cuál interpolaremos más adelante. Asimismo, utilizaremos el contorno del mismo para dar contexto a los valores obtenidos en la interpolación y así poder identificar con mayor facilidad a que zona se corresponde cada dato obtenido.
Para la obtención del plano hacemos uso de la libreria mapSpain y, como bien hemos explicado anteriormente, utilizaremos el de la Península Ibérica por lo que eliminamos las observaciones cuyo código de comundiad autónoma se corresponde con alguno de los siguientes: 04, 05, 18, 19. En otras palabras, eliminamos aquellos datos que se atribuyen a Ceuta, Melilla, Canarias y Baleares.
library(mapSpain)
mapa_ca<-mapSpain::esp_get_prov()
mapa_ca<- mapa_ca%>%
filter(!codauto %in% c("04","05","18","19")) %>%
group_by(codauto) %>%
summarise()
plot(mapa_ca$geometry)
Transformamos la variable del mapa en un objeto espacial y le asignamos el sistema de coordenads de referencia con el que vamos a trabajar que es el mismo que hemos asignado al objeto que contiene los datos, el “EPSG:4258”.
mapa.sf <- st_as_sf(mapa_ca, coords = geometry)
st_crs(mapa.sf) <- crs
ggplot() +
geom_sf(data = mapa.sf) +
geom_sf(data = no2.sf, color="cadetblue4")+#, mapping = aes(col = VALORLOG))+
labs(title = "Localización de las estaciones de medición de NO2",
subtitle="España 2018",
caption = "Fuente: European Environment Agency")+
theme_void()
La dependencia espacial local significa que cuanto más cerca estén los puntos en el espacio geográfico, más cerca también lo están en el espacio de atributos. Esto se llama autocorrelación. Una variable puede correlacionarse consigo misma, dependiendo la fuerza de la correlación de la distancia de separación entre puntos. También es importante concocer el concepto de semivarianza, que es una medida de la similitud que existe entre observaciones situadas a una determinada distancia. Cada par de puntos, estará separado por una distancia en el espacio geográfico y una semivarianza en el espacio de atributos. Fuentes: · https://www.redalyc.org/pdf/360/36014577008.pdf
· https://www.uv.es/pegivir/BIA/sesion_practica_7.html
Podemos calcular el número de pares de puntos que hay en el conjunto de datos:
n <- length(no2.sf$VALORLOG)
n*(n-1)/2
## [1] 84666
Calculamos la distancia y la semivarianza para los primeros dos puntos del dataset:
dim(st_coordinates(no2.sf))
## [1] 412 2
(zi <- st_coordinates(no2.sf)[1,]) #coordenadas del primer punto
## X Y
## -8.2522 43.4916
(zj <- st_coordinates(no2.sf)[2,]) #coordenadas del segundo punto
## X Y
## -3.07414 43.32050
(sep <- dist(st_coordinates(no2.sf)[1:2,])) #distancia entre ambos puntos
## 1
## 2 5.180886
La distancia entre ambos puntos es de 8.4 metros.
(gamma <- 0.5 * (no2.sf$VALORLOG[1] - no2.sf$VALORLOG[2])^2) # semivarianza, unidades log(mg kg-1)^2
## [1] 0.4037887
Ahora graficaremos el variograma experimental, teniendo en cuenta las concentraciones de NO2 (en log). Recordemos que el variograma es una representación gráfica que agrupa los pares de puntos por intervalos de distancia, calculando la media de todas las semivarianzas para cada intervalo. En otras palabras, el variograma mide la variación existente entre la distancia y la variabilidad. Por defecto, la función variogram() usa un valor de cutoff igual a la tercera parte de la diagonal del boundg box del conjunto de puntos, y lo divide en 15 partes para obtener el valor de width. La fórmula que usaremos es VALORLOG~1. El 1 quiere decir que VALORLOG (la variable dependiente) solo depende de sí misma (“autocorrelación”). Es decir, la presencia de NO2 en el aire no depende de otra variable.
(ve <- variogram(VALORLOG~1, no2.sf))
## np dist gamma dir.hor dir.ver id
## 1 1924 14.63110 0.1422311 0 0 var1
## 2 1536 43.95134 0.4773076 0 0 var1
## 3 1876 74.30104 0.5319810 0 0 var1
## 4 1738 102.85488 0.7385145 0 0 var1
## 5 1964 132.45715 0.7033644 0 0 var1
## 6 2211 162.03678 0.6236147 0 0 var1
## 7 2413 192.13888 0.6564020 0 0 var1
## 8 2705 220.66761 0.6169243 0 0 var1
## 9 2967 250.38872 0.5641465 0 0 var1
## 10 3506 279.95074 0.6463895 0 0 var1
## 11 4470 308.81773 0.5519221 0 0 var1
## 12 4188 338.07885 0.6182939 0 0 var1
## 13 3913 367.02457 0.6308508 0 0 var1
## 14 4237 396.59295 0.6309867 0 0 var1
## 15 3709 425.71211 0.5901305 0 0 var1
plot(ve, plot.numbers = TRUE, pch = 20,
xlab = "Distancia",
ylab = "Semivarianza",
col= "cadetblue3")
Ahora repetimos el paso anterior pero esta vez en lugar de usar los valores por defecto, fijamos los intervalos (width) en una distancia de 30 metros, hasta una distancia máxima (cutoff) de 500 metros.
(ve <- variogram(VALORLOG ~ 1, no2.sf, cutoff = 500, width = 30))
## np dist gamma dir.hor dir.ver id
## 1 1951 14.83899 0.1435275 0 0 var1
## 2 1568 44.77696 0.4899381 0 0 var1
## 3 1930 75.61863 0.5205324 0 0 var1
## 4 1778 105.10569 0.7632675 0 0 var1
## 5 2013 135.10713 0.6763529 0 0 var1
## 6 2285 165.19115 0.6349109 0 0 var1
## 7 2486 195.87667 0.6670568 0 0 var1
## 8 2801 224.95162 0.5908225 0 0 var1
## 9 3062 255.25967 0.5982555 0 0 var1
## 10 3818 285.55265 0.6004536 0 0 var1
## 11 4558 314.66515 0.5729544 0 0 var1
## 12 4312 345.09318 0.6442019 0 0 var1
## 13 4030 375.41595 0.6095890 0 0 var1
## 14 4218 404.85771 0.6367131 0 0 var1
## 15 3589 434.62169 0.5660850 0 0 var1
## 16 3641 465.80155 0.6092482 0 0 var1
## 17 3146 490.33361 0.5514807 0 0 var1
plot(ve, plot.numbers = TRUE, pch = 20,
xlab = "Distancia",
ylab = "Semivarianza",
col= "cadetblue3")
Calculamos la diagonal del bounding box
min <- st_point(c(st_bbox(no2.sf)[1],st_bbox(no2.sf)[2]))
max <- st_point(c(st_bbox(no2.sf)[3],st_bbox(no2.sf)[4]))
st_distance(min,max)
## [,1]
## [1,] 14.17983
Pueden representarse los valores de la semivarianza frente a las distancias, obteniendo una nube de puntos (nube del variograma) como esta:
plot(variogram(VALORLOG ~ 1, no2.sf, cloud=TRUE), pch = 20,
xlab = "Distancia",
ylab = "Semivarianza",
col= "cadetblue3")
Se puede ver que a medida que aumenta la distancia, los valores que toma la semivarianza se van “despegando” del fondo (dejan de concentrarse en los valores más bajos) y van tomando valores más altos. No obstante, esta nube aporta, en principio, poca información, pero puede resumirse agrupando los pares de puntos por intervalos de distancia, y calculando la media de todas las semivarianzas en cada intervalo, como veremos más adelante.
También podemos mostrar con la función variogram() los pares de puntos que están más próximos/lejanos entre sí:
vc <- variogram(VALORLOG ~ 1, no2.sf, cloud=TRUE)
min(vc$dist)
## [1] 0.7222379
max(vc$dist)
## [1] 440.9152
vc <- vc %>% arrange(dist)
head(vc)
## dist gamma dir.hor dir.ver id left right
## 1 0.7222379 0.051326991 0 0 var1 185 146
## 2 0.8298581 0.030524949 0 0 var1 137 83
## 3 0.8782850 0.059199794 0 0 var1 181 93
## 4 0.9318389 0.002764745 0 0 var1 156 52
## 5 0.9947059 0.048959306 0 0 var1 183 82
## 6 1.0165444 0.032729850 0 0 var1 401 278
Y verificar que distancia y semivarianza tienen una relación positiva:
ggplot(data = vc, aes(x=dist, y=gamma)) +
geom_point(color="cadetblue3") +
geom_smooth(method='lm', formula=y~x,
col="cadetblue4")+
xlab("Distancia") + ylab("Gamma") +
theme_classic()
vemos que hay una relacion positiva entre la distancia y gamma. Una pendiente positiva nos dice que a mayor distancia mayor es la variabilidad mientras que a menos distacnia, más parecidos son los puntos.
Un aspecto importante a tener en cuenta a la hora de hacer una interpolación espacial es la determinación de efectos direccionales. En caso de existir anistropía espacial, influirá bastante la dirección de la interpolación, pues, la estructura espacial será diferente en distintas direcciones. Por el contrario, en caso de exisitir isotpía espacial, la dirección en la que se haga la interpolación no influirá en los resultados obtenidos ya que la estructura espacial será la misma en diferentes direcciones. No se procederá, por tanto, de la misma manera en un caso que en otro.
“En caso de detectar anisotropía espacial se definiría una elipse cuyos ejes mayor y menor reflejasen adecuadamente la misma. En caso de no detectar anisotropía se optaría por el modelo isotrópico,en el que sólo se tendría en cuenta la variación de los valores en función de la distancia.”
Los gases son considerados materiales isótropos.
“Un material se considera isotrópico si sus propiedades no varían con la dirección. Los materiales isotrópicos, por lo tanto, tienen un módulo elástico, un coeficiente de Poisson, una conductividad térmica, un coeficiente de expansión térmica, etc. idénticos en todas direcciones. El término isotérmico algunas veces se utiliza para indicar materiales sin direcciones preferidas para coeficientes de expansión térmica.”
No obstante, vamos a ajustar un variograma en diferentes grados de dirección, concretamente en 0º,45º,90º y 135º para comprobar si nuestra variable de estudio es isótropa.
Para ello, usamos la función variogram de nuevo pero con la peculariedad de que esta vez añadimos el argumento alpha= en el que le damos un vector con las direcciones en plano en grados positivos. Posteriormente, dibujamos los variogramas obtenidos para cada una de las direcciones. Esto lo haremos con la función ggplot y, dentro de esta, usaremos facet_wrap para que muestre cada uno de los variogramas.
vgm.aniso <- variogram(VALORLOG ~ 1, no2.sf, alpha = c(0, 45, 90, 135))
(no2.aniso.plot<- ggplot(aes(x = dist, y = gamma), data = vgm.aniso)+ # dibujamos los variogramas
geom_point(color="cadetblue4")+ # queremos que muestre los puntos y en ese color
facet_wrap(~dir.hor)+ # queremos que divida en función de la dirección
scale_y_continuous(limits = c(0, 1.3))+ # ajustamos el eje de las y
xlab("Distancia") + ylab("Gamma") + # ponemos nombre a los ejes
labs(title = "Determinación de efectos direccionales") + # ponemos titulo
theme_bw()+
theme(strip.background = element_rect(fill="cadetblue3"))) # queremos pintar los títulos de ese color
La correlación espacial se mantiene igual de fuerte en las diferentes direcciones, por lo que confirmamos que el gas, en este caso el NO2, es un material isótropo.
Ahora queremos conocer cómo se relacionan las semivarianzas respecto de las distancias, en promedio. Para saberlo, debemos comparar nuestro viariograma experimiental con los distintos variogramas empíricos existentes. Primero, veamos los diferentes tipos:
print(show.vgms())
Una vez tenemos claro qué modelo se asemeja más a nuestro variograma experimental, lo generamos. En nuestro caso, usaremos el modelo esférico: “Sph”. Para ello, debemos introducir los parámetros del que conforman el variograma empírico, es decir, sill, range y nugget:
vm <- vgm(model="Sph",
psill=0.51,
range=95,
nugget=0.1)
plot(ve, pl=T, model=vm, pch=20, xlab = "distance", ylab = "semivariance",
col= "cadetblue3")
Vemos que a partir de una determinada distancia, la variabilidad no cambia, la distancia ya no influye en la variabilidad de los valores.
Pero puede que no veamos de forma clara los valores, o no hayamos detectado bien el modelo del variograma empírico. Esto mismo lo podemos hacer de forma automática:
(vmf <- fit.variogram(ve, vm))
## model psill range
## 1 Nug 0.0000000 0.00000
## 2 Sph 0.6418862 93.74466
plot(ve, pl=T, model=vmf, pch=20, xlab = "distance", ylab = "semivariance")
Podemos afirmar, por tanto, que nuestro conjunto de datos sigue un modelo esférico.
Para interpolar primero debemos decidir dónde lo queremos hacer. Normalmente esto se suele realizar en un grid (una red o cuadrícula) que cubre el área de interés. En nuestro caso, queremos interpolar en el área de España, por lo que, partiendo del contorno del país, creamos un grid uniforme con 10km de celdas de grid sobre la región:
st_bbox(mapa.sf) %>% # seleccionamos el contorno del mapa de España
st_as_stars(mapa.sf = 10000) %>% # creamos un grid uniforme con 10km de celdas de grid sobre España
st_crop(mapa.sf) -> grd # creamos el objeto grd que contendrá el grid
plot(grd, main="GRID", col="cadetblue3")
Ahora vamos a practicar la interpolación espacial, es decir, calcular valores desconocidos a partir de una muestra. El resultado que vamos a obtener se conoce habitualmente por superficie estadística, una superficie “continua” (capa raster) con valores interpolados a partir de otros conocidos.
Este método de interpolación es considerado como método no-geoestadístico. Al igual que el IDW no requiere ningún modelado especial de relaciones espaciales. En este se producen cambios abruptos de bordes y solo utiliza un punto para cada predicción.
thiessen <- krige(VALORLOG ~ 1, no2.sf, grd, nmax = 1)
## [inverse distance weighted interpolation]
plot(thiessen)
Esta representación gráfica permite hacernos una idea de las zonas en las que el nivel de NO2 es mayor. No obstante, no resulta muy visual y hay zonas en las que puede ser complicado determinar a que comunidad autónoma pertenece. Por tanto, representamos gráficamente con ggplot la predicción obtenida y añadiremos una capa con el contorno del mapa de España y sus comunidades autónomas así como una paleta de colores:
ggplot() + geom_stars(data = thiessen, aes(fill = var1.pred, x = x, y = y)) +
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) +
xlab("Longitud") + ylab("Latitud") +
labs(title = "Predicción Poligonos de Thiessen log NO2 (ug/m3)") +
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+
theme_void()
Resulta difícil dar una buena interpretación de la predicción obtenida ya que la presencia de tantas figuras geométricas (polígonos) dentro del mapa hace que sea un poco caótico y algo impreciso.
La interpolación mediante distancia inversa ponderada (IDW) determina los valores desconocidos a partir de una combinación ponderada de un conjunto de puntos de muestra. Este método presupone que el valor de la variable observada disminuye conforme aumenta la distancia respecto del punto muestreado.
Quizá este sea el método de interpolación más simple ya que no requiere ningún modelado especial de relaciones espaciales.
Utilizamos la función idw:
i = idw(VALORLOG~1, no2.sf, grd)
## [inverse distance weighted interpolation]
ggplot() + geom_stars(data = i, aes(fill = var1.pred, x = x, y = y)) + # predicción IDW
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) + # contorno del mapa de España
xlab("Longitud") + ylab("Latitud") + # damos nombre a los ejes aunque no se verán
labs(title = "Predicción IDW log NO2 (ug/m3)") + # ponemos un título al mapa
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+ # añadimos color y ponemos el fondo blanco (transparente)
theme_void() # utilizamos este tema por lo que no aparecerán los nombres de los ejes
Observamos que los niveles de concentración de NO2 en el aire más elevados se situan en Madrid y Barcelona, los centros urbanos más grandes de España. Estas provincias destacan además por concentrar valores elevados de este gas contaminante en todas las áreas. También se aprecian concentraciones elevadas en ciudades como Valencia, Bilbao, Girona y Ourense, así como algunas áreas puntuales en la Comunidad Autónoma de Sevilla.
Otro método de interpolación muy utilizado es el kriging ordinario (ordinary kriging). La principal ventaja que tiene este método respecto al anterior es que los pesos asignados a cada valor de la muestra son los óptimos, teniendo en cuenta el variograma (IDW solo tiene en cuenta la distancia).
ok <- krige(formula = VALORLOG ~ 1,
locations = no2.sf,
newdata = grd,
model = vmf)
## [using ordinary kriging]
ggplot() + geom_stars(data = ok, aes(fill = var1.pred, x = x, y = y)) +
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) +
xlab("Longitud") + ylab("Latitud") +
labs(title = "Predicción Kriging Ordinario log NO2 (ug/m3) 03/04/2022") +
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+
theme_void()
Observando el gráfico podemos identificar que las zonas con mayores concentraciones de NO2 están definidas de una manera más homogénea. Se trata de Madrid, área en la mayor es la concentración de NO2, además de Barcelona, Valencia, Bilbao, Girona y Ourense, así como algunas áreas puntuales en la Comunidad Autónoma de Sevilla.
En este caso, también podemos representar las predicciones de las varianzas:
ggplot() + geom_stars(data = ok, aes(fill = var1.var, x = x, y = y)) +
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) +
xlab("Longitud") + ylab("Latitud") +
labs(title = "Predicción varianzas Kriging Ordinario log NO2 (ug/m3)") +
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+
theme_void()
Se puede observar que, en general, las zonas con predicciones de las varianzas más bajas están en las proximidades al mar, es decir, en las zonas costeras de las comunidades autónomas que son colindantes con el mar mediterráneo, el mar cantábrico y el océano atlántico.
También cabe mencionar Madrid y algunas zonas del interior de Andalucía, Extremadura, Aragón, Castilla-La Mancha y Castilla y León.
Puede ser útil visualizar las predicciones y las observaciones en un mismo gráfico. Para ello añadimos una capa con las observaciones las cuales estarán representadas con un círculo cuya grandaria representa el nivel de concentración. A mayor nivel de concentración, mayor es el tamaño del círculo.
La línea con la capa que añadimos es: geom_sf(data = no2.sf, pch=1,col="white", cex=12*no2.sf$AirPollutionLevel/max(no2.sf$AirPollutionLevel))
ggplot() + geom_stars(data = ok, aes(fill = var1.pred, x = x, y = y)) +
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) +
geom_sf(data = no2.sf, pch=1,col="white", cex=5*no2.sf$AirPollutionLevel/max(no2.sf$AirPollutionLevel))+
xlab("Longitud") + ylab("Latitud") +
labs(title = "Predicción Kriging Ordinario log NO2 (ug/m3) + NO2 (ug/m3)") +
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+
theme_void()
Una vez hemos hecho la interpolación con el método Kriging Ordinario, llevar a cabo un Kriging Simple es bastante fácil. Este utiliza los mismos argumentos utilizados en el OK con la diferencia de que tiene un argumento adicional, beta.
Si usamos la ayuda de lafunción krige nos dice que si no hay variables independientes (como es el caso) “el modelo solo contiene un intercepto y beta debería ser la media del Kriging simple”. En nuestro caso, como estamos trabajando con el logaritmo usaremos una media igual a 1. Obtenemos lo siguiente:
sk<-krige(VALORLOG~ 1,
loc=no2.sf,
newdata=grd,
model = vmf,
beta = 1)
## [using simple kriging]
ggplot() + geom_stars(data = sk, aes(fill = var1.pred, x = x, y = y)) +
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) +
xlab("Longitud") + ylab("Latitud") +
labs(title = "Predicción Kriging Simple log NO2 (ug/m3)") +
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+
theme_void()
Observando el gráfico podemos identificar que las zonas con mayores concentraciones de NO2 están definidas de una manera más homogénea.
Otra de las características del método kriging es que permite usar el mismo conjunto de datos tanto para modelar y predecir como para evaluar dichas predicciones. Esto se llama validación cruzada. Consiste en predecir los valores en las localizaciones observadas a partir de los datos generados anteriormente. De este modo podemos comparar las predicciones con la realidad. Esta técnica la podemos aplicar también a otro tipo de interpolaciones, como la IDW, mediante la función krige.cv() del paquete gstat:
idw.cv <- krige.cv(formula = log10(AirPollutionLevel) ~ 1, locations = no2.sf)
head(idw.cv)
## Simple feature collection with 6 features and 6 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -8.2522 ymin: 38.3403 xmax: -0.471944 ymax: 43.4916
## Geodetic CRS: ETRS89
## var1.pred var1.var observed residual zscore fold geometry
## 1 1.113660 NA 0.863423 -0.25023693 NA 1 POINT (-8.2522 43.4916)
## 2 1.104284 NA 1.253703 0.14941934 NA 2 POINT (-3.07414 43.3205)
## 3 1.167544 NA 1.081796 -0.08574748 NA 3 POINT (-2.3937 42.849)
## 4 1.366619 NA 1.148880 -0.21773881 NA 4 POINT (-0.513889 38.3511)
## 5 1.255236 NA 1.332646 0.07741023 NA 5 POINT (-0.471944 38.3594)
## 6 1.169909 NA 1.376275 0.20636578 NA 6 POINT (-0.506667 38.3403)
thiessen.cv <- krige.cv(formula = log10(AirPollutionLevel) ~ 1, locations = no2.sf, nmax = 1)
head(thiessen.cv)
## Simple feature collection with 6 features and 6 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -8.2522 ymin: 38.3403 xmax: -0.471944 ymax: 43.4916
## Geodetic CRS: ETRS89
## var1.pred var1.var observed residual zscore fold geometry
## 1 1.1189932 NA 0.863423 -0.25557025 NA 1 POINT (-8.2522 43.4916)
## 2 0.9336456 NA 1.253703 0.32005746 NA 2 POINT (-3.07414 43.3205)
## 3 1.1026303 NA 1.081796 -0.02083402 NA 3 POINT (-2.3937 42.849)
## 4 1.3762752 NA 1.148880 -0.22739478 NA 4 POINT (-0.513889 38.3511)
## 5 1.3762752 NA 1.332646 -0.04362916 NA 5 POINT (-0.471944 38.3594)
## 6 1.1488804 NA 1.376275 0.22739478 NA 6 POINT (-0.506667 38.3403)
ok.cv <- krige.cv(formula = log10(AirPollutionLevel) ~ 1, locations = no2.sf, model = vmf)
head(ok.cv)
## Simple feature collection with 6 features and 6 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -8.2522 ymin: 38.3403 xmax: -0.471944 ymax: 43.4916
## Geodetic CRS: ETRS89
## var1.pred var1.var observed residual zscore fold
## 1 1.102084 0.02678944 0.863423 -0.23866066 -1.4581393 1
## 2 1.048951 0.04555412 1.253703 0.20475240 0.9593237 2
## 3 1.194732 0.19248550 1.081796 -0.11293549 -0.2574136 3
## 4 1.357330 0.02464658 1.148880 -0.20844919 -1.3277671 4
## 5 1.241641 0.06419993 1.332646 0.09100518 0.3591690 5
## 6 1.183676 0.02427743 1.376275 0.19259935 1.2360997 6
## geometry
## 1 POINT (-8.2522 43.4916)
## 2 POINT (-3.07414 43.3205)
## 3 POINT (-2.3937 42.849)
## 4 POINT (-0.513889 38.3511)
## 5 POINT (-0.471944 38.3594)
## 6 POINT (-0.506667 38.3403)
sk.cv <- krige.cv(formula = log10(AirPollutionLevel) ~ 1, locations = no2.sf, model = vmf, beta=1)
head(sk.cv)
## Simple feature collection with 6 features and 6 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -8.2522 ymin: 38.3403 xmax: -0.471944 ymax: 43.4916
## Geodetic CRS: ETRS89
## var1.pred var1.var observed residual zscore fold
## 1 1.103176 0.02678602 0.863423 -0.23975263 -1.4649044 1
## 2 1.049311 0.04555377 1.253703 0.20439242 0.9576408 2
## 3 1.196345 0.19247839 1.081796 -0.11454886 -0.2610957 3
## 4 1.356889 0.02464606 1.148880 -0.20800820 -1.3249720 4
## 5 1.242696 0.06419693 1.332646 0.08995017 0.3550135 5
## 6 1.184243 0.02427657 1.376275 0.19203266 1.2324843 6
## geometry
## 1 POINT (-8.2522 43.4916)
## 2 POINT (-3.07414 43.3205)
## 3 POINT (-2.3937 42.849)
## 4 POINT (-0.513889 38.3511)
## 5 POINT (-0.471944 38.3594)
## 6 POINT (-0.506667 38.3403)
Comparamos las predicciones con los datos reales, para los dos tipos de interpolación:
par(mfrow=c(2,2))
plot(var1.pred~observed, thiessen.cv, pch = 20, main="Thiessen", col="cadetblue3", reset = FALSE)
plot(var1.pred~observed, idw.cv, pch = 20, main="IDW", col="cadetblue3", reset = FALSE)
plot(var1.pred~observed, ok.cv, pch = 20, main="Kriging ordinario", col="cadetblue3",reset = FALSE)
plot(var1.pred~observed, sk.cv, pch = 20, main="Kriging simple", col="cadetblue3")
Da la sensación de que en el gráfico del Kriging Ordinario los puntos están ligeramente más agrupados que en el de IDW.
Podemos calcular el coeficiente de correlación entre valores reales y predicciones (1 equivale a relación perfecta):
cor.idw.cv <- cor(idw.cv$var1.pred, idw.cv$observed)
cor.thiessen.cv <- cor(thiessen.cv$var1.pred, thiessen.cv$observed)
cor.ok.cv <- cor(ok.cv$var1.pred, ok.cv$observed)
cor.sk.cv <- cor(sk.cv$var1.pred, sk.cv$observed)
Y también podemos calcular el error de la interpolación (la media de los residuos). Cuanto más pequeño, mejor:
mean.idw.resid <- mean(idw.cv$residual)
mean.thiessen.resid <- mean(thiessen.cv$residual)
mean.ok.resid <- mean(ok.cv$residual)
mean.sk.resid <- mean(sk.cv$residual)
Y también podemos calcular el error cuadrático medio (RMSE):
rmse.idw <- sqrt(sum(idw.cv$residual^2)/length(idw.cv$residual))
rmse.thiessen <- sqrt(sum(thiessen.cv$residual^2)/length(thiessen.cv$residual))
rmse.ok <- sqrt(sum(ok.cv$residual^2)/length(ok.cv$residual))
rmse.sk <- sqrt(sum(sk.cv$residual^2)/length(sk.cv$residual))
Volcamos toda esta iformación en una tabla y comparamos los dos métodos para ver cuál de los dos seleccionamos:
col1<-cbind(cor.thiessen.cv,cor.idw.cv,cor.ok.cv,cor.sk.cv)
col2<-cbind(mean.thiessen.resid,mean.idw.resid,mean.ok.resid,mean.sk.resid)
col3<-cbind(rmse.thiessen,rmse.idw,rmse.ok,rmse.sk)
tabla<-rbind(col1,col2,col3)
rownames(tabla) <- c("Correlación", "Media residuos", "RMSE")
colnames(tabla) <- c("Polígonos de Thiessen", "IDW", "Kriging Ordinario","Kriging Simple")
kbl(tabla) %>%
kable_styling(c("striped","bordered"))%>%
column_spec(1:5, bold = T)%>%
add_header_above(c("DATOS VALIDACIÓN CRUZADA" = 5),background = "blue",color = "white")
| Polígonos de Thiessen | IDW | Kriging Ordinario | Kriging Simple | |
|---|---|---|---|---|
| Correlación | 0.6263509 | 0.6787736 | 0.6948987 | 0.6910236 |
| Media residuos | -0.0289108 | -0.0856011 | -0.0252730 | -0.0302377 |
| RMSE | 0.2850853 | 0.2562169 | 0.2401582 | 0.2415531 |
Los datos obtenidos muestran que el método Kriging Ordinario ofrece mejores predicciones que el resto de métodos utilizados. No sólo la correlación es mayor sino que, además, tanto la media de los resiudos como el error cuadrático medio son más bajos. Por lo que nos quedamos con la predicción que proporciona el método de Kriging Ordinario
ok.pred<-ggplot() + geom_stars(data = ok, aes(fill = var1.pred, x = x, y = y)) +
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) +
xlab("Longitud") + ylab("Latitud") +
labs(title = "Predicción OK log NO2 (ug/m3)") +
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+
theme_void()
ok.var<-ggplot() + geom_stars(data = ok, aes(fill = var1.var, x = x, y = y)) +
geom_sf(data = st_cast(mapa.sf, "MULTILINESTRING")) +
xlab("Longitud") + ylab("Latitud") +
labs(title = "Predicción varianzas OK log NO2 (ug/m3)") +
scale_fill_gradientn(colours = sf.colors(20), na.value=NA)+
theme_void()
grid.arrange(ok.pred, ok.var, nrow = 1)