1 Contexto del experimento

Esta sección presenta el estudio para alguien que no estuvo en el campo: qué se quería saber, dónde y cuándo se midió y con qué diseño. Todo lo que aparece aquí se calcula a partir de la misma base que se analiza más adelante.

1.1 Ficha del experimento

# Lectura de la base original (todo como texto, para revisar cada valor tal como se escribió).
stopifnot(file.exists(params$archivo))
bruto <- readxl::read_excel(params$archivo, sheet = 1, skip = 1,
                            col_types = "text", .name_repair = "minimal")
bruto <- as.data.frame(bruto, stringsAsFactors = FALSE)
if (all(is.na(bruto[[1]]))) bruto <- bruto[, -1]      # la plantilla deja la columna A vacía
stopifnot(ncol(bruto) >= 9)
bruto <- bruto[, 1:9]
names(bruto) <- c("grupo", "id_arbol", "lat", "lon", "n_hoja", "area_cm2",
                  "exposicion", "fecha", "observaciones")   # nombres exigidos por la guía
bruto <- bruto[!is.na(bruto$id_arbol), ]

fechas   <- as.Date(as.numeric(bruto$fecha), origin = "1899-12-30")
n_arbol  <- length(unique(toupper(trimws(bruto$id_arbol))))

ficha <- data.frame(
  Elemento = c("Pregunta", "Tipo de estudio", "Lugar", "Fecha de recolección",
               "Factor de interés", "Variable respuesta", "Unidad experimental",
               "Unidad de muestreo", "Tamaño de la muestra", "Instrumentos"),
  Descripción = c(
    "¿Cómo cambia el área de las hojas según el lugar donde crece el árbol?",
    "Observacional y comparativo: no se asignó sol o sombra; se eligieron árboles que ya estaban en cada condición",
    "Campus de la Universidad del Valle, sede Meléndez, Cali (Valle del Cauca, Colombia)",
    if (min(fechas) == max(fechas)) format(min(fechas), "%d/%m/%Y")
    else paste(format(min(fechas), "%d/%m/%Y"), "a", format(max(fechas), "%d/%m/%Y")),
    "Exposición del árbol a la luz: sol (luz directa la mayor parte del día), semisombra o sombra",
    "Área de la hoja en cm²",
    "El árbol (la exposición es una propiedad suya)",
    "La hoja: 10 hojas adultas y sanas por árbol",
    paste0("Grupo ", params$grupo, ": ", n_arbol, " árboles × 10 hojas; ", nrow(bruto), " filas registradas"),
    "Celular (cámara y GPS), papel blanco, lápiz y regla, y un script de Google Colab que calcula el área a partir de la foto"))
tabla(ficha, align = "ll")
Elemento Descripción
Pregunta ¿Cómo cambia el área de las hojas según el lugar donde crece el árbol?
Tipo de estudio Observacional y comparativo: no se asignó sol o sombra; se eligieron árboles que ya estaban en cada condición
Lugar Campus de la Universidad del Valle, sede Meléndez, Cali (Valle del Cauca, Colombia)
Fecha de recolección 22/10/2026
Factor de interés Exposición del árbol a la luz: sol (luz directa la mayor parte del día), semisombra o sombra
Variable respuesta Área de la hoja en cm²
Unidad experimental El árbol (la exposición es una propiedad suya)
Unidad de muestreo La hoja: 10 hojas adultas y sanas por árbol
Tamaño de la muestra Grupo 1: 5 árboles × 10 hojas; 50 filas registradas
Instrumentos Celular (cámara y GPS), papel blanco, lápiz y regla, y un script de Google Colab que calcula el área a partir de la foto

1.2 Sitio de estudio y protocolo

El estudio se hizo en el campus Meléndez de la Universidad del Valle, en Cali, en el valle geográfico del río Cauca: una zona tropical a unos 1 000 m de altitud, con temperaturas medias cercanas a los 24 °C y dos temporadas de lluvia al año (valores aproximados). Dentro del campus hay árboles en zonas abiertas, junto a edificios y vías pavimentadas, y entre otros árboles, lo que da un gradiente natural de luz. Las fotos tomadas el día del muestreo muestran el cielo nublado.

El protocolo, en resumen:

  1. El grupo eligió 5 árboles adultos y sanos de hojas simples y registró sus coordenadas con el GPS del celular.
  2. De cada árbol se tomaron 10 hojas adultas, expandidas y sin daño evidente, repartidas por toda la copa.
  3. Cada hoja se trazó sobre papel blanco, se fotografió dentro de un marco rectangular de lados conocidos y se midió su área con el script de Colab.
  4. Los datos se capturaron en la plantilla común (una fila por hoja).

1.3 Mapa de los sitios

Cada punto es un árbol. Al hacer clic en un punto se ve su foto y sus datos; arriba a la derecha se puede cambiar el fondo (satélite o calles) y ocultar categorías. Con la vista de satélite se comprueba si el árbol está realmente a cielo abierto o bajo otras copas, que es justo lo que define su exposición.

# Un registro por árbol. Se usa la coordenada de la primera hoja de cada árbol
# (la revisión completa de las coordenadas está en la sección 4).
norm_expo <- function(x) {
  x <- tolower(trimws(x))
  x[x %in% c("sol", "soleado", "pleno sol")]                        <- "sol"
  x[x %in% c("sombra", "sombreado")]                                <- "sombra"
  x[x %in% c("semi", "semisombra", "semi-sombra", "semi sombra")]   <- "semi"
  x
}
b <- bruto
b$id_arbol   <- toupper(trimws(b$id_arbol))
b$exposicion <- norm_expo(b$exposicion)
b$lat_dec    <- dms_a_dec(b$lat)
b$lon_dec    <- dms_a_dec(b$lon)

sitios <- do.call(rbind, lapply(split(b, b$id_arbol), function(d)
  data.frame(grupo = as.integer(as.numeric(d$grupo[1])), id_arbol = d$id_arbol[1],
             lat = d$lat_dec[1], lon = d$lon_dec[1], exposicion = d$exposicion[1],
             hojas = nrow(d), stringsAsFactors = FALSE)))
sitios <- sitios[order(as.integer(sub(".*_T", "", sitios$id_arbol))), ]
rownames(sitios) <- NULL

# Foto de cada árbol: busca fotos/ID.jpg (o .jpeg, .png) y avisa si falta alguna
buscar_foto <- function(id) {
  candidatas <- file.path(params$carpeta_fotos, paste0(id, c(".jpg", ".jpeg", ".png")))
  existentes <- candidatas[file.exists(candidatas)]
  if (length(existentes) > 0) existentes[1] else NA_character_
}
sitios$foto <- vapply(sitios$id_arbol, buscar_foto, character(1))
cat("Árboles:", nrow(sitios), "| con foto:", sum(!is.na(sitios$foto)), "\n")
## Árboles: 5 | con foto: 5
sitios$popup <- vapply(seq_len(nrow(sitios)), function(i) {
  s <- sitios[i, ]
  img <- if (is.na(s$foto)) "" else paste0("<img src='", knitr::image_uri(s$foto), "' width='200'><br>")
  paste0(img, "<b>", s$id_arbol, "</b><br>Exposición: ", s$exposicion, "<br>Hojas registradas: ",
         s$hojas, "<br>", fmt(s$lat, 5), "; ", fmt(s$lon, 5))
}, character(1))

presentes <- intersect(niveles_expo, unique(sitios$exposicion))

if (requireNamespace("leaflet", quietly = TRUE)) {
  library(leaflet)
  mapa <- leaflet() %>%
    addProviderTiles(providers$Esri.WorldImagery, group = "Satélite") %>%
    addTiles(group = "Calles")
  for (e in presentes) {
    d <- sitios[sitios$exposicion == e, ]
    mapa <- addCircleMarkers(mapa, data = d, lng = ~lon, lat = ~lat, radius = 9, weight = 2,
                             color = "white", fillColor = col_expo[[e]], fillOpacity = 0.95,
                             label = ~id_arbol, popup = ~popup,
                             popupOptions = popupOptions(maxWidth = 240), group = e)
  }
  mapa %>%
    addLayersControl(baseGroups = c("Satélite", "Calles"), overlayGroups = presentes,
                     options = layersControlOptions(collapsed = FALSE)) %>%
    addLegend("bottomright", colors = unname(col_expo[presentes]), labels = presentes,
              title = "Exposición", opacity = 1) %>%
    addScaleBar("bottomleft")
} else {
  cat("El paquete 'leaflet' no está instalado: se muestra un mapa simple.",
      "Para el mapa interactivo ejecuta install.packages('leaflet').\n")
  plot(sitios$lon, sitios$lat, pch = 21, cex = 3, bg = col_expo[sitios$exposicion],
       asp = 1 / cos(mean(sitios$lat) * pi / 180), xlab = "Longitud", ylab = "Latitud",
       main = "Ubicación de los árboles")
  text(sitios$lon, sitios$lat, sub(".*_", "", sitios$id_arbol), cex = 0.8)
  legend("topright", legend = presentes, pt.bg = col_expo[presentes], pch = 21, bty = "n")
}

1.4 Fotos de los árboles

Una foto de cada árbol sirve para comprobar después la clasificación en sol, semisombra y sombra, y para que el lector se imagine el sitio.

tarjetas <- vapply(seq_len(nrow(sitios)), function(i) {
  s <- sitios[i, ]
  img <- if (is.na(s$foto)) "<div class='sinfoto'>foto pendiente</div>"
         else paste0("<img src='", knitr::image_uri(s$foto), "'>")
  paste0("<figure>", img, "<figcaption><b>", s$id_arbol, "</b> · ", s$exposicion, "</figcaption></figure>")
}, character(1))
cat("\n\n<div class='galeria'>\n", paste(tarjetas, collapse = "\n"), "\n</div>\n\n")
G1_T1 · semi
G1_T2 · sombra
G1_T3 · semi
G1_T4 · sol
G1_T5 · sol

2 Introducción

Pregunta. ¿Cómo cambia el área de las hojas según el lugar donde crece el árbol?

Por qué esperamos una diferencia. Una hoja equilibra dos cosas: captar luz y no perder demasiada agua ni calentarse de más. A pleno sol sobra luz y el riesgo es el exceso de calor y de transpiración, así que se esperan hojas más pequeñas. A la sombra la luz es el recurso escaso, y una lámina más grande intercepta más de ella.

Hipótesis. Las hojas de los árboles que crecen a la sombra tienen, en promedio, mayor área que las de los árboles que crecen a pleno sol, y los árboles de semisombra tendrían valores intermedios. Si es cierta, la diferencia debería verse en los promedios por árbol y no depender de un solo árbol.

3 Métodos

Diseño. El grupo 1 midió 5 árboles y 10 hojas por árbol (50 hojas). Un árbol es de sol si recibe luz directa la mayor parte del día y no lo cubren otros árboles ni edificios; en caso contrario, no. Además de sol y sombra, el grupo usó una categoría intermedia, semi (semisombra), que no estaba prevista en la guía y se trata como categoría propia. Las hojas fueron adultas, completamente expandidas, sin daño evidente y repartidas por toda la copa.

Medición. Cada hoja se trazó sobre papel, se fotografió dentro de un marco rectangular de lados conocidos y un script calculó el área en cm². Los datos se anotaron en la plantilla de la guía: una fila por hoja, con grupo, árbol, coordenadas, número de hoja, área, exposición, fecha y observaciones.

Análisis:

  1. Revisión de la base: estructura, valores y conversión de las coordenadas a grados decimales.
  2. Por árbol: n, media, mediana, desviación estándar y coeficiente de variación.
  3. Gráficas: un diagrama de cajas por árbol y otro por exposición.
  4. Valores atípicos: un criterio fijo (regla de Tukey dentro de cada árbol) más la revisión de las observaciones.
  5. Comparación entre exposiciones con el árbol como unidad de análisis: se promedian las 10 hojas de cada árbol y se comparan esos promedios.
  6. Diseño experimental y ANOVA: de un factor entre árboles (con Tukey) y anidado (hojas dentro de árboles dentro de exposición).

4 Datos y revisión de calidad

Antes de calcular nada hay que mirar la base. Las revisiones se hacen sobre una copia (datos): la base original (bruto) no se toca.

4.1 Lo que trae la base

head(bruto, 6)        # las primeras filas, tal como vienen en el Excel
##   grupo id_arbol          lat           lon n_hoja area_cm2 exposicion fecha
## 1     1    G1_T1 N 3°22'35.27 W 76°31'51.26      1    27.50       semi 46317
## 2     1    G1_T1 N 3°22'35.27 W 76°31'51.26      2    22.07       semi 46317
## 3     1    G1_T1 N 3°22'35.27 W 76°31'51.26      3    35.89       semi 46317
## 4     1    G1_T1 N 3°22'35.27 W 76°31'51.26      4    38.70       semi 46317
## 5     1    G1_T1 N 3°22'35.27 W 76°31'51.26      5    39.41       semi 46317
## 6     1    G1_T1 N 3°22'35.27 W 76°31'51.26      6    41.87       semi 46317
##   observaciones
## 1          <NA>
## 2          <NA>
## 3          <NA>
## 4          <NA>
## 5          <NA>
## 6          <NA>

La base tiene 50 filas y 5 árboles: 5 árboles × 10 hojas = 50 filas, como pide la guía.

4.2 Revisiones

datos <- bruto

# 1. Identificador (formato G#_T#) y grupo
datos$id_arbol <- toupper(trimws(datos$id_arbol))
datos$grupo    <- as.integer(as.numeric(datos$grupo))
datos$n_hoja   <- as.integer(as.numeric(datos$n_hoja))
id_mal <- unique(datos$id_arbol[!grepl("^G[0-9]+_T[0-9]+$", datos$id_arbol)])
id_mal                                                    # debe estar vacío
## character(0)
stopifnot(all(as.integer(sub("^G([0-9]+)_.*", "\\1", datos$id_arbol)) == datos$grupo))

# 2. Área: número mayor que 0 en todas las filas
datos$area_cm2 <- as.numeric(gsub(",", ".", trimws(datos$area_cm2)))
datos[is.na(datos$area_cm2) | datos$area_cm2 <= 0, c("id_arbol", "n_hoja", "area_cm2")]
## [1] id_arbol n_hoja   area_cm2
## <0 rows> (o 0- extensión row.names)
# 3. Exposición: una sola por árbol
datos$exposicion <- norm_expo(datos$exposicion)
table(datos$exposicion)
## 
##   semi    sol sombra 
##     20     20     10
n_expo <- tapply(datos$exposicion, datos$id_arbol, function(x) length(unique(x)))
names(n_expo)[n_expo > 1]                                 # debe estar vacío
## character(0)
# 4. Filas repetidas, número de hoja y hojas por árbol
repetida <- duplicated(datos[, c("id_arbol", "n_hoja")])
sum(repetida)
## [1] 0
range(datos$n_hoja)
## [1]  1 10
n_por_arbol <- table(datos$id_arbol)
n_por_arbol
## 
## G1_T1 G1_T2 G1_T3 G1_T4 G1_T5 
##    10    10    10    10    10
# 5. Fecha de recolección
datos$fecha <- as.Date(as.numeric(datos$fecha), origin = "1899-12-30")
table(datos$fecha)
## 
## 2026-10-22 
##         50
# 6. Observaciones anotadas por los estudiantes
n_obs <- sum(!is.na(datos$observaciones))
n_obs
## [1] 0

No hay filas repetidas, todas las áreas son mayores que 0, el número de hoja va de 1 a 10 en cada árbol y cada árbol tiene una sola exposición. Todas las hojas se midieron el mismo día. La columna de observaciones está vacía (0 anotaciones): no se registró ninguna hoja dañada ni trazo dudoso, así que no hay motivos documentados para excluir hojas.

4.3 Coordenadas

La guía pide las coordenadas en grados decimales, y la base las trae en grados-minutos-segundos con la letra del hemisferio (por ejemplo N 3°22'35.27). Se convierten a grados decimales (latitud norte positiva, longitud oeste negativa). Como las 10 hojas de un árbol comparten el mismo punto, se usa un solo punto por árbol.

head(unique(bruto[, c("id_arbol", "lat", "lon")]), 6)     # como venían
##    id_arbol          lat           lon
## 1     G1_T1 N 3°22'35.27 W 76°31'51.26
## 11    G1_T2 N 3°22'36.42 W 76°31'52.92
## 21    G1_T3 N 3°22'37.24 W 76°31'53.23
## 31    G1_T4 N 3°22'38.14 W 76°31'54.25
## 41    G1_T5 N 3°22'38.26 W 76°31'52.51
datos$lat <- dms_a_dec(bruto$lat)
datos$lon <- dms_a_dec(bruto$lon)

primera <- !duplicated(datos$id_arbol)
arb <- datos[primera, c("id_arbol", "grupo", "exposicion", "lat", "lon")]
arb <- arb[order(as.integer(sub(".*_T", "", arb$id_arbol))), ]
rownames(arb) <- NULL
tabla(transform(arb, lat = fmt(lat, 5), lon = fmt(lon, 5)), row.names = FALSE,
      align = "lllrr", caption = "Coordenadas en grados decimales (un punto por árbol).")
Coordenadas en grados decimales (un punto por árbol).
id_arbol grupo exposicion lat lon
G1_T1 1 semi 3,37646 -76,53091
G1_T2 1 sombra 3,37678 -76,53137
G1_T3 1 semi 3,37701 -76,53145
G1_T4 1 sol 3,37726 -76,53174
G1_T5 1 sol 3,37729 -76,53125
# Distancia en metros entre los árboles (sirve para detectar una coordenada mal digitada)
D <- outer(seq_len(nrow(arb)), seq_len(nrow(arb)),
           Vectorize(function(i, j) distancia_m(arb$lat[i], arb$lon[i], arb$lat[j], arb$lon[j])))
dimnames(D) <- list(arb$id_arbol, arb$id_arbol)
tabla(round(D), caption = "Distancia en metros entre los árboles del grupo.")
Distancia en metros entre los árboles del grupo.
G1_T1 G1_T2 G1_T3 G1_T4 G1_T5
G1_T1 0 62 86 128 100
G1_T2 62 0 27 67 58
G1_T3 86 27 0 42 39
G1_T4 128 67 42 0 54
G1_T5 100 58 39 54 0
# Coordenada única por árbol en la base de trabajo
datos$lat <- arb$lat[match(datos$id_arbol, arb$id_arbol)]
datos$lon <- arb$lon[match(datos$id_arbol, arb$id_arbol)]

Los cinco árboles quedan a menos de 130 m unos de otros, dentro del campus (se ve en el mapa de la sección 1).

4.4 Cumplimiento de la guía

n_sol    <- sum(arb$exposicion == "sol")
n_sombra <- sum(arb$exposicion == "sombra")
n_semi   <- sum(arb$exposicion == "semi")

cumple <- data.frame(
  Requisito = c("5 árboles por grupo", "10 hojas por árbol", "Al menos 2 árboles a pleno sol",
                "Al menos 2 árboles a la sombra", "Exposición solo `sol` o `sombra`",
                "Coordenadas en grados decimales", "Área > 0 en todas las filas", "Hojas medidas el mismo día"),
  Resultado = c(paste(nrow(arb), "árboles"),
                ifelse(all(n_por_arbol == 10), "sí, 10 en todos", "no en todos"),
                paste(n_sol, "árboles"), paste(n_sombra, "árbol(es)"),
                paste0("no: hay ", n_semi, " árboles `semi`"),
                "venían en grados-minutos-segundos (convertidas)",
                ifelse(all(datos$area_cm2 > 0, na.rm = TRUE), "sí", "no"),
                ifelse(length(unique(datos$fecha)) == 1, paste("sí:", format(unique(datos$fecha), "%d/%m/%Y")), "no")),
  Cumple = c(nrow(arb) == 5, all(n_por_arbol == 10), n_sol >= 2, n_sombra >= 2, n_semi == 0,
             TRUE, all(datos$area_cm2 > 0, na.rm = TRUE), length(unique(datos$fecha)) == 1))
cumple$Cumple <- ifelse(cumple$Cumple, "✔", "✘")
tabla(cumple, row.names = FALSE, align = "llc", caption = "Cumplimiento de la guía por parte de la base.")
Cumplimiento de la guía por parte de la base.
Requisito Resultado Cumple
5 árboles por grupo 5 árboles ✔
10 hojas por árbol sí, 10 en todos ✔
Al menos 2 árboles a pleno sol 2 árboles ✔
Al menos 2 árboles a la sombra 1 árbol(es) ✘
Exposición solo sol o sombra no: hay 2 árboles semi ✘
Coordenadas en grados decimales venían en grados-minutos-segundos (convertidas) ✔
Área > 0 en todas las filas sí ✔
Hojas medidas el mismo día sí: 22/10/2026 ✔

La guía solo admite sol o sombra, se usó además semi (semisombra). Se conserva como categoría propia.

limpios <- datos[order(datos$grupo, as.integer(sub(".*_T", "", datos$id_arbol)), datos$n_hoja), ]

cat("Filas:", nrow(limpios), "\n", "Árboles:", length(unique(limpios$id_arbol)), "\n")
## Filas: 50 
##  Árboles: 5
if (params$guardar_limpios) {
  salida <- limpios[, c("grupo", "id_arbol", "lat", "lon", "n_hoja", "area_cm2", "exposicion",
                        "fecha", "observaciones")]
  write.csv(salida, "datos_limpios_area_foliar.csv", row.names = FALSE, na = "", fileEncoding = "UTF-8")
}

La base revisada (datos_limpios_area_foliar.csv, con las columnas de la plantilla y las coordenadas en grados decimales) es parte de lo que se entrega.

5 Resultados del grupo 1

Desde aquí se analiza el grupo, como en la guía: 5 árboles y 50 hojas.

g  <- params$grupo
dg <- limpios[limpios$grupo == g, ]
stopifnot(nrow(dg) > 0)
dg$exposicion <- factor(dg$exposicion, levels = niveles_expo)

5.1 Estadística descriptiva por árbol

resumen <- do.call(rbind, lapply(split(dg, dg$id_arbol), function(d) {
  data.frame(arbol = d$id_arbol[1], exposicion = as.character(d$exposicion[1]), n = nrow(d),
             media = mean(d$area_cm2), mediana = median(d$area_cm2),
             de = sd(d$area_cm2), cv_pct = cv(d$area_cm2),
             minimo = min(d$area_cm2), maximo = max(d$area_cm2))
}))
resumen <- resumen[order(as.integer(sub(".*_T", "", resumen$arbol))), ]

tabla(resumen, row.names = FALSE, digits = c(0, 0, 0, 1, 1, 1, 1, 1, 1),
      col.names = c("Árbol", "Exposición", "n", "Media", "Mediana", "DE", "CV (%)", "Mín.", "Máx."),
      caption = "Área foliar (cm²) por árbol. DE = desviación estándar; CV = DE / media.")
Área foliar (cm²) por árbol. DE = desviación estándar; CV = DE / media.
Árbol Exposición n Media Mediana DE CV (%) Mín. Máx.
G1_T1 semi 10 43,5 40,6 13,9 32,0 22,1 64,2
G1_T2 sombra 10 57,8 52,6 23,4 40,5 30,8 102,0
G1_T3 semi 10 70,9 76,6 25,4 35,9 17,7 97,6
G1_T4 sol 10 63,2 67,5 15,7 24,9 35,0 82,9
G1_T5 sol 10 158,1 120,6 93,6 59,2 59,7 319,8

Dos cosas para mirar en esta tabla.

  1. La diferencia entre árboles: las medias van de 43,5 a 158,1 cm², y el árbol con la media más alta (G1_T5) tiene hojas entre 2,2 y 3,6 veces más grandes, en promedio, que los otros cuatro.

  2. El CV, que mide cuánto varían las hojas de un árbol respecto a su propia media, va de 25 % a 59 %: G1_T5 es el más variable (hojas de 59,7 a 319,8 cm²) y G1_T4 el más homogéneo. En G1_T5 la media (158,1) supera con claridad a la mediana (120,6), señal de que unas pocas hojas muy grandes jalan el promedio hacia arriba.

5.2 Un diagrama de cajas por árbol

dg$id_arbol <- factor(dg$id_arbol, levels = resumen$arbol)
par(mar = c(4.5, 4.8, 3, 1))
boxplot(area_cm2 ~ id_arbol, data = dg, outline = FALSE, las = 1,
        col = adjustcolor(col_expo[resumen$exposicion], 0.55), border = "grey25",
        xlab = "Árbol", ylab = expression("Área foliar (cm"^2*")"),
        main = paste("Grupo", g, "- área foliar por árbol"))
stripchart(area_cm2 ~ id_arbol, data = dg, vertical = TRUE, method = "jitter", jitter = 0.12,
           pch = 16, col = adjustcolor("black", 0.45), add = TRUE)
legend("topleft", legend = presentes, fill = adjustcolor(col_expo[presentes], 0.55), bty = "n")
Área foliar de cada hoja, por árbol. Color = exposición.

Área foliar de cada hoja, por árbol. Color = exposición.

El gráfico muestra lo mismo que la tabla: las cajas de los árboles distintos de G1_T5 se solapan bastante entre sí (sus medianas van de 40,6 a 76,6 cm²), mientras que la caja de G1_T5 queda casi toda por encima y es la más ancha. Tampoco los árboles de la misma exposición se parecen necesariamente: los dos árboles de sol (G1_T4 y G1_T5) tienen medianas muy distintas (67,5 y 120,6 cm²).

5.3 Valores atípicos: ¿error o hoja realmente distinta?

Criterio. Se revisa cada hoja con dos preguntas, en este orden:

  1. ¿Hay una causa de medición? (unidad equivocada, hoja dañada, trazo dudoso, foto borrosa, marco mal medido). Si sí, se corrige o se excluye.
  2. Si no hay causa de medición pero la hoja queda fuera de la regla de Tukey de su árbol (por debajo de Q1 − 1,5·RIC o por encima de Q3 + 1,5·RIC), se considera una hoja realmente distinta: se conserva y se comprueba cuánto cambia el resultado si no estuviera (análisis de sensibilidad).
fuera <- do.call(rbind, lapply(split(dg, dg$id_arbol), function(d) {
  d[d$area_cm2 %in% boxplot.stats(d$area_cm2)$out,
    c("id_arbol", "n_hoja", "area_cm2", "observaciones")]
}))

if (is.null(fuera) || nrow(fuera) == 0) {
  cat("Ninguna hoja queda fuera de los límites de Tukey de su árbol.\n")
} else {
  tabla(fuera, row.names = FALSE, digits = 2,
        caption = "Hojas fuera de los límites de Tukey de su árbol.")
}
## Ninguna hoja queda fuera de los límites de Tukey de su árbol.
sens <- do.call(rbind, lapply(split(dg, dg$id_arbol), function(d) {
  out <- boxplot.stats(d$area_cm2)$out
  data.frame(arbol = d$id_arbol[1], exposicion = as.character(d$exposicion[1]),
             media = mean(d$area_cm2), mediana = median(d$area_cm2),
             media_sin_atipicas = mean(d$area_cm2[!d$area_cm2 %in% out]))
}))
sens <- sens[order(as.integer(sub(".*_T", "", sens$arbol))), ]
tabla(sens, row.names = FALSE, digits = 1,
      col.names = c("Árbol", "Exposición", "Media (todas)", "Mediana", "Media sin atípicas"),
      caption = "Sensibilidad del resumen de cada árbol (cm²) a las hojas atípicas.")
Sensibilidad del resumen de cada árbol (cm²) a las hojas atípicas.
Árbol Exposición Media (todas) Mediana Media sin atípicas
G1_T1 semi 43,5 40,6 43,5
G1_T2 sombra 57,8 52,6 57,8
G1_T3 semi 70,9 76,6 70,9
G1_T4 sol 63,2 67,5 63,2
G1_T5 sol 158,1 120,6 158,1

Resultado del criterio. Ninguna hoja queda fuera de los límites de Tukey de su árbol, ni siquiera las más grandes de G1_T5 (hasta 319,8 cm²): como ese árbol es tan variable, sus límites son muy amplios. Respecto a la primera pregunta (causa de medición), no hay errores de medición lo mas probable es que se hayan tomado hojas que no estaban completamente desarrolladas por error; las hojas del T1_G5 corresponden a un arbol de Noni ( Morinda citrifolia L. ), el cual naturalmente tiene hojas visiblemente mucho más grandes que el resto de los arboles del experimento. Decisión: se conservan todas las hojas.

5.4 Un diagrama de cajas por exposición

dg$expo <- factor(dg$exposicion, levels = presentes)
simbolo <- c(16, 17, 15, 18, 8)[as.integer(dg$id_arbol)]
par(mar = c(4.5, 4.8, 3, 1))
boxplot(area_cm2 ~ expo, data = dg, outline = FALSE, las = 1,
        ylim = range(dg$area_cm2),   # que el eje incluya todas las hojas, también las más grandes
        col = adjustcolor(col_expo[presentes], 0.5), border = "grey25",
        xlab = "Exposición", ylab = expression("Área foliar (cm"^2*")"),
        main = paste("Grupo", g, "- hojas por exposición"))
set.seed(2)   # para que la separación horizontal de los puntos sea siempre la misma
points(as.integer(dg$expo) + runif(nrow(dg), -0.15, 0.15), dg$area_cm2,
       pch = simbolo, col = adjustcolor("black", 0.6))
legend("topleft", legend = levels(dg$id_arbol), pch = c(16, 17, 15, 18, 8), bty = "n",
       title = "Árbol", cex = 0.85)
Hojas por exposición. Cada símbolo es un árbol distinto.

Hojas por exposición. Cada símbolo es un árbol distinto.

Este gráfico es descriptivo: muestra la forma de las nubes de hojas de cada categoría. Los símbolos iguales (hojas del mismo árbol) tienden a quedar juntos; eso es justo lo que hace que las hojas no sean independientes y lo que impide usarlas como si fueran 50 datos independientes en una prueba. Además, sombra está representada por un solo árbol, así que su caja describe a ese árbol, no a “la sombra” en general.

Las medianas de las hojas son sol = 76,2 cm²; semi = 54,4 cm²; sombra = 52,6 cm². El orden (sol por encima) es el contrario al que planteaba la hipótesis, pero la caja de sol está dominada por las hojas de G1_T5, y semisombra y sombra son casi iguales entre sí.

5.5 Unidad de análisis: el árbol

Lo que se asignó a sol, semisombra o sombra es el árbol, no la hoja. Las 10 hojas de un mismo árbol comparten suelo, edad, genética y luz. Por eso se promedian y se compara un valor por árbol. La pregunta de la guía es cuántos árboles hay realmente por categoría:

medias_g <- aggregate(area_cm2 ~ id_arbol + exposicion, data = dg, FUN = mean)
medias_g$exposicion <- factor(medias_g$exposicion, levels = niveles_expo)
medias_g <- medias_g[order(medias_g$exposicion, medias_g$id_arbol), ]
medias_g <- medias_g[medias_g$area_cm2 > 0 & !is.na(medias_g$area_cm2), ]

tabla(medias_g, row.names = FALSE, digits = 1, col.names = c("Árbol", "Exposición", "Media del árbol (cm²)"))
Árbol Exposición Media del árbol (cm²)
G1_T4 sol 63,2
G1_T5 sol 158,1
G1_T1 semi 43,5
G1_T3 semi 70,9
G1_T2 sombra 57,8
por_expo <- do.call(rbind, lapply(split(medias_g, droplevels(medias_g$exposicion)), function(d)
  data.frame(exposicion = as.character(d$exposicion[1]), arboles = nrow(d), hojas = 10 * nrow(d),
             media = mean(d$area_cm2), de = if (nrow(d) > 1) sd(d$area_cm2) else NA_real_,
             minimo = min(d$area_cm2), maximo = max(d$area_cm2))))
tabla(por_expo, row.names = FALSE, digits = c(0, 0, 0, 1, 1, 1, 1),
      col.names = c("Exposición", "Árboles", "Hojas", "Media de los árboles", "DE entre árboles", "Mín.", "Máx."),
      caption = "Árboles realmente independientes por categoría de exposición (unidad = el árbol).")
Árboles realmente independientes por categoría de exposición (unidad = el árbol).
Exposición Árboles Hojas Media de los árboles DE entre árboles Mín. Máx.
sol 2 20 110,6 67,1 63,2 158,1
semi 2 20 57,2 19,3 43,5 70,9
sombra 1 10 57,8 — 57,8 57,8
mg <- medias_g
mg$x <- match(as.character(mg$exposicion), presentes)
set.seed(3)
par(mar = c(4.5, 4.8, 3, 1))
plot(mg$x + runif(nrow(mg), -0.08, 0.08), mg$area_cm2, pch = 21, bg = col_expo[as.character(mg$exposicion)],
     cex = 3, xaxt = "n", xlim = c(0.5, length(presentes) + 0.5), las = 1,
     ylim = c(0, max(mg$area_cm2) * 1.1), xlab = "Exposición",
     ylab = expression("Área foliar media del árbol (cm"^2*")"), main = "Un punto por árbol")
axis(1, at = seq_along(presentes), labels = presentes)
text(mg$x + 0.22, mg$area_cm2, sub(".*_", "", mg$id_arbol), cex = 0.9)
Cada punto es un árbol (promedio de sus 10 hojas). Es la comparación con la que se puede razonar: 5 puntos.

Cada punto es un árbol (promedio de sus 10 hojas). Es la comparación con la que se puede razonar: 5 puntos.

En total hay 5 datos independientes, no 50: 2 árboles de sol, 2 de semisombra y 1 de sombra. Con un solo árbol de sombra no se puede estimar la variación dentro de esa categoría (la DE queda sin calcular), y la comparación sol–sombra de la guía no puede hacerse como prueba estadística.

5.5.1 ¿Qué tan raro sería ver esa diferencia por azar?

Con tan pocos árboles se puede hacer algo mejor que suponer una distribución: una prueba de permutación exacta. Como solo hay un árbol de sombra, la comparación se hace entre sol (2 árboles) y sin sol pleno (semisombra y sombra juntas, 3 árboles). Si la exposición no importara, cualquiera de las formas de repartir las etiquetas entre los árboles sería igual de probable.

medias_g$sin_sol <- medias_g$exposicion != "sol"
y        <- medias_g$area_cm2
es_ss    <- medias_g$sin_sol
n_ss     <- sum(es_ss)

repartos <- combn(length(y), n_ss)                       # todas las formas de elegir los árboles "sin sol pleno"
dif_perm <- apply(repartos, 2, function(i) mean(y[i]) - mean(y[-i]))
dif_obs  <- mean(y[es_ss]) - mean(y[!es_ss])
p_perm   <- mean(abs(dif_perm) >= abs(dif_obs) - 1e-9)
p_min    <- 1 / ncol(repartos)

par(mar = c(4.5, 4.8, 3, 1))
plot(dif_perm, rep(0, length(dif_perm)), pch = 21, bg = "grey85", cex = 2.2, yaxt = "n",
     ylab = "", xlab = "Diferencia (sin sol pleno - sol) en cm²",
     main = paste(ncol(repartos), "repartos posibles"))
points(dif_obs, 0, pch = 21, bg = col_expo["sombra"], cex = 2.6)
abline(v = 0, lty = 3)
legend("topright", legend = "reparto observado", pch = 21, pt.bg = col_expo["sombra"], bty = "n", pt.cex = 1.6)

Hay 10 repartos posibles. La diferencia observada entre los promedios de árbol es de -53,2 cm² (sin sol pleno − sol) y el valor de p exacto es 0,200. El valor más pequeño que cualquier resultado podría dar con 3 y 2 árboles es 0,100: con 5 árboles el diseño no puede dar resultados “significativos” al 5 %, sean cuales sean los datos.

5.5.2 Lo que habría pasado ignorando el árbol

dg$sin_sol <- dg$exposicion != "sol"
t_hojas <- t.test(area_cm2 ~ sin_sol, data = dg)           # trata las hojas como independientes
p_hojas <- t_hojas$p.value
dif_hojas <- unname(t_hojas$estimate[2] - t_hojas$estimate[1])

comparacion_g <- data.frame(
  Análisis = c("Hojas como datos independientes (incorrecto)", "Un promedio por árbol (permutación exacta)"),
  n_sol = c(sum(!dg$sin_sol), sum(!es_ss)), n_sin_sol = c(sum(dg$sin_sol), n_ss),
  Diferencia = c(fmt(dif_hojas), fmt(dif_obs)), p = c(fmt_p(p_hojas), fmt_p(p_perm)))
tabla(comparacion_g, col.names = c("Análisis", "n sol", "n sin sol pleno", "Diferencia (cm²)", "p"), align = "lrrrr")
Análisis n sol n sin sol pleno Diferencia (cm²) p
Hojas como datos independientes (incorrecto) 20 30 -53,2 0,010
Un promedio por árbol (permutación exacta) 2 3 -53,2 0,200

Tratar las hojas como independientes multiplica por 10 el tamaño de la muestra (50 hojas en lugar de 5 árboles) y cambia el p de 0,200 a 0,010. Esa precisión es falsa: se llama pseudorreplicación. Las hojas de un árbol son 10 mediciones de lo mismo, no 10 árboles.

5.5.3 ¿Depende el resultado de un solo árbol?

Con 5 árboles, quitar uno cambia mucho el panorama. Esta tabla repite la diferencia entre sol y sin sol pleno quitando cada árbol por turno:

loo <- do.call(rbind, lapply(seq_len(nrow(medias_g)), function(i) {
  d <- medias_g[-i, ]
  data.frame(sin_el_arbol = medias_g$id_arbol[i], exposicion = as.character(medias_g$exposicion[i]),
             n_sol = sum(!d$sin_sol), n_sin_sol = sum(d$sin_sol),
             dif = if (any(!d$sin_sol) && any(d$sin_sol))
                     mean(d$area_cm2[d$sin_sol]) - mean(d$area_cm2[!d$sin_sol]) else NA_real_)
}))
tabla(loo, row.names = FALSE, digits = 1,
      col.names = c("Árbol que se quita", "Su exposición", "Árboles de sol", "Árboles sin sol pleno", "Diferencia (sin sol pleno − sol), cm²"),
      caption = "Sensibilidad de la diferencia al quitar un árbol.")
Sensibilidad de la diferencia al quitar un árbol.
Árbol que se quita Su exposición Árboles de sol Árboles sin sol pleno Diferencia (sin sol pleno − sol), cm²
G1_T4 sol 1 3 -100,7
G1_T5 sol 1 3 -5,7
G1_T1 semi 2 2 -46,3
G1_T3 semi 2 2 -60,0
G1_T2 sombra 2 2 -53,4
dif_sin_max <- loo$dif[loo$sin_el_arbol == arbol_max]

Sin G1_T5 la diferencia pasa de -53,2 a -5,7 cm²: casi desaparece. Y si se quita G1_T4 o G1_T5, queda un solo árbol de sol y la comparación depende de un único punto. El resultado entero se apoya en G1_T5.

6 Diseño experimental, ANOVA y comparaciones post-ANOVA

Hasta aquí se comparó con promedios por árbol y una prueba de permutación. El análisis de varianza (ANOVA) permite usar la estructura completa del diseño. En este informe se usan dos modelos, cada uno para una pregunta distinta:

Preguntas y herramientas.
Pregunta Herramienta Después del ANOVA
¿Difieren entre sí los árboles del grupo? ANOVA de un factor (árbol) Tukey HSD: ¿cuáles árboles?
¿Hay efecto de la exposición respetando que las hojas están dentro de árboles? ANOVA anidado (árbol dentro de exposición) Componentes de varianza

6.1 El diseño experimental

La actividad es un estudio observacional con estructura jerárquica. Cada elemento del diseño tiene un papel:

  • Factor de interés (fijo): exposición, con 3 niveles registrados (sol, semisombra, sombra).
  • Unidad experimental: el árbol, porque la exposición es una propiedad del árbol.
  • Unidad de muestreo (submuestra): la hoja. Las 10 hojas de un árbol miden al árbol; no son réplicas de la exposición.
  • Bloque (el grupo): no se puede usar aquí, porque con un solo grupo no hay con qué comparar; el ANOVA de bloques del ejemplo de la clase requiere la base conjunta de todos los grupos.

Con esos elementos, el modelo es

log(área) ~ exposición + árbol(exposición) + error de hoja

El término árbol(exposición) es el error experimental contra el que se prueba la exposición. Si se ignorara y se usara el error de las hojas, se caería en la pseudorreplicación de la sección anterior. El diseño está desbalanceado y es muy pequeño: 2 árboles de sol, 2 de semisombra y 1 de sombra.

¿Por qué el logaritmo del área? El área es positiva y las hojas grandes varían más que las pequeñas. Con el logaritmo, la variabilidad se parece más entre árboles y los efectos se interpretan como razones (por ejemplo, “una hoja es 1,5 veces mayor”).

por_arbol     <- aggregate(area_cm2 ~ id_arbol, data = dg, FUN = function(x) c(media = mean(x), de = sd(x)))
por_arbol_log <- aggregate(log(area_cm2) ~ id_arbol, data = dg, FUN = function(x) c(media = mean(x), de = sd(x)))
r_orig <- cor(por_arbol$area_cm2[, "media"], por_arbol$area_cm2[, "de"])
r_log  <- cor(por_arbol_log[[2]][, "media"], por_arbol_log[[2]][, "de"])
c(escala_original = r_orig, escala_log = r_log)
## escala_original      escala_log 
##       0,9874797       0,7911316

Entre los 5 árboles, la correlación entre la desviación estándar y la media es 0,99 en la escala original y 0,79 con el logaritmo. (Con solo 5 puntos esta correlación es una pista, no una prueba.)

6.2 ANOVA de un factor: ¿difieren los árboles del grupo?

Se compara el log del área de las hojas entre los 5 árboles del grupo. Aquí las hojas sí son réplicas (de cada árbol), y la pregunta es por esos árboles concretos, no por la exposición.

dg$id_arbol <- factor(dg$id_arbol)
m1 <- lm(log(area_cm2) ~ id_arbol, data = dg)
a1 <- tabla_anova(m1)
a1$Fuente <- c("Entre árboles", "Dentro de árboles (hojas)")
tabla(formato_anova(a1), row.names = FALSE, align = "lrrrrr",
      caption = "ANOVA de un factor sobre log(área), hojas del grupo.")
ANOVA de un factor sobre log(área), hojas del grupo.
Fuente gl SC CM F p
Entre árboles 4 7,693 1,923 9,81 < 0,001
Dentro de árboles (hojas) 45 8,821 0,196

Hay diferencias entre árboles: F(4; 45) = 9,8, p < 0,001. El ANOVA solo dice que al menos un árbol difiere; para saber cuáles hay que hacer comparaciones múltiples.

Supuestos. Los residuos deberían ser aproximadamente normales y tener varianza parecida entre árboles.

par(mfrow = c(1, 2), mar = c(4.5, 4.8, 3, 1))
plot(fitted(m1), resid(m1), pch = 16, col = adjustcolor("black", 0.5), las = 1,
     xlab = "Valor ajustado (log área)", ylab = "Residuo", main = "Residuos vs ajustados")
abline(h = 0, lty = 2)
qqnorm(resid(m1), pch = 16, col = adjustcolor("black", 0.5), las = 1, main = "Normalidad de los residuos",
       xlab = "Cuantiles teóricos", ylab = "Cuantiles de los residuos")
qqline(resid(m1))
Diagnóstico del ANOVA de un factor: residuos frente a valores ajustados (izquierda) y gráfico cuantil-cuantil (derecha).

Diagnóstico del ANOVA de un factor: residuos frente a valores ajustados (izquierda) y gráfico cuantil-cuantil (derecha).

par(mfrow = c(1, 1))

shap1 <- shapiro.test(resid(m1))
flig1 <- fligner.test(log(area_cm2) ~ id_arbol, data = dg)
kw1   <- kruskal.test(log(area_cm2) ~ id_arbol, data = dg)     # alternativa sin supuesto de normalidad
ow1   <- oneway.test(log(area_cm2) ~ id_arbol, data = dg)      # ANOVA de Welch: no exige varianzas iguales
c(shapiro_p = shap1$p.value, fligner_p = flig1$p.value, welch_p = ow1$p.value, kruskal_p = kw1$p.value)
##    shapiro_p    fligner_p      welch_p    kruskal_p 
## 0,5302701978 0,0542976225 0,0009155429 0,0002241962

La prueba de Shapiro-Wilk (normalidad de los residuos) da p = 0,530 y la de Fligner-Killeen (igualdad de varianzas entre árboles) p = 0,054. Un p grande significa que no hay evidencia contra el supuesto; un p pequeño, que sí la hay. Aquí no hay evidencia contra la normalidad de los residuos, pero la igualdad de varianzas queda en el límite: en escala logarítmica los árboles G1_T3 y G1_T5 varían más que G1_T1 y G1_T4. Por eso se comprueba la conclusión con dos alternativas que no exigen esos supuestos: el ANOVA de Welch (no pide varianzas iguales; p < 0,001) y Kruskal-Wallis (no pide normalidad; p < 0,001). Las tres pruebas coinciden en que hay diferencias entre árboles.

6.3 Post-ANOVA: ¿qué árboles difieren? (Tukey HSD)

Con 5 árboles hay 10 comparaciones por pares. Si se hicieran 10 pruebas t sueltas al 5 %, la probabilidad de al menos un falso positivo sería de alrededor del 40 %. La prueba de Tukey HSD compara todos los pares y controla ese error.

tk <- TukeyHSD(aov(log(area_cm2) ~ id_arbol, data = dg))$id_arbol
tk_tab <- data.frame(comparacion = gsub("-", " / ", rownames(tk)),
                     razon = exp(tk[, "diff"]), li = exp(tk[, "lwr"]), ls = exp(tk[, "upr"]),
                     p_aj = tk[, "p adj"], stringsAsFactors = FALSE)
tk_tab <- tk_tab[order(tk_tab$p_aj), ]

# ¿Cuántos pares que difieren son de árboles con la MISMA exposición?
pares_ids  <- strsplit(tk_tab$comparacion, " / ")
misma_expo <- sapply(pares_ids, function(x) length(unique(resumen$exposicion[match(x, resumen$arbol)])) == 1)
n_sig       <- sum(tk_tab$p_aj < 0.05)
n_misma_sig <- sum(misma_expo & tk_tab$p_aj < 0.05)

tabla_tk <- data.frame(tk_tab$comparacion, fmt(tk_tab$razon, 2),
                       paste0("[", fmt(tk_tab$li, 2), "; ", fmt(tk_tab$ls, 2), "]"),
                       fmt_p(tk_tab$p_aj), ifelse(tk_tab$p_aj < 0.05, "sí", "no"),
                       ifelse(misma_expo, "misma", "distinta"))
tabla(tabla_tk, col.names = c("Comparación (A / B)", "Razón A / B", "IC 95 % (Tukey)", "p ajustado", "Difieren (5 %)", "Exposición"),
      align = "lrrrrc", caption = "Comparaciones de Tukey entre árboles (escala logarítmica, mostradas como razones).")
Comparaciones de Tukey entre árboles (escala logarítmica, mostradas como razones).
Comparación (A / B) Razón A / B IC 95 % (Tukey) p ajustado Difieren (5 %) Exposición
G1_T5 / G1_T1 3,25 [1,85; 5,70] < 0,001 sí distinta
G1_T5 / G1_T2 2,49 [1,42; 4,37] < 0,001 sí distinta
G1_T5 / G1_T4 2,20 [1,25; 3,86] 0,002 sí misma
G1_T5 / G1_T3 2,08 [1,18; 3,65] 0,005 sí distinta
G1_T3 / G1_T1 1,56 [0,89; 2,74] 0,181 no misma
G1_T4 / G1_T1 1,48 [0,84; 2,59] 0,299 no distinta
G1_T2 / G1_T1 1,30 [0,74; 2,29] 0,672 no distinta
G1_T3 / G1_T2 1,20 [0,68; 2,10] 0,890 no distinta
G1_T4 / G1_T2 1,13 [0,65; 1,99] 0,969 no distinta
G1_T4 / G1_T3 0,95 [0,54; 1,66] 0,999 no distinta
k <- nrow(tk_tab)
par(mar = c(4.5, 9, 3, 1))
plot(tk_tab$razon, seq_len(k), log = "x", pch = 16, yaxt = "n", ylab = "",
     xlim = range(c(tk_tab$li, tk_tab$ls)), cex = 1.2,
     col = ifelse(tk_tab$p_aj < 0.05, "#D55E00", "grey40"),
     xlab = "Razón entre árboles (escala logarítmica)", main = "Comparaciones por pares")
segments(tk_tab$li, seq_len(k), tk_tab$ls, seq_len(k), lwd = 2,
         col = ifelse(tk_tab$p_aj < 0.05, "#D55E00", "grey40"))
axis(2, at = seq_len(k), labels = tk_tab$comparacion, las = 1, cex.axis = 0.8)
abline(v = 1, lty = 2)
Razón entre árboles con su intervalo de Tukey. Si el intervalo cruza 1, no hay diferencia detectable.

Razón entre árboles con su intervalo de Tukey. Si el intervalo cruza 1, no hay diferencia detectable.

En 4 de las 10 comparaciones los árboles difieren de forma detectable (en naranja), y todas incluyen a G1_T5, cuyas hojas son entre 2,1 y 3,2 veces mayores que las de cada uno de los otros cuatro. Entre esos otros cuatro no se detectó ninguna diferencia (todos los p ajustados son ≥ 0,18), aunque sean de exposiciones distintas.

Al cruzar esto con la exposición de cada árbol, 1 de las 4 diferencias es entre árboles de la misma exposición (G1_T5 frente a G1_T4, ambos de sol). Es decir, las hojas grandes de G1_T5 no se explican solo por estar al sol: G1_T4, que también es de sol, tiene hojas del mismo tamaño que las de los árboles de semisombra y sombra. Esto ilustra por qué el árbol, y no solo la exposición, es lo que hay que mirar.

6.4 ANOVA anidado: el efecto de la exposición con el error correcto

Las hojas están dentro de árboles y los árboles dentro de una exposición: un diseño anidado. El efecto de la exposición se prueba contra la variación entre árboles, no contra la variación entre hojas.

dg$arbol <- factor(dg$id_arbol)
dg$expo2 <- factor(dg$exposicion, levels = presentes)

m_anid <- lm(log(area_cm2) ~ expo2 + arbol, data = dg)    # el árbol está dentro de la exposición (cada árbol tiene una sola)
a_an   <- tabla_anova(m_anid)

# Cada fuente se prueba contra el cuadrado medio que corresponde a su unidad
F_expo  <- a_an$CM[1] / a_an$CM[2];  p_expo  <- pf(F_expo,  a_an$gl[1], a_an$gl[2], lower.tail = FALSE)
F_arbol <- a_an$CM[2] / a_an$CM[3];  p_arbol <- pf(F_arbol, a_an$gl[2], a_an$gl[3], lower.tail = FALSE)

tab_an <- data.frame(
  Fuente = c("Exposición", "Árbol (dentro de exposición)", "Hojas (residual)"),
  gl = a_an$gl, SC = fmt(a_an$SC, 3), CM = fmt(a_an$CM, 3),
  F = c(fmt(F_expo, 2), fmt(F_arbol, 2), ""),
  p = c(fmt_p(p_expo), fmt_p(p_arbol), ""),
  `Se prueba contra` = c("Árbol (dentro de exposición)", "Hojas (residual)", ""),
  check.names = FALSE)
tabla(tab_an, row.names = FALSE, align = "lrrrrrl",
      caption = "ANOVA anidado sobre log(área): la exposición se prueba con los árboles como error.")
ANOVA anidado sobre log(área): la exposición se prueba con los árboles como error.
Fuente gl SC CM F p Se prueba contra
Exposición 2 3,599 1,799 0,88 0,532 Árbol (dentro de exposición)
Árbol (dentro de exposición) 2 4,095 2,047 10,44 < 0,001 Hojas (residual)
Hojas (residual) 45 8,821 0,196
# Componentes de varianza (10 hojas por árbol)
n_h        <- 10
s2_hoja    <- a_an$CM[3]
s2_arbol   <- max((a_an$CM[2] - a_an$CM[3]) / n_h, 0)
pct_arbol  <- 100 * s2_arbol / (s2_arbol + s2_hoja)
r2_arbol   <- summary(lm(log(area_cm2) ~ arbol, data = dg))$r.squared

La exposición no tiene un efecto detectable sobre el log del área: F(2; 2) = 0,88, p = 0,532. Hay que leerlo con cuidado: el error contra el que se prueba tiene solo 2 grados de libertad (5 árboles menos 3 exposiciones), así que la prueba tiene muy poca potencia y solo detectaría diferencias enormes. No demuestra que la exposición no importe; demuestra que con este diseño no se puede saber.

Lo que sí es claro es que los árboles dentro de una misma exposición difieren entre sí (F = 10,4, p < 0,001). Según los componentes de varianza, cerca del 49 % de la variación del log del área está entre árboles de una misma exposición, y la identidad del árbol por sí sola explica el 47 % de la variación total. Una parte muy grande de la variación (cerca de la mitad) está entre árboles y no entre hojas: medir más hojas por árbol ayudaría poco para comparar exposiciones; haría falta medir más árboles.

7 Discusión

Qué encontramos. Con el árbol como unidad de análisis, los árboles de sol tuvieron hojas de mayor área media (110,6 cm², 2 árboles) que los de semisombra (57,2 cm², 2 árboles) y que el de sombra (57,8 cm², 1 árbol). Es el sentido contrario al de la hipótesis, que esperaba hojas más grandes a la sombra. Pero este resultado es frágil, por tres razones:

  1. La diferencia entre sol y sin sol pleno (-53,2 cm²) tiene un p exacto de 0,200, y con 5 árboles el menor valor posible es 0,1: el diseño no permite obtener evidencia estadística de una diferencia.
  2. Depende de un solo árbol: sin G1_T5 la diferencia pasa de -53,2 a -5,7 cm².
  3. El ANOVA anidado, que prueba la exposición contra la variación entre árboles, da p = 0,532.

¿Coincide con la hipótesis inicial? No: los datos no respaldan que las hojas de sombra sean más grandes, pero tampoco la refutan. Con un solo árbol de sombra y dos de sol, el diseño no tenía capacidad para detectar ni para descartar esa diferencia. Lo que sí se ve con claridad es que los árboles difieren entre sí (ANOVA, Welch y Kruskal-Wallis coinciden) y que esa diferencia no sigue la exposición: G1_T5 (sol) tiene hojas 2 a 3 veces mayores que las de los demás, mientras que G1_T4 (también sol) es indistinguible de los árboles de semisombra y sombra.

Posibles explicaciones ambientales (son hipótesis para explicar el patrón, no algo que estos datos demuestren):

  • Luz. A la sombra, una lámina mayor intercepta más luz; a pleno sol las hojas, más pequeñas y gruesas, ya tienen luz de sobra. Este patrón clásico no aparece en nuestros datos, y no se puede saber si es porque no existe en estas especies o porque el diseño no alcanza a verlo.
  • Temperatura. Una hoja pequeña tiene una capa de aire más delgada en su borde y se enfría con más facilidad, lo que es una ventaja a pleno sol. Una hoja grande a pleno sol necesitaría compensarlo con otros mecanismos, como una mayor transpiración.
  • Agua. A pleno sol la demanda de evaporación es mayor y las hojas pequeñas pierden menos agua; pero si el agua no es limitante, un árbol de sol puede sostener hojas grandes. El muestreo fue en octubre, un mes que suele ser lluvioso en Cali (conviene confirmarlo con la estación meteorológica más cercana), y no se midió la humedad del suelo.
  • Suelo pavimentado. En las fotos se ven varios árboles junto a vías, andenes o concreto (por ejemplo G1_T1, G1_T3 y G1_T4), lo que puede limitar el espacio de las raíces y el agua que llega a ellas. No se registró cuánto suelo pavimentado rodea a cada árbol, así que no se puede evaluar.

Qué más pudo influir (explicaciones alternativas que hay que descartar antes de atribuirlo todo a la luz):

  • El árbol, y no solo su exposición. La identidad del árbol explica cerca de la mitad de la variación del área. Si G1_T5 pertenece a una especie o morfotipo de hoja grande, la diferencia sería de composición de especies y no de respuesta a la luz. En la plantilla no se registró la especie, y las fotos muestran árboles de porte y follaje distintos, lo que hace plausible que sean morfotipos diferentes (no se identificaron).
  • Edad y tamaño del árbol, y la calidad del suelo donde crece. Tampoco se registraron.
  • Cómo se eligieron las hojas. Las hojas accesibles a mano son las de ramas bajas, y suelen ser más sombreadas y de otro tamaño que las de la parte alta de la copa. Escoger “las más bonitas” también sesga el área.
  • La clasificación en sol, semisombra y sombra la hizo el grupo a ojo y no se midió la luz. La categoría “semi” no estaba en la guía y no tiene una definición escrita. En la foto de G1_T4 se ven otros árboles con copas cercanas: conviene confirmar con la vista de satélite del mapa que de verdad recibe luz directa la mayor parte del día. Además, las fotos muestran un día nublado.
  • Error de medición. El área depende del trazo, de la foto y de los lados del marco que se escriben en el script. Un error en el marco cambia el área de todas las hojas de esa hoja de papel, y las hojas más grandes de G1_T5 son justamente las que más conviene revisar (sección 5.3).

Resultados frente a hipótesis. Con solo 5 árboles, estas son las conclusiones que se pueden sostener y las que no:

Es un resultado (lo que los datos muestran) Es una hipótesis (lo que habría que probar)
En estos 5 árboles del campus, los de sol tuvieron la mayor área foliar media, pero solo por un árbol (G1_T5). Que las hojas de sombra son más grandes que las de sol en los árboles del campus (o lo contrario).
Los árboles difieren entre sí en el tamaño de sus hojas, y no de forma ordenada por exposición. Que las diferencias entre árboles se deben a la especie, a la edad o al suelo, y no a la luz.
El diseño tiene 2 árboles de sol, 2 de semisombra y 1 de sombra: no permite probar la hipótesis. Que la respuesta a la luz es igual en todas las especies y en cualquier época del año.

Qué haría falta para responder la pregunta. Más árboles por categoría (al menos 2 de sombra, como pide la guía, y mejor muchos más: la base conjunta de todos los grupos sería el paso natural); registrar la especie o morfotipo; medir la luz en lugar de estimarla; y verificar las hojas de G1_T5.

8 Limitaciones

  • Pocos árboles. Son 5 árboles en total, con 2 de sol, 2 de semisombra y 1 de sombra. Con una sola réplica de sombra no se puede estimar la variación dentro de esa categoría, y la comparación sol–sombra de la guía no puede hacerse. La guía pide al menos 2 árboles de sombra y no se cumplió.
  • Un solo grupo. Las estimaciones de un grupo con 5 árboles son muy inestables: quitar un árbol (G1_T5) cambia el resultado por completo.
  • No hubo asignación al azar. Los árboles ya estaban en el sol o en la sombra: es un estudio observacional. Se puede hablar de asociación, no de que la luz cause hojas más grandes o más pequeñas.
  • La exposición es una categoría gruesa, decidida a ojo. No se midió la luz ni se registró la especie, y la categoría “semi” no estaba en la guía ni tiene definición escrita.
  • Hojas elegidas por personas. Aunque la guía pide repartirlas por toda la copa, no se eligieron al azar, y las hojas altas son difíciles de alcanzar.
  • Error de medición. El área depende de la foto, del trazo y del script (por ejemplo, de los lados del marco que se escriben en el script de Colab). Las hojas más grandes de G1_T5 conviene revisarlas.
  • Un solo momento. Las hojas se midieron el 22/10/2026, un día nublado; el área puede cambiar con la época del año y con el estado hídrico del árbol.
  • Supuestos de las pruebas. Con 10 hojas por árbol y 5 árboles, los supuestos del ANOVA se comprueban con poca potencia. La igualdad de varianzas queda en el límite, por lo que se verificó la conclusión con Welch y Kruskal-Wallis.
  • Potencia del ANOVA anidado. El error para probar la exposición tiene solo 2 grados de libertad, así que un resultado no significativo no es evidencia de que no haya efecto.
  • Comparaciones múltiples. Las 10 comparaciones entre árboles se ajustaron con Tukey; aun así, son exploratorias.
  • Sin base conjunta. No se pudo comprobar si otro grupo midió alguno de estos árboles ni usar el grupo como bloque, porque este informe solo usa los datos del grupo 1.

9 Información de la sesión

sessionInfo()
## R version 4.5.2 (2025-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8  LC_CTYPE=Spanish_Colombia.utf8   
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C                     
## [5] LC_TIME=Spanish_Colombia.utf8    
## 
## time zone: America/Bogota
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] leaflet_2.2.3
## 
## loaded via a namespace (and not attached):
##  [1] vctrs_0.7.1             cli_3.6.5               knitr_1.51             
##  [4] rlang_1.1.7             xfun_0.56               otel_0.2.0             
##  [7] jsonlite_2.0.0          glue_1.8.0              htmltools_0.5.9        
## [10] sass_0.4.10             readxl_1.4.5            rmarkdown_2.30         
## [13] cellranger_1.1.0        crosstalk_1.2.2         evaluate_1.0.5         
## [16] jquerylib_0.1.4         tibble_3.3.1            fastmap_1.2.0          
## [19] yaml_2.3.12             lifecycle_1.0.5         compiler_4.5.2         
## [22] htmlwidgets_1.6.4       pkgconfig_2.0.3         rstudioapi_0.18.0      
## [25] leaflet.providers_2.0.0 digest_0.6.39           R6_2.6.1               
## [28] pillar_1.11.1           magrittr_2.0.4          bslib_0.10.0           
## [31] tools_4.5.2             mime_0.13               cachem_1.1.0