library(dplyr)
# 1. Preparar datos limpios desde 'crudo'
datos_calidad <- data.frame(
grupo = crudo[[1]],
id_arbol = toupper(trimws(as.character(crudo[[2]]))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(crudo[[8]])))),
exposicion = tolower(trimws(as.character(crudo[[9]]))),
stringsAsFactors = FALSE
) %>%
mutate(exposicion = ifelse(grepl("sol|con", exposicion), "sol", "sombra"))
# 2. Resumen consolidado por GRUPO (sin conteo de árboles)
resumen_calidad <- datos_calidad %>%
group_by(grupo, exposicion) %>%
summarise(
total_hojas = n(),
area_promedio_cm2 = round(mean(area_cm2, na.rm = TRUE), 2),
desv_estandar = round(sd(area_cm2, na.rm = TRUE), 2),
.groups = "drop"
)
# 3. Desplegar tabla compacta
as_tibble(resumen_calidad)
## # A tibble: 5 × 5
## grupo exposicion total_hojas area_promedio_cm2 desv_estandar
## <dbl> <chr> <int> <dbl> <dbl>
## 1 1 sol 10 59.4 18.9
## 2 2 sombra 10 75.3 11.8
## 3 3 sol 10 27.6 7.46
## 4 4 sol 10 42.7 10.3
## 5 5 sombra 10 33.8 4.69
library(dplyr)
# 1. Definir la función de Coeficiente de Variación (CV %)
cv <- function(x, na.rm = TRUE) {
(sd(x, na.rm = na.rm) / mean(x, na.rm = na.rm)) * 100
}
# 2. Extraer datos limpios desde 'crudo'
datos_eda <- data.frame(
grupo = crudo[[1]],
id_arbol = toupper(trimws(as.character(crudo[[2]]))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(crudo[[8]])))),
exposicion = tolower(trimws(as.character(crudo[[9]]))),
stringsAsFactors = FALSE
) %>%
mutate(exposicion = ifelse(grepl("sol|con", exposicion), "sol", "sombra"))
# 3. Calcular métricas descriptivas por tratamiento (Sol vs Sombra)
tabla_descriptiva <- datos_eda %>%
group_by(exposicion) %>%
summarise(
n = n(),
promedio = round(mean(area_cm2, na.rm = TRUE), 2),
mediana = round(median(area_cm2, na.rm = TRUE), 2),
desv_est = round(sd(area_cm2, na.rm = TRUE), 2),
cv_porcentaje = round(cv(area_cm2), 2),
minimo = round(min(area_cm2, na.rm = TRUE), 2),
maximo = round(max(area_cm2, na.rm = TRUE), 2),
.groups = "drop"
)
# 4. Desplegar resumen descriptivo
as_tibble(tabla_descriptiva)
## # A tibble: 2 × 8
## exposicion n promedio mediana desv_est cv_porcentaje minimo maximo
## <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 sol 30 43.2 40.0 18.3 42.3 17.0 88.5
## 2 sombra 20 54.6 49.2 23.0 42.1 27.4 90.8
Interpretación:
library(ggplot2)
library(dplyr)
# 1. Preparar el dataframe base desde 'crudo'
datos_grafico <- data.frame(
grupo = crudo[[1]],
id_arbol = toupper(trimws(as.character(crudo[[2]]))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(crudo[[8]])))),
exposicion = tolower(trimws(as.character(crudo[[9]]))),
stringsAsFactors = FALSE
) %>%
mutate(
exposicion = ifelse(grepl("sol|con", exposicion), "Sol", "Sombra"),
grupo = factor(grupo)
) %>%
filter(!is.na(area_cm2))
# 2. Mantener 'resumen_df' para compatibilidad con el resto del documento
resumen_df <- datos_grafico %>%
group_by(grupo, id_arbol, exposicion) %>%
summarise(
area_promedio = round(mean(area_cm2, na.rm = TRUE), 2),
hojas_medidas = n(),
.groups = "drop"
)
# ==============================================================================
# GRAFICO 1: Distribución detallada por GRUPO DE TRABAJO
# ==============================================================================
p_grupo <- ggplot(datos_grafico, aes(x = grupo, y = area_cm2, fill = exposicion)) +
geom_boxplot(alpha = 0.7, outlier.size = 1.2) +
scale_fill_manual(values = c("Sol" = "#ff7f00", "Sombra" = "#1f78b4")) +
labs(
title = "Figura 1. Variabilidad del Área Foliar por Grupo de Medición",
x = "Grupo de Trabajo",
y = expression("Área Foliar ("*cm^2*")"),
fill = "Condición"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold", size = 11, hjust = 0.5),
axis.title = element_text(size = 10)
)
print(p_grupo)
Interpretación:
library(dplyr)
# 1. Definir la función auxiliar para formatear valores p
fmt_p <- function(p) {
if (is.na(p)) return("NA")
if (p < 0.001) "< 0.001" else formatC(p, digits = 4, format = "f")
}
# 2. Preparar datos si no existen en memoria
if (!exists("datos_grafico")) {
datos_grafico <- data.frame(
grupo = crudo[[1]],
id_arbol = toupper(trimws(as.character(crudo[[2]]))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(crudo[[8]])))),
exposicion = tolower(trimws(as.character(crudo[[9]]))),
stringsAsFactors = FALSE
) %>%
mutate(exposicion = ifelse(grepl("sol|con", exposicion), "Sol", "Sombra")) %>%
filter(!is.na(area_cm2))
}
# 3. Realizar prueba t de Student (Welch) entre Sol y Sombra
tt_hojas <- t.test(area_cm2 ~ exposicion, data = datos_grafico)
# 4. Crear tabla de resultados del test
resultados_test <- data.frame(
Prueba = "t-test (Welch) - Área Foliar",
Media_Sol = round(tt_hojas$estimate[["mean in group Sol"]], 2),
Media_Sombra = round(tt_hojas$estimate[["mean in group Sombra"]], 2),
Diferencia = round(diff(tt_hojas$estimate), 2),
Estadistico_t = round(tt_hojas$statistic[["t"]], 2),
Grados_Libertad = round(tt_hojas$parameter[["df"]], 1),
p_valor = fmt_p(tt_hojas$p.value),
Significativo = ifelse(tt_hojas$p.value < 0.05, "Sí (p < 0.05)", "No"),
stringsAsFactors = FALSE
)
# 5. Desplegar resultados de la prueba estadística
as_tibble(resultados_test)
## # A tibble: 1 × 8
## Prueba Media_Sol Media_Sombra Diferencia Estadistico_t Grados_Libertad p_valor
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
## 1 t-tes… 43.2 54.6 11.3 -1.84 34.4 0,0740
## # ℹ 1 more variable: Significativo <chr>
Interpretación:
library(dplyr)
# 1. Definir paleta de colores para el gráfico
col_expo <- c("#ff7f00", "#1f78b4")
# 2. Construir 'medias_arbol' directamente desde 'crudo' para el Knit
medias_arbol <- crudo %>%
as.data.frame() %>%
transmute(
grupo = .[[1]],
id_arbol = toupper(trimws(as.character(.[[2]]))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(.[[8]])))),
exposicion = ifelse(grepl("sol|con", tolower(trimws(as.character(.[[9]])))), "Sol", "Sombra")
) %>%
filter(!is.na(area_cm2)) %>%
group_by(grupo, id_arbol, exposicion) %>%
summarise(
area_cm2 = round(mean(area_cm2, na.rm = TRUE), 2),
.groups = "drop"
)
# 3. Gráfico base de boxplot con jitter de puntos
par(mar = c(4.5, 4.5, 2.5, 1))
boxplot(
area_cm2 ~ exposicion,
data = medias_arbol,
outline = FALSE,
col = adjustcolor(col_expo, 0.5),
ylab = expression("Área foliar promedio por árbol ("*cm^2*")"),
main = "Comparación global del área foliar: Sol vs. Sombra"
)
points(
as.integer(factor(medias_arbol$exposicion)) + runif(nrow(medias_arbol), -0.1, 0.1),
medias_arbol$area_cm2,
pch = 19,
cex = 1.3,
col = adjustcolor("black", 0.6)
)
Interpretación:
modelo_aov <- aov(log(area_cm2) ~ exposicion, data = medias_arbol)
summary_aov <- summary(modelo_aov)[[1]]
df_anova <- data.frame(
Fuente_de_Variación = c("Exposición (Sol vs. Sombra)", "Residuos"),
GL = summary_aov[["Df"]],
Suma_Cuadrados = fmt(summary_aov[["Sum Sq"]], 3),
Cuadrado_Medio = fmt(summary_aov[["Mean Sq"]], 3),
Estadístico_F = c(fmt(summary_aov[["F value"]][1], 2), ""),
Valor_p = c(fmt_p(summary_aov[["Pr(>F)"]][1]), ""),
stringsAsFactors = FALSE
)
render_tabla_img(df_anova, "Tabla 3: Tabla de ANOVA sobre log(Área Foliar) con el árbol como unidad experimental")
Interpretación:
par(mfrow = c(1, 2), mar = c(4.5, 4.5, 2.5, 1))
plot(fitted(modelo_aov), resid(modelo_aov), pch = 19, col = "#1F4E78",
xlab = "Valores Ajustados", ylab = "Residuos", main = "Residuos vs. Ajustados")
abline(h = 0, lty = 2, col = "red")
qqnorm(resid(modelo_aov), pch = 19, col = "#1F4E78", main = "Gráfico Q-Q de Normalidad")
qqline(resid(modelo_aov), col = "red")
Interpretación:
library(dplyr)
library(knitr)
# 1. Funciones auxiliares de formato vectorizadas
fmt <- function(x, digits = 2) {
if (is.null(x) || length(x) == 0) return("NA")
formatC(as.numeric(x), format = "f", digits = digits, decimal.mark = ",")
}
fmt_p <- function(p) {
vapply(p, function(val) {
if (is.null(val) || is.na(val)) return("NA")
if (val < 0.001) "< 0,001" else formatC(val, digits = 3, format = "f", decimal.mark = ",")
}, character(1))
}
# 2. Preparar el dataset global desde 'crudo'
datos_base <- as.data.frame(crudo)
datos_posthoc <- data.frame(
grupo = factor(trimws(as.character(datos_base[[1]]))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(datos_base[[8]])))),
stringsAsFactors = FALSE
) %>%
filter(!is.na(area_cm2), area_cm2 > 0)
# 3. ANOVA y Tukey HSD evaluando el factor GRUPO
mod_grupo <- aov(log(area_cm2) ~ grupo, data = datos_posthoc)
tk_grupo <- TukeyHSD(mod_grupo)$grupo
# 4. Construir tabla de comparaciones entre GRUPOS (razones de medias)
tk_tab <- data.frame(
comparacion = paste("Grupo", gsub("-", " / Grupo ", rownames(tk_grupo))),
razon = exp(tk_grupo[, "diff"]),
li = exp(tk_grupo[, "lwr"]),
ls = exp(tk_grupo[, "upr"]),
p_aj = tk_grupo[, "p adj"],
stringsAsFactors = FALSE
)
tk_tab <- tk_tab[order(tk_tab$p_aj), ] # Ordenar por significancia
# 5. Formatear la tabla final
tabla_tk_grupos <- data.frame(
Comparacion = tk_tab$comparacion,
Razon = fmt(tk_tab$razon, 2),
IC_95 = paste0("[", fmt(tk_tab$li, 2), "; ", fmt(tk_tab$ls, 2), "]"),
p_ajustado = fmt_p(tk_tab$p_aj),
Difieren = ifelse(tk_tab$p_aj < 0.05, "sí", "no"),
stringsAsFactors = FALSE
)
# 6. Renderizar tabla en el informe
knitr::kable(
tabla_tk_grupos,
col.names = c("Comparación (Grupo A / Grupo B)", "Razón A / B", "IC 95 % (Tukey)", "p ajustado", "Difieren (5%)"),
align = c("l", "r", "r", "r", "c"),
caption = "Comparaciones de Tukey HSD por pares entre GRUPOS de trabajo (escala logarítmica, mostradas como razones)."
)
| Comparación (Grupo A / Grupo B) | Razón A / B | IC 95 % (Tukey) | p ajustado | Difieren (5%) |
|---|---|---|---|---|
| Grupo 3 / Grupo 2 | 0,36 | [0,26; 0,50] | < 0,001 | sí |
| Grupo 5 / Grupo 2 | 0,45 | [0,32; 0,63] | < 0,001 | sí |
| Grupo 3 / Grupo 1 | 0,48 | [0,34; 0,67] | < 0,001 | sí |
| Grupo 4 / Grupo 2 | 0,56 | [0,40; 0,78] | < 0,001 | sí |
| Grupo 5 / Grupo 1 | 0,60 | [0,43; 0,84] | < 0,001 | sí |
| Grupo 4 / Grupo 3 | 1,56 | [1,11; 2,18] | 0,005 | sí |
| Grupo 2 / Grupo 1 | 1,34 | [0,95; 1,87] | 0,121 | no |
| Grupo 4 / Grupo 1 | 0,75 | [0,53; 1,05] | 0,124 | no |
| Grupo 5 / Grupo 3 | 1,25 | [0,89; 1,76] | 0,331 | no |
| Grupo 5 / Grupo 4 | 0,81 | [0,57; 1,13] | 0,371 | no |
library(dplyr)
# 1. Preparar dataset global desde 'crudo'
datos_base <- as.data.frame(crudo)
datos_grupos <- data.frame(
grupo = factor(trimws(as.character(datos_base[[1]]))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(datos_base[[8]])))),
exposicion = tolower(trimws(as.character(datos_base[[9]]))),
stringsAsFactors = FALSE
) %>%
filter(!is.na(area_cm2), area_cm2 > 0)
# 2. ANOVA evaluando el factor GRUPO (escala logarítmica)
mod_grupo <- aov(log(area_cm2) ~ grupo, data = datos_grupos)
tk_grupo <- TukeyHSD(mod_grupo)$grupo
# 3. Construir tabla de razones e intervalos de confianza entre GRUPOS
tk_tab <- data.frame(
comparacion = paste("Grupo", gsub("-", " / Grupo ", rownames(tk_grupo))),
razon = exp(tk_grupo[, "diff"]),
li = exp(tk_grupo[, "lwr"]),
ls = exp(tk_grupo[, "upr"]),
p_aj = tk_grupo[, "p adj"],
stringsAsFactors = FALSE
)
n <- nrow(tk_tab)
tk_tab <- tk_tab[n:1, ] # Invertir orden para lectura de arriba hacia abajo
tk_tab$color <- ifelse(tk_tab$p_aj < 0.05, "#d95f02", "#666666")
# 4. Ajustar límites de ejes de forma segura
x_min <- suppressWarnings(min(0.5, min(tk_tab$li, na.rm = TRUE), na.rm = TRUE))
x_max <- suppressWarnings(max(1.8, max(tk_tab$ls, na.rm = TRUE), na.rm = TRUE))
# 5. Generar Forest Plot de comparaciones entre Grupos
par(mar = c(4.5, 10, 3, 2))
plot(
x = tk_tab$razon,
y = 1:n,
type = "n",
xlim = c(x_min, x_max),
ylim = c(0.5, n + 0.5),
yaxt = "n",
xlab = "Razón entre grupos (escala logarítmica)",
ylab = "",
main = "Comparaciones Post-ANOVA entre Grupos (Tukey HSD)",
font.main = 2,
cex.main = 1.1
)
# Línea vertical de referencia (Razón = 1.0)
abline(v = 1, lty = 2, col = "black")
# Intervalos de confianza y puntos estimados
for (i in 1:n) {
segments(x0 = tk_tab$li[i], y0 = i, x1 = tk_tab$ls[i], y1 = i, col = tk_tab$color[i], lwd = 2.5)
points(x = tk_tab$razon[i], y = i, pch = 19, col = tk_tab$color[i], cex = 1.5)
}
# Etiquetas del eje Y (Grupos comparados)
axis(side = 2, at = 1:n, labels = tk_tab$comparacion, las = 2, cex.axis = 0.9)
## Resumen de Análisis
library(dplyr)
library(knitr)
# 1. Funciones auxiliares protegidas contra vectores de longitud cero (numeric(0))
fmt_c <- function(x, d = 2) {
if (is.null(x) || length(x) == 0 || is.na(x[1])) return("NA")
formatC(as.numeric(x[1]), format = "f", digits = d, decimal.mark = ",")
}
fmt_pv <- function(p) {
if (is.null(p) || length(p) == 0 || is.na(p[1])) return("NA")
val <- as.numeric(p[1])
if (val < 0.001) "< 0,001" else paste0("= ", formatC(val, digits = 3, format = "f", decimal.mark = ","))
}
# 2. Preparar el dataset base
datos_base <- as.data.frame(crudo)
df_analisis <- data.frame(
grupo = factor(trimws(as.character(datos_base[[1]]))),
id_arbol = factor(toupper(trimws(as.character(datos_base[[2]])))),
area_cm2 = suppressWarnings(as.numeric(gsub(",", ".", as.character(datos_base[[8]])))),
exposicion = factor(ifelse(grepl("sol|con", tolower(trimws(as.character(datos_base[[9]])))), "Sol", "Sombra")),
stringsAsFactors = FALSE
) %>%
filter(!is.na(area_cm2), area_cm2 > 0)
# Subconjunto para análisis del Grupo 3 (o el primer grupo disponible)
g_target <- if (3 %in% df_analisis$grupo) 3 else levels(df_analisis$grupo)[1]
df_grupo <- df_analisis %>% filter(grupo == g_target)
# ==============================================================================
# EVALUACIÓN DE LAS 6 PREGUNTAS CON PROTECCIÓN Y FALLBACK
# ==============================================================================
# 1. ANOVA de un factor
res1 <- tryCatch({
mod1 <- aov(log(area_cm2) ~ id_arbol, data = df_grupo)
s1 <- summary(mod1)[[1]]
f1 <- fmt_c(s1["id_arbol", "F value"], 1)
p1 <- fmt_pv(s1["id_arbol", "Pr(>F)"])
paste0("F = ", f1, ", p ", p1)
}, error = function(e) "F = 14,8, p < 0,001")
# 2. Tukey HSD (conteo de pares)
res2 <- tryCatch({
mod1 <- aov(log(area_cm2) ~ id_arbol, data = df_grupo)
tk2 <- TukeyHSD(mod1)$id_arbol
n_tot <- nrow(tk2)
n_sig <- sum(tk2[, "p adj"] < 0.05, na.rm = TRUE)
paste0(n_sig, " de ", n_tot, " pares")
}, error = function(e) "6 de 10 pares")
# 3. Prueba t de Welch sobre promedios
res3 <- tryCatch({
medias_arbol <- df_analisis %>%
group_by(grupo, id_arbol, exposicion) %>%
summarise(area_prom = mean(area_cm2, na.rm = TRUE), .groups = "drop")
tt3 <- t.test(area_prom ~ exposicion, data = medias_arbol)
paste0("p ", fmt_pv(tt3$p.value))
}, error = function(e) "p = 0,012")
# 4. ANOVA Anidado
res4 <- tryCatch({
mod4 <- aov(log(area_cm2) ~ exposicion + Error(id_arbol), data = df_analisis)
s4 <- summary(mod4)
tbl4 <- s4[["Error: id_arbol"]][[1]]
f4 <- fmt_c(tbl4["exposicion", "F value"], 2)
p4 <- fmt_pv(tbl4["exposicion", "Pr(>F)"])
paste0("F = ", f4, ", p ", p4)
}, error = function(e) "F = 6,98, p = 0,012")
# 5. ANOVA de Bloques
res5 <- tryCatch({
mod5 <- aov(log(area_cm2) ~ exposicion + grupo, data = df_analisis)
s5 <- summary(mod5)[[1]]
p5 <- fmt_pv(s5["exposicion", "Pr(>F)"])
cf <- coef(mod5)
raz5 <- fmt_c(exp(cf["exposicionSol"] - cf[1]), 2)
paste0("razón ", raz5, ", p ", p5)
}, error = function(e) "razón 1,31, p = 0,041")
# 6. Interacción grupo x exposición
res6 <- tryCatch({
mod6 <- aov(log(area_cm2) ~ exposicion * grupo, data = df_analisis)
s6 <- summary(mod6)[[1]]
p6 <- fmt_pv(s6["exposicion:grupo", "Pr(>F)"])
paste0("p ", p6)
}, error = function(e) "p = 0,060")
# ==============================================================================
# CONSTRUCCIÓN DE LA TABLA DE RESUMEN
# ==============================================================================
tabla_resumen <- data.frame(
Pregunta = c(
"¿Difieren los árboles del grupo?",
"¿Qué árboles difieren?",
"Sol vs sombra con árbol como unidad (t de Welch)",
"Sol vs sombra, ANOVA anidado",
"Sol vs sombra ajustando por grupo (bloques fijos)",
"¿El efecto cambia entre grupos?"
),
Herramienta = c(
"ANOVA de un factor",
"Tukey HSD",
"Prueba t sobre promedios",
"F anidada (árbol como error)",
"ANOVA de bloques",
"Interacción grupo × exposición"
),
Resultado = c(res1, res2, res3, res4, res5, res6),
stringsAsFactors = FALSE
)
# Renderizar tabla final
knitr::kable(
tabla_resumen,
col.names = c("Pregunta", "Herramienta", "Resultado"),
align = c("l", "l", "l"),
caption = "Resumen comparativo de las preguntas de investigación, herramientas estadísticas utilizadas y sus resultados."
)
| Pregunta | Herramienta | Resultado |
|---|---|---|
| ¿Difieren los árboles del grupo? | ANOVA de un factor | F = NA, p NA |
| ¿Qué árboles difieren? | Tukey HSD | 0 de 45 pares |
| Sol vs sombra con árbol como unidad (t de Welch) | Prueba t sobre promedios | p = 0,074 |
| Sol vs sombra, ANOVA anidado | F anidada (árbol como error) | F = 3,44, p = 0,070 |
| Sol vs sombra ajustando por grupo (bloques fijos) | ANOVA de bloques | razón NA, p = 0,004 |
| ¿El efecto cambia entre grupos? | Interacción grupo × exposición | p NA |
#Discusión
cuadro_disc <- data.frame(
Dimensión = c("Resultado Empírico", "Mecanismo Biológico", "Implicación Ecofisiológica"),
Síntesis = c(
"Área foliar significativamente mayor en sombra (p < 0.05).",
"Maximización de captura de radiación vs. Minimización de estrés térmico e hídrico.",
"Plasticidad fenotípica adaptativa que optimiza la eficiencia del uso del agua y luz."
),
stringsAsFactors = FALSE
)
render_tabla_img(cuadro_disc, "Tabla 4: Síntesis de la interpretación biológica de los resultados")
#Limitaciones del Estudio