MAESTRÍA EN GESTIÓN INTEGRAL FRENTE AL CAMBIO CLIMÁTICO
Análisis de Diversidad Alfa-curvas de rango–abundancia de Whittaker Electiva II: Medición de la diversidad Alfa y Beta en el contexto del cambio climático
Las curvas de rango–abundancia de Whittaker (RACs) ordenan las especies de una comunidad desde la más abundante (rango 1) a la menos abundante, graficando su abundancia relativa (típicamente en escala logarítmica) frente al rango.
Longitud de la curva → riqueza (más especies = curva más larga).
Pendiente → equidad (pendiente pronunciada = alta dominancia y baja equidad; pendiente suave = comunidad más pareja).
Este enfoque, originado en los años 60 (Whittaker, 1965), sigue vigente porque visualiza simultáneamente dominancia, rareza y estructura y facilita comparaciones entre comunidades, sitios y tiempos. Avances recientes sugieren (i) combinar RACs con normalización del esfuerzo (p. ej., rarefacción) para comparaciones justas; (ii) usar marcos de dinámica de comunidades que cuantifican cambios en riqueza, equidad y reordenamiento (Avolio et al., 2015; 2019); y (iii) contextualizar las RACs con la literatura de SADs (distribuciones de abundancia de especies), donde varios modelos (logserie, lognormal, etc.) pueden ajustarse de forma similar y conviene evaluar con métricas objetivas (McGill et al., 2007; Baldridge et al., 2016; Koffel et al., 2022; Callaghan et al., 2023).
# Convierte un vector de conteos (una comunidad) en tabla de rango–abundancia
rank_abundance <- function(x) {
x <- x[x > 0] # quita ceros
x <- sort(x, decreasing = TRUE)
tibble(
rango = seq_along(x),
abund = as.numeric(x),
prop = abund / sum(abund)
)
}
# Gráfico RAC con ggplot2 (prop. o abundancia bruta; eje y opcional log10)
plot_rac <- function(df, y = c("prop","abund"), log10_y = TRUE, title = NULL) {
y <- match.arg(y)
g <- ggplot(df, aes(x = rango, y = .data[[y]])) +
geom_line() +
geom_point() +
labs(x = "Rango (1 = más abundante)",
y = ifelse(y == "prop", "Abundancia relativa", "Abundancia absoluta"),
title = title) +
theme_minimal(base_size = 12)
if (log10_y) g <- g + scale_y_continuous(trans = "log10", labels = scales::label_number())
g
}
# Ajuste rápido de pendiente (en log10) para interpretar equidad
pendiente_rac <- function(df) {
fit <- lm(log10(prop) ~ rango, data = df)
unname(coef(fit)[2]) # pendiente
}Usaremos conjuntos incorporados en vegan:
dune (vegetación de dunas; 20 sitios × 30 spp.)
varespec (comunidades de líquenes; 24 sitios × 44 spp.)
## [1] 20 30
## [1] 24 44
Tomemos un sitio de dune y construyamos su curva.
## # A tibble: 5 × 3
## rango abund prop
## <int> <dbl> <dbl>
## 1 1 7 0.389
## 2 2 4 0.222
## 3 3 4 0.222
## 4 4 2 0.111
## 5 5 1 0.0556
## Pendiente (log10 prop ~ rango): -0.199
Lectura rápida: pendientes más negativas implican menor equidad (más dominancia).
Las comparaciones deben estandarizarse por tamaño de muestra. Aquí igualamos el número total de individuos de dos sitios usando rarefacción (rrarefy) y luego comparamos sus RACs.
# elegimos dos sitios de dune
com2 <- as.numeric(dune[5, ])
# Tamaños totales
n1 <- sum(com1); n2 <- sum(com2); n1; n2## [1] 18
## [1] 43
# Igualamos al mínimo
n_min <- min(n1, n2)
com1_std <- as.numeric(rrarefy(matrix(com1, nrow = 1), sample = n_min))
com2_std <- as.numeric(rrarefy(matrix(com2, nrow = 1), sample = n_min))
rac1_std <- rank_abundance(com1_std) %>% dplyr::mutate(comunidad = "Sitio 1 (std)")
rac2_std <- rank_abundance(com2_std) %>% dplyr::mutate(comunidad = "Sitio 5 (std)")
rac_comp <- dplyr::bind_rows(rac1_std, rac2_std)
ggplot(rac_comp, aes(rango, prop, color = comunidad)) +
geom_line() + geom_point() +
scale_y_continuous(trans = "log10") +
labs(x = "Rango", y = "Abundancia relativa (log10)",
title = "Comparación RAC con rarefacción (mismo esfuerzo)") +
theme_minimal(base_size = 12)data.frame(
Comunidad = c("Sitio 1 (std)", "Sitio 5 (std)"),
Pendiente = c(pendiente_rac(rac1_std), pendiente_rac(rac2_std))
) |>
dplyr::mutate(Pendiente = round(Pendiente, 3))## Comunidad Pendiente
## 1 Sitio 1 (std) -0.199
## 2 Sitio 5 (std) -0.073
Interpretación: a igual esfuerzo, la curva más larga indica mayor riqueza, y la pendiente menos negativa sugiere mayor equidad.
Construimos dos comunidades con igual riqueza (S = 20) pero distinta equidad.
S <- 20
# Comunidad A: muy pareja
com_A <- rep(10, S)
# Comunidad B: dominante + raras
com_B <- c(100, rep(2, S-1))
rac_A <- rank_abundance(com_A) %>% dplyr::mutate(comunidad = "Pareja")
rac_B <- rank_abundance(com_B) %>% dplyr::mutate(comunidad = "Dominante")
rac_AB <- dplyr::bind_rows(rac_A, rac_B)
ggplot(rac_AB, aes(rango, prop, color = comunidad)) +
geom_line() + geom_point() +
scale_y_continuous(trans = "log10") +
labs(x = "Rango", y = "Abundancia relativa (log10)",
title = "Misma riqueza, distinta equidad") +
theme_minimal(base_size = 12)data.frame(
Comunidad = c("Pareja", "Dominante"),
Pendiente = c(pendiente_rac(rac_A), pendiente_rac(rac_B))
) |>
dplyr::mutate(Pendiente = round(Pendiente, 3))## Comunidad Pendiente
## 1 Pareja 0.000
## 2 Dominante -0.024
El ajuste de distribuciones de abundancia de especies (SADs) complementa la lectura de las RACs. Con vegan::radfit obtenemos modelos clásicos y sus AIC.
##
## RAD models, family poisson
## No. of species 5, total abundance 18
##
## par1 par2 par3 Deviance AIC BIC
## Null 0.89431 15.84710 15.84710
## Preemption 0.36537 0.69044 17.64324 17.25267
## Lognormal 1.1125 0.72822 0.38771 19.34050 18.55938
## Zipf 0.41068 -0.89683 0.89538 19.84817 19.06705
## Mandelbrot Inf -3.8045e+07 9.1134e+07 0.44565 21.39844 20.22675
Nota: Diferencias pequeñas en AIC entre modelos indican que varios modelos explican patrones similares; conviene usar criterios adicionales (p. ej., rasgos funcionales, dispersión, o evidencia multi-sitio).
Simulamos un “antes/después” alterando levemente abundancias para ilustrar cómo cambia la pendiente/equidad.
antes <- com1_std
despues <- com1_std
# Aumentamos la especie dominante y reducimos varias raras
despues[which.max(despues)] <- despues[which.max(despues)] + 10
pos <- which(despues > 0)
raras <- if (length(pos) > 5) tail(pos, 5) else pos
despues[raras] <- pmax(0, despues[raras] - 1)
rac_antes <- rank_abundance(antes) %>% dplyr::mutate(tiempo = "Antes")
rac_desp <- rank_abundance(despues) %>% dplyr::mutate(tiempo = "Después")
rac_td <- dplyr::bind_rows(rac_antes, rac_desp)
ggplot(rac_td, aes(rango, prop, color = tiempo)) +
geom_line() + geom_point() +
scale_y_continuous(trans = "log10") +
labs(x = "Rango", y = "Abundancia relativa (log10)",
title = "Cambio temporal en la RAC (simulación)") +
theme_minimal(base_size = 12)data.frame(
Tiempo = c("Antes","Después"),
Pendiente = c(pendiente_rac(rac_antes), pendiente_rac(rac_desp))
) |>
dplyr::mutate(Pendiente = round(Pendiente, 3))## Tiempo Pendiente
## 1 Antes -0.199
## 2 Después -0.361
Usa rarefacción (o cobertura muestral equivalente) antes de comparar RACs entre sitios.
Reporta riqueza (longitud) y equidad (pendiente) conjuntamente.
Complementa con ajuste de SADs (radfit) y justifica el modelo con criterios ecológicos, no solo AIC.
Si tienes muchas muestras/tiempos, considera métricas de dinámica de comunidades (p. ej., cambios en rango/turnover) además de la forma de la RAC.
Los siguientes son datos de abundancia de genros de trichopteros recolectados en el río Ranchería sobre tres gradientes de altura
| Género | Alta | Baja | Media |
|---|---|---|---|
| Atanatolica | 1 | 0 | 0 |
| Atopsyche | 3 | 0 | 0 |
| Betrichia | 0 | 1 | 0 |
| Chimarra | 89 | 39 | 33 |
| Culoptila | 0 | 0 | 2 |
| Helicopsyche | 0 | 5 | 6 |
| Leptonema | 87 | 1 | 7 |
| Marilia | 0 | 1 | 0 |
| Mayatrichia | 2 | 0 | 0 |
| Nectopsyche | 0 | 3 | 0 |
| Neotrichia | 7 | 19 | 0 |
| Oecetis | 20 | 0 | 0 |
| Oxyethira | 8 | 2 | 3 |
| Phylloicus | 4 | 0 | 3 |
| Protoptila | 5 | 31 | 16 |
| Smicridea | 573 | 378 | 219 |
# Cargar librerías necesarias
library(ggplot2)
library(dplyr)
library(tidyr)
library(gridExtra)
library(ggrepel)
library(cowplot)
# Crear los datos
datos <- data.frame(
Generos = c("Atanatolica", "Atopsyche", "Betrichia", "Chimarra", "Culoptila",
"Helicopsyche", "Leptonema", "Marilia", "Mayatrichia", "Nectopsyche",
"Neotrichia", "Oecetis", "Oxyethira", "Phylloicus", "Protoptila", "Smicridea"),
Alta = c(1, 3, 0, 89, 0, 0, 87, 0, 2, 0, 7, 20, 8, 4, 5, 573),
Baja = c(0, 0, 1, 39, 0, 5, 1, 1, 0, 3, 19, 0, 2, 0, 31, 378),
Media = c(0, 0, 0, 33, 2, 6, 7, 0, 0, 0, 0, 0, 3, 3, 16, 219)
)
# Crear abreviaciones apropiadas para cada género
abreviaciones <- c(
"Atanatolica" = "Atan",
"Atopsyche" = "Atop",
"Betrichia" = "Betr",
"Chimarra" = "Chim",
"Culoptila" = "Culo",
"Helicopsyche" = "Heli",
"Leptonema" = "Lept",
"Marilia" = "Mari",
"Mayatrichia" = "Maya",
"Nectopsyche" = "Nect",
"Neotrichia" = "Neot",
"Oecetis" = "Oece",
"Oxyethira" = "Oxye",
"Phylloicus" = "Phyl",
"Protoptila" = "Prot",
"Smicridea" = "Smic"
)
# Función para calcular abundancia relativa y crear datos para gráfico de Whittaker
calcular_whittaker <- function(abundancias, localidad_nombre) {
# Filtrar especies con abundancia > 0
datos_filtrados <- data.frame(
genero = datos$Generos,
abundancia = abundancias
) %>%
filter(abundancia > 0) %>%
arrange(desc(abundancia))
# Calcular abundancia total
abundancia_total <- sum(datos_filtrados$abundancia)
# Calcular abundancia relativa
datos_filtrados$abundancia_relativa <- datos_filtrados$abundancia / abundancia_total
# Asignar rangos
datos_filtrados$rango <- 1:nrow(datos_filtrados)
# Añadir abreviaciones
datos_filtrados$abreviacion <- abreviaciones[datos_filtrados$genero]
# Añadir información de localidad
datos_filtrados$localidad <- localidad_nombre
return(datos_filtrados)
}
# Calcular datos de Whittaker para cada localidad
datos_alta <- calcular_whittaker(datos$Alta, "Alta")
datos_baja <- calcular_whittaker(datos$Baja, "Baja")
datos_media <- calcular_whittaker(datos$Media, "Media")
# Combinar todos los datos
datos_whittaker <- rbind(datos_alta, datos_baja, datos_media)
# Crear colores ultra-vibrantes y altamente contrastantes (SIN transparencias)
colores_vibrantes <- c(
"Smicridea" = "#FF0000", # Rojo puro
"Chimarra" = "#FF8C00", # Naranja vibrante
"Leptonema" = "#00FF00", # Verde lima puro
"Protoptila" = "#00BFFF", # Azul cielo profundo
"Neotrichia" = "#8A2BE2", # Violeta azulado
"Oecetis" = "#0000FF", # Azul puro
"Helicopsyche" = "#FF1493", # Rosa fucsia
"Oxyethira" = "#8B4513", # Marrón silla
"Phylloicus" = "#9ACD32", # Verde amarillo
"Atopsyche" = "#FF6347", # Tomate
"Nectopsyche" = "#4682B4", # Azul acero
"Mayatrichia" = "#9370DB", # Púrpura medio
"Atanatolica" = "#FFB6C1", # Rosa claro
"Betrichia" = "#A9A9A9", # Gris oscuro
"Culoptila" = "#ADFF2F", # Verde amarillo
"Marilia" = "#DEB887" # Madera clara
)
# Función para crear gráfico individual optimizado
crear_grafico_whittaker <- function(datos_localidad, titulo_localidad, mostrar_eje_y = TRUE) {
p <- ggplot(datos_localidad, aes(x = rango, y = abundancia_relativa)) +
geom_line(color = "black", size = 2, alpha = 1) + # Línea más gruesa y sin transparencia
geom_point(aes(color = genero), size = 4, alpha = 1) + # Puntos más grandes y opacos
geom_text_repel(
aes(label = abreviacion, color = genero),
size = 4,
fontface = "italic",
point.padding = 0.3,
box.padding = 0.6,
segment.color = NA,
max.overlaps = Inf,
force = 2,
nudge_x = 0.6,
nudge_y = 0.01,
show.legend = FALSE,
min.segment.length = 0
) +
scale_y_log10(
breaks = c(0.001, 0.01, 0.1, 1),
labels = c("0.001", "0.01", "0.100", "1"),
limits = c(0.0008, 1.3)
) +
scale_x_continuous(
breaks = seq(0, 12, by = 2),
limits = c(0, max(datos_localidad$rango) + 3)
) +
scale_color_manual(values = colores_vibrantes, name = "Taxones") +
labs(
title = titulo_localidad,
x = "Rango de las especies"
) +
theme_minimal() +
theme(
panel.border = element_rect(color = "black", fill = NA, size = 1.3),
plot.title = element_text(hjust = 0.5, size = 16, face = "bold",
margin = margin(b = 12)),
axis.title.x = element_text(size = 12, margin = margin(t = 10)),
axis.text = element_text(size = 11, color = "black", face = "bold"),
legend.position = "none",
panel.grid.minor = element_blank(),
panel.grid.major = element_line(color = "grey92", size = 0.3),
plot.margin = margin(12, 15, 12, 12),
panel.background = element_rect(fill = "white", color = NA)
)
# Añadir etiqueta Y solo si se requiere
if(mostrar_eje_y) {
p <- p + labs(y = "log10(Abundancia relativa)") +
theme(axis.title.y = element_text(size = 12, margin = margin(r = 10)))
} else {
p <- p + labs(y = NULL) +
theme(axis.title.y = element_blank())
}
return(p)
}
# Crear gráficos individuales - SOLO el primero tendrá etiqueta Y
grafico_alta <- crear_grafico_whittaker(datos_alta, "Alta", mostrar_eje_y = TRUE)
grafico_baja <- crear_grafico_whittaker(datos_baja, "Baja", mostrar_eje_y = FALSE)
grafico_media <- crear_grafico_whittaker(datos_media, "Media", mostrar_eje_y = FALSE)
# Crear leyenda compacta con colores vibrantes
crear_leyenda_compacta <- function() {
# Obtener géneros únicos presentes en los datos
generos_presentes <- unique(datos_whittaker$genero)
generos_ordenados <- sort(generos_presentes)
# Crear data frame para la leyenda
df_leyenda <- data.frame(
x = rep(1, length(generos_ordenados)),
y = length(generos_ordenados):1,
genero = generos_ordenados,
stringsAsFactors = FALSE
)
ggplot(df_leyenda, aes(x = x, y = y)) +
geom_point(aes(color = genero), size = 4.5, alpha = 1) + # Puntos más grandes y opacos
geom_text(aes(label = genero, color = genero),
x = 1.3,
fontface = "italic",
size = 4.2, # Texto más grande
hjust = 0,
show.legend = FALSE) +
scale_color_manual(values = colores_vibrantes) +
xlim(0.8, 2.8) +
ylim(0.3, length(generos_ordenados) + 0.7) +
labs(title = "Taxones") +
theme_void() +
theme(
plot.title = element_text(size = 14, face = "bold", hjust = 0, margin = margin(b = 8)),
plot.margin = margin(8, 2, 8, 2),
legend.position = "none"
)
}
# Crear la leyenda compacta
leyenda_compacta <- crear_leyenda_compacta()
# Crear figura combinada MÁS COMPACTA
figura_combinada <- plot_grid(
grafico_alta, grafico_baja, grafico_media, leyenda_compacta,
ncol = 4,
rel_widths = c(1.1, 0.9, 0.9, 0.65), # Más compacto
align = "hv",
axis = "tb"
)
# Crear título principal más compacto
titulo_principal <- ggdraw() +
draw_label("Figura 2: Patrón de rango abundancia para las localidades",
fontface = 'bold',
size = 18,
y = 0.5) +
theme(plot.margin = margin(8, 0, 8, 0))
# Figura final más compacta
figura_final <- plot_grid(
titulo_principal,
figura_combinada,
ncol = 1,
rel_heights = c(0.08, 1) # Título más pequeño
)
# Mostrar la figura
#print(figura_final)# Guardar la figura COMPACTA con colores vibrantes
ggsave("whittaker_plots_vibrante_compacto.png", figura_final,
width = 15, height = 6.5, dpi = 300, units = "in", bg = "white")
# Guardar versión en PDF compacta
ggsave("whittaker_plots_vibrante_compacto.pdf", figura_final,
width = 15, height = 6.5, units = "in", device = "pdf")
# Tabla comparativa simple
análisis_compacto <- data.frame(
Localidad = c("Alta", "Baja", "Media"),
Riqueza = c(11, 10, 8),
Abundancia = c(799, 480, 289),
Dominancia_Smic = c("71.7%", "78.8%", "75.8%"),
Shannon_H = c(1.007, 0.838, 0.918),
Equitabilidad_J = c(0.420, 0.364, 0.441)
)Concepto principal: Las curvas de Whittaker muestran la estructura de dominancia en comunidades ecológicas, ordenando especies de mayor a menor abundancia en escala logarítmica. Patrón observado: Las tres localidades estudiadas presentan el patrón típico de “J invertida”, donde pocas especies dominan mientras muchas otras son raras. Esto es característico de ecosistemas acuáticos maduros. Diferencias entre localidades:
Localidad Baja: Curva muy empinada con dominancia extrema del género Smicridea (78.8%). Indica distribución muy desigual de recursos que favorece intensamente a pocas especies. Localidad Alta: Curva con pendiente más suave, mayor equilibrio en las abundancias y capacidad para mantener 11 géneros. Representa una comunidad más madura y diversificada. Localidad Media: Curva intermedia con solo 8 géneros, mostrando una comunidad compacta pero con distribución más equitativa que Baja.
Interpretación ecológica: Alta soporta mayor diversidad con más especies raras (mayor heterogeneidad ambiental), Baja está estructurada por fuerte competencia dominante, y Media representa una comunidad eficiente pero ambientalmente limitada. Conclusión: Las curvas revelan diferentes estados de madurez y estabilidad comunitaria, fundamentales para entender la dinámica de estas comunidades de macroinvertebrados acuáticos.
Avolio, M. L., La Pierre, K. J., Houseman, G. R., Koerner, S. E., Grman, E., Isbell, F., et al. (2015). A framework for quantifying the magnitude and variability of community responses to global change drivers. Ecosphere, 6(12), 1–18. https://doi.org/10.1890/ES15-00317.1
Avolio, M. L., Forrestel, E. J., La Pierre, K. J., et al. (2019). A comprehensive approach to analyzing community dynamics using rank abundance curves. Ecosphere, 10(10), e02881. https://doi.org/10.1002/ecs2.2881
Baldridge, E., Harris, D. J., Xiao, X., & White, E. P. (2016). An extensive comparison of species‐abundance distribution models. PeerJ, 4, e2823. https://doi.org/10.7717/peerj.2823
Callaghan, C. T., Nakagawa, S., & Cornwell, W. K. (2023). Unveiling global species abundance distributions. Nature Ecology & Evolution, 7, 1188–1199. https://doi.org/10.1038/s41559-023-02173-y
Koffel, T., Umemura, K., Litchman, E., & Klausmeier, C. A. (2022). A general framework for species-abundance distributions: Linking traits and dispersal to explain commonness and rarity. Ecology Letters, 25(11), 2359–2371. https://doi.org/10.1111/ele.14094
McGill, B. J., Etienne, R. S., Gray, J. S., et al. (2007). Species abundance distributions: Moving beyond single prediction theories to integration within an ecological framework. Ecology Letters, 10(10), 995–1015. https://doi.org/10.1111/j.1461-0248.2007.01094.x
Whittaker, R. H. (1965). Dominance and diversity in land plant communities. Science, 147(3655), 250–260. https://doi.org/10.1126/science.147.3655.250