Se investiga si la concentración de croscarmelosa sódica modifica el tiempo de desintegración de comprimidos de paracetamol de 500 mg. Se comparan cuatro concentraciones (1 %, 2 %, 3 % y 4 %), con tres lotes experimentales independientes por concentración. La asignación de concentraciones a las unidades experimentales se considera aleatoria. Las demás condiciones de fabricación se mantienen controladas.
Hipótesis del ANOVA: H₀: las cuatro medias poblacionales son iguales; H₁: al menos una difiere. Nivel de significancia: α = 0.05.
Importante: todos los datos son simulados. El tiempo menor no define por sí solo la formulación óptima: deben evaluarse especificaciones, disolución, friabilidad y otros atributos pertinentes.
datos <- data.frame(
Concentracion = factor(rep(c('1 %','2 %','3 %','4 %'), each=3), levels=c('1 %','2 %','3 %','4 %')),
Lote_independiente = rep(paste0('Replica ',1:3), times=4),
Tiempo_min = c(12.1,11.6,12.5, 9.8,9.1,9.5, 7.6,7.0,7.4, 5.7,5.2,5.5)
)
knitr::kable(datos, digits=2, caption='Datos ficticios: una respuesta por lote experimental independiente')| Concentracion | Lote_independiente | Tiempo_min |
|---|---|---|
| 1 % | Replica 1 | 12.1 |
| 1 % | Replica 2 | 11.6 |
| 1 % | Replica 3 | 12.5 |
| 2 % | Replica 1 | 9.8 |
| 2 % | Replica 2 | 9.1 |
| 2 % | Replica 3 | 9.5 |
| 3 % | Replica 1 | 7.6 |
| 3 % | Replica 2 | 7.0 |
| 3 % | Replica 3 | 7.4 |
| 4 % | Replica 1 | 5.7 |
| 4 % | Replica 2 | 5.2 |
| 4 % | Replica 3 | 5.5 |
descriptivos <- datos %>% group_by(Concentracion) %>% summarise(
n=n(), Media=mean(Tiempo_min), Mediana=median(Tiempo_min),
DE=sd(Tiempo_min), Varianza=var(Tiempo_min),
Minimo=min(Tiempo_min), Maximo=max(Tiempo_min),
CV_porcentaje=100*sd(Tiempo_min)/mean(Tiempo_min),
EE=sd(Tiempo_min)/sqrt(n()), .groups='drop'
)
knitr::kable(descriptivos, digits=3, caption='Descriptivos por concentración')| Concentracion | n | Media | Mediana | DE | Varianza | Minimo | Maximo | CV_porcentaje | EE |
|---|---|---|---|---|---|---|---|---|---|
| 1 % | 3 | 12.067 | 12.1 | 0.451 | 0.203 | 11.6 | 12.5 | 3.737 | 0.260 |
| 2 % | 3 | 9.467 | 9.5 | 0.351 | 0.123 | 9.1 | 9.8 | 3.710 | 0.203 |
| 3 % | 3 | 7.333 | 7.4 | 0.306 | 0.093 | 7.0 | 7.6 | 4.166 | 0.176 |
| 4 % | 3 | 5.467 | 5.5 | 0.252 | 0.063 | 5.2 | 5.7 | 4.604 | 0.145 |
El CV expresa la variabilidad relativa; la DE mide dispersión en minutos; el EE cuantifica la precisión de la media muestral.
ggplot(datos, aes(Concentracion, Tiempo_min, fill=Concentracion)) +
geom_boxplot(width=.55, alpha=.48, outlier.shape=NA) +
geom_jitter(width=.09, height=0, size=3.8, shape=21, color='#1B2631', alpha=.95) +
scale_fill_manual(values=colores) +
labs(title='Distribución del tiempo de desintegración', subtitle='Cada punto es un lote experimental independiente', x='Croscarmelosa sódica', y='Tiempo de desintegración (min)') +
tema + theme(legend.position='none')ggplot(descriptivos, aes(Concentracion, Media, fill=Concentracion)) +
geom_col(width=.65, alpha=.85) +
geom_errorbar(aes(ymin=pmax(0,Media-DE), ymax=Media+DE), width=.18, linewidth=.85) +
geom_text(aes(label=sprintf('%.2f min',Media)), vjust=-.8, fontface='bold', size=4.5) +
scale_fill_manual(values=colores) +
scale_y_continuous(limits=c(0,max(descriptivos$Media+descriptivos$DE)+2),expand=c(0,0)) +
labs(title='Tiempo medio de desintegración ± DE', x='Concentración de desintegrante', y='Tiempo medio (min)') +
tema + theme(legend.position='none')ggplot(descriptivos, aes(Concentracion, Media, group=1)) +
geom_line(color='#2C3E50',linewidth=1.1) +
geom_point(aes(color=Concentracion),size=4) +
geom_text(aes(label=sprintf('%.2f',Media)), vjust=-1.0,fontface='bold') +
scale_color_manual(values=colores) +
labs(title='Tendencia descriptiva del tiempo medio', x='Concentración de croscarmelosa sódica', y='Tiempo medio (min)') +
tema + theme(legend.position='none')modelo <- aov(Tiempo_min ~ Concentracion, data=datos)
tabla_anova <- as.data.frame(summary(modelo)[[1]])
tabla_anova$Fuente <- trimws(rownames(tabla_anova))
tabla_anova <- tabla_anova[,c('Fuente','Df','Sum Sq','Mean Sq','F value','Pr(>F)')]
names(tabla_anova) <- c('Fuente','GL','SC','CM','F','p')
knitr::kable(tabla_anova, digits=5, caption='Tabla ANOVA del DCA')| Fuente | GL | SC | CM | F | p | |
|---|---|---|---|---|---|---|
| Concentracion | Concentracion | 3 | 72.57000 | 24.19000 | 200.1931 | 0 |
| Residuals | Residuals | 8 | 0.96667 | 0.12083 | NA | NA |
p_anova <- tabla_anova$p[match('Concentracion', tabla_anova$Fuente)]
cat('**Interpretación:** p =', formato_p(p_anova),'. ',
if(p_anova<alpha) 'Se rechaza H₀: al menos una media de desintegración difiere.' else 'No se rechaza H₀: no se detectan diferencias suficientes entre medias.', '\n')## **Interpretación:** p = < 0.0001 . Se rechaza H₀: al menos una media de desintegración difiere.
sc_factor <- tabla_anova$SC[match('Concentracion',tabla_anova$Fuente)]
sc_error <- tabla_anova$SC[match('Residuals',tabla_anova$Fuente)]
eta2 <- sc_factor/(sc_factor+sc_error)
knitr::kable(data.frame(Medida='Eta cuadrado (η²)',Valor=eta2),digits=4)| Medida | Valor |
|---|---|
| Eta cuadrado (η²) | 0.9869 |
Interpretación: η² describe la proporción de variabilidad total de la respuesta asociada al factor en este conjunto de datos; no equivale a una prueba de significancia ni garantiza relevancia clínica o farmacotécnica.
H₀: los errores tienen distribución normal. H₁: no tienen distribución normal.
residuos <- residuals(modelo)
normalidad <- shapiro.test(residuos)
knitr::kable(data.frame(Prueba='Shapiro-Wilk',W=unname(normalidad$statistic),p=normalidad$p.value),digits=5)| Prueba | W | p |
|---|---|---|
| Shapiro-Wilk | 0.93113 | 0.39219 |
cat('**Interpretación:** p =',formato_p(normalidad$p.value),'. ',
if(normalidad$p.value<alpha) 'Se rechaza H₀: existe evidencia contra la normalidad de los residuos.' else 'No se rechaza H₀: no hay evidencia suficiente contra la normalidad de los residuos.', '\n')## **Interpretación:** p = 0.3922 . No se rechaza H₀: no hay evidencia suficiente contra la normalidad de los residuos.
ggplot(data.frame(Residuos=residuos),aes(sample=Residuos)) +
stat_qq(color='#2471A3',size=3) + stat_qq_line(color='#E74C3C',linewidth=1) +
labs(title='Gráfico Q-Q de los residuos',x='Cuantiles teóricos',y='Cuantiles observados') + temaH₀: las varianzas son iguales. H₁: al menos una varianza difiere.
lev <- car::leveneTest(Tiempo_min ~ Concentracion,data=datos,center=median)
knitr::kable(as.data.frame(lev),digits=5,caption='Prueba de Levene')| Df | F value | Pr(>F) | |
|---|---|---|---|
| group | 3 | 0.22013 | 0.87977 |
| 8 | NA | NA |
p_levene <- as.numeric(lev[1,'Pr(>F)'])
cat('**Interpretación:** p =',formato_p(p_levene),'. ',
if(p_levene<alpha) 'Se rechaza H₀: evidencia de varianzas diferentes.' else 'No se rechaza H₀: no se detectan diferencias suficientes entre varianzas.', '\n')## **Interpretación:** p = 0.8798 . No se rechaza H₀: no se detectan diferencias suficientes entre varianzas.
diagnostico <- data.frame(Ajustados=fitted(modelo),Residuos=residuos,Concentracion=datos$Concentracion)
ggplot(diagnostico,aes(Ajustados,Residuos,color=Concentracion)) +
geom_hline(yintercept=0,linetype='dashed',color='#C0392B') +
geom_point(size=3.8,alpha=.9) + scale_color_manual(values=colores) +
labs(title='Residuos frente a valores ajustados',x='Tiempo ajustado (min)',y='Residuo (min)',color='Concentración') + temaLa independencia no se demuestra con Shapiro-Wilk ni con Levene. Depende de que los tres lotes por tratamiento sean unidades experimentales independientes, que la asignación haya sido aleatoria y que no exista pseudorreplicación. El código no puede verificar cómo se fabricaron los lotes.
tukey <- TukeyHSD(modelo,which='Concentracion')
tabla_tukey <- data.frame(Comparacion=rownames(tukey$Concentracion),tukey$Concentracion,row.names=NULL,check.names=FALSE)
knitr::kable(tabla_tukey,digits=5,caption='Comparaciones de Tukey (IC 95 % y p ajustado)')| Comparacion | diff | lwr | upr | p adj |
|---|---|---|---|---|
| 2 %-1 % | -2.60000 | -3.50890 | -1.69110 | 0.00008 |
| 3 %-1 % | -4.73333 | -5.64223 | -3.82443 | 0.00000 |
| 4 %-1 % | -6.60000 | -7.50890 | -5.69110 | 0.00000 |
| 3 %-2 % | -2.13333 | -3.04223 | -1.22443 | 0.00031 |
| 4 %-2 % | -4.00000 | -4.90890 | -3.09110 | 0.00000 |
| 4 %-3 % | -1.86667 | -2.77557 | -0.95777 | 0.00079 |
Interpretación: una comparación es significativa cuando el p ajustado es menor que 0.05 o el intervalo de confianza de la diferencia no contiene cero. Tukey se interpreta bajo los supuestos del modelo ANOVA.
kw <- kruskal.test(Tiempo_min ~ Concentracion,data=datos)
knitr::kable(data.frame(Prueba='Kruskal-Wallis',Estadistico=unname(kw$statistic),GL=unname(kw$parameter),p=kw$p.value),digits=5)| Prueba | Estadistico | GL | p |
|---|---|---|---|
| Kruskal-Wallis | 10.38462 | 3 | 0.01556 |
cat('**Interpretación:** p =',formato_p(kw$p.value),'. ',
if(kw$p.value<alpha) 'Se rechaza H₀: hay evidencia de diferencias en la distribución de las respuestas entre concentraciones.' else 'No se rechaza H₀: no se detectan diferencias suficientes entre concentraciones.', '\n')## **Interpretación:** p = 0.0156 . Se rechaza H₀: hay evidencia de diferencias en la distribución de las respuestas entre concentraciones.
Si procede una comparación post hoc no paramétrica, pueden explorarse pruebas de Wilcoxon por pares con ajuste de Holm (exploratorias, dada la muestra pequeña):
posthoc_kw <- pairwise.wilcox.test(datos$Tiempo_min,datos$Concentracion,p.adjust.method='holm',exact=FALSE)
knitr::kable(as.data.frame(as.table(posthoc_kw$p.value)),digits=5,caption='Wilcoxon por pares: p ajustados por Holm (NA = comparación no definida en esa mitad de la matriz)')| Var1 | Var2 | Freq |
|---|---|---|
| 2 % | 1 % | 0.48513 |
| 3 % | 1 % | 0.48513 |
| 4 % | 1 % | 0.48513 |
| 2 % | 2 % | NA |
| 3 % | 2 % | 0.48513 |
| 4 % | 2 % | 0.48513 |
| 2 % | 3 % | NA |
| 3 % | 3 % | NA |
| 4 % | 3 % | 0.48513 |
resumen <- data.frame(
Prueba=c('ANOVA: concentración','Shapiro-Wilk: residuos','Levene: igualdad de varianzas','Kruskal-Wallis'),
p=c(p_anova,normalidad$p.value,p_levene,kw$p.value)
)
resumen$Decision <- ifelse(resumen$p<alpha,'Rechazar H0','No rechazar H0')
knitr::kable(resumen,digits=5,caption='Resumen de contrastes (α = 0.05)')| Prueba | p | Decision |
|---|---|---|
| ANOVA: concentración | 0.00000 | Rechazar H0 |
| Shapiro-Wilk: residuos | 0.39219 | No rechazar H0 |
| Levene: igualdad de varianzas | 0.87977 | No rechazar H0 |
| Kruskal-Wallis | 0.01556 | Rechazar H0 |
menor <- descriptivos[which.min(descriptivos$Media),]
mayor <- descriptivos[which.max(descriptivos$Media),]
cat('- La concentración con menor tiempo medio observado fue **',as.character(menor$Concentracion),'** (',sprintf('%.2f',menor$Media),' min).\n',sep='')cat('- La concentración con mayor tiempo medio observado fue **',as.character(mayor$Concentracion),'** (',sprintf('%.2f',mayor$Media),' min).\n',sep='')cat('- El ANOVA ',if(p_anova<alpha) 'detectó' else 'no detectó',' diferencias estadísticamente significativas entre las medias (p ',formato_p(p_anova),').\n',sep='')cat('- Shapiro-Wilk ',if(normalidad$p.value<alpha) 'cuestiona' else 'no cuestiona estadísticamente',' la normalidad de los residuos al nivel α = 0.05.\n',sep='')cat('- Levene ',if(p_levene<alpha) 'detectó diferencias' else 'no detectó diferencias estadísticamente significativas',' entre las varianzas por concentración.\n',sep='')cat('- Kruskal-Wallis ',if(kw$p.value<alpha) 'detectó' else 'no detectó',' diferencias estadísticamente significativas entre grupos.\n',sep='')Alcance: estos datos son ficticios y no constituyen evidencia de desempeño real. Una formulación no debe seleccionarse únicamente por reducir el tiempo de desintegración: deben considerarse criterios de aceptación definidos para el producto, disolución, friabilidad y otros atributos de calidad. Para un estudio real se requiere documentar la aleatorización y la independencia de los lotes experimentales.