Usaremos datos morfométricos de tortugas pintadas (Chrysemys picta), un modelo clásico en ecología de reptiles. El objetivo es predecir la longitud de la concha a partir de variables morfológicas y ambientales.
La diferencia entre enfoque explicativo y predictivo es central en bioestadística moderna: en el explicativo nos interesan los coeficientes e inferencia; en el predictivo nos interesa el desempeño en datos nuevos no vistos.
# ── Datos morfométricos basados en relaciones alométricas reales ──────────────
# Referencia: Janzen et al. (1992) Ecology
set.seed(42)
n <- 280
datos_tortugas <- tibble(
longitud_concha_mm = rnorm(n, mean = 138, sd = 22),
sexo = sample(c("Hembra", "Macho"), n, replace = TRUE,
prob = c(0.55, 0.45))
) %>%
mutate(
longitud_concha_mm = longitud_concha_mm + ifelse(sexo == "Hembra", 8, 0),
peso_g = 0.0018 * longitud_concha_mm^2.7 +
ifelse(sexo == "Hembra", 60, 0) + rnorm(n, 0, 35),
ancho_concha_mm = 0.82 * longitud_concha_mm + rnorm(n, 0, 8),
altura_concha_mm= 0.45 * longitud_concha_mm + rnorm(n, 0, 6),
largo_cabeza_mm = 0.28 * longitud_concha_mm + rnorm(n, 0, 5),
temp_agua_c = runif(n, 12, 28),
profundidad_m = runif(n, 0.2, 3.5),
edad_años = pmax(round(longitud_concha_mm/18 + rnorm(n, 0, 1.5)), 1)
)
glimpse(datos_tortugas)## Rows: 280
## Columns: 9
## $ longitud_concha_mm <dbl> 168.16109, 133.57664, 153.98883, 159.92298, 154.893…
## $ sexo <chr> "Macho", "Hembra", "Hembra", "Hembra", "Hembra", "H…
## $ peso_g <dbl> 1867.4922, 997.0991, 1513.8624, 1645.5372, 1564.640…
## $ ancho_concha_mm <dbl> 140.24963, 112.67478, 118.26409, 128.53102, 118.946…
## $ altura_concha_mm <dbl> 80.23671, 57.76213, 75.35438, 68.20350, 59.89479, 7…
## $ largo_cabeza_mm <dbl> 56.97275, 34.32343, 50.71398, 42.61396, 38.21520, 3…
## $ temp_agua_c <dbl> 13.69241, 24.35050, 22.17509, 21.47685, 12.18190, 1…
## $ profundidad_m <dbl> 2.9048596, 0.7727506, 0.5567349, 1.5877060, 0.42013…
## $ edad_años <dbl> 10, 6, 7, 8, 9, 8, 8, 10, 11, 9, 9, 11, 8, 9, 9, 6,…
Este proceso se realizo para la generación de los datos que serian posteriormente usados para el estudio de las tortugas, dando por resultado un total de 280 tortugas divididas entre machos y hembras, cada una con los valores de sus datos ya definidos.
p1 <- ggplot(datos_tortugas, aes(x = longitud_concha_mm, fill = sexo)) +
geom_histogram(bins = 25, color = "white", alpha = 0.85) +
scale_fill_manual(values = c("#FF9EBC", "#00BCD4")) +
labs(title = "Variable respuesta: longitud de concha",
x = "Longitud (mm)", y = "Frecuencia") + theme_minimal()
p2 <- ggplot(datos_tortugas, aes(x = peso_g, y = longitud_concha_mm,
color = sexo)) +
geom_point(alpha = 0.45, size = 1.6) +
geom_smooth(method = "lm", se = TRUE, linewidth = 1) +
scale_color_manual(values = c("#FF9EBC", "#00BCD4")) +
labs(title = "Longitud vs Peso", x = "Peso (g)",
y = "Longitud concha (mm)") + theme_minimal()
p3 <- ggplot(datos_tortugas, aes(x = temp_agua_c, y = longitud_concha_mm,
color = sexo)) +
geom_point(alpha = 0.45, size = 1.6) +
geom_smooth(method = "loess", se = TRUE, color = "gray30", linewidth = 1) +
scale_color_manual(values = c("#FF9EBC", "#00BCD4")) +
labs(title = "Longitud vs Temperatura", x = "Temperatura (°C)",
y = "Longitud concha (mm)") + theme_minimal()
p4 <- ggplot(datos_tortugas, aes(x = sexo, y = longitud_concha_mm,
fill = sexo)) +
geom_violin(alpha = 0.35) +
geom_boxplot(width = 0.18, alpha = 0.85) +
scale_fill_manual(values = c("#FF9EBC", "#00BCD4"), guide = "none") +
labs(title = "Longitud por sexo", x = NULL,
y = "Longitud concha (mm)") + theme_minimal()
grid.arrange(p1, p2, p3, p4, ncol = 2)
Los gráficos exploratorios revelan que las hembras tienden a poseer
caparazones más largos que los machos en promedio. Esto es consistente
con la biología reproductiva de Chrysemys picta: dado que el caparazón
no puede expandirse para dar cabida a los ovarios y embriones en
crecimiento, la selección natural favorece un mayor tamaño corporal en
las hembras como medio para maximizar el tamaño de la nidada o el tamaño
de los huevos (Litzgus & Smith, 2010). La relación entre peso y
longitud es fuertemente positiva en ambos sexos, mientras que la
temperatura del agua no muestra ninguna tendencia sistemática con el
tamaño corporal.
vars_num <- datos_tortugas %>%
select(longitud_concha_mm, peso_g, ancho_concha_mm, altura_concha_mm,
largo_cabeza_mm, temp_agua_c, profundidad_m, edad_años)
corrplot(cor(vars_num, use = "complete.obs"),
method = "color", type = "upper",
addCoef.col = "black", number.cex = 0.72,
tl.cex = 0.9, tl.col = "gray20",
col = COL2("RdBu", 10),
title = "Correlaciones entre variables morfométricas",
mar = c(0, 0, 2, 0))
La matriz de correlaciones confirma que los aspectos morfológicos poseen
una alta correlación entre sí. La variable con mayor relación con la
longitud de la concha es el peso, seguido del ancho, la altura, el largo
de cabeza y finalmente la edad, que presenta una asociación
considerablemente menor. Esto se explica porque el tamaño corporal y las
estructuras anatómicas de las tortugas escalan casi isométricamente.
Esto a su vez, se centra en las bases evolutivas de este grupo de
animales, siendo que la evolución del caparazón limitó las proporciones
corporales del clado, observándose desviaciones importantes solo en
grupos ecológicos altamente especializados (Hermanson & Evers,
2024). Las variables ambientales temp_agua_c y profundidad_m muestran
correlaciones cercanas a cero, siendo claramente irrelevantes al ser
comparadas con todas las variables morfométricas, lo que sugiere que no
aportan un valor predictivo real al modelo. Debido a esto, sería valioso
reemplazarlas por variables con mayor relevancia biológica, como por
ejemplo características del hábitat o variables geoespaciales que
capturen la variación ambiental entre sitios de nacimiento, y a su vez
nos puedan dar pistas de qué otros posibles factores están afectando el
desarrollo de estos animales. ## División train / test y
preprocesamiento
set.seed(123)
split <- initial_split(datos_tortugas, prop = 0.75, strata = sexo)
train <- training(split); test <- testing(split)
cat(sprintf("Entrenamiento: %d | Prueba: %d\n", nrow(train), nrow(test)))## Entrenamiento: 210 | Prueba: 70
receta <- recipe(longitud_concha_mm ~ ., data = train) %>%
step_dummy(all_nominal_predictors()) %>%
step_normalize(all_numeric_predictors()) %>%
step_corr(all_numeric_predictors(), threshold = 0.90)
receta_prep <- prep(receta, training = train)
train_proc <- bake(receta_prep, new_data = NULL)
test_proc <- bake(receta_prep, new_data = test)
cat("Predictores tras preproceso:",
paste(setdiff(names(train_proc), "longitud_concha_mm"), collapse = ", "), "\n")## Predictores tras preproceso: ancho_concha_mm, altura_concha_mm, largo_cabeza_mm, temp_agua_c, profundidad_m, edad_años, sexo_Macho
Aqui se distributeronn los datos para alimentar el modelo en entrenamiento y prueba, mientras que tambien se evaluaron las variables a tener en cuenta para realizar las predicciones. ## Ajuste y evaluación del modelo
modelo_lm <- lm(longitud_concha_mm ~ ., data = train_proc)
tidy(modelo_lm) %>%
mutate(across(where(is.numeric), ~ round(.x, 4)),
sig = case_when(p.value < 0.001 ~ "***", p.value < 0.01 ~ "**",
p.value < 0.05 ~ "*", TRUE ~ "")) %>%
kable(caption = "Coeficientes del modelo (datos de entrenamiento)",
col.names = c("Término","Estimado","Error est.","t","p-valor","")) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)| Término | Estimado | Error est. | t | p-valor | |
|---|---|---|---|---|---|
| (Intercept) | 141.5412 | 0.4492 | 315.1075 | 0.0000 | *** |
| ancho_concha_mm | 11.4196 | 0.8743 | 13.0613 | 0.0000 | *** |
| altura_concha_mm | 5.8791 | 0.7724 | 7.6118 | 0.0000 | *** |
| largo_cabeza_mm | 4.3439 | 0.6843 | 6.3480 | 0.0000 | *** |
| temp_agua_c | -0.2816 | 0.4520 | -0.6230 | 0.5340 | |
| profundidad_m | -0.7981 | 0.4529 | -1.7620 | 0.0796 | |
| edad_años | 1.8540 | 0.6167 | 3.0064 | 0.0030 | ** |
| sexo_Macho | -0.8371 | 0.4593 | -1.8228 | 0.0698 |
La tabla de coeficientes permite profundizar la información obtenida mediante la interpretación de las gráficas exploratorias, señalando que, aunque las hembras tienen caparazones más largos en promedio, el sexo no muestra una significancia estadística relevante sobre la longitud del caparazón una vez que se controlan los predictores morfométricos, lo que indica que la diferencia entre sexos es una consecuencia de las relaciones isométricas de tamaño corporal más que un efecto anatómico o fisiológico independiente. Al mismo tiempo, esta tabla nos permite evidenciar cuáles son los predictores con mayor influencia dentro de las predicciones del modelo, siendo que los datos morfométricos representan la información más importante a la hora de evaluar la posible longitud de la concha, lo cual es biológicamente esperable dado que todas estas variables están gobernadas por las mismas relaciones alométricas.
glance(modelo_lm) %>%
select(r.squared, adj.r.squared, sigma, AIC, BIC) %>%
mutate(across(everything(), ~ round(.x, 4))) %>%
kable(caption = "Bondad de ajuste — datos de entrenamiento",
col.names = c("R²","R² ajustado","RMSE (σ)","AIC","BIC")) %>%
kable_styling(bootstrap_options = c("striped"), full_width = FALSE)| R² | R² ajustado | RMSE (σ) | AIC | BIC |
|---|---|---|---|---|
| 0.9174 | 0.9145 | 6.5093 | 1392.555 | 1422.678 |
pred_vals <- predict(modelo_lm, newdata = test_proc)
res_test <- tibble(
observado = test$longitud_concha_mm,
predicho = pred_vals,
residuo = observado - predicho
)
rmse_v <- sqrt(mean(res_test$residuo^2))
mae_v <- mean(abs(res_test$residuo))
r2_v <- cor(res_test$observado, res_test$predicho)^2
tibble(Métrica = c("RMSE (mm)","MAE (mm)","R²"),
`Prueba (no visto)` = c(round(rmse_v,3), round(mae_v,3), round(r2_v,4))) %>%
kable(caption = "Métricas de predicción en datos de PRUEBA") %>%
kable_styling(bootstrap_options = c("striped"), full_width = FALSE)| Métrica | Prueba (no visto) |
|---|---|
| RMSE (mm) | 6.3460 |
| MAE (mm) | 5.1580 |
| R² | 0.8935 |
Seguido de la evaluación de coeficientes y su relevancia para el funcionamiento del modelo, se decidió explorar más a fondo los datos brindados por este tanto en el periodo de entrenamiento como en el de prueba. Siguiendo esta idea, al comparar el R² entre entrenamiento y prueba se observó una disminución moderada entre la calidad del modelo en ambas fases, siendo que el modelo logró explicar aproximadamente un 92% de la variabilidad durante el entrenamiento, mientras que en la prueba este porcentaje se redujo a un 89%, marcando una diferencia de solo 3 puntos porcentuales que no constituye evidencia preocupante de sobreajuste. Aún así, se debe recalcar que la RMSE obtenida de los datos de prueba es más informativa y confiable que la de entrenamiento, ya que esta primera fue calculada sobre valores completamente desconocidos para el modelo, eliminando el posible sesgo optimista que hubiera podido tener el modelo en caso de un sobreajuste.
ggplot(res_test, aes(x = observado, y = predicho)) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed",
color = "gray50", linewidth = 0.9) +
geom_point(aes(color = abs(residuo)), size = 2, alpha = 0.7) +
scale_color_viridis_c(option = "plasma", name = "|Residuo|\n(mm)") +
annotate("text",
x = min(res_test$observado) + 3,
y = max(res_test$predicho) - 3,
label = sprintf("RMSE = %.2f mm\nR² = %.3f", rmse_v, r2_v),
hjust = 0, size = 4, color = "gray20") +
labs(title = "Observado vs predicho — datos de prueba",
subtitle = "Línea punteada = predicción perfecta",
x = "Longitud observada (mm)", y = "Longitud predicha (mm)") +
theme_minimal(base_size = 12)
El gráfico de observado vs predicho respalda visualmente las
conclusiones previas, demostrando que los puntos del conjunto de prueba
siguen de cerca la línea de predicción perfecta sin recaer en un
sobreajuste que hiciera que los datos perdieran sentido predictivo,
confirmando que el modelo generaliza de forma coherente a datos no
vistos. ## Validación cruzada 10-fold
set.seed(456)
cv_res <- workflow() %>%
add_formula(longitud_concha_mm ~ .) %>%
add_model(linear_reg() %>% set_engine("lm")) %>%
fit_resamples(
resamples = vfold_cv(train_proc, v = 10),
metrics = metric_set(rmse, rsq, mae)
)
collect_metrics(cv_res) %>%
select(.metric, mean, std_err, n) %>%
mutate(across(where(is.numeric), ~ round(.x, 4))) %>%
kable(caption = "Validación cruzada 10-fold",
col.names = c("Métrica","Media","Error est.","Folds")) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE)| Métrica | Media | Error est. | Folds |
|---|---|---|---|
| mae | 5.2584 | 0.2673 | 10 |
| rmse | 6.5165 | 0.3358 | 10 |
| rsq | 0.9116 | 0.0101 | 10 |
Como una evidencia aún más sólida de la viabilidad del modelo, se realizó una validación cruzada, la cual terminó por complementar la evaluación anterior, promediando el desempeño sobre 10 combinaciones de datos distintas y ofreciendo una estimación más robusta y menos dependiente de una división particular de los datos. ## Diagnósticos de residuos
par(mfrow = c(2, 2), mar = c(4, 4, 3, 1))
plot(modelo_lm, which = 1:4, col = "#3F51B380", pch = 19, cex = 0.8)Mediante la generación de estos cuatro gráficos de diagnóstico, se evidenció que la mayoría de los supuestos del modelo lineal se cumplen satisfactoriamente. Al analizar el gráfico Residuals vs Fitted, se puede ver que este presenta sus puntos distribuidos aleatoriamente alrededor de cero sin patrones sistemáticos, confirmando el supuesto de linealidad. Luego, el Q-Q plot indica una clara normalidad de residuos, solo presentando desviaciones leves en las colas extremas, lo cual termina por aportar datos insuficientes como para comprometer el supuesto. Y a su vez, la distancia de Cook confirma que ninguna observación ejerce una influencia desproporcionada sobre los coeficientes.
Por último, el único supuesto que podría estar siendo afectado es el de la homocedasticidad, siendo que el gráfico Scale-Location muestra una línea roja con inclinación en ambos extremos, indicando mayor variabilidad en los residuos para tortugas pequeñas y grandes. Aun así, estas desviaciones son poco abruptas y no comprometen la validez general del modelo, por lo que se podría afirmar que los resultados en su totalidad terminaron por cumplir con los supuestos de linealidad planteados.
Finalmente, se sugeriría añadir otros predictores que no estén directamente relacionados con la morfología de los individuos, ya que se evidenció que esta posee una gran correlación entre sí y hay un punto en el que deja de brindarnos información relevante para el modelo. Al mismo tiempo, podría ser importante considerar una variable que tenga que ver con el peso de la tortuga o algo de geoestadística, ya que podría alimentar bastante al modelo, considerando que puede que las variaciones morfométricas también estén dadas por el tipo de ambiente en el que nazcan las tortugas. Además de esto, una sugerencia general sería ampliar aún más la base de datos, ya que podría ser bueno para darle al modelo más información y que logre predecir de mejor manera los posibles datos futuros.
Hermanson, G., & Evers, S. W. (2024). Shell constraints on evolutionary body size-limb size allometry can explain morphological conservatism in the turtle body plan. Ecology and Evolution, 14(11), e70504. https://doi.org/10.1002/ece3.70504 Litzgus, J. D., & Smith, S. E. (2010). Geographic variation in sexual size dimorphism in painted turtles (Chrysemys picta). Journal of Herpetology, 44(2), 320-326. https://doi.org/10.1670/08-333.1