El presente documento desarrolla una exploración de datos espaciales mediante herramientas de geoestadística. La actividad se enfoca en la variable Temperature, registrada en una finca de aguacate, con el propósito de analizar su comportamiento espacial e interpolar la temperatura en zonas no muestreadas mediante kriging.
De acuerdo con las indicaciones del caso, el análisis se restringe
únicamente al primer periodo de medición del
01/10/2020, identificado en el campo
FORMATTED_DATE_TIME. Para este periodo se espera trabajar
con 534 registros, correspondientes a puntos espaciales
con coordenadas de latitud y longitud.
options(repos = c(CRAN = "https://cloud.r-project.org"))
paquetes <- c(
"readxl", "dplyr", "tidyr", "stringr", "lubridate", "janitor",
"ggplot2", "knitr", "kableExtra", "purrr", "tibble",
"sf", "sp", "gstat", "spdep", "viridis", "patchwork"
)
paquetes_faltantes <- paquetes[!vapply(paquetes, requireNamespace, logical(1), quietly = TRUE)]
if (length(paquetes_faltantes) > 0) {
install.packages(paquetes_faltantes, dependencies = TRUE)
}
invisible(lapply(paquetes, library, character.only = TRUE))Se toma como fuente de datos el archivo
Datos_Completos_Aguacate.xlsx relacionado
en la actividad.
archivo_excel <- "Datos_Completos_Aguacate.xlsx"
if (!file.exists(archivo_excel)) {
stop("No se encontró el archivo Datos_Completos_Aguacate.xlsx.")
}
datos_raw <- readxl::read_excel(archivo_excel, sheet = 1) |>
janitor::clean_names()
dim(datos_raw)## [1] 20271 21
tibble(
numero = seq_along(names(datos_raw)),
variable = names(datos_raw)
) |>
knitr::kable(caption = "Variables disponibles en la base de datos") |>
kableExtra::kable_styling(full_width = FALSE)| numero | variable |
|---|---|
| 1 | id_arbol |
| 2 | latitude |
| 3 | longitude |
| 4 | formatted_date_time |
| 5 | psychro_wet_bulb_temperature |
| 6 | station_pressure |
| 7 | relative_humidity |
| 8 | crosswind |
| 9 | temperature |
| 10 | barometric_pressure |
| 11 | headwind |
| 12 | direction_true |
| 13 | direction_mag |
| 14 | wind_speed |
| 15 | heat_stress_index |
| 16 | altitude |
| 17 | dew_point |
| 18 | density_altitude |
| 19 | wind_chill |
| 20 | estado_fenologico_predominante |
| 21 | frutos_afectados |
La columna original FORMATTED_DATE_TIME contiene fecha y
hora en formato de texto. Para cumplir con la indicación del caso, se
extrae únicamente la fecha y se filtra el periodo
01/10/2020.
extraer_fecha <- function(x) {
x <- as.character(x)
x <- stringr::str_replace_all(x, "\\u00A0", " ")
x <- stringr::str_squish(x)
fecha_txt <- stringr::str_extract(x, "\\d{1,2}/\\d{1,2}/\\d{4}")
lubridate::dmy(fecha_txt)
}
datos <- datos_raw |>
mutate(
fecha_medicion = extraer_fecha(formatted_date_time),
latitude = as.numeric(latitude),
longitude = as.numeric(longitude),
temperature = as.numeric(temperature)
)
fecha_objetivo <- lubridate::dmy("01/10/2020")
datos_periodo <- datos |>
filter(fecha_medicion == fecha_objetivo) |>
filter(!is.na(latitude), !is.na(longitude), !is.na(temperature))
validacion_periodo <- tibble(
fecha_analizada = as.character(fecha_objetivo),
registros_periodo = nrow(datos_periodo),
registros_esperados = 534,
cumple_cantidad_esperada = nrow(datos_periodo) == 534,
coordenadas_unicas = dplyr::n_distinct(
paste(datos_periodo$latitude, datos_periodo$longitude)),
temperaturas_faltantes = sum(is.na(datos_periodo$temperature))
)
validacion_periodo |>
knitr::kable(caption = "Validación del periodo seleccionado") |>
kableExtra::kable_styling(full_width = FALSE)| fecha_analizada | registros_periodo | registros_esperados | cumple_cantidad_esperada | coordenadas_unicas | temperaturas_faltantes |
|---|---|---|---|---|---|
| 2020-10-01 | 534 | 534 | TRUE | 534 | 0 |
if (nrow(datos_periodo) != 534) {
stop("La selección del periodo 01/10/2020 no generó 534 registros. Revise el formato de la columna FORMATTED_DATE_TIME.")
}El análisis se realizará exclusivamente con los 534 registros del periodo 01/10/2020. Esto garantiza que no se mezclen diferentes momentos de medición y que la interpolación espacial represente un único instante de observación.
resumen_temperature <- datos_periodo |>
summarise(
n = n(),
minimo = min(temperature, na.rm = TRUE),
q1 = quantile(temperature, 0.25, na.rm = TRUE),
media = mean(temperature, na.rm = TRUE),
mediana = median(temperature, na.rm = TRUE),
q3 = quantile(temperature, 0.75, na.rm = TRUE),
maximo = max(temperature, na.rm = TRUE),
desviacion_estandar = sd(temperature, na.rm = TRUE),
coeficiente_variacion = sd(temperature, na.rm = TRUE) / mean(temperature, na.rm = TRUE)
)
resumen_temperature |>
knitr::kable(caption = "Resumen descriptivo de la variable Temperature") |>
kableExtra::kable_styling(full_width = FALSE)| n | minimo | q1 | media | mediana | q3 | maximo | desviacion_estandar | coeficiente_variacion |
|---|---|---|---|---|---|---|---|---|
| 534 | 22.2 | 24.5 | 25.82903 | 25.8 | 27.175 | 29.7 | 1.771073 | 0.0685691 |
ggplot(datos_periodo, aes(x = temperature)) +
geom_histogram(bins = 25, color = "white") +
labs(
title = "Distribución de la variable Temperature",
subtitle = "Periodo analizado: 01/10/2020",
x = "Temperature",
y = "Frecuencia"
) +
theme_minimal()La distribución de la variable Temperature muestra valores aproximados entre 22 °C y 30 °C, con mayor concentración de observaciones entre 24 °C y 28 °C. El histograma evidencia una concentración importante alrededor de los 26 °C, lo cual sugiere que la temperatura presenta una distribución relativamente concentrada, aunque con variabilidad suficiente para justificar un análisis espacial.
ggplot(datos_periodo, aes(y = temperature)) +
geom_boxplot() +
labs(
title = "Diagrama de caja de Temperature",
subtitle = "Evaluación inicial de dispersión y posibles valores atípicos",
y = "Temperature"
) +
theme_minimal()El diagrama de caja confirma que la mediana de la temperatura se ubica aproximadamente en 25.8 °C. La mayor parte de los datos se encuentra entre aproximadamente 24.5 °C y 27.2 °C. No se observan valores atípicos extremos marcados en el boxplot, lo que indica que los datos son consistentes para continuar con el análisis geoestadístico sin necesidad de eliminar observaciones por comportamiento extremo evidente.
ggplot(datos_periodo, aes(x = longitude, y = latitude, color = temperature)) +
geom_point(size = 2, alpha = 0.9) +
scale_color_viridis_c(option = "C") +
coord_equal() +
labs(
title = "Distribución espacial de Temperature",
subtitle = "Puntos observados en coordenadas geográficas",
x = "Longitud",
y = "Latitud",
color = "Temperature"
) +
theme_minimal()El mapa de puntos en coordenadas geográficas permite observar que la temperatura no se distribuye de manera completamente aleatoria en el espacio. Existen zonas con temperaturas más altas, representadas por colores amarillos y naranjas, y zonas con temperaturas más bajas, representadas por tonos morados.
Esta primera visualización sugiere la existencia de una posible estructura espacial, ya que puntos cercanos tienden a presentar valores similares de temperatura.
Para el análisis geoestadístico se crea un objeto espacial utilizando
las coordenadas de latitud y longitud con la librería sf.
Inicialmente se declaran las coordenadas en el sistema geográfico
WGS84, EPSG:4326. Posteriormente se transforman a un
sistema proyectado en metros, UTM zona 18N, EPSG:32618,
apropiado para calcular distancias espaciales.
datos_sf <- datos_periodo |>
st_as_sf(coords = c("longitude", "latitude"), crs = 4326, remove = FALSE)
datos_utm <- datos_sf |>
st_transform(32618)
coords_utm <- st_coordinates(datos_utm)
x_centro <- mean(coords_utm[, 1])
y_centro <- mean(coords_utm[, 2])
datos_utm <- datos_utm |>
mutate(
x = coords_utm[, 1],
y = coords_utm[, 2],
x_c = x - x_centro,
y_c = y - y_centro
)
datos_sp <- as(datos_utm, "Spatial")
class(datos_sf)## [1] "sf" "tbl_df" "tbl" "data.frame"
## [1] "sf" "tbl_df" "tbl" "data.frame"
## [1] "SpatialPointsDataFrame"
## attr(,"package")
## [1] "sp"
datos_utm |>
st_drop_geometry() |>
summarise(
min_x = min(x),
max_x = max(x),
rango_x_metros = max(x) - min(x),
min_y = min(y),
max_y = max(y),
rango_y_metros = max(y) - min(y)
) |>
knitr::kable(caption = "Resumen de coordenadas proyectadas en UTM") |>
kableExtra::kable_styling(full_width = FALSE)| min_x | max_x | rango_x_metros | min_y | max_y | rango_y_metros |
|---|---|---|---|---|---|
| 309656.1 | 309832.4 | 176.3104 | 264519.2 | 264688.6 | 169.4073 |
El resumen de coordenadas proyectadas muestra que el área de estudio tiene una extensión aproximada de:
176.31 metros en el eje X. 169.41 metros en el eje Y.
Esto indica que el análisis se realiza sobre una zona relativamente pequeña y bien delimitada, lo cual es apropiado para evaluar variabilidad espacial local dentro del cultivo o zona de monitoreo.
La geoestadística clásica asume estacionariedad de segundo orden o, en términos prácticos, que la media no cambia sistemáticamente en el espacio. Por ello, antes de calcular el semivariograma y aplicar kriging, se evalúa si la temperatura presenta tendencia respecto a las coordenadas espaciales.
La evaluación de tendencia se realizó analizando la relación entre Temperature y las coordenadas centradas x_c y y_c.
ggplot(st_drop_geometry(datos_utm), aes(x = x_c, y = temperature)) +
geom_point(alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE) +
labs(
title = "Evaluación de tendencia de Temperature respecto al eje X",
x = "Coordenada X centrada, metros",
y = "Temperature"
) +
theme_minimal()ggplot(st_drop_geometry(datos_utm), aes(x = y_c, y = temperature)) +
geom_point(alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE) +
labs(
title = "Evaluación de tendencia de Temperature respecto al eje Y",
x = "Coordenada Y centrada, metros",
y = "Temperature"
) +
theme_minimal()Los gráficos de tendencia muestran una relación negativa entre la temperatura y ambos ejes espaciales. Esto significa que, a medida que aumentan las coordenadas X o Y, la temperatura tiende a disminuir ligeramente.
modelo_tendencia <- lm(temperature ~ x_c + y_c, data = st_drop_geometry(datos_utm))
resumen_tendencia <- summary(modelo_tendencia)
resumen_tendencia##
## Call:
## lm(formula = temperature ~ x_c + y_c, data = st_drop_geometry(datos_utm))
##
## Residuals:
## Min 1Q Median 3Q Max
## -4.0761 -1.1765 -0.0286 1.1375 4.1201
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 25.829026 0.073548 351.187 < 0.0000000000000002 ***
## x_c -0.007194 0.002194 -3.279 0.001108 **
## y_c -0.006433 0.001928 -3.337 0.000905 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.7 on 531 degrees of freedom
## Multiple R-squared: 0.08257, Adjusted R-squared: 0.07911
## F-statistic: 23.89 on 2 and 531 DF, p-value: 0.0000000001157
coef_tendencia <- as.data.frame(coef(resumen_tendencia)) |>
tibble::rownames_to_column("termino") |>
rename(
estimacion = Estimate,
error_estandar = `Std. Error`,
valor_t = `t value`,
valor_p = `Pr(>|t|)`
)
coef_tendencia |>
knitr::kable(caption = "Coeficientes del modelo de tendencia espacial") |>
kableExtra::kable_styling(full_width = FALSE)| termino | estimacion | error_estandar | valor_t | valor_p |
|---|---|---|---|---|
| (Intercept) | 25.8290262 | 0.0735477 | 351.187242 | 0.0000000 |
| x_c | -0.0071940 | 0.0021937 | -3.279460 | 0.0011082 |
| y_c | -0.0064329 | 0.0019275 | -3.337378 | 0.0009050 |
p_tendencia <- coef_tendencia |>
filter(termino %in% c("x_c", "y_c")) |>
pull(valor_p)
hay_tendencia <- any(p_tendencia < 0.05, na.rm = TRUE)
metodo_kriging <- ifelse(
hay_tendencia,
"Kriging universal, porque se identificó tendencia espacial significativa en función de las coordenadas.",
"Kriging ordinario, porque no se identificó tendencia espacial significativa en función de las coordenadas."
)
metodo_kriging## [1] "Kriging universal, porque se identificó tendencia espacial significativa en función de las coordenadas."
El modelo lineal de tendencia presentó los siguientes resultados principales:
El modelo tuvo un R² aproximado de 0.0826, es decir, las coordenadas explican cerca del 8.26 % de la variabilidad de la temperatura.
Aunque el porcentaje explicado no es alto, los coeficientes son estadísticamente significativos. Por tanto, se concluye que existe una tendencia espacial significativa en la variable Temperature.
Debido a esta tendencia, la metodología más adecuada para la interpolación no es el kriging ordinario, sino el kriging universal, ya que este permite incorporar la tendencia espacial asociada a las coordenadas.
La decisión metodológica para la interpolación será: Kriging universal, porque se identificó tendencia espacial significativa en función de las coordenadas.
La autocorrelación espacial permite evaluar si valores cercanos en el espacio tienden a ser más similares entre sí que valores lejanos. Para ello se utiliza el estadístico Moran’s I, construyendo una matriz de vecinos con los 6 puntos más cercanos.
coords <- cbind(datos_utm$x, datos_utm$y)
vecinos_knn <- spdep::knearneigh(coords, k = 6)
vecinos_nb <- spdep::knn2nb(vecinos_knn)
vecinos_w <- spdep::nb2listw(vecinos_nb, style = "W")
moran_temperature <- spdep::moran.test(datos_utm$temperature, vecinos_w)
moran_temperature##
## Moran I test under randomisation
##
## data: datos_utm$temperature
## weights: vecinos_w
##
## Moran I statistic standard deviate = 24.824, p-value <
## 0.00000000000000022
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic Expectation Variance
## 0.5997716415 -0.0018761726 0.0005874067
spdep::moran.plot(
datos_utm$temperature,
vecinos_w,
labels = FALSE,
main = "Diagrama de Moran para Temperature"
)El estadístico de Moran’s I obtenido fue aproximadamente:
con un valor p menor a 0.05, prácticamente cercano a cero.
Este resultado indica una autocorrelación espacial positiva fuerte en la variable Temperature. En otras palabras, los puntos cercanos espacialmente tienden a tener temperaturas similares. Esto confirma que la temperatura no se comporta como una variable espacialmente aleatoria, sino que presenta dependencia espacial.
El semivariograma mide cómo cambia la semivarianza entre observaciones a medida que aumenta la distancia espacial. Es una herramienta central para decidir si existe estructura espacial y para parametrizar el kriging.
distancias <- as.matrix(dist(coords))
distancia_maxima <- max(distancias)
cutoff_variograma <- distancia_maxima * 0.5
width_variograma <- cutoff_variograma / 15
formula_geo <- if (hay_tendencia) {
temperature ~ x_c + y_c
} else {
temperature ~ 1
}
formula_geo## temperature ~ x_c + y_c
vg_empirico <- gstat::variogram(
formula_geo,
data = datos_sp,
cutoff = cutoff_variograma,
width = width_variograma
)
vg_empirico |>
head(10) |>
knitr::kable(caption = "Primeros valores del semivariograma empírico") |>
kableExtra::kable_styling(full_width = FALSE)| np | dist | gamma | dir.hor | dir.ver | id |
|---|---|---|---|---|---|
| 1086 | 5.750208 | 1.131413 | 0 | 0 | var1 |
| 3339 | 11.310900 | 1.919209 | 0 | 0 | var1 |
| 5296 | 18.298557 | 2.484671 | 0 | 0 | var1 |
| 6983 | 25.473851 | 2.744942 | 0 | 0 | var1 |
| 8075 | 32.712558 | 2.802815 | 0 | 0 | var1 |
| 8893 | 39.889848 | 2.735930 | 0 | 0 | var1 |
| 9576 | 47.091770 | 2.783918 | 0 | 0 | var1 |
| 10017 | 54.343093 | 2.958415 | 0 | 0 | var1 |
| 9946 | 61.593082 | 3.131483 | 0 | 0 | var1 |
| 9809 | 68.805459 | 3.065673 | 0 | 0 | var1 |
ggplot(vg_empirico, aes(x = dist, y = gamma)) +
geom_point(aes(size = np), alpha = 0.8) +
labs(
title = "Semivariograma empírico de Temperature",
subtitle = "El tamaño del punto representa el número de pares por intervalo de distancia",
x = "Distancia, metros",
y = "Semivarianza",
size = "Pares"
) +
theme_minimal()El semivariograma empírico muestra que la semivarianza aumenta conforme aumenta la distancia entre los puntos. Esto significa que las observaciones cercanas tienen valores de temperatura más parecidos, mientras que las observaciones más alejadas presentan diferencias mayores.
En los primeros intervalos de distancia, la semivarianza es menor. Por ejemplo, a distancias cercanas a 5.75 metros, la semivarianza es aproximadamente 1.13. Luego aumenta progresivamente hasta ubicarse alrededor de valores cercanos a 3.0 en distancias mayores.
Este comportamiento es coherente con la teoría geoestadística: cuando existe autocorrelación espacial, los puntos cercanos son más similares y, por tanto, presentan menor semivarianza. A medida que la distancia aumenta, la similitud disminuye y la semivarianza crece hasta aproximarse a una meseta.
El semivariograma direccional permite evaluar si el comportamiento espacial de la temperatura cambia según la dirección analizada. En la gráfica se comparan las direcciones 0°, 45°, 90° y 135°.
vg_direccional <- gstat::variogram(
formula_geo,
data = datos_sp,
alpha = c(0, 45, 90, 135),
cutoff = cutoff_variograma,
width = width_variograma
)
head(vg_direccional)ggplot(vg_direccional, aes(x = dist, y = gamma, color = factor(dir.hor))) +
geom_point(alpha = 0.7) +
geom_line() +
labs(
title = "Semivariograma direccional de Temperature",
subtitle = "Evaluación exploratoria de anisotropía",
x = "Distancia, metros",
y = "Semivarianza",
color = "Dirección"
) +
theme_minimal()Se observa que las curvas no son completamente iguales entre direcciones. Algunas direcciones presentan mayor semivarianza que otras, especialmente la dirección de 135°, que muestra mayor variabilidad en algunos intervalos de distancia. Esto sugiere una posible anisotropía, es decir, que la dependencia espacial podría variar según la dirección.
Sin embargo, para efectos del análisis principal, se trabajó con un modelo isotrópico general.
Se compararon tres modelos teóricos de semivariograma:
El mejor modelo se selecciona con base en el menor error de ajuste
reportado por fit.variogram, usando el atributo
SSErr.
var_temperature <- var(datos_utm$temperature, na.rm = TRUE)
rango_inicial <- max(vg_empirico$dist, na.rm = TRUE) / 3
ajustar_modelo <- function(modelo) {
modelo_inicial <- gstat::vgm(
psill = var_temperature * 0.8,
model = modelo,
range = rango_inicial,
nugget = var_temperature * 0.2
)
ajuste <- try(
suppressWarnings(gstat::fit.variogram(vg_empirico, modelo_inicial)),
silent = TRUE
)
if (inherits(ajuste, "try-error")) {
return(tibble(
modelo = modelo,
nugget = NA_real_,
psill = NA_real_,
rango = NA_real_,
sse = Inf,
ajuste = list(NULL)
))
}
tibble(
modelo = modelo,
nugget = ajuste$psill[1],
psill = ajuste$psill[2],
rango = ajuste$range[2],
sse = attr(ajuste, "SSErr"),
ajuste = list(ajuste)
)
}
modelos_evaluados <- c("Sph", "Exp", "Gau")
ajustes_modelos <- purrr::map_dfr(modelos_evaluados, ajustar_modelo)
ajustes_resumen <- ajustes_modelos |>
mutate(
modelo_nombre = case_when(
modelo == "Sph" ~ "Esférico",
modelo == "Exp" ~ "Exponencial",
modelo == "Gau" ~ "Gaussiano",
TRUE ~ modelo
)
) |>
select(modelo_nombre, modelo, nugget, psill, rango, sse) |>
arrange(sse)
ajustes_resumen |>
knitr::kable(caption = "Comparación de modelos teóricos de semivariograma") |>
kableExtra::kable_styling(full_width = FALSE)| modelo_nombre | modelo | nugget | psill | rango | sse |
|---|---|---|---|---|---|
| Exponencial | Exp | 0.0000000 | 3.006653 | 11.37984 | 0.7687701 |
| Esférico | Sph | 0.3473595 | 2.509836 | 26.42126 | 1.0470685 |
| Gaussiano | Gau | 0.8478602 | 1.997911 | 13.83694 | 1.0985083 |
ajustes_validos <- ajustes_modelos |>
filter(is.finite(sse), !is.na(psill), psill > 0, !is.na(rango), rango > 0) |>
arrange(sse)
if (nrow(ajustes_validos) == 0) {
stop("No fue posible ajustar un modelo teórico de semivariograma válido. Revise la variable, las coordenadas o los parámetros iniciales.")
}
mejor_modelo <- ajustes_validos |> slice(1)
modelo_variograma_final <- mejor_modelo$ajuste[[1]]
nombre_mejor_modelo <- case_when(
mejor_modelo$modelo == "Sph" ~ "Esférico",
mejor_modelo$modelo == "Exp" ~ "Exponencial",
mejor_modelo$modelo == "Gau" ~ "Gaussiano",
TRUE ~ mejor_modelo$modelo
)
modelo_variograma_final## [1] "Exponencial"
Los resultados fueron:
El modelo con menor error fue el modelo exponencial, por lo cual se seleccionó como el modelo teórico más adecuado para representar la estructura espacial de la temperatura.
lineas_modelos <- ajustes_validos |>
mutate(modelo_nombre = case_when(
modelo == "Sph" ~ "Esférico",
modelo == "Exp" ~ "Exponencial",
modelo == "Gau" ~ "Gaussiano",
TRUE ~ modelo
)) |>
mutate(linea = purrr::map(ajuste, ~gstat::variogramLine(.x, maxdist = max(vg_empirico$dist), n = 100))) |>
select(modelo_nombre, linea) |>
tidyr::unnest(linea)
ggplot() +
geom_point(data = vg_empirico, aes(x = dist, y = gamma, size = np), alpha = 0.7) +
geom_line(data = lineas_modelos, aes(x = dist, y = gamma, color = modelo_nombre), size = 1) +
labs(
title = "Ajuste de modelos teóricos al semivariograma empírico",
subtitle = paste("Modelo seleccionado:", nombre_mejor_modelo),
x = "Distancia, metros",
y = "Semivarianza",
size = "Pares",
color = "Modelo"
) +
theme_minimal()El modelo exponencial ajustado tuvo los siguientes parámetros principales:
En términos prácticos, el modelo indica que existe dependencia espacial a distancias cortas y que la variabilidad de la temperatura se estabiliza conforme aumenta la distancia entre puntos.
La predicción espacial se realiza sobre una malla regular de 4.921 puntos construida dentro del polígono convexo que cubre los puntos observados. La resolución de la malla se define en metros, aprovechando que las coordenadas fueron proyectadas a UTM.
poligono_finca <- datos_utm |>
st_union() |>
st_convex_hull()
grilla_sf <- st_sf(
geometry = st_make_grid(
poligono_finca,
cellsize = 2,
what = "centers"
)
)
st_crs(grilla_sf) <- st_crs(datos_utm)
grilla_sf <- grilla_sf |>
st_filter(poligono_finca, .predicate = st_intersects)
coords_grilla <- st_coordinates(grilla_sf)
grilla_sf <- grilla_sf |>
mutate(
x = coords_grilla[, 1],
y = coords_grilla[, 2],
x_c = x - x_centro,
y_c = y - y_centro
)
grilla_sp <- as(grilla_sf, "Spatial")
nrow(grilla_sf)## [1] 4921
resultado_kriging <- gstat::krige(
formula = formula_geo,
locations = datos_sp,
newdata = grilla_sp,
model = modelo_variograma_final
)## [using universal kriging]
ggplot() +
geom_raster(
data = kriging_df,
aes(x = coords.x1, y = coords.x2, fill = var1.pred)
) +
geom_point(
data = st_drop_geometry(datos_utm),
aes(x = x, y = y),
size = 0.8,
alpha = 0.6
) +
scale_fill_viridis_c(option = "C") +
coord_equal() +
labs(
title = "Predicción espacial de Temperature mediante kriging",
subtitle = paste("Periodo: 01/10/2020 | Modelo de semivariograma:", nombre_mejor_modelo),
x = "Coordenada X UTM, metros",
y = "Coordenada Y UTM, metros",
fill = "Temperature\npredicha"
) +
theme_minimal()El mapa muestra la distribución espacial estimada de la temperatura para las zonas donde no se tenían mediciones directas.
En el mapa se observan zonas con temperaturas más altas, representadas en tonos amarillos y naranjas, y zonas con temperaturas más bajas, representadas en tonos morados. También se evidencian franjas o patrones espaciales que muestran variaciones locales de temperatura dentro del área analizada.
El resultado permite visualizar cómo se comporta la temperatura en toda la superficie del área de estudio, no solo en los puntos donde se tomaron mediciones.
La varianza de kriging representa la incertidumbre asociada a la predicción. En general, la incertidumbre tiende a ser menor cerca de puntos observados y mayor en zonas con menor densidad de muestreo.
ggplot() +
geom_raster(
data = kriging_df,
aes(x = coords.x1, y = coords.x2, fill = var1.var)
) +
geom_point(
data = st_drop_geometry(datos_utm),
aes(x = x, y = y),
size = 0.8,
alpha = 0.6
) +
scale_fill_viridis_c(option = "B") +
coord_equal() +
labs(
title = "Varianza de kriging para Temperature",
subtitle = "Mapa de incertidumbre de la interpolación espacial",
x = "Coordenada X UTM, metros",
y = "Coordenada Y UTM, metros",
fill = "Varianza\nde kriging"
) +
theme_minimal()Las zonas con menor varianza corresponden a áreas donde el modelo tiene mayor confianza en la predicción, generalmente porque están cerca de puntos observados o en zonas con alta densidad de muestreo.
Las zonas con mayor varianza se ubican principalmente hacia los bordes del área de estudio, especialmente en sectores donde hay menor soporte de puntos cercanos. Esto es esperable, ya que el kriging suele ser menos preciso en los límites del área analizada y en zonas con menor cantidad de observaciones cercanas.
Este mapa es importante porque permite diferenciar entre las zonas donde la predicción es más confiable y aquellas donde debe interpretarse con mayor precaución.
La validación cruzada permite evaluar el desempeño predictivo del modelo geoestadístico. Se retira cada observación, se predice su valor con las restantes y se comparan los valores observados contra los predichos.
cv_kriging <- gstat::krige.cv(
formula = formula_geo,
locations = datos_sp,
model = modelo_variograma_final,
nfold = nrow(datos_sp)
)
cv_df <- as.data.frame(cv_kriging)
metricas_cv <- cv_df |>
summarise(
me = mean(residual, na.rm = TRUE),
mae = mean(abs(residual), na.rm = TRUE),
rmse = sqrt(mean(residual^2, na.rm = TRUE)),
correlacion_obs_pred = cor(observed, var1.pred, use = "complete.obs")
)
metricas_cv |>
knitr::kable(caption = "Métricas de validación cruzada del kriging") |>
kableExtra::kable_styling(full_width = FALSE)| me | mae | rmse | correlacion_obs_pred |
|---|---|---|---|
| -0.0108918 | 0.786249 | 1.014373 | 0.8196698 |
ggplot(cv_df, aes(x = observed, y = var1.pred)) +
geom_point(alpha = 0.7) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
labs(
title = "Validación cruzada: valores observados vs. predichos",
x = "Temperature observada",
y = "Temperature predicha"
) +
theme_minimal()La validación cruzada permitió evaluar el desempeño predictivo del modelo geoestadístico. Las métricas obtenidas fueron:
El valor de ME está muy cerca de cero, lo cual indica que el modelo no presenta un sesgo sistemático importante. Es decir, en promedio, el modelo no sobreestima ni subestima fuertemente la temperatura.
El MAE indica que el error absoluto promedio de predicción es de aproximadamente 0.79 °C, mientras que el RMSE indica un error típico cercano a 1.01 °C. Estos valores son razonables para una variable ambiental como la temperatura, especialmente considerando la variabilidad espacial observada.
La correlación de aproximadamente 0.82 entre valores observados y predichos indica una buena capacidad predictiva del modelo. El gráfico de observados contra predichos muestra que muchos puntos se ubican cerca de la línea diagonal, lo que confirma que las predicciones guardan una relación fuerte con los valores reales.
ggplot(cv_df, aes(x = residual)) +
geom_histogram(bins = 25, color = "white") +
labs(
title = "Distribución de residuos de validación cruzada",
x = "Residuo: observado - predicho",
y = "Frecuencia"
) +
theme_minimal()El histograma de residuos muestra que los errores se concentran alrededor de cero, lo cual refuerza la idea de que el modelo tiene un comportamiento balanceado y no presenta un sesgo marcado.
El análisis geoestadístico realizado sobre la variable Temperature para el periodo 01/10/2020 permitió identificar una estructura espacial clara en los datos. La variable presentó autocorrelación espacial positiva, tendencia significativa respecto a las coordenadas y un patrón de dependencia espacial evidenciado en el semivariograma empírico.
El modelo teórico que mejor representó la estructura espacial fue el modelo exponencial, al presentar el menor error de ajuste frente a los modelos esférico y gaussiano. Debido a la existencia de tendencia espacial significativa, se aplicó kriging universal para realizar la interpolación de la temperatura.
Los mapas generados permiten observar la distribución espacial estimada de la temperatura y la incertidumbre asociada a las predicciones. La validación cruzada mostró un desempeño adecuado, con error promedio cercano a cero, RMSE aproximado de 1.01 °C y correlación cercana a 0.82 entre valores observados y predichos.
El análisis demuestra que la temperatura en el área de estudio presenta dependencia espacial suficiente para ser modelada e interpolada mediante kriging.
El ejercicio permitió aplicar el flujo completo de un análisis geoestadístico sobre datos espaciales de una finca de aguacate. La selección del primer periodo evitó mezclar mediciones tomadas en diferentes momentos, lo cual es importante porque la temperatura puede variar temporalmente y afectar la interpretación espacial.
La creación del geodata y la transformación a coordenadas proyectadas fueron pasos fundamentales para calcular distancias reales entre puntos. Posteriormente, el análisis de tendencia permitió decidir si era más apropiado aplicar kriging ordinario o kriging universal.
El semivariograma empírico y el ajuste de modelos teóricos permitieron caracterizar la estructura de dependencia espacial de la temperatura. Finalmente, el kriging generó una superficie continua de predicción, útil para visualizar zonas más cálidas o más frías dentro del área analizada.