En este taller aprenderemos comandos tanto básicos como avanzados para:
Antes de comenzar, debemos descargar varios paquetes (librerías o bibliotecas). Son varias ya que veremos varias herramientas que nos ofrece R para procesar datos de suelo en específico.
install.packages(c("Hmisc","dplyr","soilDB","soiltexture","ggplot2","sf",
"caret","rnaturalearth","skimr","cowsay","randomForest","kernlab",
"MASS"),
repos = "http://cran.us.r-project.org")
Cargaremos datos de suelos chilenos muestreados por la National Cooperative Soil Survey, USDA. El comando bbox de la función `fetchKSSL corresponde al bounding box para Chile, correspondiente a (Longitud mínima, Latitud mínima, Longitud máxima, Latitud máxima). Para mas información de los datos descargados: https://ncsslabdatamart.sc.egov.usda.gov
library(soilDB)
## Warning: package 'soilDB' was built under R version 3.5.2
## Warning: package 'aqp' was built under R version 3.5.2
suelos.chile<-fetchKSSL(bbox=c(-75.64, -55.61, -66.96, -17.58))
Se cargaron 25 pedones muestreados a nivel nacional. El objeto suelos.chile es de tipo SoilProfileCollection (SPC). Con la función plot() los podemos visualizar de forma rápida
plot(suelos.chile)
Los objetos SPC son útiles para visualización. Además es posible transformar datos propios en este tipo de objetos. Para más información de las opciones de los paquetes aqp y soilDB: http://ncss-tech.github.io/AQP/. Ahora graficaremos la arcilla en cada horizonte.
par(mar=c(0,0,4,0)) #ajuste margen
plot(suelos.chile, color='clay', col.label="") #colorearemos según % arcilla
title("Arcilla (%)", line = 3) #título del gráfico
Primero, transformamos el objeto SPC en un data.frame, lo cual nos permitirá trabajar con el de forma más directa
data.chile<-as(suelos.chile, 'data.frame')
Podemos explorar los datos de forma directa en la consola o en RStudio.
summary(data.chile)
View(data.chile)
Nos enfocaremos en las siguientes variables cuantitativas:
| Nombre base de datos | Variables |
|---|---|
| sand, silt, clay | Arena, Limo y Arcilla |
| oc | Carbono orgánico |
| p_nz | Fosforo New Zealand |
| db_od | Densidad Aparente |
| base_sum | Suma de bases |
| ph_h2o | pH en agua |
| w3cld | Capacidad de Campo |
| w15l2 | Punto de Marchitez Permanente |
Ahora limpiamos la base de datos y vemos correlacion entre variables. Es esta parte usaremos pipes ( %>%, más info: https://r4ds.had.co.nz/pipes.html). Removemos datos repetidos con la función distinct(), luego seleccionamos variables numéricas de interés (select()) y finalmente vemos la correlación estadística entre ellas.
library(dplyr)
## Warning: package 'dplyr' was built under R version 3.5.2
library(Hmisc)
## Warning: package 'Hmisc' was built under R version 3.5.2
data.chile %>%
distinct(pedon_key,labsampnum,sand,silt,clay, .keep_all= TRUE) %>%
select(sand,silt,clay,oc,p_nz,db_od,base_sum,ph_h2o,w3cld,w15l2) %>% #seleccionamos variables
as.matrix() %>% # los ordenamos como matriz
rcorr() # vemos correlacion y significancia, ademas hay muchos datos incompletos
## sand silt clay oc p_nz db_od base_sum ph_h2o w3cld w15l2
## sand 1.00 -0.67 -0.69 -0.19 -0.04 -0.15 -0.32 0.02 0.23 -0.27
## silt -0.67 1.00 -0.08 0.44 0.32 -0.37 -0.03 -0.07 0.06 0.38
## clay -0.69 -0.08 1.00 -0.15 -0.28 0.54 0.41 0.04 -0.35 -0.01
## oc -0.19 0.44 -0.15 1.00 0.31 -0.57 0.42 -0.54 0.40 0.72
## p_nz -0.04 0.32 -0.28 0.31 1.00 -0.80 -0.33 -0.33 0.67 0.75
## db_od -0.15 -0.37 0.54 -0.57 -0.80 1.00 0.45 0.35 -0.73 -0.60
## base_sum -0.32 -0.03 0.41 0.42 -0.33 0.45 1.00 0.66 -0.21 -0.02
## ph_h2o 0.02 -0.07 0.04 -0.54 -0.33 0.35 0.66 1.00 -0.25 -0.24
## w3cld 0.23 0.06 -0.35 0.40 0.67 -0.73 -0.21 -0.25 1.00 0.58
## w15l2 -0.27 0.38 -0.01 0.72 0.75 -0.60 -0.02 -0.24 0.58 1.00
##
## n
## sand silt clay oc p_nz db_od base_sum ph_h2o w3cld w15l2
## sand 180 180 180 75 167 152 133 176 147 174
## silt 180 180 180 75 167 152 133 176 147 174
## clay 180 180 180 75 167 152 133 176 147 174
## oc 75 75 75 75 74 61 74 75 56 75
## p_nz 167 167 167 74 167 139 120 163 134 161
## db_od 152 152 152 61 139 152 111 152 147 148
## base_sum 133 133 133 74 120 111 133 133 106 133
## ph_h2o 176 176 176 75 163 152 133 176 147 170
## w3cld 147 147 147 56 134 147 106 147 147 143
## w15l2 174 174 174 75 161 148 133 170 143 174
##
## P
## sand silt clay oc p_nz db_od base_sum ph_h2o w3cld
## sand 0.0000 0.0000 0.0947 0.6304 0.0719 0.0002 0.8217 0.0056
## silt 0.0000 0.2848 0.0000 0.0000 0.0000 0.7208 0.3788 0.4351
## clay 0.0000 0.2848 0.1901 0.0003 0.0000 0.0000 0.5810 0.0000
## oc 0.0947 0.0000 0.1901 0.0079 0.0000 0.0002 0.0000 0.0020
## p_nz 0.6304 0.0000 0.0003 0.0079 0.0000 0.0002 0.0000 0.0000
## db_od 0.0719 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
## base_sum 0.0002 0.7208 0.0000 0.0002 0.0002 0.0000 0.0000 0.0329
## ph_h2o 0.8217 0.3788 0.5810 0.0000 0.0000 0.0000 0.0000 0.0022
## w3cld 0.0056 0.4351 0.0000 0.0020 0.0000 0.0000 0.0329 0.0022
## w15l2 0.0003 0.0000 0.8446 0.0000 0.0000 0.0000 0.7876 0.0017 0.0000
## w15l2
## sand 0.0003
## silt 0.0000
## clay 0.8446
## oc 0.0000
## p_nz 0.0000
## db_od 0.0000
## base_sum 0.7876
## ph_h2o 0.0017
## w3cld 0.0000
## w15l2
Hay varias opciones para manejar datos ausentes, rellenar con promedios, usar funciones de pedotransferencia, etc. Para este taller, filtraremos el dataset solo con datos completos (sin variables ausentes)
dataset<-data.chile %>%
distinct(pedon_key,labsampnum,sand,silt,clay, .keep_all= TRUE) %>%
select(sand,silt,clay,oc,p_nz,db_od,base_sum,ph_h2o,w3cld,w15l2) %>%
filter(complete.cases(.)) # solamente nos quedamos son filas con todos los datos en las columnas
Un paquete que nos ayuda a visualizar datos es skimRr. Revisemos el dataset filtrado en la consola.
library(skimr)
## Warning: package 'skimr' was built under R version 3.5.2
skim_to_wide(dataset)
## # A tibble: 10 x 13
## type variable missing complete n mean sd p0 p25 p50
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 inte… p_nz 0 54 54 91.59 17.01 10 92.5 98
## 2 nume… base_sum 0 54 54 " 3.… " 3.… " 0 … " 0.… " 2.…
## 3 nume… clay 0 54 54 10.68 21.91 " 0 … " 0.… " 1.…
## 4 nume… db_od 0 54 54 " 0.… " 0.… " 0.… " 0.… " 0.…
## 5 nume… oc 0 54 54 " 4.… " 4.… " 0.… " 0.… " 3.…
## 6 nume… ph_h2o 0 54 54 " 5.… " 0.… " 5 … " 5.… " 5.…
## 7 nume… sand 0 54 54 58.37 25.73 " 3.… "47.… "67.…
## 8 nume… silt 0 54 54 30.95 15.82 " 1.… "19.… "30.…
## 9 nume… w15l2 0 54 54 "27.… 10.88 " 2 … 21.92 28.05
## 10 nume… w3cld 0 54 54 84.97 44.81 "16.… "46.… 75.65
## # … with 3 more variables: p75 <chr>, p100 <chr>, hist <chr>
Siguiendo con la visualización, podemos hacer un triángulo de textura con los datos filtrados
library(soiltexture)
TT.plot(class.sys = "USDA.TT",
tri.data = dataset,
css.names = c("clay","silt","sand"),
cex.axis=1,cex.lab=1)
Ahora avanzaremos con el código para modelos predictivos. Por tiempo, veremos dos ejemplos: a) Un modelo de regresión lineal (LM), con un proceso de selección de variables (step-wise), b) Máquina de vectores de soporte (Support Vector Machine) y c) Árbol aleatorio (Random Forest). Sin embargo, la sintáxis general puede ser replicada para modelos de regresión utilizando redes neuronales, árboles de decisión, etc. Para mas detalles del paquete carety sus funcionalidades ver: http://topepo.github.io/caret/. Esta tabla resume a grandes rasgos características de cada modelo. Sin embargo, el paquete caret permite hacer ajustes y preprocesamiento para optimizar el uso de estos modelos.
| Características | LM | SVM | RF |
|---|---|---|---|
| Parámetros de ajuste | NO | SI (C y sigma) |
SI (mtry) |
| Preprocesamiento | NO | SI | NO |
| Seleccion variables | SI (Step-wise) | NO | NO |
Vamos a predecir los valores de w15l2, punto de marchitez permanente, en función del resto. Primero, dividiremos los datos del set en entrenamiento (80%,p=0.8) y testeo (20%). Luego seleccionaremos las variables para el testeo.
library(caret)
set.seed(1234)
dataset.part <- createDataPartition(dataset$w15l2, p = 0.8, list = FALSE)
training <- dataset[dataset.part,] #dataset para entrenar
testing <- dataset[-dataset.part,] #dataset para testear
x.testing <- testing[,1:9] # variables input para el testeo
y.testing <- testing[,10] # w15l2 en el set de testeo
El comando train nos permite buscar valores óptimos de los parámetros de ajuste, usando validación cruzada method="cv" u otras técnicas de remuestreo. Además, permite comparar modelos.
train.control<-trainControl(method="repeatedcv", number=10, repeats =5) #validación cruzada 10-fold
set.seed(1234)
lm.model<-train(w15l2 ~., data = training, #se selecciona los datos de training
method = "lmStepAIC", # método LM con Step-wise
trControl = train.control) #train.control definido anteriormente
set.seed(1234)
svm.model<-train(w15l2 ~., data = training,
method = "svmRadial",
preProcess = c("center", "scale"), #paso preprocesamiento
trControl = train.control)
set.seed(1234)
rf.model<-train(w15l2 ~., data = training,
method = "rf",
trControl = train.control)
Una vez que los modelos están listos, podemos sacar información detallada de su ajuste, parámetros, etc. Veremos un ejemplo para cada modelo.
Para el modelo lm.model podemos ver la ecuación y las variables seleccionadas
lm.model$finalModel
##
## Call:
## lm(formula = .outcome ~ sand + silt + oc + p_nz + ph_h2o, data = dat)
##
## Coefficients:
## (Intercept) sand silt oc p_nz
## -34.5352 -0.1584 -0.1162 1.7611 0.3032
## ph_h2o
## 6.5767
Para el modelo svm.model podemos ver la importancia de las variables en la predicción
varImp(svm.model)
## loess r-squared variable importance
##
## Overall
## oc 100.00
## ph_h2o 65.53
## db_od 57.11
## w3cld 55.46
## p_nz 45.76
## silt 45.64
## base_sum 42.42
## sand 41.71
## clay 0.00
Para el modelo rf.model podemos ver como ajustaron los parámetros por defecto. Esto también se puede realizar de forma manual, lo cual es útil para optimizar los modelos.
plot(rf.model)
Ya están listos nuestros modelos predictivos, ahora podemos comparar los resultados entre ellos y evaluar su performance.
modelos<-resamples(list(LM = lm.model, SVM = svm.model, RF = rf.model))
summary(modelos) # resumen del cross-validation y resampleo
##
## Call:
## summary.resamples(object = modelos)
##
## Models: LM, SVM, RF
## Number of resamples: 50
##
## MAE
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## LM 1.399175 4.634382 6.543970 6.501128 8.173876 12.61716 0
## SVM 2.324841 3.987246 5.184039 5.952885 7.166975 14.09890 0
## RF 2.219670 4.030877 5.045302 5.633944 6.556685 13.92811 0
##
## RMSE
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## LM 1.802279 5.949179 7.897221 7.989510 9.204648 18.11070 0
## SVM 2.814433 5.091970 6.816799 7.910893 9.109436 22.74019 0
## RF 2.560375 5.361428 6.374673 7.470620 8.049140 20.72977 0
##
## Rsquared
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## LM 0.0003799249 0.08860291 0.5304505 0.4784419 0.8196291 0.9999250 0
## SVM 0.0014103830 0.15909263 0.5455629 0.4965747 0.7878340 0.9899102 0
## RF 0.0006877534 0.11645334 0.5322692 0.5134676 0.8516935 0.9999192 0
compare_models(lm.model,rf.model) #vemos si hay diferencias sig. entre dos modelos
##
## One Sample t-test
##
## data: x
## t = 1.733, df = 49, p-value = 0.08939
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
## -0.08282111 1.12060077
## sample estimates:
## mean of x
## 0.5188898
bwplot(modelos, scales = list(relation = "free")) #graficamos estadistica para cada uno
La función predict usa el modelo predictivo desarrollado y utiliza las variables x.testing para hacer una estimación
lm.pred<-predict(lm.model,x.testing) #hacemos la prediccion del 20%
svm.pred<-predict(svm.model,x.testing)
rf.pred<-predict(rf.model,x.testing)
Ahora guardaremos los resultados de la predicción en el restante 20% y los almacenaremos ordenados como un data.frameen formato largo para graficar
lm.prediccion<-as.data.frame(cbind(modelo = rep("LM", nrow(x.testing)),
predicho = lm.pred,
observado = y.testing))
svm.prediccion<-as.data.frame(cbind(modelo = rep("SVM", nrow(x.testing)),
predicho = svm.pred,
observado = y.testing))
rf.prediccion<-as.data.frame(cbind(modelo = rep("RF", nrow(x.testing)),
predicho = rf.pred,
observado = y.testing))
prediccion.modelos<-rbind(lm.prediccion,svm.prediccion,rf.prediccion)#unimos la informacion en un solo dataframe
prediccion.modelos$predicho <- as.numeric(as.character(prediccion.modelos$predicho))# nos aseguramos que sean valores numéricos
prediccion.modelos$observado <- as.numeric(as.character(prediccion.modelos$observado))
Ahora usando el paquete ggplot2 graficaremos los resultados para el set de testeo. Hay un error ya que un punto no está en el rango del gráfico para el modelo LM.
library(ggplot2)
ggplot(prediccion.modelos, aes(x=observado, y=predicho, color=modelo, shape=modelo)) +
geom_point() + # grafico puntos scatter plot
geom_abline()+ # agrego linea 1:1
geom_smooth(method=lm)+ #agrego una regresion lineal con interval de confianza
ggtitle("Comparación métodos") +
xlim(0,50)+
ylim(0,50)+
ylab("% gravimetrico 1500 kPa predicho") +
xlab("% gravimetrico 1500 kPa observado")
Vemos que todos los modelos subestiman de forme importante los valores de PMP sobre 40%. RF es el que muestra mejor performance en general.
R tiene varias librerias para gráficas mapas. A continuación mostramos un ejemplo rápido para crear un mapa simple con los datos georeferenciados obtenidos al inicio del taller.Para mas info de mapas en R con ggplot:https://www.r-spatial.org/r/2018/10/25/ggplot2-sf.html
library(sf)
library(rnaturalearth)
mundo <- ne_countries(scale = "medium", returnclass = "sf") #cargo un mapa del mundo
ggplot(data = mundo) + #mapa tipo ggplot
geom_sf() + #datos geográficos
geom_point(data = data.chile, aes(x = x, y = y), size = 1)+ #cargamos los puntos
coord_sf(xlim = c(-75.64, -66.96), # zoom al mapa igual al bounding box inicial
ylim = c(-55.61, -17.58), expand = FALSE)
Gracias por asistir! Cualquier consulta, pueden consultar al email: biofisica@ing.puc.cl
library(cowsay)
say("Gracias por venir \n Sígannos en @soilbiophysicslab", by="cat")
## Colors cannot be applied in this environment :( Try using a terminal or RStudio.
##
## --------------
## Gracias por venir
## Sígannos en @soilbiophysicslab
## --------------
## \
## \
## \
## |\___/|
## ==) ^Y^ (==
## \ ^ /
## )=*=(
## / \
## | |
## /| | | |\
## \| | |_|/\
## jgs //_// ___/
## \_)
##