Desarrollar herramientas estadísticas para comparar grupos cuando las condiciones de los métodos paramétricos no resultan adecuadas y evaluar el grado de concordancia entre diferentes criterios de clasificación, utilizando datos microbiológicos y manteniendo el punto de muestreo como eje de la interpretación.
En este módulo se estudiarán:
En los módulos anteriores estudiamos procedimientos como:
\[ t,\qquad ANOVA,\qquad \text{Pearson} \]
Estos procedimientos permiten realizar inferencia sobre determinados parámetros poblacionales y requieren condiciones relacionadas con:
Sin embargo, los resultados microbiológicos pueden presentar:
En estas situaciones los procedimientos basados en rangos constituyen una alternativa útil.
Un método no paramétrico no significa que no tenga supuestos.
Significa que la inferencia depende menos de una forma paramétrica específica de la distribución.
Muchos procedimientos transforman:
\[ x_1,x_2,\ldots,x_n \]
en:
\[ R_1,R_2,\ldots,R_n \]
donde:
\[ R_i \]
representa el rango o posición de la observación.
Supongamos:
\[ 20,\quad50,\quad10,\quad80,\quad30 \]
Al ordenar:
\[ 10<20<30<50<80 \]
obtenemos:
| Valor | Rango |
|---|---|
| 10 | 1 |
| 20 | 2 |
| 30 | 3 |
| 50 | 4 |
| 80 | 5 |
Supongamos:
\[ 10,\quad20,\quad20,\quad40 \]
Las dos observaciones iguales ocuparían los rangos 2 y 3.
Por tanto se asigna:
\[ \frac{2+3}{2}=2.5 \]
a ambas observaciones.
| Valor | Rango |
|---|---|
| 10 | 1 |
| 20 | 2.5 |
| 20 | 2.5 |
| 40 | 4 |
R realiza automáticamente este ajuste.
\[ \boxed{\text{Dos grupos independientes}} \]
\[ \Downarrow \]
\[ \boxed{\text{U de Mann–Whitney}} \]
\[ \Downarrow \]
\[ \boxed{\text{Dos mediciones relacionadas}} \]
\[ \Downarrow \]
\[ \boxed{\text{Wilcoxon de rangos con signo}} \]
\[ \Downarrow \]
\[ \boxed{\text{Dos clasificaciones de las mismas unidades}} \]
\[ \Downarrow \]
\[ \boxed{\text{Kappa de Cohen}} \]
| Técnica | Estructura | Pregunta |
|---|---|---|
| Mann–Whitney | Dos grupos independientes | ¿Los grupos presentan distinto comportamiento? |
| Wilcoxon signed-rank | Dos mediciones relacionadas | ¿Existe un cambio sistemático? |
| Kappa de Cohen | Dos clasificaciones sobre las mismas observaciones | ¿Qué tanto coinciden más allá del azar? |
Trabajaremos nuevamente con:
data/DATOS I- SEM 2026.xlsx
library(readxl)
datos <- read_excel(
"DATOS I- SEM 2026.xlsx"
)
names(datos) <- c(
"analisis",
"punto",
"resultado_nmp",
"matriz",
"fecha"
)
# Eliminar registros con punto incompleto
datos <- datos[
!is.na(datos$punto) &
trimws(datos$punto) != "PUNTO_",
]
# Convertir NMP a numérico
datos$resultado_nmp <- as.numeric(
gsub(
",",
".",
as.character(datos$resultado_nmp),
fixed = TRUE
)
)
# Fecha
datos$fecha <- as.Date(
datos$fecha
)
# Escala log10
datos$log10_nmp <- ifelse(
!is.na(datos$resultado_nmp) &
is.finite(datos$resultado_nmp) &
datos$resultado_nmp > 0,
log10(
datos$resultado_nmp
),
NA_real_
)
# Mes
datos$mes_num <- as.integer(
format(
datos$fecha,
"%m"
)
)
datos$mes <- factor(
datos$mes_num,
levels = 1:12,
labels = c(
"Enero", "Febrero", "Marzo", "Abril",
"Mayo", "Junio", "Julio", "Agosto",
"Septiembre", "Octubre", "Noviembre", "Diciembre"
)
)
# P90 global
P90_global <- as.numeric(
quantile(
datos$resultado_nmp,
probs = 0.90,
na.rm = TRUE
)
)
datos$evento_alerta <- ifelse(
datos$resultado_nmp > P90_global,
"Alerta",
"Sin alerta"
)
datos$evento_alerta <- factor(
datos$evento_alerta,
levels = c(
"Alerta",
"Sin alerta"
)
)
# P90 específico por punto
datos$P90_punto <- ave(
datos$resultado_nmp,
datos$punto,
FUN = function(x) {
rep(
as.numeric(
quantile(
x,
probs = 0.90,
na.rm = TRUE
)
),
length(x)
)
}
)
datos$evento_alerta_punto <- ifelse(
datos$resultado_nmp > datos$P90_punto,
"Alerta",
"Sin alerta"
)
datos$evento_alerta_punto <- factor(
datos$evento_alerta_punto,
levels = c(
"Alerta",
"Sin alerta"
)
)
puntos_mod4 <- sort(
unique(
datos$punto
)
)
El punto será utilizado de tres formas.
En Mann–Whitney:
\[ \boxed{\text{compararemos dos puntos independientes}} \]
En Wilcoxon:
\[ \boxed{\text{compararemos los mismos puntos en dos periodos}} \]
En concordancia:
\[ \boxed{ \text{evaluaremos cómo dos criterios clasifican los resultados de los puntos} } \]
La prueba U de Mann–Whitney permite comparar una variable cuantitativa entre dos grupos independientes.
También recibe el nombre:
\[ \boxed{ \text{Wilcoxon rank-sum test} } \]
No debe confundirse con:
\[ \boxed{ \text{Wilcoxon signed-rank test} } \]
utilizado para datos pareados.
En nuestra base podemos estudiar:
¿Dos puntos de muestreo presentan el mismo comportamiento microbiológico o existen diferencias en la distribución de sus resultados?
Tenemos:
\[ Y= \log_{10}(\text{NMP/100 mL}) \]
y:
\[ X= \text{Punto} \]
con dos categorías.
Puede ser útil cuando:
La prueba requiere principalmente:
Cuando las formas son diferentes, la interpretación debe plantearse en términos generales de:
\[ \boxed{\text{diferencias en distribuciones/rangos}} \]
Una formulación general es:
\[ H_0: F_1(x)=F_2(x) \]
frente a:
\[ H_1: F_1(x)\neq F_2(x) \]
donde:
conteo_mw <- table(
datos$punto
)
puntos_mw <- names(
conteo_mw[
conteo_mw >= 5
]
)
if (
length(
puntos_mw
) >= 2
) {
puntos_mw <- puntos_mw[
1:2
]
datos_mw <- datos[
datos$punto %in% puntos_mw &
is.finite(
datos$log10_nmp
),
c(
"punto",
"fecha",
"resultado_nmp",
"log10_nmp"
)
]
datos_mw$punto <- droplevels(
factor(
datos_mw$punto,
levels = puntos_mw
)
)
knitr::kable(
head(
datos_mw,
12
),
digits = 3,
caption = paste(
"Ejemplo Mann–Whitney:",
paste(
puntos_mw,
collapse = " versus "
)
)
)
} else {
datos_mw <- NULL
cat(
"No existen dos puntos con suficientes observaciones."
)
}
| punto | fecha | resultado_nmp | log10_nmp |
|---|---|---|---|
| PUNTO_11 | 2026-05-06 | 3500.0 | 3.544 |
| PUNTO_16 | 2026-05-06 | 230.0 | 2.362 |
| PUNTO_11 | 2026-01-29 | 8164.0 | 3.912 |
| PUNTO_16 | 2026-01-29 | 1935.0 | 3.287 |
| PUNTO_11 | 2026-02-10 | 3448.0 | 3.538 |
| PUNTO_16 | 2026-02-10 | 5172.0 | 3.714 |
| PUNTO_11 | 2026-03-03 | 8664.0 | 3.938 |
| PUNTO_11 | 2026-03-03 | 866.4 | 2.938 |
| PUNTO_16 | 2026-03-03 | 360.9 | 2.557 |
| PUNTO_16 | 2026-03-03 | 3609.0 | 3.557 |
| PUNTO_11 | 2026-03-24 | 4352.0 | 3.639 |
| PUNTO_11 | 2026-03-24 | 435.2 | 2.639 |
if (
!is.null(
datos_mw
)
) {
resumen_mw <- do.call(
rbind,
lapply(
split(
datos_mw$log10_nmp,
datos_mw$punto
),
function(x) {
x <- x[
is.finite(x)
]
data.frame(
N = length(x),
Media = mean(x),
Mediana = median(x),
Q1 = as.numeric(
quantile(
x,
0.25
)
),
Q3 = as.numeric(
quantile(
x,
0.75
)
),
IQR = IQR(x)
)
}
)
)
resumen_mw$Punto <- rownames(
resumen_mw
)
rownames(
resumen_mw
) <- NULL
resumen_mw <- resumen_mw[
,
c(
"Punto",
"N",
"Media",
"Mediana",
"Q1",
"Q3",
"IQR"
)
]
knitr::kable(
resumen_mw,
digits = 3,
caption = "Resumen descriptivo de los puntos"
)
}
| Punto | N | Media | Mediana | Q1 | Q3 | IQR |
|---|---|---|---|---|---|---|
| PUNTO_11 | 21 | 2.872 | 3.030 | 2.184 | 3.203 | 1.019 |
| PUNTO_16 | 21 | 2.697 | 2.613 | 2.354 | 3.287 | 0.933 |
Antes de realizar la prueba debemos identificar:
Una mediana superior indica que el punto tiende a presentar resultados microbiológicos más elevados.
if (
!is.null(
datos_mw
)
) {
boxplot(
log10_nmp ~ punto,
data = datos_mw,
col = "#edf8f7",
border = "#083d5c",
xlab = "Punto de muestreo",
ylab = expression(
log[10]*"(NMP/100 mL)"
),
main = "Comparación microbiológica entre dos puntos"
)
stripchart(
log10_nmp ~ punto,
data = datos_mw,
vertical = TRUE,
method = "jitter",
add = TRUE,
pch = 19,
cex = 0.65,
col = "#1596a5"
)
}
Supongamos:
\[ A: a_1,\ldots,a_{n_1} \]
y:
\[ B: b_1,\ldots,b_{n_2} \]
Se combinan las:
\[ n_1+n_2 \]
observaciones y se ordenan.
A cada resultado se le asigna un rango:
\[ R_i \]
if (
!is.null(
datos_mw
)
) {
grupo1_mw <- datos_mw$log10_nmp[
datos_mw$punto ==
puntos_mw[1]
]
grupo2_mw <- datos_mw$log10_nmp[
datos_mw$punto ==
puntos_mw[2]
]
grupo1_5 <- head(
grupo1_mw,
5
)
grupo2_5 <- head(
grupo2_mw,
5
)
ejemplo_manual_mw <- data.frame(
Valor = c(
grupo1_5,
grupo2_5
),
Grupo = factor(
c(
rep(
puntos_mw[1],
length(
grupo1_5
)
),
rep(
puntos_mw[2],
length(
grupo2_5
)
)
),
levels = puntos_mw
)
)
ejemplo_manual_mw$Rango <- rank(
ejemplo_manual_mw$Valor,
ties.method = "average"
)
ejemplo_manual_mw <- ejemplo_manual_mw[
order(
ejemplo_manual_mw$Valor
),
]
knitr::kable(
ejemplo_manual_mw,
digits = 3,
caption = "Asignación de rangos"
)
}
| Valor | Grupo | Rango | |
|---|---|---|---|
| 6 | 2.362 | PUNTO_16 | 1 |
| 9 | 2.557 | PUNTO_16 | 2 |
| 5 | 2.938 | PUNTO_11 | 3 |
| 7 | 3.287 | PUNTO_16 | 4 |
| 3 | 3.538 | PUNTO_11 | 5 |
| 1 | 3.544 | PUNTO_11 | 6 |
| 10 | 3.557 | PUNTO_16 | 7 |
| 8 | 3.714 | PUNTO_16 | 8 |
| 2 | 3.912 | PUNTO_11 | 9 |
| 4 | 3.938 | PUNTO_11 | 10 |
Para el grupo 1:
\[ R_1 = \sum R_i \]
Para el grupo 2:
\[ R_2 = \sum R_i \]
if (
exists(
"ejemplo_manual_mw"
)
) {
suma_rangos_mw <- tapply(
ejemplo_manual_mw$Rango,
ejemplo_manual_mw$Grupo,
sum
)
suma_rangos_mw
}
## PUNTO_11 PUNTO_16
## 33 22
\[ U_1 = R_1 - \frac{ n_1(n_1+1) }{ 2 } \]
\[ U_2 = R_2 - \frac{ n_2(n_2+1) }{ 2 } \]
y:
\[ U_1+U_2 = n_1n_2 \]
if (
exists(
"ejemplo_manual_mw"
)
) {
n1_mw <- sum(
ejemplo_manual_mw$Grupo ==
puntos_mw[1]
)
n2_mw <- sum(
ejemplo_manual_mw$Grupo ==
puntos_mw[2]
)
R1_mw <- sum(
ejemplo_manual_mw$Rango[
ejemplo_manual_mw$Grupo ==
puntos_mw[1]
]
)
R2_mw <- sum(
ejemplo_manual_mw$Rango[
ejemplo_manual_mw$Grupo ==
puntos_mw[2]
]
)
U1_mw <- R1_mw -
n1_mw *
(
n1_mw + 1
) / 2
U2_mw <- R2_mw -
n2_mw *
(
n2_mw + 1
) / 2
tabla_u_mw <- data.frame(
Estadistico = c(
"R1",
"R2",
"U1",
"U2"
),
Valor = c(
R1_mw,
R2_mw,
U1_mw,
U2_mw
)
)
knitr::kable(
tabla_u_mw,
digits = 3,
caption = "Cálculo manual del estadístico U"
)
}
| Estadistico | Valor |
|---|---|
| R1 | 33 |
| R2 | 22 |
| U1 | 18 |
| U2 | 7 |
El estadístico U está relacionado con la cantidad de veces que los valores de un grupo tienden a superar a los del otro.
Si las observaciones de ambos puntos se mezclan considerablemente en el ordenamiento:
\[ U \]
no mostrará una separación pronunciada.
Si un grupo tiende a concentrar rangos superiores, existe mayor evidencia de diferencia.
Importante en R: cuando utilizamos la sintaxis de fórmula
respuesta ~ grupo
no debemos incluir el argumento paired.
La estructura de fórmula utilizada aquí corresponde automáticamente a dos grupos independientes.
if (
!is.null(
datos_mw
) &&
nlevels(
droplevels(
datos_mw$punto
)
) == 2
) {
prueba_mw <- wilcox.test(
log10_nmp ~ punto,
data = datos_mw,
exact = FALSE,
conf.int = TRUE,
conf.level = 0.95
)
prueba_mw
} else {
prueba_mw <- NULL
cat(
"La prueba Mann–Whitney requiere exactamente dos grupos con observaciones válidas."
)
}
##
## Wilcoxon rank sum test with continuity correction
##
## data: log10_nmp by punto
## W = 249, p-value = 0.4812
## alternative hypothesis: true location shift is not equal to 0
## 95 percent confidence interval:
## -0.3540329 0.6940347
## sample estimates:
## difference in location
## 0.2573363
R utiliza:
wilcox.test()
y reporta un estadístico denominado:
\[ W \]
basado en suma de rangos.
Dependiendo de la convención, puede relacionarse con U mediante:
\[ U = W- \frac{ n_1(n_1+1) }{ 2 } \]
La inferencia es equivalente.
Con:
conf.int = TRUE
R proporciona, cuando es posible, un estimador robusto del desplazamiento entre las distribuciones.
Este estimador no corresponde necesariamente a:
\[ \bar X_1-\bar X_2 \]
ni simplemente a:
\[ \widetilde X_1-\widetilde X_2 \]
Representa una estimación basada en diferencias entre observaciones.
Si:
\[ p<0.05 \]
existe evidencia de diferencias entre los rangos/distribuciones de los puntos.
Si:
\[ p\geq0.05 \]
no existe evidencia suficiente para concluir que difieran.
Un resultado significativo indica que ambos puntos no presentan el mismo perfil microbiológico.
Uno de ellos tiende a ocupar posiciones superiores o inferiores en la distribución de resultados.
Esto debe complementarse con:
Podemos calcular:
\[ r_{rb} = \frac{ 2U_1 }{ n_1n_2 } - 1 \]
donde:
if (
!is.null(
datos_mw
)
) {
valores_mw <- datos_mw$log10_nmp
grupos_mw <- datos_mw$punto
rangos_total_mw <- rank(
valores_mw,
ties.method = "average"
)
n1_total_mw <- sum(
grupos_mw ==
puntos_mw[1]
)
n2_total_mw <- sum(
grupos_mw ==
puntos_mw[2]
)
R1_total_mw <- sum(
rangos_total_mw[
grupos_mw ==
puntos_mw[1]
]
)
U1_total_mw <- R1_total_mw -
n1_total_mw *
(
n1_total_mw + 1
) / 2
r_rb_mw <- (
2 *
U1_total_mw /
(
n1_total_mw *
n2_total_mw
)
) - 1
r_rb_mw
}
## [1] 0.1292517
| \(|r_{rb}|\) | Interpretación aproximada |
|---|---|
| 0.10 | Pequeña |
| 0.30 | Moderada |
| 0.50 o superior | Grande |
Estas referencias son orientativas.
La conclusión debe combinar:
\[ \boxed{ \text{Boxplot} + \text{Medianas} + IQR } \]
\[ + \]
\[ \boxed{ U/W + p + IC + r_{rb} } \]
Los resultados permiten determinar si dos puntos presentan perfiles microbiológicos semejantes o si uno de ellos tiende a registrar valores sistemáticamente superiores. Esta diferencia debe interpretarse en conjunto con la variabilidad interna y el comportamiento temporal de cada punto.
\[ \boxed{\text{Dos puntos independientes}} \]
\[ \Downarrow \]
\[ \boxed{\text{Resumen + Boxplot}} \]
\[ \Downarrow \]
\[ \boxed{\text{Ordenar resultados}} \]
\[ \Downarrow \]
\[ \boxed{\text{Asignar rangos}} \]
\[ \Downarrow \]
\[ \boxed{R_1,R_2} \]
\[ \Downarrow \]
\[ \boxed{U_1,U_2} \]
\[ \Downarrow \]
\[ \boxed{p+\text{IC}+r_{rb}} \]
\[ \Downarrow \]
\[ \boxed{\text{Interpretación microbiológica}} \]
El test de Wilcoxon de rangos con signo se utiliza para dos mediciones relacionadas.
La unidad observacional debe mantenerse.
En nuestro contexto podemos utilizar:
\[ \text{Punto }j \]
medido en:
\[ \text{Periodo 1} \]
y nuevamente en:
\[ \text{Periodo 2} \]
Mann–Whitney:
\[ \boxed{\text{dos grupos independientes}} \]
Wilcoxon signed-rank:
\[ \boxed{\text{dos mediciones pareadas}} \]
Para evitar que un punto con mayor número de registros tenga más peso, resumiremos cada:
\[ \text{Punto}\times\text{Mes} \]
mediante:
\[ \widetilde X_{jm} \]
la mediana en escala log10.
datos_wilcox_base <- datos[
is.finite(
datos$log10_nmp
) &
!is.na(
datos$mes_num
),
]
medianas_mes_punto <- aggregate(
log10_nmp ~ punto + mes_num,
data = datos_wilcox_base,
FUN = median
)
names(
medianas_mes_punto
)[3] <- "mediana_log10"
knitr::kable(
head(
medianas_mes_punto,
15
),
digits = 3,
caption = "Mediana mensual por punto"
)
| punto | mes_num | mediana_log10 |
|---|---|---|
| PUNTO_11 | 1 | 3.912 |
| PUNTO_16 | 1 | 3.287 |
| PUNTO_17 | 1 | 3.368 |
| PUNTO_18 | 1 | 3.304 |
| PUNTO_19 | 1 | 5.158 |
| PUNTO_20 | 1 | 4.638 |
| PUNTO_21 | 1 | 4.501 |
| PUNTO_23 | 1 | 5.639 |
| PUNTO_4 | 1 | 5.995 |
| PUNTO_5 | 1 | 7.639 |
| PUNTO_6 | 1 | 6.763 |
| PUNTO_7 | 1 | 4.934 |
| PUNTO_8 | 1 | 6.034 |
| PUNTO_9 | 1 | 5.946 |
| PUNTO_11 | 2 | 3.538 |
meses_disponibles_w <- sort(
unique(
medianas_mes_punto$mes_num
)
)
mejor_par_w <- NULL
max_comunes_w <- 0
if (
length(
meses_disponibles_w
) >= 2
) {
pares_w <- combn(
meses_disponibles_w,
2
)
for (
j in seq_len(
ncol(
pares_w
)
)
) {
m1 <- pares_w[
1,
j
]
m2 <- pares_w[
2,
j
]
puntos_m1 <- medianas_mes_punto$punto[
medianas_mes_punto$mes_num ==
m1
]
puntos_m2 <- medianas_mes_punto$punto[
medianas_mes_punto$mes_num ==
m2
]
n_comunes <- length(
intersect(
puntos_m1,
puntos_m2
)
)
if (
n_comunes >
max_comunes_w
) {
max_comunes_w <- n_comunes
mejor_par_w <- c(
m1,
m2
)
}
}
}
if (
!is.null(
mejor_par_w
) &&
max_comunes_w >= 3
) {
periodo1_w <- medianas_mes_punto[
medianas_mes_punto$mes_num ==
mejor_par_w[1],
c(
"punto",
"mediana_log10"
)
]
periodo2_w <- medianas_mes_punto[
medianas_mes_punto$mes_num ==
mejor_par_w[2],
c(
"punto",
"mediana_log10"
)
]
names(
periodo1_w
)[2] <- "Periodo_1"
names(
periodo2_w
)[2] <- "Periodo_2"
datos_pareados_w <- merge(
periodo1_w,
periodo2_w,
by = "punto"
)
datos_pareados_w$Diferencia <- (
datos_pareados_w$Periodo_2 -
datos_pareados_w$Periodo_1
)
knitr::kable(
datos_pareados_w,
digits = 3,
caption = paste(
"Comparación pareada entre los meses",
mejor_par_w[1],
"y",
mejor_par_w[2]
)
)
} else {
datos_pareados_w <- NULL
cat(
"No existen suficientes puntos comunes entre dos periodos."
)
}
| punto | Periodo_1 | Periodo_2 | Diferencia |
|---|---|---|---|
| PUNTO_11 | 3.056 | 2.430 | -0.626 |
| PUNTO_16 | 2.362 | 2.062 | -0.300 |
| PUNTO_17 | 2.688 | 2.292 | -0.396 |
| PUNTO_18 | 3.114 | 2.318 | -0.796 |
| PUNTO_19 | 5.538 | 4.013 | -1.525 |
| PUNTO_20 | 3.362 | 4.058 | 0.696 |
| PUNTO_21 | 3.852 | 2.600 | -1.253 |
| PUNTO_23 | 3.362 | 4.151 | 0.789 |
| PUNTO_24 | 5.845 | 3.382 | -2.463 |
| PUNTO_25 | 4.380 | 3.988 | -0.392 |
| PUNTO_26 | 4.380 | 4.101 | -0.279 |
| PUNTO_5 | 7.041 | 5.229 | -1.812 |
| PUNTO_6 | 6.664 | 4.539 | -2.125 |
| PUNTO_7 | 4.362 | 3.944 | -0.418 |
| PUNTO_8 | 5.362 | 4.650 | -0.712 |
| PUNTO_9 | 5.768 | 3.762 | -2.006 |
Para cada punto \(j\):
\[ (X_{j1},X_{j2}) \]
corresponde a la misma unidad observada en dos momentos.
La variable fundamental es:
\[ D_j = X_{j2}-X_{j1} \]
Una formulación general es:
\[ H_0: \text{la distribución de las diferencias está centrada en 0} \]
frente a:
\[ H_1: \text{existe un desplazamiento distinto de 0} \]
Cuando la distribución de diferencias es aproximadamente simétrica:
\[ H_0: \widetilde D=0 \]
La prueba requiere:
if (
!is.null(
datos_pareados_w
)
) {
rango_y_w <- range(
c(
datos_pareados_w$Periodo_1,
datos_pareados_w$Periodo_2
),
na.rm = TRUE
)
plot(
NA,
xlim = c(
0.8,
2.2
),
ylim = rango_y_w,
xaxt = "n",
xlab = "Periodo",
ylab = expression(
"Mediana de " *
log[10] *
"(NMP/100 mL)"
),
main = "Cambio de los mismos puntos entre periodos"
)
for (
i in seq_len(
nrow(
datos_pareados_w
)
)
) {
lines(
c(
1,
2
),
c(
datos_pareados_w$Periodo_1[i],
datos_pareados_w$Periodo_2[i]
),
type = "b",
pch = 19,
col = "#1596a5"
)
}
axis(
1,
at = 1:2,
labels = c(
paste0(
"Mes ",
mejor_par_w[1]
),
paste0(
"Mes ",
mejor_par_w[2]
)
)
)
}
Cada línea corresponde al mismo punto.
Si la mayoría asciende:
\[ \text{Periodo 2} > \text{Periodo 1} \]
existe una señal descriptiva de incremento.
Si la mayoría desciende, ocurre lo contrario.
if (
!is.null(
datos_pareados_w
)
) {
knitr::kable(
datos_pareados_w[
,
c(
"punto",
"Periodo_1",
"Periodo_2",
"Diferencia"
)
],
digits = 3,
caption = "Diferencias entre periodos por punto"
)
}
| punto | Periodo_1 | Periodo_2 | Diferencia |
|---|---|---|---|
| PUNTO_11 | 3.056 | 2.430 | -0.626 |
| PUNTO_16 | 2.362 | 2.062 | -0.300 |
| PUNTO_17 | 2.688 | 2.292 | -0.396 |
| PUNTO_18 | 3.114 | 2.318 | -0.796 |
| PUNTO_19 | 5.538 | 4.013 | -1.525 |
| PUNTO_20 | 3.362 | 4.058 | 0.696 |
| PUNTO_21 | 3.852 | 2.600 | -1.253 |
| PUNTO_23 | 3.362 | 4.151 | 0.789 |
| PUNTO_24 | 5.845 | 3.382 | -2.463 |
| PUNTO_25 | 4.380 | 3.988 | -0.392 |
| PUNTO_26 | 4.380 | 4.101 | -0.279 |
| PUNTO_5 | 7.041 | 5.229 | -1.812 |
| PUNTO_6 | 6.664 | 4.539 | -2.125 |
| PUNTO_7 | 4.362 | 3.944 | -0.418 |
| PUNTO_8 | 5.362 | 4.650 | -0.712 |
| PUNTO_9 | 5.768 | 3.762 | -2.006 |
Primero:
\[ D_i= Y_i-X_i \]
Se excluyen los casos:
\[ D_i=0 \]
Posteriormente calculamos:
\[ |D_i| \]
y asignamos rangos.
Finalmente recuperamos el signo de:
\[ D_i \]
if (
!is.null(
datos_pareados_w
)
) {
tabla_w <- datos_pareados_w[
datos_pareados_w$Diferencia != 0,
]
if (
nrow(
tabla_w
) > 0
) {
tabla_w$Abs_Diferencia <- abs(
tabla_w$Diferencia
)
tabla_w$Rango <- rank(
tabla_w$Abs_Diferencia,
ties.method = "average"
)
tabla_w$Signo <- ifelse(
tabla_w$Diferencia > 0,
"+",
"-"
)
tabla_w$Rango_con_signo <- ifelse(
tabla_w$Diferencia > 0,
tabla_w$Rango,
-tabla_w$Rango
)
knitr::kable(
tabla_w[
,
c(
"punto",
"Diferencia",
"Abs_Diferencia",
"Rango",
"Signo",
"Rango_con_signo"
)
],
digits = 3,
caption = "Construcción de los rangos con signo"
)
} else {
tabla_w <- NULL
cat(
"Todas las diferencias son iguales a cero."
)
}
}
| punto | Diferencia | Abs_Diferencia | Rango | Signo | Rango_con_signo |
|---|---|---|---|---|---|
| PUNTO_11 | -0.626 | 0.626 | 6 | - | -6 |
| PUNTO_16 | -0.300 | 0.300 | 2 | - | -2 |
| PUNTO_17 | -0.396 | 0.396 | 4 | - | -4 |
| PUNTO_18 | -0.796 | 0.796 | 10 | - | -10 |
| PUNTO_19 | -1.525 | 1.525 | 12 | - | -12 |
| PUNTO_20 | 0.696 | 0.696 | 7 | + | 7 |
| PUNTO_21 | -1.253 | 1.253 | 11 | - | -11 |
| PUNTO_23 | 0.789 | 0.789 | 9 | + | 9 |
| PUNTO_24 | -2.463 | 2.463 | 16 | - | -16 |
| PUNTO_25 | -0.392 | 0.392 | 3 | - | -3 |
| PUNTO_26 | -0.279 | 0.279 | 1 | - | -1 |
| PUNTO_5 | -1.812 | 1.812 | 13 | - | -13 |
| PUNTO_6 | -2.125 | 2.125 | 15 | - | -15 |
| PUNTO_7 | -0.418 | 0.418 | 5 | - | -5 |
| PUNTO_8 | -0.712 | 0.712 | 8 | - | -8 |
| PUNTO_9 | -2.006 | 2.006 | 14 | - | -14 |
\[ W^+ = \sum_{D_i>0} R_i \]
\[ W^- = \sum_{D_i<0} R_i \]
if (
exists(
"tabla_w"
) &&
!is.null(
tabla_w
)
) {
W_positivo <- sum(
tabla_w$Rango[
tabla_w$Diferencia > 0
]
)
W_negativo <- sum(
tabla_w$Rango[
tabla_w$Diferencia < 0
]
)
data.frame(
Estadistico = c(
"W positivo",
"W negativo"
),
Valor = c(
W_positivo,
W_negativo
)
)
}
## Estadistico Valor
## 1 W positivo 16
## 2 W negativo 120
Si:
\[ W^+ \gg W^- \]
predominan aumentos.
Si:
\[ W^- \gg W^+ \]
predominan disminuciones.
Aquí sí podemos utilizar:
paired = TRUE
porque estamos utilizando la interfaz de dos vectores numéricos, no la interfaz de fórmula.
if (
!is.null(
datos_pareados_w
) &&
nrow(
datos_pareados_w
) >= 3 &&
any(
datos_pareados_w$Diferencia != 0
)
) {
prueba_wilcoxon <- suppressWarnings(
wilcox.test(
x = datos_pareados_w$Periodo_2,
y = datos_pareados_w$Periodo_1,
paired = TRUE,
exact = FALSE,
conf.int = TRUE,
conf.level = 0.95
)
)
prueba_wilcoxon
} else {
prueba_wilcoxon <- NULL
cat(
"No existen suficientes pares con diferencias distintas de cero para ejecutar Wilcoxon."
)
}
##
## Wilcoxon signed rank test with continuity correction
##
## data: datos_pareados_w$Periodo_2 and datos_pareados_w$Periodo_1
## V = 16, p-value = 0.007745
## alternative hypothesis: true location shift is not equal to 0
## 95 percent confidence interval:
## -1.3815328 -0.3589934
## sample estimates:
## (pseudo)median
## -0.8225421
Cuando es posible, la prueba proporciona una estimación robusta del desplazamiento entre ambas condiciones.
Esto permite complementar:
\[ p \]
con información sobre:
\[ \boxed{\text{magnitud del cambio}} \]
Si:
\[ p<0.05 \]
existe evidencia de un cambio sistemático entre los dos periodos.
Si:
\[ p\geq0.05 \]
no existe evidencia suficiente para afirmar que las diferencias estén desplazadas de cero.
Podemos utilizar:
\[ r_{rb} = \frac{ W^+-W^- }{ W^++W^- } \]
if (
exists(
"W_positivo"
) &&
exists(
"W_negativo"
) &&
(
W_positivo +
W_negativo
) > 0
) {
r_rb_wilcoxon <- (
W_positivo -
W_negativo
) /
(
W_positivo +
W_negativo
)
r_rb_wilcoxon
}
## [1] -0.7647059
Un resultado significativo indica que los puntos presentan un cambio sistemático entre ambos periodos.
Si:
\[ r_{rb}>0 \]
según la orientación utilizada, predominan incrementos hacia el segundo periodo.
Si:
\[ r_{rb}<0 \]
predominan reducciones.
La causa de esos cambios no puede determinarse únicamente mediante esta prueba.
Porque:
\[ \boxed{ \text{el mismo punto aparece en ambos periodos} } \]
Por ello existe una relación natural entre ambas mediciones.
El análisis debe aprovechar:
\[ D_i = Y_i-X_i \]
en lugar de ignorar el pareamiento.
\[ \boxed{\text{Mismos puntos}} \]
\[ \Downarrow \]
\[ \boxed{\text{Dos periodos}} \]
\[ \Downarrow \]
\[ \boxed{D_i=Y_i-X_i} \]
\[ \Downarrow \]
\[ \boxed{|D_i|} \]
\[ \Downarrow \]
\[ \boxed{\text{Rangos}} \]
\[ \Downarrow \]
\[ \boxed{W^+,W^-} \]
\[ \Downarrow \]
\[ \boxed{p+\text{IC}+r_{rb}} \]
\[ \Downarrow \]
\[ \boxed{\text{Interpretación microbiológica}} \]
Hasta ahora estudiamos:
\[ \boxed{\text{diferencias}} \]
Ahora queremos estudiar:
\[ \boxed{\text{coincidencia}} \]
La pregunta será:
¿Dos criterios asignan la misma categoría a las mismas observaciones?
Dos criterios pueden estar asociados y, sin embargo, no clasificar exactamente igual.
La concordancia requiere coincidencia de categorías.
Cada resultado tiene dos clasificaciones.
\[ X_i>P90_{\text{global}} \]
\[ X_i>P90_j \]
Podemos estudiar la concordancia entre:
\[ \boxed{\text{Alerta global}} \]
y:
\[ \boxed{\text{Alerta específica del punto}} \]
Estas clasificaciones no representan dos analistas independientes.
Representan dos criterios estadísticos de referencia.
Por ello Kappa se utiliza aquí para cuantificar:
\[ \boxed{ \text{concordancia entre dos reglas} } \]
No para demostrar validez normativa.
La estructura es:
| Local: Alerta | Local: Sin alerta | |
|---|---|---|
| Global: Alerta | \(a\) | \(b\) |
| Global: Sin alerta | \(c\) | \(d\) |
tabla_kappa <- table(
Global = factor(
datos$evento_alerta,
levels = c(
"Alerta",
"Sin alerta"
)
),
Local = factor(
datos$evento_alerta_punto,
levels = c(
"Alerta",
"Sin alerta"
)
)
)
tabla_kappa
## Local
## Global Alerta Sin alerta
## Alerta 11 20
## Sin alerta 20 256
Resultado elevado frente a toda la base y también inusual para su punto.
Elevado frente al conjunto general, pero relativamente habitual para ese punto.
No es extremo globalmente, pero sí es inusual para el comportamiento propio del punto.
No supera ninguno de los dos criterios estadísticos.
\[ P_o = \frac{ a+d }{ N } \]
N_kappa <- sum(
tabla_kappa
)
Po <- sum(
diag(
tabla_kappa
)
) /
N_kappa
Po
## [1] 0.8697068
Po * 100
## [1] 86.97068
Si la mayoría de los resultados pertenece a:
\[ \text{Sin alerta} \]
ambos criterios pueden coincidir frecuentemente simplemente porque esa categoría es dominante.
Por ello debemos estimar el acuerdo esperado.
\[ P_e = \sum_{j=1}^{k} P_{Aj}P_{Bj} \]
Para dos categorías:
\[ P_e = P_{A1}P_{B1} + P_{A2}P_{B2} \]
proporcion_global_k <- prop.table(
margin.table(
tabla_kappa,
1
)
)
proporcion_local_k <- prop.table(
margin.table(
tabla_kappa,
2
)
)
Pe <- sum(
proporcion_global_k *
proporcion_local_k
)
Pe
## [1] 0.8184384
\[ \kappa = \frac{ P_o-P_e }{ 1-P_e } \]
donde:
if (
is.finite(
Pe
) &&
(
1 -
Pe
) > 0
) {
kappa_mod4 <- (
Po -
Pe
) /
(
1 -
Pe
)
} else {
kappa_mod4 <- NA_real_
}
kappa_mod4
## [1] 0.2823749
resumen_kappa <- data.frame(
Medida = c(
"Acuerdo observado",
"Acuerdo esperado",
"Kappa de Cohen"
),
Valor = c(
Po,
Pe,
kappa_mod4
)
)
knitr::kable(
resumen_kappa,
digits = 4,
caption = "Medidas de concordancia"
)
| Medida | Valor |
|---|---|
| Acuerdo observado | 0.8697 |
| Acuerdo esperado | 0.8184 |
| Kappa de Cohen | 0.2824 |
| \(\kappa\) | Interpretación descriptiva |
|---|---|
| < 0 | Inferior al azar |
| 0.00–0.20 | Ligero |
| 0.21–0.40 | Bajo |
| 0.41–0.60 | Moderado |
| 0.61–0.80 | Sustancial |
| 0.81–1.00 | Muy alto |
Estos límites son orientativos y no deben utilizarse como fronteras absolutas.
Kappa puede verse afectado por:
Por ello siempre debe reportarse:
\[ \boxed{ \text{Tabla} + P_o + P_e + \kappa } \]
Para expresar la incertidumbre utilizaremos remuestreo.
calcular_kappa <- function(a, b) {
tabla <- table(
factor(
a,
levels = c(
"Alerta",
"Sin alerta"
)
),
factor(
b,
levels = c(
"Alerta",
"Sin alerta"
)
)
)
N <- sum(
tabla
)
if (
N == 0
) {
return(
NA_real_
)
}
Po_temp <- sum(
diag(
tabla
)
) /
N
pa <- prop.table(
margin.table(
tabla,
1
)
)
pb <- prop.table(
margin.table(
tabla,
2
)
)
Pe_temp <- sum(
pa *
pb
)
if (
!is.finite(
Pe_temp
) ||
(
1 -
Pe_temp
) <= 0
) {
return(
NA_real_
)
}
(
Po_temp -
Pe_temp
) /
(
1 -
Pe_temp
)
}
set.seed(
123
)
B_kappa <- 1000
kappa_boot <- replicate(
B_kappa,
{
indices <- sample(
seq_len(
nrow(
datos
)
),
size = nrow(
datos
),
replace = TRUE
)
calcular_kappa(
datos$evento_alerta[
indices
],
datos$evento_alerta_punto[
indices
]
)
}
)
kappa_boot <- kappa_boot[
is.finite(
kappa_boot
)
]
if (
length(
kappa_boot
) > 20
) {
IC_kappa <- quantile(
kappa_boot,
probs = c(
0.025,
0.975
),
na.rm = TRUE
)
IC_kappa
} else {
IC_kappa <- c(
NA_real_,
NA_real_
)
cat(
"No se obtuvieron suficientes remuestras válidas para estimar el intervalo de Kappa."
)
}
## 2.5% 97.5%
## 0.1175583 0.4470682
Un intervalo estrecho indica mayor precisión.
Un intervalo amplio indica que la verdadera concordancia compatible con los datos puede encontrarse en un rango considerable.
porcentaje_kappa <- prop.table(
tabla_kappa
) * 100
barplot(
porcentaje_kappa,
beside = TRUE,
col = c(
"#1596a5",
"#dcecef"
),
border = NA,
xlab = "Clasificación local",
ylab = "Porcentaje total",
main = "Concordancia entre criterios global y local"
)
legend(
"topright",
legend = rownames(
porcentaje_kappa
),
fill = c(
"#1596a5",
"#dcecef"
),
border = NA,
bty = "n",
title = "Clasificación global"
)
Aunque Kappa global resume la base completa, también podemos identificar qué sucede en cada punto.
acuerdo_por_punto <- do.call(
rbind,
lapply(
split(
datos,
datos$punto
),
function(df) {
validos <- !is.na(
df$evento_alerta
) &
!is.na(
df$evento_alerta_punto
)
if (
sum(
validos
) > 0
) {
acuerdo <- mean(
as.character(
df$evento_alerta[
validos
]
) ==
as.character(
df$evento_alerta_punto[
validos
]
)
)
} else {
acuerdo <- NA_real_
}
data.frame(
N = sum(
validos
),
Acuerdo = acuerdo,
Porcentaje =
acuerdo * 100
)
}
)
)
acuerdo_por_punto$Punto <- rownames(
acuerdo_por_punto
)
rownames(
acuerdo_por_punto
) <- NULL
acuerdo_por_punto <- acuerdo_por_punto[
order(
acuerdo_por_punto$Porcentaje,
decreasing = TRUE,
na.last = TRUE
),
]
knitr::kable(
acuerdo_por_punto,
digits = 2,
caption = "Concordancia entre criterios por punto"
)
| N | Acuerdo | Porcentaje | Punto | |
|---|---|---|---|---|
| 15 | 21 | 1.00 | 100.00 | PUNTO_7 |
| 17 | 22 | 0.95 | 95.45 | PUNTO_9 |
| 2 | 21 | 0.95 | 95.24 | PUNTO_16 |
| 5 | 22 | 0.91 | 90.91 | PUNTO_19 |
| 8 | 22 | 0.91 | 90.91 | PUNTO_23 |
| 10 | 11 | 0.91 | 90.91 | PUNTO_25 |
| 11 | 11 | 0.91 | 90.91 | PUNTO_26 |
| 1 | 21 | 0.90 | 90.48 | PUNTO_11 |
| 3 | 21 | 0.90 | 90.48 | PUNTO_17 |
| 4 | 21 | 0.90 | 90.48 | PUNTO_18 |
| 6 | 21 | 0.90 | 90.48 | PUNTO_20 |
| 7 | 21 | 0.90 | 90.48 | PUNTO_21 |
| 9 | 7 | 0.86 | 85.71 | PUNTO_24 |
| 12 | 6 | 0.83 | 83.33 | PUNTO_4 |
| 16 | 20 | 0.70 | 70.00 | PUNTO_8 |
| 14 | 20 | 0.65 | 65.00 | PUNTO_6 |
| 13 | 19 | 0.63 | 63.16 | PUNTO_5 |
Un porcentaje elevado indica que ambos criterios suelen producir la misma clasificación en esa ubicación.
Una concordancia menor indica que:
\[ P90_{\text{global}} \]
y:
\[ P90_j \]
están señalando aspectos distintos del comportamiento microbiológico.
Incluso si:
\[ \kappa=1 \]
solo podemos afirmar:
\[ \boxed{ \text{los dos criterios coinciden perfectamente} } \]
No podemos afirmar:
\[ \boxed{ \text{los dos criterios son normativamente correctos} } \]
porque ambos P90 son criterios estadísticos de priorización.
Si casi todas las observaciones son:
\[ \text{Sin alerta} \]
puede encontrarse:
\[ P_o \]
muy alto.
Sin embargo, una parte de esa coincidencia puede explicarse por el predominio de la categoría.
Kappa intenta corregir parcialmente este fenómeno mediante:
\[ P_e \]
| Medida | Pregunta |
|---|---|
| \(P_o\) | ¿Qué proporción coincide realmente? |
| \(P_e\) | ¿Qué acuerdo esperaríamos según las proporciones marginales? |
| \(\kappa\) | ¿Cuánto supera el acuerdo observado al esperado? |
| IC de \(\kappa\) | ¿Qué incertidumbre tiene la estimación? |
| Tabla | ¿Dónde ocurren los desacuerdos? |
| Acuerdo por punto | ¿En qué ubicaciones coinciden más los criterios? |
Una redacción apropiada sería:
La clasificación global y la clasificación específica del punto coincidieron en el \(\ldots\%\) de los resultados. El acuerdo esperado fue de \(\ldots\%\). El índice Kappa de Cohen fue \(\kappa=\ldots\), con un intervalo de confianza aproximado de \(\ldots\) a \(\ldots\). Estos resultados indican un nivel de concordancia \(\ldots\) entre ambos criterios.
La concordancia refleja hasta qué punto la identificación de resultados elevados es semejante cuando la referencia es toda la base o cuando se utiliza el comportamiento propio de cada punto. Los desacuerdos permiten detectar resultados que pueden ser moderados globalmente pero inusuales para una ubicación específica, así como resultados elevados globalmente que corresponden al comportamiento habitual de determinados puntos.
Un comportamiento habitualmente elevado no debe interpretarse como aceptable únicamente porque el resultado no supere el P90 específico del punto.
\[ \boxed{\text{Mismas observaciones}} \]
\[ \Downarrow \]
\[ \boxed{\text{Dos clasificaciones}} \]
\[ \Downarrow \]
\[ \boxed{\text{Tabla de concordancia}} \]
\[ \Downarrow \]
\[ \boxed{P_o} \]
\[ \Downarrow \]
\[ \boxed{P_e} \]
\[ \Downarrow \]
\[ \boxed{ \kappa= \frac{ P_o-P_e }{ 1-P_e } } \]
\[ \Downarrow \]
\[ \boxed{\text{IC de Kappa}} \]
\[ \Downarrow \]
\[ \boxed{\text{Revisión por punto}} \]
\[ \Downarrow \]
\[ \boxed{\text{Interpretación microbiológica}} \]
| Procedimiento | Estructura | Medida principal | Tamaño / magnitud | Aplicación |
|---|---|---|---|---|
| Mann–Whitney | Dos grupos independientes | \(U/W\) | \(r_{rb}\) | Comparar dos puntos |
| Wilcoxon | Dos mediciones pareadas | \(W\) | \(r_{rb}\) | Comparar los mismos puntos entre periodos |
| Kappa | Dos clasificaciones | \(\kappa\) | \(\kappa\) + IC | Comparar criterios global y local |
Si tenemos:
\[ \boxed{ \text{dos grupos diferentes e independientes} } \]
utilizamos:
\[ \boxed{\text{Mann–Whitney}} \]
Si tenemos:
\[ \boxed{ \text{las mismas unidades en dos condiciones} } \]
utilizamos:
\[ \boxed{\text{Wilcoxon signed-rank}} \]
Si tenemos:
\[ \boxed{ \text{dos clasificaciones de las mismas unidades} } \]
utilizamos:
\[ \boxed{\text{Kappa}} \]
Nunca debemos concluir solamente con:
\[ p<0.05 \]
Para Mann–Whitney debemos integrar:
\[ p+r_{rb}+\text{distribuciones} \]
Para Wilcoxon:
\[ p+r_{rb}+\text{diferencias} \]
Para Kappa:
\[ P_o+P_e+\kappa+IC \]
La inferencia estadística responde:
¿Existe evidencia de una diferencia o concordancia?
La interpretación microbiológica debe responder:
¿Qué representa esa diferencia o concordancia para el comportamiento de los puntos y la vigilancia microbiológica?
Ambas interpretaciones son necesarias.
Los métodos de rangos presentan menor sensibilidad a valores extremos que los métodos basados directamente en medias.
Sin embargo, un resultado extremo no debe ignorarse.
Puede representar:
Debe revisarse su trazabilidad.
Mann–Whitney debe acompañarse de:
Wilcoxon debe acompañarse de:
Kappa debe acompañarse de:
Cada estudiante o grupo trabajará con el periodo asignado.
Para Mann–Whitney deberá:
Para Wilcoxon deberá:
Para Kappa deberá:
1. Los métodos no paramétricos también poseen condiciones de aplicación.
2. Los rangos reducen la dependencia directa de la escala original.
3. Mann–Whitney compara dos grupos independientes.
4. Wilcoxon analiza diferencias dentro de pares.
5. El pareamiento debe respetar las mismas unidades de análisis.
6. El valor p no mide la magnitud de una diferencia.
7. Los tamaños del efecto complementan las pruebas no paramétricas.
8. Kappa cuantifica concordancia corregida por el acuerdo esperado.
9. Un porcentaje elevado de acuerdo no garantiza un Kappa elevado.
10. Concordancia no significa validez.
11. El punto de muestreo debe permanecer visible en la interpretación.
12. Toda conclusión debe integrar estadística y significado microbiológico.
Blair, R. C., & Taylor, R. A. (2008). Bioestadística. Pearson Educación.
Bermúdez, J. D. (2013). Diez lecciones de estadística básica. Universitat de València.
Díaz Portillo, J. (2011). Guía práctica del curso de Bioestadística aplicada a las Ciencias de la Salud. Instituto Nacional de Gestión Sanitaria.
Yousefi, M., Najafi Saleh, H., Yaseri, M., Mahvi, A. H., Soleimani, H., Saeedi, Z., Zohdi, S., & Mohammadi, A. A. (2018). Data on microbiological quality assessment of rural drinking water supplies in Poldasht county. Data in Brief, 17, 763–769.