En esta práctica trabajaremos como si estuviéramos desarrollando una pequeña investigación biológica. El objetivo no es aprender comandos aislados, sino seguir un flujo de análisis:
pregunta biológica → datos → descripción → visualización → interpretación → inferencia.
La base contiene información simulada con fines didácticos sobre 60 puntos de muestreo de una ciénaga. En cada punto se registraron el sector, el hábitat, la concentración de clorofila-a, la temperatura, la turbidez y la profundidad.
La pregunta general es:
¿Cómo se comporta la concentración de clorofila-a en la ciénaga y qué podemos concluir, a partir de la muestra, sobre su valor medio en la población?
Antes de cualquier inferencia debemos conocer bien los datos.
Para evitar problemas con rutas, guarde en una misma carpeta:
Codigos_Estudiante_Clorofila.Rclorofila_cienaga_v3.csvAbra el archivo .Rmd desde RStudio y ejecute el
siguiente código.
Si es la primera vez que usa estos paquetes, instálelos una sola vez con:
En cada nueva sesión de R solo necesita cargarlos:
# El archivo CSV debe estar en la misma carpeta que este documento.
datos <- read.csv("clorofila_cienaga_v3.csv")Eso es todo lo que necesitamos para importar la base.
## punto sector habitat clorofila temperatura turbidez profundidad
## 1 1 Norte Litoral 13.53 27.0 9.5 0.7
## 2 2 Norte Litoral 14.33 27.6 8.6 1.6
## 3 3 Norte Litoral 12.69 28.2 9.4 1.7
## 4 4 Norte Litoral 13.47 27.7 8.7 1.3
## 5 5 Norte Pelágico 13.74 26.9 10.0 3.3
## 6 6 Norte Pelágico 12.84 27.9 10.5 3.0
## [1] 60 7
## 'data.frame': 60 obs. of 7 variables:
## $ punto : int 1 2 3 4 5 6 7 8 9 10 ...
## $ sector : chr "Norte" "Norte" "Norte" "Norte" ...
## $ habitat : chr "Litoral" "Litoral" "Litoral" "Litoral" ...
## $ clorofila : num 13.5 14.3 12.7 13.5 13.7 ...
## $ temperatura: num 27 27.6 28.2 27.7 26.9 27.9 28.2 27.1 27.5 26.4 ...
## $ turbidez : num 9.5 8.6 9.4 8.7 10 10.5 14.3 11.5 10.5 9.3 ...
## $ profundidad: num 0.7 1.6 1.7 1.3 3.3 3 1.2 1.2 1.8 2.3 ...
Interpretación. Cada fila corresponde a un punto de
muestreo. La base tiene 60 observaciones y siete variables. Las
variables sector y habitat son cualitativas;
clorofila, temperatura, turbidez
y profundidad son cuantitativas.
Para que R trate correctamente las variables cualitativas, las convertimos en factores:
Durante el análisis intentaremos responder progresivamente:
Las primeras seis preguntas son principalmente descriptivas. La última requiere inferencia estadística.
Las variables cualitativas se describen principalmente mediante frecuencias absolutas, frecuencias relativas y diagramas de barras.
##
## Centro Norte Sur
## 20 20 20
##
## Centro Norte Sur
## 33.3 33.3 33.3
ggplot(datos, aes(x = sector)) +
geom_bar(width = 0.65) +
labs(
title = "Puntos de muestreo por sector",
x = "Sector",
y = "Frecuencia"
) +
theme_minimal(base_size = 13)Interpretación. Los tres sectores aportan el mismo número de observaciones. Esto es útil porque evita que una diferencia descriptiva entre sectores sea simplemente consecuencia de tamaños muestrales muy distintos.
##
## Litoral Pelágico
## 34 26
##
## Litoral Pelágico
## 56.7 43.3
ggplot(datos, aes(x = habitat)) +
geom_bar(width = 0.65) +
labs(
title = "Puntos de muestreo según hábitat",
x = "Hábitat",
y = "Frecuencia"
) +
theme_minimal(base_size = 13)Interpretación. La muestra incluye puntos litorales y pelágicos. El diagrama de barras permite comparar rápidamente la representación de ambas categorías.
Ahora observamos cómo se distribuye el hábitat dentro de cada sector.
##
## Litoral Pelágico
## Centro 10 10
## Norte 12 8
## Sur 12 8
ggplot(datos, aes(x = sector, fill = habitat)) +
geom_bar(position = "fill") +
scale_y_continuous(
labels = function(x) paste0(round(100 * x), "%")
) +
labs(
title = "Composición del hábitat dentro de cada sector",
x = "Sector",
y = "Porcentaje",
fill = "Hábitat"
) +
theme_minimal(base_size = 13)Interpretación. Este gráfico ya no muestra el número total de puntos, sino la composición porcentual de cada sector. Es especialmente útil cuando queremos comparar una variable cualitativa entre grupos.
Para variables cualitativas, los diagramas de barras suelen ser más fáciles de comparar que los diagramas circulares.
La variable principal del estudio es clorofila, medida
en mg/m³.
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 9.43 13.09 14.59 14.49 15.73 20.72
La salida anterior proporciona mínimo, cuartiles, mediana, media y máximo. Complementamos con otras medidas.
n <- length(x)
media <- mean(x)
mediana <- median(x)
varianza <- var(x)
desv <- sd(x)
rango <- diff(range(x))
Q1 <- quantile(x, 0.25)
Q3 <- quantile(x, 0.75)
RIC <- IQR(x)
CV <- 100 * desv / media
resumen <- data.frame(
Medida = c("n", "Media", "Mediana", "Varianza", "Desviación estándar",
"Mínimo", "Q1", "Q3", "Máximo", "RIC", "CV (%)"),
Valor = c(n, media, mediana, varianza, desv,
min(x), Q1, Q3, max(x), RIC, CV)
)
knitr::kable(resumen, digits = 2)| Medida | Valor |
|---|---|
| n | 60.00 |
| Media | 14.49 |
| Mediana | 14.59 |
| Varianza | 5.08 |
| Desviación estándar | 2.25 |
| Mínimo | 9.43 |
| Q1 | 13.09 |
| Q3 | 15.73 |
| Máximo | 20.72 |
| RIC | 2.64 |
| CV (%) | 15.55 |
Lectura integrada de los resultados. La media y la mediana son muy próximas, lo que sugiere que el centro de la distribución no está fuertemente desplazado por una cola extrema. La desviación estándar describe la dispersión alrededor de la media, mientras que el RIC resume la amplitud del 50 % central de las observaciones. El CV expresa la dispersión en términos relativos respecto a la media.
ggplot(datos, aes(x = clorofila)) +
geom_histogram(
bins = 10,
color = "white"
) +
geom_vline(
xintercept = mean(datos$clorofila),
linetype = 2,
linewidth = 0.9
) +
labs(
title = "Distribución de la clorofila-a",
subtitle = "La línea punteada representa la media muestral",
x = "Clorofila-a (mg/m³)",
y = "Frecuencia"
) +
theme_minimal(base_size = 13)Qué debemos observar. El histograma permite estudiar la forma global de la distribución: concentración de observaciones, posibles colas, huecos, grupos o valores poco frecuentes.
ggplot(datos, aes(x = clorofila)) +
geom_density(linewidth = 1.1) +
geom_vline(
xintercept = mean(datos$clorofila),
linetype = 2
) +
geom_vline(
xintercept = median(datos$clorofila),
linetype = 3
) +
labs(
title = "Densidad de la clorofila-a",
subtitle = "Líneas verticales: media y mediana",
x = "Clorofila-a (mg/m³)",
y = "Densidad"
) +
theme_minimal(base_size = 13)Interpretación. La curva de densidad es una versión suavizada de la distribución. No muestra frecuencias absolutas; ayuda a observar con mayor claridad la forma general.
ggplot(datos, aes(x = "Muestra", y = clorofila)) +
geom_boxplot(width = 0.25, outlier.shape = NA) +
geom_jitter(
width = 0.08,
alpha = 0.55
) +
labs(
title = "Boxplot y observaciones individuales",
y = "Clorofila-a (mg/m³)",
x = NULL
) +
theme_minimal(base_size = 13)Interpretación. El boxplot resume mediana, cuartiles y posibles observaciones atípicas. Al superponer los puntos individuales evitamos ocultar la estructura real de la muestra.
ggplot(datos, aes(x = "Muestra", y = clorofila)) +
geom_violin(trim = FALSE) +
geom_boxplot(width = 0.12, outlier.shape = NA) +
geom_jitter(width = 0.06, alpha = 0.45) +
labs(
title = "Distribución completa de la clorofila-a",
x = NULL,
y = "Clorofila-a (mg/m³)"
) +
theme_minimal(base_size = 13)El violín combina información de densidad con la lógica del boxplot y permite observar dónde se concentran más datos.
Hasta ahora analizamos todos los puntos conjuntamente. Pero el contexto biológico sugiere preguntar si los sectores o hábitats presentan patrones diferentes.
resumen_sector <- datos %>%
group_by(sector) %>%
summarise(
n = n(),
media = mean(clorofila),
mediana = median(clorofila),
DE = sd(clorofila),
Q1 = quantile(clorofila, 0.25),
Q3 = quantile(clorofila, 0.75),
CV = 100 * DE / media
)
knitr::kable(resumen_sector, digits = 2)| sector | n | media | mediana | DE | Q1 | Q3 | CV |
|---|---|---|---|---|---|---|---|
| Centro | 20 | 15.58 | 15.80 | 2.39 | 14.63 | 16.68 | 15.31 |
| Norte | 20 | 13.37 | 13.74 | 1.56 | 12.67 | 14.41 | 11.64 |
| Sur | 20 | 14.52 | 14.58 | 2.25 | 12.92 | 15.56 | 15.50 |
ggplot(datos, aes(x = sector, y = clorofila)) +
geom_boxplot(outlier.shape = NA) +
geom_jitter(width = 0.10, alpha = 0.60) +
labs(
title = "Clorofila-a por sector",
subtitle = "Se muestran el resumen del boxplot y los datos individuales",
x = "Sector",
y = "Clorofila-a (mg/m³)"
) +
theme_minimal(base_size = 13)Interpretación. Descriptivamente, el sector Centro presenta una concentración media mayor que Norte y Sur. Sin embargo, los grupos se solapan y todavía no hemos realizado una comparación inferencial. Una diferencia entre medias muestrales no implica automáticamente una diferencia poblacional.
ggplot(datos, aes(x = clorofila)) +
geom_histogram(
bins = 7,
color = "white"
) +
facet_wrap(~ sector) +
labs(
title = "Distribución de la clorofila-a en cada sector",
x = "Clorofila-a (mg/m³)",
y = "Frecuencia"
) +
theme_minimal(base_size = 12)Estos paneles permiten comparar no solo las medias, sino también la dispersión y la forma de las distribuciones.
resumen_habitat <- datos %>%
group_by(habitat) %>%
summarise(
n = n(),
media = mean(clorofila),
mediana = median(clorofila),
DE = sd(clorofila),
CV = 100 * DE / media
)
knitr::kable(resumen_habitat, digits = 2)| habitat | n | media | mediana | DE | CV |
|---|---|---|---|---|---|
| Litoral | 34 | 14.96 | 15.02 | 2.42 | 16.15 |
| Pelágico | 26 | 13.88 | 14.23 | 1.89 | 13.64 |
ggplot(datos, aes(x = habitat, y = clorofila)) +
geom_violin(trim = FALSE) +
geom_boxplot(width = 0.15, outlier.shape = NA) +
geom_jitter(width = 0.08, alpha = 0.55) +
labs(
title = "Clorofila-a según hábitat",
x = "Hábitat",
y = "Clorofila-a (mg/m³)"
) +
theme_minimal(base_size = 13)Una pregunta adicional es si los puntos con mayor turbidez tienden a mostrar también mayores concentraciones de clorofila-a.
ggplot(datos, aes(x = turbidez, y = clorofila)) +
geom_point(size = 2.4, alpha = 0.75) +
labs(
title = "Clorofila-a y turbidez",
x = "Turbidez",
y = "Clorofila-a (mg/m³)"
) +
theme_minimal(base_size = 13)Interpretación. El diagrama de dispersión permite explorar dirección, forma y fuerza aparente de una relación. En esta etapa no buscamos demostrar causalidad ni ajustar todavía un modelo de regresión.
Llegados a este punto sabemos bastante sobre la muestra:
Pero todavía hay una pregunta que el análisis descriptivo no puede responder por sí solo:
¿Qué tan cerca está la media de esta muestra de la verdadera media de clorofila-a de la población de puntos de la ciénaga?
La media que calculamos es:
## [1] 14.492
Ese número describe nuestra muestra. Si volviéramos a seleccionar otros 60 puntos, la media cambiaría ligeramente. A esta variación entre muestras la llamamos variabilidad muestral.
Por eso necesitamos pasar de:
describir lo observado
hacia:
cuantificar la incertidumbre al generalizar desde la muestra hacia la población.
Ese es el origen natural de la inferencia estadística.
Para la media, una medida de incertidumbre es el error estándar:
\[ SE(\bar X)=\frac{s}{\sqrt{n}}. \]
## [1] 0.29085
El intervalo de confianza del 95 % puede calcularse como:
\[ \bar x \pm t_{0.975,n-1}\frac{s}{\sqrt n}. \]
tcritico <- qt(0.975, df = n - 1)
LI <- media - tcritico * SE
LS <- media + tcritico * SE
c(LI = LI, Media = media, LS = LS)## LI Media LS
## 13.91001 14.49200 15.07399
También podemos pedirle a R el mismo resultado directamente:
##
## One Sample t-test
##
## data: datos$clorofila
## t = 49.826, df = 59, p-value < 2.2e-16
## alternative hypothesis: true mean is not equal to 0
## 95 percent confidence interval:
## 13.91001 15.07399
## sample estimates:
## mean of x
## 14.492
ic <- data.frame(
grupo = "Ciénaga",
media = media,
LI = LI,
LS = LS
)
ggplot(ic, aes(x = grupo, y = media)) +
geom_point(size = 3) +
geom_errorbar(
aes(ymin = LI, ymax = LS),
width = 0.10,
linewidth = 0.9
) +
geom_hline(
yintercept = 12,
linetype = 2
) +
labs(
title = "Media estimada e intervalo de confianza del 95 %",
subtitle = "La línea horizontal representa el valor de referencia de 12 mg/m³",
x = NULL,
y = "Clorofila-a (mg/m³)"
) +
theme_minimal(base_size = 13)Interpretación. El intervalo proporciona un rango de valores plausibles para la media poblacional bajo el procedimiento utilizado. Es más informativo que reportar solo la media muestral porque muestra también la precisión de la estimación.
Supongamos que, por razones del problema de investigación, 12 mg/m³ se ha establecido como un valor de referencia y queremos estudiar si la media poblacional es mayor.
Planteamos:
\[ H_0:\mu=12 \]
frente a
\[ H_1:\mu>12. \]
##
## One Sample t-test
##
## data: datos$clorofila
## t = 8.568, df = 59, p-value = 3.017e-12
## alternative hypothesis: true mean is greater than 12
## 95 percent confidence interval:
## 14.00596 Inf
## sample estimates:
## mean of x
## 14.492
Podemos extraer los elementos más importantes:
resultado_inferencia <- data.frame(
Media = mean(datos$clorofila),
DE = sd(datos$clorofila),
t = unname(prueba$statistic),
gl = unname(prueba$parameter),
p_valor = prueba$p.value
)
knitr::kable(resultado_inferencia, digits = 4)| Media | DE | t | gl | p_valor |
|---|---|---|---|---|
| 14.492 | 2.2529 | 8.568 | 59 | 0 |
Interpretación estadística. Si el valor p es pequeño respecto al nivel de significación elegido, los datos son poco compatibles con una media poblacional de 12 mg/m³ bajo el modelo del contraste.
Interpretación biológica. La conclusión debe volver siempre a la pregunta de investigación. No basta con escribir “p < 0.05”. Debemos expresar qué evidencia aportan los datos sobre la concentración media de clorofila-a en la ciénaga.
El análisis completo siguió una secuencia lógica:
La inferencia no aparece porque “ahora toca aplicar una prueba”. Aparece porque queremos pasar de lo que observamos en una muestra a una conclusión sobre una población y necesitamos cuantificar la incertidumbre de ese salto.
Utilizando la misma base:
habitat.turbidez mediante media, mediana, DE, Q1, Q3,
RIC y CV.turbidez un histograma, una curva de
densidad y un boxplot.temperatura y
clorofila.