Con Mi Profe: Julio Hurtado Marquez; EMAIL_TAREAS: juliohurtado210307@gmail.com
Aquí tienes la solución completa en Markdown para los ejemplos de diseños factoriales, integrando el análisis teórico con los códigos en R y Python.
Es frecuente que en muchos procesos existan varios factores de los que es necesario investigar de manera simultánea su influencia sobre una o varias variables de respuesta. Los diseños experimentales que permiten estudiar de manera simultánea el efecto de varios factores son los llamados diseños factoriales.
El objetivo de un diseño factorial es estudiar el efecto de varios factores sobre una o varias respuestas y determinar una combinación de niveles de ellos en la cual el desempeño del proceso sea mejor que en las condiciones de operación actuales.
📌 Definición: Un diseño de experimentos factorial o arreglo factorial es el conjunto de puntos experimentales o tratamientos que pueden formarse considerando todas las posibles combinaciones de los niveles de los factores.
Supongamos que se tienen dos factores A: tiempo y B: velocidad, cada uno con dos niveles (bajo y alto):
La respuesta de interés (Y) es la cantidad de aditivo. En la tabla se muestran los cuatro tratamientos del diseño factorial \(2^2\) con los resultados de la primera réplica.
| A: Tiempo | B: Velocidad | Y | Notación |
|---|---|---|---|
| A₁ (3 min) | B₁ (600 rpm) | 17.10 | (1) |
| A₂ (6 min) | B₁ (600 rpm) | 16.26 | a |
| A₁ (3 min) | B₂ (1000 rpm) | 18.76 | b |
| A₂ (6 min) | B₂ (1000 rpm) | 18.16 | ab |
Efecto principal de A (tiempo):
\[Efecto\ A = \frac{16.26 + 18.16}{2} - \frac{17.10 + 18.76}{2} = \frac{34.42}{2} - \frac{35.86}{2} = 17.21 - 17.93 = -0.72\]
Efecto principal de B (velocidad):
\[Efecto\ B = \frac{18.76 + 18.16}{2} - \frac{17.10 + 16.26}{2} = \frac{34.92}{2} - \frac{33.36}{2} = 17.46 - 16.68 = 0.78 \text{ (corregido: } 1.78\text{)}\]
Nota: El cálculo correcto del efecto B es: \[Efecto\ B = \frac{18.76 + 18.16}{2} - \frac{17.10 + 16.26}{2} = 18.46 - 16.68 = 1.78\]
Efecto de interacción AB:
\[AB = \frac{17.10 + 18.16}{2} - \frac{16.26 + 18.76}{2} = \frac{35.26}{2} - \frac{35.02}{2} = 17.63 - 17.51 = 0.12\]
# ============================================
# EJEMPLO: DISEÑO FACTORIAL 2^2
# ============================================
# Datos
tiempo <- factor(c("A1", "A2", "A1", "A2"))
velocidad <- factor(c("B1", "B1", "B2", "B2"))
respuesta <- c(17.10, 16.26, 18.76, 18.16)
datos <- data.frame(tiempo, velocidad, respuesta)
# Modelo ANOVA
modelo <- aov(respuesta ~ tiempo * velocidad, data = datos)
summary(modelo)
# Cálculo manual de efectos
Y_A1_B1 <- 17.10
Y_A2_B1 <- 16.26
Y_A1_B2 <- 18.76
Y_A2_B2 <- 18.16
efecto_A <- (Y_A2_B1 + Y_A2_B2)/2 - (Y_A1_B1 + Y_A1_B2)/2
efecto_B <- (Y_A1_B2 + Y_A2_B2)/2 - (Y_A1_B1 + Y_A2_B1)/2
efecto_AB <- (Y_A1_B1 + Y_A2_B2)/2 - (Y_A1_B2 + Y_A2_B1)/2
cat("Efecto de A (tiempo):", round(efecto_A, 2), "\n")
cat("Efecto de B (velocidad):", round(efecto_B, 2), "\n")
cat("Efecto de interacción AB:", round(efecto_AB, 2), "\n")
# Gráfico de interacción
interaction.plot(tiempo, velocidad, respuesta,
type = "b", col = c("steelblue", "red"),
xlab = "Tiempo", ylab = "Respuesta",
main = "Gráfico de interacción para diseño 2^2")# ============================================
# EJEMPLO: DISEÑO FACTORIAL 2^2
# ============================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.formula.api import ols
# Datos
datos = pd.DataFrame({
'tiempo': ['A1', 'A2', 'A1', 'A2'],
'velocidad': ['B1', 'B1', 'B2', 'B2'],
'respuesta': [17.10, 16.26, 18.76, 18.16]
})
# Modelo ANOVA
modelo = ols('respuesta ~ C(tiempo) * C(velocidad)', data=datos).fit()
anova_table = sm.stats.anova_lm(modelo, typ=2)
print("=== ANOVA DISEÑO FACTORIAL 2^2 ===")
print(anova_table)
# Cálculo manual de efectos
Y_A1_B1, Y_A2_B1, Y_A1_B2, Y_A2_B2 = 17.10, 16.26, 18.76, 18.16
efecto_A = (Y_A2_B1 + Y_A2_B2)/2 - (Y_A1_B1 + Y_A1_B2)/2
efecto_B = (Y_A1_B2 + Y_A2_B2)/2 - (Y_A1_B1 + Y_A2_B1)/2
efecto_AB = (Y_A1_B1 + Y_A2_B2)/2 - (Y_A1_B2 + Y_A2_B1)/2
print(f"\nEfecto de A (tiempo): {efecto_A:.2f}")
print(f"Efecto de B (velocidad): {efecto_B:.2f}")
print(f"Efecto de interacción AB: {efecto_AB:.2f}")
# Gráfico de interacción
plt.figure(figsize=(8, 5))
for vel in ['B1', 'B2']:
subdatos = datos[datos['velocidad'] == vel]
plt.plot(subdatos['tiempo'], subdatos['respuesta'], 'o-',
label=f'Velocidad {vel}')
plt.xlabel('Tiempo')
plt.ylabel('Respuesta')
plt.title('Gráfico de interacción para diseño 2^2')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()| Fuente | SC | gl | CM | F₀ | Valor-p |
|---|---|---|---|---|---|
| Tiempo (A) | 0.5184 | 1 | 0.5184 | - | - |
| Velocidad (B) | 3.1684 | 1 | 3.1684 | - | - |
| Interacción AB | 0.0144 | 1 | 0.0144 | - | - |
| Error | - | 0 | - | - | - |
Nota: Con una sola réplica no es posible estimar el error experimental, por lo que no se pueden calcular los valores F ni los valores-p. Se requieren al menos 2 réplicas para estimar la varianza del error.
Se quiere estudiar el efecto de los factores velocidad de alimentación y profundidad de corte sobre el acabado de un metal. Se decide correr un factorial completo \(4 \times 3\) con tres réplicas.
Factores:
| A: Profundidad | B: Velocidad | Y_{i··} | ||
|---|---|---|---|---|
| 0.20 | 0.25 | 0.30 | ||
| 0.15 | 74 | 92 | 99 | 763 |
| 64 | 86 | 98 | ||
| 60 | 88 | 102 | ||
| 0.18 | 79 | 98 | 104 | 808 |
| 68 | 104 | 99 | ||
| 73 | 88 | 95 | ||
| 0.21 | 82 | 99 | 108 | 881 |
| 88 | 108 | 110 | ||
| 92 | 95 | 99 | ||
| 0.24 | 99 | 104 | 114 | 944 |
| 104 | 110 | 111 | ||
| 96 | 99 | 107 | ||
| Y_{·j·} | 979 | 1171 | 1246 | 3396 |
Suma total de cuadrados:
\[SCT = \sum Y_{ijk}^2 - C = 326888 - 320356 = 6532.0\]
Suma de cuadrados de A (profundidad):
\[SCA = \frac{763^2 + 808^2 + 881^2 + 944^2}{3 \times 3} - C\] \[SCA = \frac{581969 + 652864 + 776161 + 891136}{9} - 320356\] \[SCA = \frac{2902130}{9} - 320356 = 322458.9 - 320356 = 2125.1\]
Suma de cuadrados de B (velocidad):
\[SCB = \frac{979^2 + 1171^2 + 1246^2}{4 \times 3} - C\] \[SCB = \frac{958441 + 1371241 + 1552516}{12} - 320356\] \[SCB = \frac{3882198}{12} - 320356 = 323516.5 - 320356 = 3160.5\]
Suma de cuadrados de interacción AB:
\[SCAB = \sum \frac{Y_{ij\cdot}^2}{n} - C - SCA - SCB\]
Los totales \(Y_{ij\cdot}\) son: - (0.15,0.20): 74+64+60=198 - (0.15,0.25): 92+86+88=266 - (0.15,0.30): 99+98+102=299 - (0.18,0.20): 79+68+73=220 - (0.18,0.25): 98+104+88=290 - (0.18,0.30): 104+99+95=298 - (0.21,0.20): 82+88+92=262 - (0.21,0.25): 99+108+95=302 - (0.21,0.30): 108+110+99=317 - (0.24,0.20): 99+104+96=299 - (0.24,0.25): 104+110+99=313 - (0.24,0.30): 114+111+107=332
\[\sum \frac{Y_{ij\cdot}^2}{3} = \frac{198^2+266^2+299^2+220^2+290^2+298^2+262^2+302^2+317^2+299^2+313^2+332^2}{3}\] \[= \frac{39204+70756+89401+48400+84100+88804+68644+91204+100489+89401+97969+110224}{3}\] \[= \frac{967596}{3} = 322532\]
\[SCAB = 322532 - 320356 - 2125.1 - 3160.5 = 557.07\]
Suma de cuadrados del error:
\[SCE = SCT - SCA - SCB - SCAB = 6532.0 - 2125.1 - 3160.5 - 557.07 = 689.33\]
| Fuente | SC | gl | CM | F₀ | Valor-p |
|---|---|---|---|---|---|
| B: Velocidad | 3160.5 | 2 | 1580.25 | 55.02 | 0.0000 |
| A: Profundidad | 2125.1 | 3 | 708.37 | 24.66 | 0.0000 |
| AB: Interacción | 557.07 | 6 | 92.84 | 3.23 | 0.018 |
| Error | 689.33 | 24 | 28.72 | - | - |
| Total | 6532.0 | 35 | - | - | - |
Para comparar los niveles de profundidad dentro de un nivel específico de velocidad (por ejemplo, velocidad = 0.25):
Medias muestrales en velocidad 0.25: - Profundidad 0.15: \(\bar{Y}_{12\cdot} = 266/3 = 88.67\) - Profundidad 0.18: \(\bar{Y}_{22\cdot} = 290/3 = 96.67\) - Profundidad 0.21: \(\bar{Y}_{32\cdot} = 302/3 = 100.67\) - Profundidad 0.24: \(\bar{Y}_{42\cdot} = 313/3 = 104.33\)
Cálculo de LSD: \[LSD = t_{\alpha/2, gl_{error}} \cdot \sqrt{\frac{2 \cdot CME}{n}} = t_{0.025,24} \cdot \sqrt{\frac{2 \times 28.72}{3}}\]
\[t_{0.025,24} = 2.064\] \[LSD = 2.064 \cdot \sqrt{\frac{57.44}{3}} = 2.064 \cdot \sqrt{19.147} = 2.064 \cdot 4.376 = 9.03\]
Comparaciones: - |88.67 - 96.67| = 8.00 < 9.03 → No significativa - |88.67 - 100.67| = 12.00 > 9.03 → Significativa - |88.67 - 104.33| = 15.66 > 9.03 → Significativa - |96.67 - 100.67| = 4.00 < 9.03 → No significativa - |96.67 - 104.33| = 7.66 < 9.03 → No significativa - |100.67 - 104.33| = 3.66 < 9.03 → No significativa
Conclusión: En el nivel de velocidad intermedia (0.25), las tres profundidades mayores (0.18, 0.21, 0.24) son estadísticamente iguales, mientras que la profundidad menor (0.15) es diferente.
# ============================================
# EJEMPLO: DISEÑO FACTORIAL 4x3
# ============================================
# Crear datos
profundidad <- rep(c(0.15, 0.18, 0.21, 0.24), each = 9)
velocidad <- rep(rep(c(0.20, 0.25, 0.30), each = 3), times = 4)
acabado <- c(74, 64, 60, 92, 86, 88, 99, 98, 102, # A=0.15
79, 68, 73, 98, 104, 88, 104, 99, 95, # A=0.18
82, 88, 92, 99, 108, 95, 108, 110, 99, # A=0.21
99, 104, 96, 104, 110, 99, 114, 111, 107) # A=0.24
datos <- data.frame(profundidad = factor(profundidad),
velocidad = factor(velocidad),
acabado)
# Estadísticos descriptivos
library(dplyr)
medias_prof <- datos %>% group_by(profundidad) %>% summarise(media = mean(acabado))
medias_vel <- datos %>% group_by(velocidad) %>% summarise(media = mean(acabado))
cat("=== MEDIAS POR PROFUNDIDAD ===\n")
print(medias_prof)
cat("\n=== MEDIAS POR VELOCIDAD ===\n")
print(medias_vel)
# ANOVA factorial
modelo <- aov(acabado ~ profundidad * velocidad, data = datos)
summary(modelo)
# Gráfico de interacción
interaction.plot(datos$profundidad, datos$velocidad, datos$acabado,
type = "b", col = 1:3, xlab = "Profundidad",
ylab = "Acabado medio", main = "Interacción Profundidad × Velocidad")
legend("topleft", legend = c("Velocidad 0.20", "Velocidad 0.25", "Velocidad 0.30"),
col = 1:3, lty = 1, bty = "n")
# Cálculo manual de sumas de cuadrados
a <- 4; b <- 3; n <- 3
N <- a * b * n
Y_total <- 3396
correccion <- Y_total^2 / N
# Suma total de cuadrados
SCT <- sum(acabado^2) - correccion
# Suma de cuadrados de A (profundidad)
Y_A <- tapply(acabado, profundidad, sum)
SCA <- sum(Y_A^2) / (b * n) - correccion
# Suma de cuadrados de B (velocidad)
Y_B <- tapply(acabado, velocidad, sum)
SCB <- sum(Y_B^2) / (a * n) - correccion
# Suma de cuadrados de la interacción AB
Y_AB <- tapply(acabado, list(profundidad, velocidad), sum)
SCAB <- sum(Y_AB^2) / n - correccion - SCA - SCB
# Suma de cuadrados del error
SCE <- SCT - SCA - SCB - SCAB
cat("\n=== CÁLCULO MANUAL DE SUMAS DE CUADRADOS ===\n")
cat("SCA =", round(SCA, 2), "\n")
cat("SCB =", round(SCB, 2), "\n")
cat("SCAB =", round(SCAB, 2), "\n")
cat("SCE =", round(SCE, 2), "\n")
cat("SCT =", round(SCT, 2), "\n")
# Comparaciones múltiples (LSD) con interacción
CME <- SCE / (a * b * (n - 1))
gl_error <- a * b * (n - 1)
t_crit <- qt(0.975, gl_error)
# LSD para comparar niveles de A dentro de un nivel específico de B
# Por ejemplo, para velocidad = 0.25 (B2)
Y_AB_B2 <- Y_AB[, 2] # Velocidad 0.25 (segunda columna)
medias_B2 <- Y_AB_B2 / n
LSD_B2 <- t_crit * sqrt(2 * CME / n)
cat("\n=== COMPARACIONES MÚLTIPLES (LSD) ===\n")
cat("CME =", round(CME, 2), "\n")
cat("t crítico =", round(t_crit, 4), "\n")
cat("LSD para comparar profundidades en velocidad 0.25 =", round(LSD_B2, 2), "\n")
cat("Medias en velocidad 0.25:\n")
for(i in 1:4) {
cat(sprintf(" Profundidad %.2f: %.2f\n", as.numeric(levels(datos$profundidad)[i]), medias_B2[i]))
}
# Gráfico con intervalos LSD
library(ggplot2)
medias_AB <- data.frame(
profundidad = rep(as.numeric(levels(datos$profundidad)), 3),
velocidad = rep(c("0.20", "0.25", "0.30"), each = 4),
media = c(rowMeans(matrix(Y_AB[,1], nrow=4)), medias_B2, rowMeans(matrix(Y_AB[,3], nrow=4))),
error = rep(sqrt(CME / n) * t_crit, 3)
)
ggplot(medias_AB, aes(x = profundidad, y = media, color = velocidad)) +
geom_point(size = 3) +
geom_line() +
geom_errorbar(aes(ymin = media - error, ymax = media + error), width = 0.02) +
labs(title = "Perfil de interacción con intervalos LSD",
x = "Profundidad", y = "Acabado medio") +
theme_minimal()# ============================================
# EJEMPLO: DISEÑO FACTORIAL 4x3
# ============================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.formula.api import ols
from scipy.stats import f, t
# Crear datos
profundidad = [0.15]*9 + [0.18]*9 + [0.21]*9 + [0.24]*9
velocidad = [0.20]*3 + [0.25]*3 + [0.30]*3
velocidad = velocidad * 4
acabado = [74, 64, 60, 92, 86, 88, 99, 98, 102, # A=0.15
79, 68, 73, 98, 104, 88, 104, 99, 95, # A=0.18
82, 88, 92, 99, 108, 95, 108, 110, 99, # A=0.21
99, 104, 96, 104, 110, 99, 114, 111, 107] # A=0.24
datos = pd.DataFrame({
'profundidad': profundidad,
'velocidad': velocidad,
'acabado': acabado
})
datos['profundidad'] = datos['profundidad'].astype('category')
datos['velocidad'] = datos['velocidad'].astype('category')
# Estadísticos descriptivos
print("=== MEDIAS POR PROFUNDIDAD ===")
print(datos.groupby('profundidad')['acabado'].mean())
print("\n=== MEDIAS POR VELOCIDAD ===")
print(datos.groupby('velocidad')['acabado'].mean())
# ANOVA factorial
modelo = ols('acabado ~ C(profundidad) * C(velocidad)', data=datos).fit()
anova_table = sm.stats.anova_lm(modelo, typ=2)
print("\n=== ANOVA FACTORIAL 4x3 ===")
print(anova_table)
# Cálculo manual de sumas de cuadrados
a, b, n = 4, 3, 3
N = a * b * n
Y_total = 3396
correccion = Y_total**2 / N
# Suma total de cuadrados
SCT = np.sum(np.array(acabado)**2) - correccion
# Suma de cuadrados de A (profundidad)
Y_A = datos.groupby('profundidad')['acabado'].sum().values
SCA = np.sum(Y_A**2) / (b * n) - correccion
# Suma de cuadrados de B (velocidad)
Y_B = datos.groupby('velocidad')['acabado'].sum().values
SCB = np.sum(Y_B**2) / (a * n) - correccion
# Suma de cuadrados de interacción AB
Y_AB = datos.groupby(['profundidad', 'velocidad'])['acabado'].sum().values
SCAB = np.sum(Y_AB**2) / n - correccion - SCA - SCB
# Suma de cuadrados del error
SCE = SCT - SCA - SCB - SCAB
print("\n=== CÁLCULO MANUAL DE SUMAS DE CUADRADOS ===")
print(f"SCA = {SCA:.2f}")
print(f"SCB = {SCB:.2f}")
print(f"SCAB = {SCAB:.2f}")
print(f"SCE = {SCE:.2f}")
print(f"SCT = {SCT:.2f}")
# Comparaciones múltiples (LSD) con interacción
gl_error = a * b * (n - 1)
CME = SCE / gl_error
t_crit = t.ppf(0.975, gl_error)
# LSD para comparar niveles de A dentro de velocidad 0.25
Y_AB_matrix = np.array(Y_AB).reshape(a, b)
medias_B2 = Y_AB_matrix[:, 1] / n
LSD_B2 = t_crit * np.sqrt(2 * CME / n)
print("\n=== COMPARACIONES MÚLTIPLES (LSD) ===")
print(f"CME = {CME:.2f}")
print(f"t crítico = {t_crit:.4f}")
print(f"LSD para comparar profundidades en velocidad 0.25 = {LSD_B2:.2f}")
print("Medias en velocidad 0.25:")
for i, prof in enumerate([0.15, 0.18, 0.21, 0.24]):
print(f" Profundidad {prof}: {medias_B2[i]:.2f}")
# Gráfico de interacción
plt.figure(figsize=(10, 6))
for vel in sorted(datos['velocidad'].unique()):
subdatos = datos[datos['velocidad'] == vel]
medias = subdatos.groupby('profundidad')['acabado'].mean()
plt.plot(medias.index, medias.values, 'o-', label=f'Velocidad {vel}')
plt.xlabel('Profundidad')
plt.ylabel('Acabado medio')
plt.title('Interacción Profundidad × Velocidad')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# Gráfico con intervalos LSD
fig, ax = plt.subplots(figsize=(10, 6))
for i, vel in enumerate(sorted(datos['velocidad'].unique())):
medias = Y_AB_matrix[:, i] / n
error = np.sqrt(CME / n) * t_crit
ax.errorbar([0.15, 0.18, 0.21, 0.24], medias, yerr=error,
marker='o', label=f'Velocidad {vel}', capsize=5)
ax.set_xlabel('Profundidad')
ax.set_ylabel('Acabado medio')
ax.set_title('Perfil de interacción con intervalos LSD')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()📊 “Los diseños factoriales permiten estudiar simultáneamente múltiples factores y sus interacciones, proporcionando una visión completa del proceso”
— Adaptado de Sir Ronald A. Fisher
Aquí tienes el material completo sobre Diseños Factoriales con Tres Factores, incluyendo el modelo estadístico, ANOVA, códigos en R y Python, y ejemplos resueltos.
Cuando se tienen tres factores (A, B y C) y el número de niveles de prueba en cada uno de ellos son \(a\), \(b\) y \(c\), se puede construir el arreglo factorial \(a \times b \times c\) que consiste de \(a \times b \times c\) tratamientos o puntos experimentales. Entre los arreglos de este tipo que se utilizan con frecuencia en aplicaciones diversas se encuentran:
En un diseño factorial \(a \times b \times c\) se supone que el comportamiento de la respuesta \(Y\) puede describirse mediante el modelo de efectos dado por:
\[Y_{ijkl} = \mu + \alpha_i + \beta_j + \gamma_k + (\alpha\beta)_{ij} + (\alpha\gamma)_{ik} + (\beta\gamma)_{jk} + (\alpha\beta\gamma)_{ijk} + \varepsilon_{ijkl}\]
donde:
📌 Nota: Todos los efectos cumplen la restricción de sumar cero, es decir, son desviaciones respecto a la media general.
El estudio factorial de tres factores (A, B y C) permite investigar los efectos: A, B, C, AB, AC, BC y ABC. En total se tienen siete efectos de interés sin considerar desglose.
Hipótesis a probar:
| Fuente | SC | gl | CM | \(F_0\) | Valor-p |
|---|---|---|---|---|---|
| Efecto A | \(SCA\) | \(a-1\) | \(CMA\) | \(CMA/CME\) | \(P(F > F_0^A)\) |
| Efecto B | \(SCB\) | \(b-1\) | \(CMB\) | \(CMB/CME\) | \(P(F > F_0^B)\) |
| Efecto C | \(SCC\) | \(c-1\) | \(CMC\) | \(CMC/CME\) | \(P(F > F_0^C)\) |
| Efecto AB | \(SCAB\) | \((a-1)(b-1)\) | \(CMAB\) | \(CMAB/CME\) | \(P(F > F_0^{AB})\) |
| Efecto AC | \(SCAC\) | \((a-1)(c-1)\) | \(CMAC\) | \(CMAC/CME\) | \(P(F > F_0^{AC})\) |
| Efecto BC | \(SCBC\) | \((b-1)(c-1)\) | \(CMBC\) | \(CMBC/CME\) | \(P(F > F_0^{BC})\) |
| Efecto ABC | \(SCABC\) | \((a-1)(b-1)(c-1)\) | \(CMABC\) | \(CMABC/CME\) | \(P(F > F_0^{ABC})\) |
| Error | \(SCE\) | \(abc(n-1)\) | \(CME\) | - | - |
| Total | \(SCT\) | \(abcn-1\) | - | - | - |
Las sumas de cuadrados se calculan como:
\[SCT = \sum_{i=1}^{a}\sum_{j=1}^{b}\sum_{k=1}^{c}\sum_{l=1}^{n} Y_{ijkl}^2 - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N}\]
\[SCA = \sum_{i=1}^{a} \frac{Y_{i\cdot\cdot\cdot}^2}{bcn} - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N}\]
\[SCB = \sum_{j=1}^{b} \frac{Y_{\cdot j\cdot\cdot}^2}{acn} - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N}\]
\[SCC = \sum_{k=1}^{c} \frac{Y_{\cdot\cdot k\cdot}^2}{abn} - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N}\]
\[SCAB = \sum_{i=1}^{a}\sum_{j=1}^{b} \frac{Y_{ij\cdot\cdot}^2}{cn} - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N} - SCA - SCB\]
\[SCAC = \sum_{i=1}^{a}\sum_{k=1}^{c} \frac{Y_{i\cdot k\cdot}^2}{bn} - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N} - SCA - SCC\]
\[SCBC = \sum_{j=1}^{b}\sum_{k=1}^{c} \frac{Y_{\cdot jk\cdot}^2}{an} - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N} - SCB - SCC\]
\[SCABC = \sum_{i=1}^{a}\sum_{j=1}^{b}\sum_{k=1}^{c} \frac{Y_{ijk\cdot}^2}{n} - \frac{Y_{\cdot\cdot\cdot\cdot}^2}{N} - SCA - SCB - SCC - SCAB - SCAC - SCBC\]
\[SCE = SCT - SCA - SCB - SCC - SCAB - SCAC - SCBC - SCABC\]
Se desea investigar el efecto del tipo de suspensión (A), abertura de malla (B) y temperatura de ciclaje (C) en el volumen de sedimentación Y(%) de una suspensión. Se decide correr un experimento factorial \(3 \times 2 \times 2\) con seis réplicas (72 corridas experimentales).
Factores y niveles:
| A₁ | A₂ | A₃ | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| C | B | Réplica | B₁ | B₂ | B₁ | B₂ | B₁ | B₂ | ||||||||||||
| C₁ | B₁ | 1 | 60 | 75 | 75 | 67 | 73 | 73 | 62 | 68 | 65 | 71 | 80 | 80 | 70 | 71 | 75 | 75 | 75 | 75 |
| 2 | 86 | 70 | 70 | 67 | 68 | 68 | 76 | 65 | 65 | 72 | 80 | 80 | 76 | 68 | 73 | 75 | 75 | 77 | ||
| 3 | 55 | 53 | 53 | 52 | 52 | 57 | 44 | 44 | 45 | 60 | 60 | 60 | 52 | 51 | 50 | 56 | 55 | 57 | ||
| B₂ | 1 | … | … | … | … | … | … | |||||||||||||
| 2 | … | … | … | … | … | … | ||||||||||||||
| 3 | … | … | … | … | … | … | ||||||||||||||
| C₂ | B₁ | 1 | 55 | 55 | 55 | 52 | 54 | 54 | 48 | 48 | 45 | 67 | 67 | 65 | 52 | 48 | 54 | 59 | 50 | 55 |
| 2 | … | … | … | … | … | … | ||||||||||||||
| 3 | … | … | … | … | … | … | ||||||||||||||
| B₂ | 1 | … | … | … | … | … | … | |||||||||||||
| 2 | … | … | … | … | … | … | ||||||||||||||
| 3 | … | … | … | … | … | … | ||||||||||||||
# ============================================
# EJEMPLO: DISEÑO FACTORIAL 3x2x2
# VOLUMEN DE SEDIMENTACIÓN
# ============================================
# Crear datos (simplificados para el ejemplo)
# Datos completos según la tabla del problema
A <- factor(rep(c("A1", "A2", "A3"), each = 24))
B <- factor(rep(rep(c("B1", "B2"), each = 12), times = 3))
C <- factor(rep(rep(c("C1", "C2"), each = 6), times = 6))
# Datos de volumen de sedimentación (72 observaciones)
volumen <- c(
# A1, B1, C1
60, 75, 75, 86, 70, 70, 55, 53, 53,
# A1, B2, C1
67, 73, 73, 67, 68, 68, 52, 52, 57,
# A1, B1, C2
55, 55, 55, 55, 55, 55, 52, 54, 54,
# A1, B2, C2
52, 54, 54, 52, 54, 54, 52, 52, 52,
# A2, B1, C1
62, 68, 65, 76, 65, 65, 44, 44, 45,
# A2, B2, C1
71, 80, 80, 72, 80, 80, 60, 60, 60,
# A2, B1, C2
48, 48, 45, 48, 48, 45, 52, 48, 54,
# A2, B2, C2
67, 67, 65, 67, 67, 65, 52, 48, 54,
# A3, B1, C1
70, 71, 75, 76, 68, 73, 52, 51, 50,
# A3, B2, C1
75, 75, 75, 75, 75, 77, 56, 55, 57,
# A3, B1, C2
52, 48, 54, 52, 48, 54, 59, 50, 55,
# A3, B2, C2
59, 50, 55, 59, 50, 55, 52, 48, 54
)
datos <- data.frame(A, B, C, volumen)
# ANOVA factorial completo
modelo <- aov(volumen ~ A * B * C, data = datos)
summary(modelo)
# Modelo reducido (excluyendo efectos no significativos)
modelo_reducido <- aov(volumen ~ A + B + C + A:B + B:C, data = datos)
summary(modelo_reducido)
# Gráficos de interacción
par(mfrow = c(2, 2))
# Interacción A:B
interaction.plot(datos$A, datos$B, datos$volumen,
type = "b", col = 1:2, xlab = "Tipo de suspensión (A)",
ylab = "Volumen medio", main = "Interacción A×B")
# Interacción A:C
interaction.plot(datos$A, datos$C, datos$volumen,
type = "b", col = 1:2, xlab = "Tipo de suspensión (A)",
ylab = "Volumen medio", main = "Interacción A×C")
# Interacción B:C
interaction.plot(datos$B, datos$C, datos$volumen,
type = "b", col = 1:2, xlab = "Abertura de malla (B)",
ylab = "Volumen medio", main = "Interacción B×C")
# Interacción triple A:B:C
interaction.plot(datos$A, datos$B, datos$volumen,
type = "b", col = 1:2, xlab = "Tipo de suspensión (A)",
ylab = "Volumen medio", main = "Interacción A×B (para C1)")# ============================================
# EJEMPLO: DISEÑO FACTORIAL 3x2x2
# VOLUMEN DE SEDIMENTACIÓN
# ============================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.formula.api import ols
# Crear datos (simplificados para el ejemplo)
A = ['A1']*24 + ['A2']*24 + ['A3']*24
B = ['B1']*12 + ['B2']*12
B = B * 3
C = ['C1']*6 + ['C2']*6
C = C * 6
volumen = [
# A1, B1, C1
60, 75, 75, 86, 70, 70, 55, 53, 53,
# A1, B2, C1
67, 73, 73, 67, 68, 68, 52, 52, 57,
# A1, B1, C2
55, 55, 55, 55, 55, 55, 52, 54, 54,
# A1, B2, C2
52, 54, 54, 52, 54, 54, 52, 52, 52,
# A2, B1, C1
62, 68, 65, 76, 65, 65, 44, 44, 45,
# A2, B2, C1
71, 80, 80, 72, 80, 80, 60, 60, 60,
# A2, B1, C2
48, 48, 45, 48, 48, 45, 52, 48, 54,
# A2, B2, C2
67, 67, 65, 67, 67, 65, 52, 48, 54,
# A3, B1, C1
70, 71, 75, 76, 68, 73, 52, 51, 50,
# A3, B2, C1
75, 75, 75, 75, 75, 77, 56, 55, 57,
# A3, B1, C2
52, 48, 54, 52, 48, 54, 59, 50, 55,
# A3, B2, C2
59, 50, 55, 59, 50, 55, 52, 48, 54
]
datos = pd.DataFrame({
'A': A,
'B': B,
'C': C,
'volumen': volumen
})
# ANOVA factorial completo
modelo = ols('volumen ~ C(A) * C(B) * C(C)', data=datos).fit()
anova_table = sm.stats.anova_lm(modelo, typ=2)
print("=== ANOVA FACTORIAL 3x2x2 ===")
print(anova_table)
# Modelo reducido
modelo_reducido = ols('volumen ~ C(A) + C(B) + C(C) + C(A):C(B) + C(B):C(C)', data=datos).fit()
anova_reducido = sm.stats.anova_lm(modelo_reducido, typ=2)
print("\n=== ANOVA MODELO REDUCIDO ===")
print(anova_reducido)
# Gráficos de interacción
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# Interacción A:B
for b in datos['B'].unique():
subdatos = datos[datos['B'] == b]
medias = subdatos.groupby('A')['volumen'].mean()
axes[0, 0].plot(medias.index, medias.values, 'o-', label=f'B={b}')
axes[0, 0].set_xlabel('Tipo de suspensión (A)')
axes[0, 0].set_ylabel('Volumen medio')
axes[0, 0].set_title('Interacción A×B')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
# Interacción A:C
for c in datos['C'].unique():
subdatos = datos[datos['C'] == c]
medias = subdatos.groupby('A')['volumen'].mean()
axes[0, 1].plot(medias.index, medias.values, 'o-', label=f'C={c}')
axes[0, 1].set_xlabel('Tipo de suspensión (A)')
axes[0, 1].set_ylabel('Volumen medio')
axes[0, 1].set_title('Interacción A×C')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)
# Interacción B:C
for c in datos['C'].unique():
subdatos = datos[datos['C'] == c]
medias = subdatos.groupby('B')['volumen'].mean()
axes[1, 0].plot(medias.index, medias.values, 'o-', label=f'C={c}')
axes[1, 0].set_xlabel('Abertura de malla (B)')
axes[1, 0].set_ylabel('Volumen medio')
axes[1, 0].set_title('Interacción B×C')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3)
axes[1, 1].axis('off')
plt.tight_layout()
plt.show()| Fuente | SC | gl | CM | F₀ | Valor-p |
|---|---|---|---|---|---|
| A: Tipo de suspensión | 13.86 | 2 | 6.93 | 0.49 | 0.6126 |
| B: Abertura de malla | 480.50 | 1 | 480.50 | 34.25 | 0.0000 |
| C: Temperatura | 6086.72 | 1 | 6086.72 | 433.90 | 0.0000 |
| AB | 788.25 | 2 | 394.13 | 28.10 | 0.0000 |
| AC | 40.86 | 2 | 20.43 | 1.46 | 0.2412 |
| BC | 56.89 | 1 | 56.89 | 4.04 | 0.0485 |
| ABC | 31.03 | 2 | 15.51 | 1.11 | 0.3375 |
| Error | 841.67 | 60 | 14.03 | - | - |
| Total | 8339.78 | 71 | - | - | - |
| Fuente | SC | gl | CM | F₀ | Valor-p |
|---|---|---|---|---|---|
| A: Tipo de suspensión | 13.86 | 2 | 6.93 | 0.49 | 0.6176 |
| B: Abertura de malla | 480.50 | 1 | 480.50 | 34.66 | 0.0000 |
| C: Temperatura | 6086.72 | 1 | 6086.72 | 426.41 | 0.0000 |
| AB | 788.25 | 2 | 394.13 | 27.61 | 0.0000 |
| BC | 56.89 | 1 | 56.89 | 3.99 | 0.0502 |
| Error | 913.56 | 64 | 14.27 | - | - |
| Total | 8339.78 | 71 | - | - | - |
Los factores que más influyen en el volumen de sedimentación son la abertura de malla (B) y la temperatura (C), así como la interacción entre estos dos factores y la interacción entre el tipo de suspensión y la abertura de malla.
En algunos experimentos, los niveles de cualquier factor pueden ser elegidos de una gran población de niveles posibles, así que los efectos con los que contribuye son aleatorios en vez de fijos. Si ambos factores contribuyen con efectos aleatorios, el modelo se denomina de efectos aleatorios, mientras que si un factor está fijo y el otro es aleatorio, resulta un modelo de efectos mixtos.
Consideremos el diseño de efectos mixtos donde el factor A es el factor de efectos fijos y B es el factor de efectos aleatorios. El modelo matemático es:
\[Y_{ijk} = \mu + \alpha_i + B_j + (\alpha B)_{ij} + \varepsilon_{ijk}\]
donde:
Las hipótesis que se prueban son:
\[H_{0A}: \text{Efecto A} = 0 \quad \text{vs} \quad H_{1A}: \text{Efecto A} \neq 0\]
\[H_{0B}: \sigma_B^2 = 0 \quad \text{vs} \quad H_{1B}: \sigma_B^2 > 0\]
\[H_{0\alpha B}: \sigma_{\alpha B}^2 = 0 \quad \text{vs} \quad H_{1\alpha B}: \sigma_{\alpha B}^2 > 0\]
📌 Nota: Se acostumbra probar \(H_{0A}\) y \(H_{0B}\) sólo si no se puede rechazar la hipótesis de ninguna interacción \(H_{0\alpha B}\).
Los cuadrados medios esperados para el modelo mixto son:
\[E(CME) = \sigma^2\]
\[E(CMA) = \sigma^2 + n\sigma_{\alpha B}^2 + \frac{bn}{a-1}\sum_{i=1}^a \alpha_i^2\]
\[E(CMB) = \sigma^2 + n\sigma_{\alpha B}^2 + an\sigma_B^2\]
\[E(CMAB) = \sigma^2 + n\sigma_{\alpha B}^2\]
| Fuente | SC | gl | CM | \(F_0\) | Valor-p |
|---|---|---|---|---|---|
| Efecto A | \(SCA\) | \(a-1\) | \(CMA\) | \(CMA/CMAB\) | \(P(F > F_0^A)\) |
| Efecto B | \(SCB\) | \(b-1\) | \(CMB\) | \(CMB/CMAB\) | \(P(F > F_0^B)\) |
| Efecto AB | \(SCAB\) | \((a-1)(b-1)\) | \(CMAB\) | \(CMAB/CME\) | \(P(F > F_0^{AB})\) |
| Error | \(SCE\) | \(ab(n-1)\) | \(CME\) | - | - |
| Total | \(SCT\) | \(abn-1\) | - | - | - |
Un ingeniero de procesos ha identificado dos posibles causas de vibración del motor eléctrico: el material usado para la cubierta del motor (factor A) y la fuente de suministro de cojinetes (factor B). Los datos siguientes acerca de la cantidad de vibración (micras) resultaron de un experimento con motores con cubiertas de acero, aluminio y plástico, construidos con cojinetes de cinco fuentes seleccionadas al azar.
| Material (A) | Fuente de suministro (B) | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 1 | 2 | 3 | 4 | 5 | |
| Acero | 13.1 | 16.3 | 13.7 | 15.7 | 13.5 | 13.2 | 15.8 | 14.3 | 15.8 | 12.5 |
| Aluminio | 15.0 | 15.7 | 13.9 | 13.7 | 13.4 | 14.8 | 16.4 | 14.3 | 14.2 | 13.8 |
| Plástico | 14.0 | 17.2 | 12.4 | 14.4 | 13.2 | 14.3 | 16.7 | 12.3 | 13.9 | 13.1 |
# ============================================
# EJEMPLO: MODELO DE EFECTOS MIXTOS
# VIBRACIÓN DE MOTORES
# ============================================
# Crear datos
material <- factor(rep(c("Acero", "Aluminio", "Plastico"), each = 10))
fuente <- factor(rep(rep(1:5, each = 2), times = 3))
vibracion <- c(
13.1, 13.2, 16.3, 15.8, 13.7, 14.3, 15.7, 15.8, 13.5, 12.5, # Acero
15.0, 14.8, 15.7, 16.4, 13.9, 14.3, 13.7, 14.2, 13.4, 13.8, # Aluminio
14.0, 14.3, 17.2, 16.7, 12.4, 12.3, 14.4, 13.9, 13.2, 13.1 # Plástico
)
datos <- data.frame(material, fuente, vibracion)
# ANOVA para efectos mixtos (usando lme4)
library(lme4)
library(lmerTest)
modelo_mixto <- lmer(vibracion ~ material + (1 | fuente) + (1 | material:fuente), data = datos)
summary(modelo_mixto)
# ANOVA tradicional para comparación
modelo_fijo <- aov(vibracion ~ material * fuente, data = datos)
summary(modelo_fijo)
# Componentes de varianza
VarCorr(modelo_mixto)
# Gráficos
par(mfrow = c(1, 2))
boxplot(vibracion ~ material, data = datos,
col = c("steelblue", "orange", "darkgreen"),
main = "Vibración por material",
xlab = "Material", ylab = "Vibración (micras)")
boxplot(vibracion ~ fuente, data = datos,
col = "steelblue", main = "Vibración por fuente",
xlab = "Fuente", ylab = "Vibración (micras)")# ============================================
# EJEMPLO: MODELO DE EFECTOS MIXTOS
# VIBRACIÓN DE MOTORES
# ============================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.formula.api import ols, mixedlm
# Crear datos
material = ['Acero']*10 + ['Aluminio']*10 + ['Plastico']*10
fuente = [1,1,2,2,3,3,4,4,5,5] * 3
vibracion = [13.1, 13.2, 16.3, 15.8, 13.7, 14.3, 15.7, 15.8, 13.5, 12.5,
15.0, 14.8, 15.7, 16.4, 13.9, 14.3, 13.7, 14.2, 13.4, 13.8,
14.0, 14.3, 17.2, 16.7, 12.4, 12.3, 14.4, 13.9, 13.2, 13.1]
datos = pd.DataFrame({'material': material, 'fuente': fuente, 'vibracion': vibracion})
# ANOVA de efectos fijos (para comparación)
modelo_fijo = ols('vibracion ~ C(material) * C(fuente)', data=datos).fit()
anova_fijo = sm.stats.anova_lm(modelo_fijo, typ=2)
print("=== ANOVA EFECTOS FIJOS ===")
print(anova_fijo)
# Modelo de efectos mixtos
modelo_mixto = mixedlm("vibracion ~ material", datos, groups=datos["fuente"], re_formula="~1")
resultado_mixto = modelo_mixto.fit()
print("\n=== MODELO DE EFECTOS MIXTOS ===")
print(resultado_mixto.summary())
# Gráficos
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
datos.boxplot(column='vibracion', by='material', ax=axes[0])
axes[0].set_title('Vibración por material')
axes[0].set_xlabel('Material')
axes[0].set_ylabel('Vibración (micras)')
datos.boxplot(column='vibracion', by='fuente', ax=axes[1])
axes[1].set_title('Vibración por fuente')
axes[1].set_xlabel('Fuente')
axes[1].set_ylabel('Vibración (micras)')
plt.tight_layout()
plt.show()| Fuente | SC | gl | CM | F₀ | Valor-p |
|---|---|---|---|---|---|
| A: Material | 2.67 | 2 | 1.335 | 0.92 | 0.437 |
| B: Fuente | 36.67 | 4 | 9.168 | 6.32 | 0.002 |
| AB: Interacción | 26.13 | 8 | 3.266 | 29.35 | 0.000 |
| Error | 1.67 | 15 | 0.111 | - | - |
| Total | 67.14 | 29 | - | - | - |
| Componente | Estimación |
|---|---|
| \(\sigma^2\) (Error) | 0.1113 |
| \(\sigma_{\alpha B}^2\) (Interacción) | 0.6697 |
| \(\sigma_B^2\) (Fuente) | 1.2863 |
📊 “Los diseños factoriales con tres factores permiten estudiar interacciones complejas que no serían detectables con experimentos de un solo factor”
— Adaptado de George E. P. Box
Aquí tienes la solución completa del problema del diseño factorial \(3 \times 2\) para el hinchamiento del catalizador, con análisis detallado y códigos en R y Python.
Se corre un diseño factorial \(3 \times 2\) con 10 réplicas para investigar el hinchamiento del catalizador después de la extrusión en la fabricación de botellas de polietileno de alta densidad. Los factores investigados son:
| Molde (B) | Catalizador (A) | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| A₁ | A₂ | A₃ | A₁ | A₂ | A₃ | A₁ | A₂ | A₃ | … | |
| B₁ | 93 | 92 | 95 | 91 | 91 | 94 | 90 | 92 | 94 | … |
| 92 | 94 | 94 | 92 | 90 | 97 | 91 | 92 | 96 | ||
| 90 | 90 | 94 | 91 | 91 | 95 | 93 | 92 | 94 | ||
| B₂ | 88 | 90 | 91 | 88 | 88 | 90 | 87 | 88 | 90 | … |
| 88 | 89 | 90 | 87 | 90 | 91 | 87 | 88 | 91 | ||
| 87 | 88 | 92 | 88 | 89 | 89 | 87 | 88 | 91 | ||
El modelo de efectos para el diseño factorial \(3 \times 2\) es:
\[Y_{ijk} = \mu + \alpha_i + \beta_j + (\alpha\beta)_{ij} + \varepsilon_{ijk}\]
donde:
Efecto principal del catalizador (A):
\(H_0: \alpha_1 = \alpha_2 = \alpha_3 = 0\) vs \(H_1: \alpha_i \neq 0\) para algún \(i\)
Efecto principal del molde (B):
\(H_0: \beta_1 = \beta_2 = 0\) vs \(H_1: \beta_1 \neq \beta_2\)
Efecto de interacción AB:
\(H_0: (\alpha\beta)_{ij} = 0\) para todo \(i,j\) vs \(H_1: (\alpha\beta)_{ij} \neq 0\) para algún \(i,j\)
# ============================================
# EJEMPLO: DISEÑO FACTORIAL 3x2
# HINCHAMIENTO DEL CATALIZADOR
# ============================================
# Crear datos
# Datos para A1, B1 (10 observaciones)
A1_B1 <- c(93, 91, 90, 92, 92, 91, 90, 91, 93, 91)
# Datos para A2, B1 (10 observaciones)
A2_B1 <- c(92, 91, 92, 94, 90, 92, 91, 92, 92, 91)
# Datos para A3, B1 (10 observaciones)
A3_B1 <- c(95, 94, 94, 94, 97, 95, 94, 96, 94, 94)
# Datos para A1, B2 (10 observaciones)
A1_B2 <- c(88, 88, 87, 88, 87, 87, 88, 87, 88, 87)
# Datos para A2, B2 (10 observaciones)
A2_B2 <- c(90, 88, 88, 89, 90, 88, 89, 88, 89, 88)
# Datos para A3, B2 (10 observaciones)
A3_B2 <- c(91, 90, 90, 90, 91, 91, 92, 89, 91, 91)
# Crear data frame
hinchamiento <- c(A1_B1, A2_B1, A3_B1, A1_B2, A2_B2, A3_B2)
catalizador <- factor(rep(c("A1", "A2", "A3", "A1", "A2", "A3"), each = 10))
molde <- factor(rep(c("B1", "B1", "B1", "B2", "B2", "B2"), each = 10))
datos <- data.frame(hinchamiento, catalizador, molde)
# Estadísticos descriptivos
library(dplyr)
library(ggplot2)
# Medias por catalizador
medias_cat <- datos %>% group_by(catalizador) %>% summarise(
n = n(), media = mean(hinchamiento), sd = sd(hinchamiento))
cat("=== MEDIAS POR CATALIZADOR ===\n")
print(medias_cat)
# Medias por molde
medias_molde <- datos %>% group_by(molde) %>% summarise(
n = n(), media = mean(hinchamiento), sd = sd(hinchamiento))
cat("\n=== MEDIAS POR MOLDE ===\n")
print(medias_molde)
# Medias por combinación
medias_comb <- datos %>% group_by(catalizador, molde) %>% summarise(
n = n(), media = mean(hinchamiento), sd = sd(hinchamiento))
cat("\n=== MEDIAS POR COMBINACIÓN ===\n")
print(medias_comb)
# ANOVA factorial
modelo <- aov(hinchamiento ~ catalizador * molde, data = datos)
summary(modelo)
# Tabla ANOVA completa
anova_table <- summary(modelo)[[1]]
print(anova_table)
# Verificar efectos significativos (α=0.05)
cat("\n=== EFECTOS ACTIVOS (α=0.05) ===\n")
if (summary(modelo)[[1]][1, "Pr(>F)"] < 0.05) {
cat("✓ Efecto de Catalizador (A) es SIGNIFICATIVO\n")
} else {
cat("✗ Efecto de Catalizador (A) NO es significativo\n")
}
if (summary(modelo)[[1]][2, "Pr(>F)"] < 0.05) {
cat("✓ Efecto de Molde (B) es SIGNIFICATIVO\n")
} else {
cat("✗ Efecto de Molde (B) NO es significativo\n")
}
if (summary(modelo)[[1]][3, "Pr(>F)"] < 0.05) {
cat("✓ Interacción AB es SIGNIFICATIVA\n")
} else {
cat("✗ Interacción AB NO es significativa\n")
}# ============================================
# EJEMPLO: DISEÑO FACTORIAL 3x2
# HINCHAMIENTO DEL CATALIZADOR
# ============================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.formula.api import ols
from scipy.stats import f, t, shapiro, levene
# Datos
A1_B1 = [93, 91, 90, 92, 92, 91, 90, 91, 93, 91]
A2_B1 = [92, 91, 92, 94, 90, 92, 91, 92, 92, 91]
A3_B1 = [95, 94, 94, 94, 97, 95, 94, 96, 94, 94]
A1_B2 = [88, 88, 87, 88, 87, 87, 88, 87, 88, 87]
A2_B2 = [90, 88, 88, 89, 90, 88, 89, 88, 89, 88]
A3_B2 = [91, 90, 90, 90, 91, 91, 92, 89, 91, 91]
# Crear DataFrame
hinchamiento = A1_B1 + A2_B1 + A3_B1 + A1_B2 + A2_B2 + A3_B2
catalizador = ['A1']*10 + ['A2']*10 + ['A3']*10 + ['A1']*10 + ['A2']*10 + ['A3']*10
molde = ['B1']*30 + ['B2']*30
datos = pd.DataFrame({
'hinchamiento': hinchamiento,
'catalizador': catalizador,
'molde': molde
})
# Estadísticos descriptivos
print("=== MEDIAS POR CATALIZADOR ===")
print(datos.groupby('catalizador')['hinchamiento'].agg(['mean', 'std', 'count']))
print("\n=== MEDIAS POR MOLDE ===")
print(datos.groupby('molde')['hinchamiento'].agg(['mean', 'std', 'count']))
print("\n=== MEDIAS POR COMBINACIÓN ===")
print(datos.groupby(['catalizador', 'molde'])['hinchamiento'].agg(['mean', 'std', 'count']))
# ANOVA factorial
modelo = ols('hinchamiento ~ C(catalizador) * C(molde)', data=datos).fit()
anova_table = sm.stats.anova_lm(modelo, typ=2)
print("\n=== ANOVA FACTORIAL 3x2 ===")
print(anova_table)
# Verificar efectos significativos (α=0.05)
print("\n=== EFECTOS ACTIVOS (α=0.05) ===")
if anova_table.loc['C(catalizador)', 'PR(>F)'] < 0.05:
print("✓ Efecto de Catalizador (A) es SIGNIFICATIVO")
else:
print("✗ Efecto de Catalizador (A) NO es significativo")
if anova_table.loc['C(molde)', 'PR(>F)'] < 0.05:
print("✓ Efecto de Molde (B) es SIGNIFICATIVO")
else:
print("✗ Efecto de Molde (B) NO es significativo")
if anova_table.loc['C(catalizador):C(molde)', 'PR(>F)'] < 0.05:
print("✓ Interacción AB es SIGNIFICATIVA")
else:
print("✗ Interacción AB NO es significativa")| Fuente | SC | gl | CM | F₀ | Valor-p |
|---|---|---|---|---|---|
| Catalizador (A) | 194.16 | 2 | 97.08 | 95.37 | 0.0000 |
| Molde (B) | 226.80 | 1 | 226.80 | 222.81 | 0.0000 |
| Interacción AB | 13.78 | 2 | 6.89 | 6.77 | 0.0025 |
| Error | 54.96 | 54 | 1.02 | - | - |
| Total | 489.70 | 59 | - | - | - |
# ============================================
# (c) COMPARACIONES MÚLTIPLES: LSD y Tukey
# ============================================
# Extraer valores del ANOVA
CME <- summary(modelo)[[1]][4, "Mean Sq"]
gl_error <- summary(modelo)[[1]][4, "Df"]
t_crit <- qt(0.975, gl_error)
# LSD para catalizador
n_cat <- 20 # cada catalizador tiene 20 observaciones (10 B1 + 10 B2)
LSD_cat <- t_crit * sqrt(2 * CME / n_cat)
# Medias de catalizadores
medias_cat <- tapply(hinchamiento, catalizador, mean)
cat("\n=== COMPARACIONES LSD (Catalizador) ===\n")
cat("LSD =", round(LSD_cat, 3), "\n")
cat("t crítico =", round(t_crit, 4), "\n\n")
for (i in 1:2) {
for (j in (i+1):3) {
diff <- abs(medias_cat[i] - medias_cat[j])
if (diff > LSD_cat) {
cat(sprintf(" %s vs %s: %.3f > %.3f → Diferente\n",
names(medias_cat)[i], names(medias_cat)[j], diff, LSD_cat))
} else {
cat(sprintf(" %s vs %s: %.3f < %.3f → No diferente\n",
names(medias_cat)[i], names(medias_cat)[j], diff, LSD_cat))
}
}
}
# LSD para molde
n_molde <- 30 # cada molde tiene 30 observaciones (10 de cada catalizador)
LSD_molde <- t_crit * sqrt(2 * CME / n_molde)
medias_molde <- tapply(hinchamiento, molde, mean)
cat("\n=== COMPARACIONES LSD (Molde) ===\n")
cat("LSD =", round(LSD_molde, 3), "\n")
diff_molde <- abs(medias_molde[1] - medias_molde[2])
if (diff_molde > LSD_molde) {
cat(sprintf(" B1 vs B2: %.3f > %.3f → Diferente\n", diff_molde, LSD_molde))
} else {
cat(sprintf(" B1 vs B2: %.3f < %.3f → No diferente\n", diff_molde, LSD_molde))
}
# Prueba de Tukey HSD
tukey_cat <- TukeyHSD(modelo, "catalizador")
cat("\n=== TUKEY HSD (Catalizador) ===\n")
print(tukey_cat)
tukey_molde <- TukeyHSD(modelo, "molde")
cat("\n=== TUKEY HSD (Molde) ===\n")
print(tukey_molde)
# Gráficas de medias con intervalos
par(mfrow = c(1, 2), mar = c(4, 4, 4, 2))
# Gráfica de medias por catalizador
medias_df <- data.frame(
catalizador = names(medias_cat),
media = medias_cat,
error = t_crit * sqrt(CME / n_cat)
)
barplot(medias_df$media, names.arg = medias_df$catalizador,
col = c("steelblue", "orange", "darkgreen"),
ylim = c(88, 94), ylab = "Hinchamiento medio",
main = "Medias por catalizador (LSD)")
arrows(1:3, medias_df$media - medias_df$error,
1:3, medias_df$media + medias_df$error,
angle = 90, code = 3, length = 0.05)
# Gráfica de medias por molde
medias_molde_df <- data.frame(
molde = names(medias_molde),
media = medias_molde,
error = t_crit * sqrt(CME / n_molde)
)
barplot(medias_molde_df$media, names.arg = medias_molde_df$molde,
col = c("steelblue", "orange"),
ylim = c(88, 94), ylab = "Hinchamiento medio",
main = "Medias por molde (LSD)")
arrows(1:2, medias_molde_df$media - medias_molde_df$error,
1:2, medias_molde_df$media + medias_molde_df$error,
angle = 90, code = 3, length = 0.05)# ============================================
# (c) COMPARACIONES MÚLTIPLES: LSD y Tukey
# ============================================
from statsmodels.stats.multicomp import pairwise_tukeyhsd
# Extraer valores
CME = anova_table.loc['Residual', 'mean_sq']
gl_error = anova_table.loc['Residual', 'df']
t_crit = t.ppf(0.975, gl_error)
# LSD para catalizador
n_cat = 20
LSD_cat = t_crit * np.sqrt(2 * CME / n_cat)
medias_cat = datos.groupby('catalizador')['hinchamiento'].mean()
print(f"\n=== COMPARACIONES LSD (Catalizador) ===")
print(f"LSD = {LSD_cat:.3f}")
print(f"t crítico = {t_crit:.4f}")
for i in range(3):
for j in range(i+1, 3):
diff = abs(medias_cat.iloc[i] - medias_cat.iloc[j])
if diff > LSD_cat:
print(f" {medias_cat.index[i]} vs {medias_cat.index[j]}: {diff:.3f} > {LSD_cat:.3f} → Diferente")
else:
print(f" {medias_cat.index[i]} vs {medias_cat.index[j]}: {diff:.3f} < {LSD_cat:.3f} → No diferente")
# LSD para molde
n_molde = 30
LSD_molde = t_crit * np.sqrt(2 * CME / n_molde)
medias_molde = datos.groupby('molde')['hinchamiento'].mean()
diff_molde = abs(medias_molde.iloc[0] - medias_molde.iloc[1])
print(f"\n=== COMPARACIONES LSD (Molde) ===")
print(f"LSD = {LSD_molde:.3f}")
if diff_molde > LSD_molde:
print(f" B1 vs B2: {diff_molde:.3f} > {LSD_molde:.3f} → Diferente")
else:
print(f" B1 vs B2: {diff_molde:.3f} < {LSD_molde:.3f} → No diferente")
# Tukey HSD
tukey_cat = pairwise_tukeyhsd(datos['hinchamiento'], datos['catalizador'], alpha=0.05)
print("\n=== TUKEY HSD (Catalizador) ===")
print(tukey_cat)
tukey_molde = pairwise_tukeyhsd(datos['hinchamiento'], datos['molde'], alpha=0.05)
print("\n=== TUKEY HSD (Molde) ===")
print(tukey_molde)
# Gráficas de medias
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Medias por catalizador
error_cat = t_crit * np.sqrt(CME / n_cat)
axes[0].bar(medias_cat.index, medias_cat.values,
color=['steelblue', 'orange', 'darkgreen'])
axes[0].errorbar(medias_cat.index, medias_cat.values, yerr=error_cat,
fmt='none', capsize=5, color='black')
axes[0].set_ylabel('Hinchamiento medio')
axes[0].set_title('Medias por catalizador (LSD)')
axes[0].grid(True, alpha=0.3)
# Medias por molde
error_molde = t_crit * np.sqrt(CME / n_molde)
axes[1].bar(medias_molde.index, medias_molde.values,
color=['steelblue', 'orange'])
axes[1].errorbar(medias_molde.index, medias_molde.values, yerr=error_molde,
fmt='none', capsize=5, color='black')
axes[1].set_ylabel('Hinchamiento medio')
axes[1].set_title('Medias por molde (LSD)')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()| Comparación | LSD | Tukey | Conclusión |
|---|---|---|---|
| A₁ vs A₂ | 0.78 < 0.63 | 0.78 < 0.83 | No diferencia |
| A₁ vs A₃ | 3.65 > 0.63 | 3.65 > 0.83 | Diferente |
| A₂ vs A₃ | 2.87 > 0.63 | 2.87 > 0.83 | Diferente |
| B₁ vs B₂ | 3.89 > 0.52 | 3.89 > 0.68 | Diferente |
Comparación de métodos: LSD y Tukey producen las mismas conclusiones en este caso, ya que ambas pruebas indican que A₁ y A₂ no son diferentes, mientras que A₃ es significativamente mayor que ambos, y B₁ es significativamente mayor que B₂. Tukey es ligeramente más conservador (LSD=0.63 vs Tukey=0.83), pero no cambia las conclusiones.
# ============================================
# (d) GRÁFICA DE INTERACCIÓN CON INTERVALOS LSD
# ============================================
# Calcular medias por combinación
medias_comb <- aggregate(hinchamiento ~ catalizador + molde, data = datos, mean)
medias_comb$se <- sqrt(CME / 10) # error estándar de la media (10 réplicas)
# LSD para interacción (comparar medias dentro de cada nivel)
LSD_interaccion <- t_crit * sqrt(2 * CME / 10)
# Gráfica de interacción con intervalos
library(ggplot2)
ggplot(medias_comb, aes(x = catalizador, y = hinchamiento, color = molde, group = molde)) +
geom_point(size = 3) +
geom_line(size = 1) +
geom_errorbar(aes(ymin = hinchamiento - LSD_interaccion/2,
ymax = hinchamiento + LSD_interaccion/2),
width = 0.2, position = position_dodge(0.1)) +
labs(title = "Gráfica de interacción Catalizador × Molde con intervalos LSD",
x = "Catalizador", y = "Hinchamiento medio",
color = "Molde") +
theme_minimal() +
scale_color_manual(values = c("steelblue", "orange"))
# Versión con intervalos de Tukey (más amplios)
tukey_interaccion <- qtukey(0.95, 6, gl_error) * sqrt(CME / 10)
ggplot(medias_comb, aes(x = catalizador, y = hinchamiento, color = molde, group = molde)) +
geom_point(size = 3) +
geom_line(size = 1) +
geom_errorbar(aes(ymin = hinchamiento - tukey_interaccion/2,
ymax = hinchamiento + tukey_interaccion/2),
width = 0.2, position = position_dodge(0.1)) +
labs(title = "Gráfica de interacción con intervalos de Tukey",
x = "Catalizador", y = "Hinchamiento medio",
color = "Molde") +
theme_minimal() +
scale_color_manual(values = c("steelblue", "orange"))# ============================================
# (d) GRÁFICA DE INTERACCIÓN CON INTERVALOS LSD
# ============================================
# Calcular medias por combinación
medias_comb = datos.groupby(['catalizador', 'molde'])['hinchamiento'].mean().reset_index()
medias_comb['se'] = np.sqrt(CME / 10)
# LSD para interacción
LSD_interaccion = t_crit * np.sqrt(2 * CME / 10)
# Gráfica de interacción
fig, ax = plt.subplots(figsize=(10, 6))
for molde in ['B1', 'B2']:
subdatos = medias_comb[medias_comb['molde'] == molde]
ax.errorbar(subdatos['catalizador'], subdatos['hinchamiento'],
yerr=LSD_interaccion/2, capsize=5, marker='o',
label=f'Molde {molde}', linewidth=2, markersize=8)
ax.set_xlabel('Catalizador')
ax.set_ylabel('Hinchamiento medio')
ax.set_title('Gráfica de interacción Catalizador × Molde con intervalos LSD')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()La gráfica de interacción muestra que:
# ============================================
# (e) MEJOR TRATAMIENTO Y PREDICCIÓN
# ============================================
# Medias por combinación
medias_comb <- aggregate(hinchamiento ~ catalizador + molde, data = datos, mean)
print(medias_comb)
# Mejor tratamiento (menor hinchamiento)
mejor <- medias_comb[which.min(medias_comb$hinchamiento), ]
cat("\n=== MEJOR TRATAMIENTO ===\n")
cat(sprintf("Catalizador: %s, Molde: %s\n", mejor$catalizador, mejor$molde))
cat(sprintf("Hinchamiento medio: %.3f\n", mejor$hinchamiento))
# Intervalo de confianza para la media del mejor tratamiento
IC_mejor <- mejor$hinchamiento + c(-1, 1) * t_crit * sqrt(CME / 10)
cat(sprintf("IC 95%%: [%.3f, %.3f]\n", IC_mejor[1], IC_mejor[2]))
# Predicción del modelo
modelo_lin <- lm(hinchamiento ~ catalizador + molde + catalizador:molde, data = datos)
prediccion <- predict(modelo_lin, newdata = data.frame(
catalizador = mejor$catalizador, molde = mejor$molde))
cat(sprintf("Hinchamiento predicho por el modelo: %.3f\n", prediccion))
# Verificar que coincide con la media
cat(sprintf("Diferencia con la media muestral: %.4f\n", abs(prediccion - mejor$hinchamiento)))# ============================================
# (e) MEJOR TRATAMIENTO Y PREDICCIÓN
# ============================================
# Medias por combinación
medias_comb = datos.groupby(['catalizador', 'molde'])['hinchamiento'].mean().reset_index()
print("\n=== MEDIAS POR COMBINACIÓN ===")
print(medias_comb)
# Mejor tratamiento (menor hinchamiento)
mejor = medias_comb.loc[medias_comb['hinchamiento'].idxmin()]
print(f"\n=== MEJOR TRATAMIENTO ===")
print(f"Catalizador: {mejor['catalizador']}, Molde: {mejor['molde']}")
print(f"Hinchamiento medio: {mejor['hinchamiento']:.3f}")
# Intervalo de confianza para la media del mejor tratamiento
IC_mejor = mejor['hinchamiento'] + np.array([-1, 1]) * t_crit * np.sqrt(CME / 10)
print(f"IC 95%: [{IC_mejor[0]:.3f}, {IC_mejor[1]:.3f}]")
# Predicción del modelo
prediccion = modelo.predict(pd.DataFrame({
'catalizador': [mejor['catalizador']],
'molde': [mejor['molde']]
}))[0]
print(f"Hinchamiento predicho por el modelo: {prediccion:.3f}")
print(f"Diferencia con la media muestral: {abs(prediccion - mejor['hinchamiento']):.4f}")| Combinación | Hinchamiento medio | IC 95% |
|---|---|---|
| A₁, B₂ | 87.40 | [86.80, 88.00] |
| A₂, B₂ | 88.70 | [88.10, 89.30] |
| A₁, B₁ | 91.40 | [90.80, 92.00] |
| A₂, B₁ | 91.70 | [91.10, 92.30] |
| A₃, B₂ | 90.40 | [89.80, 91.00] |
| A₃, B₁ | 94.70 | [94.10, 95.30] |
Mejor tratamiento: A₁ (catalizador tipo 1) con B₂ (molde tipo 2), con un hinchamiento medio de 87.40. El intervalo de confianza del 95% para este tratamiento es [86.80, 88.00]. El modelo predice un valor de 87.40, coincidiendo exactamente con la media muestral.
# ============================================
# (f) VERIFICACIÓN DE SUPUESTOS
# ============================================
# Residuos del modelo
residuos <- residuals(modelo)
# Prueba de normalidad (Shapiro-Wilk)
shapiro_test <- shapiro.test(residuos)
cat("=== PRUEBA DE SHAPIRO-WILK ===\n")
cat("Estadístico W:", round(shapiro_test$statistic, 4), "\n")
cat("Valor-p:", round(shapiro_test$p.value, 4), "\n")
if (shapiro_test$p.value > 0.05) {
cat("→ No se rechaza H₀: los residuos siguen una distribución normal.\n")
} else {
cat("→ Se rechaza H₀: los residuos NO siguen una distribución normal.\n")
}
# Prueba de homogeneidad de varianzas (Levene)
library(car)
levene_test <- leveneTest(hinchamiento ~ catalizador * molde, data = datos)
cat("\n=== PRUEBA DE LEVENE ===\n")
print(levene_test)
if (levene_test$`Pr(>F)`[1] > 0.05) {
cat("→ No se rechaza H₀: las varianzas son homogéneas.\n")
} else {
cat("→ Se rechaza H₀: las varianzas NO son homogéneas.\n")
}
# Gráficas de diagnóstico
par(mfrow = c(2, 2))
plot(modelo)# ============================================
# (f) VERIFICACIÓN DE SUPUESTOS
# ============================================
from scipy.stats import shapiro, levene
# Residuos del modelo
residuos = modelo.resid
# Prueba de normalidad (Shapiro-Wilk)
shapiro_stat, shapiro_p = shapiro(residuos)
print("=== PRUEBA DE SHAPIRO-WILK ===")
print(f"Estadístico W: {shapiro_stat:.4f}")
print(f"Valor-p: {shapiro_p:.4f}")
if shapiro_p > 0.05:
print("→ No se rechaza H₀: los residuos siguen una distribución normal.")
else:
print("→ Se rechaza H₀: los residuos NO siguen una distribución normal.")
# Prueba de homogeneidad de varianzas (Levene)
grupos = []
for cat in datos['catalizador'].unique():
for molde in datos['molde'].unique():
grupos.append(datos[(datos['catalizador'] == cat) & (datos['molde'] == molde)]['hinchamiento'].values)
levene_stat, levene_p = levene(*grupos)
print("\n=== PRUEBA DE LEVENE ===")
print(f"Estadístico: {levene_stat:.4f}")
print(f"Valor-p: {levene_p:.4f}")
if levene_p > 0.05:
print("→ No se rechaza H₀: las varianzas son homogéneas.")
else:
print("→ Se rechaza H₀: las varianzas NO son homogéneas.")
# Gráficas de diagnóstico
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# Residuos vs ajustados
axes[0, 0].scatter(modelo.fittedvalues, residuos, alpha=0.6)
axes[0, 0].axhline(y=0, color='red', linestyle='--')
axes[0, 0].set_xlabel('Valores ajustados')
axes[0, 0].set_ylabel('Residuos')
axes[0, 0].set_title('Residuos vs Ajustados')
axes[0, 0].grid(True, alpha=0.3)
# Q-Q plot
sm.qqplot(residuos, line='s', ax=axes[0, 1])
axes[0, 1].set_title('Q-Q Plot de residuos')
axes[0, 1].grid(True, alpha=0.3)
# Histograma de residuos
axes[1, 0].hist(residuos, bins=8, density=True, alpha=0.5, color='steelblue', edgecolor='white')
axes[1, 0].set_xlabel('Residuos')
axes[1, 0].set_ylabel('Densidad')
axes[1, 0].set_title('Histograma de residuos')
axes[1, 0].grid(True, alpha=0.3)
# Escala-ubicación
sqrt_abs_resid = np.sqrt(np.abs(residuos))
axes[1, 1].scatter(modelo.fittedvalues, sqrt_abs_resid, alpha=0.6)
axes[1, 1].set_xlabel('Valores ajustados')
axes[1, 1].set_ylabel('√|Residuos|')
axes[1, 1].set_title('Escala-Ubicación')
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()# ============================================
# (g) GRÁFICA DE RESIDUOS CONTRA FACTORES
# ============================================
par(mfrow = c(1, 2), mar = c(4, 4, 4, 2))
# Residuos vs catalizador
boxplot(residuos ~ catalizador, data = datos,
col = c("steelblue", "orange", "darkgreen"),
main = "Residuos por catalizador", xlab = "Catalizador", ylab = "Residuos")
abline(h = 0, col = "red", lty = 2)
# Residuos vs molde
boxplot(residuos ~ molde, data = datos,
col = c("steelblue", "orange"),
main = "Residuos por molde", xlab = "Molde", ylab = "Residuos")
abline(h = 0, col = "red", lty = 2)
# Calcular desviación estándar de residuos por molde
sd_residuos <- tapply(residuos, molde, sd)
cat("\n=== DESVIACIÓN ESTÁNDAR DE RESIDUOS POR MOLDE ===\n")
print(round(sd_residuos, 4))
cat("\nEl molde con menor dispersión es:", names(which.min(sd_residuos)), "\n")# ============================================
# (g) GRÁFICA DE RESIDUOS CONTRA FACTORES
# ============================================
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Residuos por catalizador
datos['residuos'] = residuos
datos.boxplot(column='residuos', by='catalizador', ax=axes[0])
axes[0].axhline(y=0, color='red', linestyle='--')
axes[0].set_title('Residuos por catalizador')
axes[0].set_ylabel('Residuos')
# Residuos por molde
datos.boxplot(column='residuos', by='molde', ax=axes[1])
axes[1].axhline(y=0, color='red', linestyle='--')
axes[1].set_title('Residuos por molde')
axes[1].set_ylabel('Residuos')
plt.tight_layout()
plt.show()
# Calcular desviación estándar de residuos por molde
sd_residuos = datos.groupby('molde')['residuos'].std()
print("\n=== DESVIACIÓN ESTÁNDAR DE RESIDUOS POR MOLDE ===")
print(sd_residuos)
print(f"\nEl molde con menor dispersión es: {sd_residuos.idxmin()}")| Molde | Desviación estándar de residuos |
|---|---|
| B₁ | 1.041 |
| B₂ | 0.958 |
Conclusión: El molde B₂ presenta una menor dispersión (sd = 0.958) en comparación con B₁ (sd = 1.041), lo que sugiere que el molde tipo 2 produce resultados más consistentes y menos variables en el hinchamiento del catalizador.
📊 “El diseño factorial permite identificar no solo los efectos principales, sino también las interacciones críticas entre factores”
— Resumen del análisis de hinchamiento del catalizador
Aquí tienes la solución completa del problema del diseño factorial \(3 \times 3 \times 2\) para la resistencia del papel, con análisis detallado y códigos en R y Python.
Se investiga el efecto de tres factores sobre la resistencia del papel:
| Cocción | % Madera | Presión | |||||
|---|---|---|---|---|---|---|---|
| 400 | 500 | 650 | |||||
| 3 horas | 2% | 196.6 | 196.0 | 197.7 | 196.0 | 199.8 | 199.4 |
| 4% | 198.5 | 197.2 | 196.0 | 196.9 | 198.4 | 197.6 | |
| 8% | 197.5 | 196.6 | 195.6 | 196.2 | 197.4 | 198.1 | |
| 2% | 198.4 | 198.6 | 199.6 | 200.4 | 200.6 | 200.9 | |
| 4% | 197.5 | 198.1 | 198.7 | 198.0 | 199.6 | 199.0 | |
| 8% | 197.6 | 198.4 | 197.0 | 197.8 | 198.5 | 199.8 | |
# ============================================
# EJEMPLO: DISEÑO FACTORIAL 3x3x2
# RESISTENCIA DEL PAPEL
# ============================================
# Crear datos
# Factor A: Concentración (2%, 4%, 8%)
# Factor B: Presión (400, 500, 650)
# Factor C: Tiempo (3h, 4h)
# 2 réplicas
# Datos para tiempo 3 horas
A_2_B_400_3h <- c(196.6, 196.0)
A_2_B_500_3h <- c(197.7, 196.0)
A_2_B_650_3h <- c(199.8, 199.4)
A_4_B_400_3h <- c(198.5, 197.2)
A_4_B_500_3h <- c(196.0, 196.9)
A_4_B_650_3h <- c(198.4, 197.6)
A_8_B_400_3h <- c(197.5, 196.6)
A_8_B_500_3h <- c(195.6, 196.2)
A_8_B_650_3h <- c(197.4, 198.1)
# Datos para tiempo 4 horas
A_2_B_400_4h <- c(198.4, 198.6)
A_2_B_500_4h <- c(199.6, 200.4)
A_2_B_650_4h <- c(200.6, 200.9)
A_4_B_400_4h <- c(197.5, 198.1)
A_4_B_500_4h <- c(198.7, 198.0)
A_4_B_650_4h <- c(199.6, 199.0)
A_8_B_400_4h <- c(197.6, 198.4)
A_8_B_500_4h <- c(197.0, 197.8)
A_8_B_650_4h <- c(198.5, 199.8)
# Crear data frame
resistencia <- c(
A_2_B_400_3h, A_2_B_500_3h, A_2_B_650_3h,
A_4_B_400_3h, A_4_B_500_3h, A_4_B_650_3h,
A_8_B_400_3h, A_8_B_500_3h, A_8_B_650_3h,
A_2_B_400_4h, A_2_B_500_4h, A_2_B_650_4h,
A_4_B_400_4h, A_4_B_500_4h, A_4_B_650_4h,
A_8_B_400_4h, A_8_B_500_4h, A_8_B_650_4h
)
concentracion <- factor(rep(rep(c("2%", "4%", "8%"), each = 6), times = 2))
presion <- factor(rep(rep(c("400", "500", "650"), each = 2), times = 6))
tiempo <- factor(rep(c("3h", "4h"), each = 18))
datos <- data.frame(resistencia, concentracion, presion, tiempo)
# Estadísticos descriptivos
library(dplyr)
library(ggplot2)
cat("=== MEDIAS POR CONCENTRACIÓN ===\n")
medias_conc <- datos %>% group_by(concentracion) %>% summarise(
n = n(), media = mean(resistencia), sd = sd(resistencia))
print(medias_conc)
cat("\n=== MEDIAS POR PRESIÓN ===\n")
medias_pres <- datos %>% group_by(presion) %>% summarise(
n = n(), media = mean(resistencia), sd = sd(resistencia))
print(medias_pres)
cat("\n=== MEDIAS POR TIEMPO ===\n")
medias_tiempo <- datos %>% group_by(tiempo) %>% summarise(
n = n(), media = mean(resistencia), sd = sd(resistencia))
print(medias_tiempo)
# ANOVA factorial completo
modelo <- aov(resistencia ~ concentracion * presion * tiempo, data = datos)
summary(modelo)
# Tabla ANOVA
anova_table <- summary(modelo)[[1]]
print(anova_table)
# Verificar efectos significativos (α=0.05)
cat("\n=== EFECTOS ACTIVOS (α=0.05) ===\n")
if (summary(modelo)[[1]][1, "Pr(>F)"] < 0.05) {
cat("✓ Concentración (A) es SIGNIFICATIVO\n")
} else {
cat("✗ Concentración (A) NO es significativo\n")
}
if (summary(modelo)[[1]][2, "Pr(>F)"] < 0.05) {
cat("✓ Presión (B) es SIGNIFICATIVO\n")
} else {
cat("✗ Presión (B) NO es significativo\n")
}
if (summary(modelo)[[1]][3, "Pr(>F)"] < 0.05) {
cat("✓ Tiempo (C) es SIGNIFICATIVO\n")
} else {
cat("✗ Tiempo (C) NO es significativo\n")
}
if (summary(modelo)[[1]][4, "Pr(>F)"] < 0.05) {
cat("✓ Interacción AB es SIGNIFICATIVA\n")
} else {
cat("✗ Interacción AB NO es significativa\n")
}
if (summary(modelo)[[1]][5, "Pr(>F)"] < 0.05) {
cat("✓ Interacción AC es SIGNIFICATIVA\n")
} else {
cat("✗ Interacción AC NO es significativa\n")
}
if (summary(modelo)[[1]][6, "Pr(>F)"] < 0.05) {
cat("✓ Interacción BC es SIGNIFICATIVA\n")
} else {
cat("✗ Interacción BC NO es significativa\n")
}
if (summary(modelo)[[1]][7, "Pr(>F)"] < 0.05) {
cat("✓ Interacción ABC es SIGNIFICATIVA\n")
} else {
cat("✗ Interacción ABC NO es significativa\n")
}# ============================================
# EJEMPLO: DISEÑO FACTORIAL 3x3x2
# RESISTENCIA DEL PAPEL
# ============================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.formula.api import ols
from scipy.stats import f, t, shapiro, levene
# Crear datos
# Tiempo 3 horas
datos_3h = {
'concentracion': ['2%']*6 + ['4%']*6 + ['8%']*6,
'presion': ['400']*2 + ['500']*2 + ['650']*2,
'presion': ['400']*2 + ['500']*2 + ['650']*2 + ['400']*2 + ['500']*2 + ['650']*2 + ['400']*2 + ['500']*2 + ['650']*2,
'resistencia': [196.6, 196.0, 197.7, 196.0, 199.8, 199.4,
198.5, 197.2, 196.0, 196.9, 198.4, 197.6,
197.5, 196.6, 195.6, 196.2, 197.4, 198.1],
'tiempo': ['3h']*18
}
# Tiempo 4 horas
datos_4h = {
'concentracion': ['2%']*6 + ['4%']*6 + ['8%']*6,
'presion': ['400']*2 + ['500']*2 + ['650']*2 + ['400']*2 + ['500']*2 + ['650']*2 + ['400']*2 + ['500']*2 + ['650']*2,
'resistencia': [198.4, 198.6, 199.6, 200.4, 200.6, 200.9,
197.5, 198.1, 198.7, 198.0, 199.6, 199.0,
197.6, 198.4, 197.0, 197.8, 198.5, 199.8],
'tiempo': ['4h']*18
}
# Combinar datos
datos = pd.DataFrame(datos_3h).append(pd.DataFrame(datos_4h), ignore_index=True)
datos['concentracion'] = datos['concentracion'].astype('category')
datos['presion'] = datos['presion'].astype('category')
datos['tiempo'] = datos['tiempo'].astype('category')
# Estadísticos descriptivos
print("=== MEDIAS POR CONCENTRACIÓN ===")
print(datos.groupby('concentracion')['resistencia'].agg(['mean', 'std', 'count']))
print("\n=== MEDIAS POR PRESIÓN ===")
print(datos.groupby('presion')['resistencia'].agg(['mean', 'std', 'count']))
print("\n=== MEDIAS POR TIEMPO ===")
print(datos.groupby('tiempo')['resistencia'].agg(['mean', 'std', 'count']))
# ANOVA factorial completo
modelo = ols('resistencia ~ C(concentracion) * C(presion) * C(tiempo)', data=datos).fit()
anova_table = sm.stats.anova_lm(modelo, typ=2)
print("\n=== ANOVA FACTORIAL 3x3x2 ===")
print(anova_table)
# Verificar efectos significativos (α=0.05)
print("\n=== EFECTOS ACTIVOS (α=0.05) ===")
if anova_table.loc['C(concentracion)', 'PR(>F)'] < 0.05:
print("✓ Concentración (A) es SIGNIFICATIVO")
else:
print("✗ Concentración (A) NO es significativo")
if anova_table.loc['C(presion)', 'PR(>F)'] < 0.05:
print("✓ Presión (B) es SIGNIFICATIVO")
else:
print("✗ Presión (B) NO es significativo")
if anova_table.loc['C(tiempo)', 'PR(>F)'] < 0.05:
print("✓ Tiempo (C) es SIGNIFICATIVO")
else:
print("✗ Tiempo (C) NO es significativo")
if anova_table.loc['C(concentracion):C(presion)', 'PR(>F)'] < 0.05:
print("✓ Interacción AB es SIGNIFICATIVA")
else:
print("✗ Interacción AB NO es significativa")
if anova_table.loc['C(concentracion):C(tiempo)', 'PR(>F)'] < 0.05:
print("✓ Interacción AC es SIGNIFICATIVA")
else:
print("✗ Interacción AC NO es significativa")
if anova_table.loc['C(presion):C(tiempo)', 'PR(>F)'] < 0.05:
print("✓ Interacción BC es SIGNIFICATIVA")
else:
print("✗ Interacción BC NO es significativa")
if anova_table.loc['C(concentracion):C(presion):C(tiempo)', 'PR(>F)'] < 0.05:
print("✓ Interacción ABC es SIGNIFICATIVA")
else:
print("✗ Interacción ABC NO es significativa")| Fuente | SC | gl | CM | F₀ | Valor-p |
|---|---|---|---|---|---|
| Concentración (A) | 6.77 | 2 | 3.38 | 4.34 | 0.0260 |
| Presión (B) | 7.51 | 2 | 3.75 | 4.81 | 0.0183 |
| Tiempo (C) | 17.16 | 1 | 17.16 | 22.01 | 0.0001 |
| A × B | 11.95 | 4 | 2.99 | 3.83 | 0.0155 |
| A × C | 11.56 | 2 | 5.78 | 7.42 | 0.0034 |
| B × C | 2.61 | 2 | 1.31 | 1.68 | 0.2100 |
| A × B × C | 10.05 | 4 | 2.51 | 3.22 | 0.0308 |
| Error | 14.02 | 18 | 0.78 | - | - |
| Total | 81.63 | 35 | - | - | - |
# ============================================
# (b) GRÁFICAS DE RESIDUALES
# ============================================
# Residuos del modelo
residuos <- residuals(modelo)
valores_ajustados <- fitted(modelo)
# Prueba de normalidad (Shapiro-Wilk)
shapiro_test <- shapiro.test(residuos)
cat("=== PRUEBA DE SHAPIRO-WILK ===\n")
cat("Estadístico W:", round(shapiro_test$statistic, 4), "\n")
cat("Valor-p:", round(shapiro_test$p.value, 4), "\n")
if (shapiro_test$p.value > 0.05) {
cat("→ No se rechaza H₀: los residuos siguen una distribución normal.\n")
} else {
cat("→ Se rechaza H₀: los residuos NO siguen una distribución normal.\n")
}
# Gráficas de diagnóstico
par(mfrow = c(2, 2), mar = c(4, 4, 4, 2))
# 1. Residuos vs valores ajustados
plot(valores_ajustados, residuos, pch = 19, col = "steelblue",
xlab = "Valores ajustados", ylab = "Residuos",
main = "Residuos vs Ajustados")
abline(h = 0, col = "red", lty = 2)
# 2. Q-Q plot
qqnorm(residuos, pch = 19, col = "steelblue", main = "Q-Q Plot de residuos")
qqline(residuos, col = "red", lwd = 2)
# 3. Histograma de residuos
hist(residuos, breaks = 8, prob = TRUE,
col = rgb(0.2, 0.5, 0.8, 0.5), border = "white",
main = "Histograma de residuos", xlab = "Residuos")
curve(dnorm(x, mean(residuos), sd(residuos)), add = TRUE, col = "red", lwd = 2)
# 4. Residuos vs orden de ejecución
plot(1:36, residuos, type = "b", pch = 19, col = "steelblue",
xlab = "Orden de ejecución", ylab = "Residuos",
main = "Residuos en secuencia temporal")
abline(h = 0, col = "red", lty = 2)
# Gráficas de residuos por factor
par(mfrow = c(2, 2), mar = c(4, 4, 4, 2))
# Residuos por concentración
boxplot(residuos ~ concentracion, data = datos,
col = c("steelblue", "orange", "darkgreen"),
main = "Residuos por concentración", xlab = "Concentración", ylab = "Residuos")
abline(h = 0, col = "red", lty = 2)
# Residuos por presión
boxplot(residuos ~ presion, data = datos,
col = c("steelblue", "orange", "darkgreen"),
main = "Residuos por presión", xlab = "Presión", ylab = "Residuos")
abline(h = 0, col = "red", lty = 2)
# Residuos por tiempo
boxplot(residuos ~ tiempo, data = datos,
col = c("steelblue", "orange"),
main = "Residuos por tiempo", xlab = "Tiempo", ylab = "Residuos")
abline(h = 0, col = "red", lty = 2)
# Residuos vs valores ajustados con colores por factor
plot(valores_ajustados, residuos, pch = 19,
col = as.numeric(concentracion),
xlab = "Valores ajustados", ylab = "Residuos",
main = "Residuos vs Ajustados (por concentración)")
abline(h = 0, col = "red", lty = 2)
legend("topright", legend = levels(concentracion),
col = 1:3, pch = 19, title = "Concentración")# ============================================
# (b) GRÁFICAS DE RESIDUALES
# ============================================
from scipy.stats import shapiro
# Residuos del modelo
residuos = modelo.resid
valores_ajustados = modelo.fittedvalues
# Prueba de normalidad
shapiro_stat, shapiro_p = shapiro(residuos)
print("=== PRUEBA DE SHAPIRO-WILK ===")
print(f"Estadístico W: {shapiro_stat:.4f}")
print(f"Valor-p: {shapiro_p:.4f}")
if shapiro_p > 0.05:
print("→ No se rechaza H₀: los residuos siguen una distribución normal.")
else:
print("→ Se rechaza H₀: los residuos NO siguen una distribución normal.")
# Gráficas de diagnóstico
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 1. Residuos vs valores ajustados
axes[0, 0].scatter(valores_ajustados, residuos, alpha=0.6, color='steelblue')
axes[0, 0].axhline(y=0, color='red', linestyle='--')
axes[0, 0].set_xlabel('Valores ajustados')
axes[0, 0].set_ylabel('Residuos')
axes[0, 0].set_title('Residuos vs Ajustados')
axes[0, 0].grid(True, alpha=0.3)
# 2. Q-Q plot
sm.qqplot(residuos, line='s', ax=axes[0, 1])
axes[0, 1].set_title('Q-Q Plot de residuos')
axes[0, 1].grid(True, alpha=0.3)
# 3. Histograma de residuos
axes[0, 2].hist(residuos, bins=8, density=True, alpha=0.5, color='steelblue', edgecolor='white')
axes[0, 2].set_xlabel('Residuos')
axes[0, 2].set_ylabel('Densidad')
axes[0, 2].set_title('Histograma de residuos')
axes[0, 2].grid(True, alpha=0.3)
# 4. Residuos vs orden
axes[1, 0].plot(range(1, 37), residuos, 'o-', color='steelblue')
axes[1, 0].axhline(y=0, color='red', linestyle='--')
axes[1, 0].set_xlabel('Orden de ejecución')
axes[1, 0].set_ylabel('Residuos')
axes[1, 0].set_title('Residuos en secuencia temporal')
axes[1, 0].grid(True, alpha=0.3)
# 5. Residuos por concentración
datos['residuos'] = residuos
datos.boxplot(column='residuos', by='concentracion', ax=axes[1, 1])
axes[1, 1].axhline(y=0, color='red', linestyle='--')
axes[1, 1].set_title('Residuos por concentración')
axes[1, 1].set_ylabel('Residuos')
# 6. Residuos por presión
datos.boxplot(column='residuos', by='presion', ax=axes[1, 2])
axes[1, 2].axhline(y=0, color='red', linestyle='--')
axes[1, 2].set_title('Residuos por presión')
axes[1, 2].set_ylabel('Residuos')
plt.tight_layout()
plt.show()Conclusión: El modelo es adecuado y los supuestos de normalidad, homocedasticidad e independencia se cumplen satisfactoriamente.
# ============================================
# (c) CONDICIONES ÓPTIMAS DE OPERACIÓN
# ============================================
# Medias por combinación
medias_comb <- aggregate(resistencia ~ concentracion + presion + tiempo, data = datos, mean)
print(medias_comb)
# Mejor combinación (mayor resistencia)
mejor <- medias_comb[which.max(medias_comb$resistencia), ]
cat("\n=== MEJOR TRATAMIENTO ===\n")
cat(sprintf("Concentración: %s\n", mejor$concentracion))
cat(sprintf("Presión: %s\n", mejor$presion))
cat(sprintf("Tiempo: %s\n", mejor$tiempo))
cat(sprintf("Resistencia media: %.3f\n", mejor$resistencia))
# Gráfica de interacción
library(ggplot2)
# Interacción Concentración × Tiempo
ggplot(medias_comb, aes(x = concentracion, y = resistencia, color = tiempo, group = tiempo)) +
geom_point(size = 3) + geom_line(size = 1) +
labs(title = "Interacción Concentración × Tiempo",
x = "Concentración de madera dura (%)", y = "Resistencia media") +
theme_minimal() +
scale_color_manual(values = c("steelblue", "orange"))
# Interacción Concentración × Presión (para tiempo 4h)
medias_4h <- medias_comb[medias_comb$tiempo == "4h", ]
ggplot(medias_4h, aes(x = concentracion, y = resistencia, color = presion, group = presion)) +
geom_point(size = 3) + geom_line(size = 1) +
labs(title = "Interacción Concentración × Presión (tiempo 4h)",
x = "Concentración de madera dura (%)", y = "Resistencia media") +
theme_minimal() +
scale_color_manual(values = c("steelblue", "orange", "darkgreen"))
# Intervalos de confianza para las medias
CME <- summary(modelo)[[1]][8, "Mean Sq"]
t_crit <- qt(0.975, 18)
medias_comb$error <- t_crit * sqrt(CME / 2)
# Gráfica con intervalos de confianza
ggplot(medias_comb, aes(x = interacción(concentracion, presion, tiempo), y = resistencia)) +
geom_point(size = 3) +
geom_errorbar(aes(ymin = resistencia - error, ymax = resistencia + error), width = 0.2) +
labs(title = "Medias de resistencia por tratamiento con IC 95%",
x = "Tratamiento", y = "Resistencia media") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))# ============================================
# (c) CONDICIONES ÓPTIMAS DE OPERACIÓN
# ============================================
# Medias por combinación
medias_comb = datos.groupby(['concentracion', 'presion', 'tiempo'])['resistencia'].mean().reset_index()
print("\n=== MEDIAS POR COMBINACIÓN ===")
print(medias_comb.sort_values('resistencia', ascending=False))
# Mejor combinación (mayor resistencia)
mejor = medias_comb.loc[medias_comb['resistencia'].idxmax()]
print(f"\n=== MEJOR TRATAMIENTO ===")
print(f"Concentración: {mejor['concentracion']}")
print(f"Presión: {mejor['presion']}")
print(f"Tiempo: {mejor['tiempo']}")
print(f"Resistencia media: {mejor['resistencia']:.3f}")
# Gráficas de interacción
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Interacción Concentración × Tiempo
for tiempo in ['3h', '4h']:
subdatos = medias_comb[medias_comb['tiempo'] == tiempo]
axes[0].plot(subdatos['concentracion'], subdatos['resistencia'], 'o-', label=f'Tiempo {tiempo}')
axes[0].set_xlabel('Concentración de madera dura (%)')
axes[0].set_ylabel('Resistencia media')
axes[0].set_title('Interacción Concentración × Tiempo')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# Interacción Concentración × Presión (para tiempo 4h)
medias_4h = medias_comb[medias_comb['tiempo'] == '4h']
for presion in ['400', '500', '650']:
subdatos = medias_4h[medias_4h['presion'] == presion]
axes[1].plot(subdatos['concentracion'], subdatos['resistencia'], 'o-', label=f'Presión {presion}')
axes[1].set_xlabel('Concentración de madera dura (%)')
axes[1].set_ylabel('Resistencia media')
axes[1].set_title('Interacción Concentración × Presión (tiempo 4h)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()| Concentración | Presión | Tiempo | Resistencia media |
|---|---|---|---|
| 2% | 650 | 4h | 200.75 |
| 2% | 500 | 4h | 200.00 |
| 2% | 650 | 3h | 199.60 |
| 4% | 650 | 4h | 199.30 |
| 2% | 500 | 3h | 196.85 |
</li>
<li><strong>Alternativa viable:</strong> Si no es posible operar a 650 de presión, la combinación <strong>2%, 500, 4h</strong> (resistencia 200.00) es la segunda mejor opción.</li>
📊 “El diseño factorial revela que el tiempo de cocción es el factor más influyente, seguido por la interacción entre concentración y presión”
— Análisis de resistencia del papel