rsmEl presente documento desarrolla un ejercicio demostrativo de Diseño de Experimentos (DOE) y Metodología de Superficie de Respuesta (RSM) aplicado a nutrición de camarón blanco (Litopenaeus vannamei). El propósito es evaluar el efecto conjunto de dos factores nutricionales —porcentaje de proteína cruda y porcentaje de lípidos— sobre indicadores productivos relevantes: ganancia de peso, peso final, factor de conversión alimenticia (FCR), supervivencia y tasa específica de crecimiento (SGR).
El estudio se construye con datos simulados exclusivamente con fines académicos y de portafolio técnico. Por tanto, las conclusiones ilustran el procedimiento estadístico y no deben interpretarse como recomendaciones nutricionales comerciales ni como resultados de un ensayo de campo real.
La lógica del análisis es:
diseño experimental → control de calidad de datos → análisis descriptivo → modelado cuadrático → evaluación de falta de ajuste → diagnóstico → superficie de respuesta → optimización individual y multirrespuesta → propuesta de ensayo confirmatorio.
¿Qué combinación de proteína cruda y lípidos en una dieta experimental permite mejorar el desempeño productivo del camarón, maximizando crecimiento y supervivencia y reduciendo el factor de conversión alimenticia?
Se consideran dos factores controlables:
| Factor | Nivel bajo (-1) | Nivel central (0) | Nivel alto (+1) |
|---|---|---|---|
| Proteína cruda (%) | 32 | 36 | 40 |
| Lípidos (%) | 6 | 8 | 10 |
La combinación de tres niveles de proteína y tres niveles de lípidos genera:
\[ 3 \times 3 = 9 \text{ tratamientos} \]
Cada tratamiento dispone de tres réplicas independientes:
\[ 9 \times 3 = 27 \text{ unidades experimentales} \]
En un ensayo real, la unidad experimental sería el tanque, no cada camarón individual. Esta distinción evita pseudorreplicación, debido a que los animales de un mismo tanque comparten dieta, agua, manejo y condiciones ambientales.
Las principales variables productivas son:
\[ SGR=100\frac{\ln(W_f)-\ln(W_i)}{t} \]
donde \(W_f\) es el peso final, \(W_i\) el peso inicial y \(t\) el número de días del ensayo.
archivos <- list.files(
pattern = "^datos_simulados_rsm_camaron.*\\.csv$",
ignore.case = TRUE
)
if (length(archivos) == 0) {
stop("No se encontró el archivo CSV. Coloque el R Markdown y el CSV en la misma carpeta.")
}
# Si existen varias versiones, se utiliza la modificada más recientemente.
info_archivos <- file.info(archivos)
archivo <- rownames(info_archivos)[which.max(info_archivos$mtime)]
datos <- readr::read_delim(
archivo,
delim = ";",
locale = locale(decimal_mark = "."),
show_col_types = FALSE,
trim_ws = TRUE
)
cat("Archivo utilizado:", archivo, "\n")
## Archivo utilizado: datos_simulados_rsm_camaron.csv
control <- tibble(
Indicador = c(
"Unidades experimentales",
"Variables",
"Valores faltantes",
"Filas duplicadas",
"Niveles de proteína",
"Niveles de lípidos"
),
Valor = c(
nrow(datos),
ncol(datos),
sum(is.na(datos)),
sum(duplicated(datos)),
n_distinct(datos$proteina_pct),
n_distinct(datos$lipidos_pct)
)
)
kable(
control,
caption = "Control de calidad y estructura general de la base"
)
| Indicador | Valor |
|---|---|
| Unidades experimentales | 27 |
| Variables | 12 |
| Valores faltantes | 0 |
| Filas duplicadas | 0 |
| Niveles de proteína | 3 |
| Niveles de lípidos | 3 |
La base contiene 27 unidades experimentales y 12 variables. Se identifican 3 niveles de proteína y 3 niveles de lípidos, correspondientes a un diseño factorial \(3\times3\) con replicación.
glimpse(datos)
## Rows: 27
## Columns: 12
## $ orden_corrida <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1…
## $ replica <dbl> 2, 3, 3, 1, 1, 1, 2, 1, 3, 2, 3, 3, 2, 2, 1, 1, 1, 2…
## $ proteina_pct <dbl> 40, 32, 40, 32, 36, 32, 40, 40, 36, 32, 32, 32, 36, …
## $ lipidos_pct <dbl> 8, 10, 6, 8, 8, 6, 6, 6, 8, 10, 8, 6, 10, 10, 10, 8,…
## $ x1 <dbl> 1, -1, 1, -1, 0, -1, 1, 1, 0, -1, -1, -1, 0, 1, -1, …
## $ x2 <dbl> 0, 1, -1, 0, 0, -1, -1, -1, 0, 1, 0, -1, 1, 1, 1, 0,…
## $ peso_inicial_g <dbl> 1.935, 1.961, 2.027, 1.964, 1.977, 2.029, 2.046, 1.9…
## $ ganancia_peso_g <dbl> 18.287, 15.546, 15.533, 15.318, 18.525, 13.680, 16.0…
## $ peso_final_g <dbl> 20.223, 17.507, 17.560, 17.282, 20.502, 15.709, 18.1…
## $ FCR <dbl> 1.281, 1.494, 1.437, 1.485, 1.321, 1.591, 1.460, 1.4…
## $ supervivencia_pct <dbl> 93.09, 93.16, 93.89, 94.18, 95.26, 92.53, 92.23, 95.…
## $ SGR_pct_dia <dbl> 4.190, 3.910, 3.855, 3.884, 4.177, 3.655, 3.894, 3.8…
tabla_replicas <- datos %>%
count(proteina_pct, lipidos_pct, name = "numero_replicas") %>%
arrange(proteina_pct, lipidos_pct)
kable(
tabla_replicas,
caption = "Número de réplicas por combinación experimental"
)
| proteina_pct | lipidos_pct | numero_replicas |
|---|---|---|
| 32 | 6 | 3 |
| 32 | 8 | 3 |
| 32 | 10 | 3 |
| 36 | 6 | 3 |
| 36 | 8 | 3 |
| 36 | 10 | 3 |
| 40 | 6 | 3 |
| 40 | 8 | 3 |
| 40 | 10 | 3 |
Un diseño balanceado debería presentar el mismo número de réplicas en cada combinación. En esta base, el número mínimo de réplicas por tratamiento es 3 y el máximo es 3, confirmándose el balance del diseño.
Para el análisis de superficie de respuesta se emplean variables codificadas:
\[ x_1=\frac{\text{Proteína}-36}{4} \]
\[ x_2=\frac{\text{Lípidos}-8}{2} \]
tabla_codificacion <- datos %>%
distinct(proteina_pct, lipidos_pct, x1, x2) %>%
arrange(proteina_pct, lipidos_pct)
kable(
tabla_codificacion,
digits = 2,
caption = "Correspondencia entre niveles reales y variables codificadas"
)
| proteina_pct | lipidos_pct | x1 | x2 |
|---|---|---|---|
| 32 | 6 | -1 | -1 |
| 32 | 8 | -1 | 0 |
| 32 | 10 | -1 | 1 |
| 36 | 6 | 0 | -1 |
| 36 | 8 | 0 | 0 |
| 36 | 10 | 0 | 1 |
| 40 | 6 | 1 | -1 |
| 40 | 8 | 1 | 0 |
| 40 | 10 | 1 | 1 |
La codificación centra el experimento en \(x_1=0\) y \(x_2=0\), equivalentes a 36% de proteína y 8% de lípidos. Los niveles extremos se representan mediante -1 y +1, facilitando la interpretación de efectos lineales, interacción y curvatura.
resumen_tratamientos <- datos %>%
group_by(proteina_pct, lipidos_pct) %>%
summarise(
n = n(),
peso_inicial_medio = mean(peso_inicial_g),
peso_final_medio = mean(peso_final_g),
ganancia_media = mean(ganancia_peso_g),
sd_ganancia = sd(ganancia_peso_g),
FCR_medio = mean(FCR),
sd_FCR = sd(FCR),
supervivencia_media = mean(supervivencia_pct),
SGR_medio = mean(SGR_pct_dia),
.groups = "drop"
)
kable(
resumen_tratamientos,
digits = 3,
caption = "Indicadores productivos medios por tratamiento"
)
| proteina_pct | lipidos_pct | n | peso_inicial_medio | peso_final_medio | ganancia_media | sd_ganancia | FCR_medio | sd_FCR | supervivencia_media | SGR_medio |
|---|---|---|---|---|---|---|---|---|---|---|
| 32 | 6 | 3 | 2.001 | 15.804 | 13.803 | 0.139 | 1.597 | 0.006 | 92.467 | 3.691 |
| 32 | 8 | 3 | 1.977 | 17.237 | 15.260 | 0.341 | 1.472 | 0.012 | 93.823 | 3.868 |
| 32 | 10 | 3 | 2.014 | 17.073 | 15.059 | 0.440 | 1.482 | 0.013 | 93.373 | 3.818 |
| 36 | 6 | 3 | 2.026 | 18.630 | 16.604 | 0.489 | 1.442 | 0.009 | 94.530 | 3.962 |
| 36 | 8 | 3 | 1.951 | 20.232 | 18.282 | 0.224 | 1.290 | 0.027 | 95.003 | 4.177 |
| 36 | 10 | 3 | 1.984 | 20.054 | 18.070 | 0.310 | 1.307 | 0.013 | 94.790 | 4.131 |
| 40 | 6 | 3 | 2.003 | 17.511 | 15.508 | 0.574 | 1.457 | 0.018 | 93.723 | 3.871 |
| 40 | 8 | 3 | 1.964 | 20.063 | 18.098 | 0.248 | 1.287 | 0.013 | 94.350 | 4.150 |
| 40 | 10 | 3 | 1.996 | 20.821 | 18.825 | 0.298 | 1.303 | 0.019 | 94.853 | 4.187 |
Interpretación descriptiva. La mayor ganancia media observada se presenta con 40% de proteína y 10% de lípidos, con aproximadamente 18.825 g de ganancia. El menor FCR observado corresponde a 40% de proteína y 8% de lípidos, con un FCR medio de 1.287. La mayor supervivencia media se observa en 36% de proteína y 8% de lípidos, con 95.00%.
Estos resultados descriptivos son útiles, pero todavía no constituyen una optimización. La RSM permitirá estimar qué ocurre entre los niveles ensayados y localizar una región óptima continua.
ggplot(
resumen_tratamientos,
aes(
x = proteina_pct,
y = ganancia_media,
group = factor(lipidos_pct),
linetype = factor(lipidos_pct)
)
) +
geom_line(linewidth = 0.9) +
geom_point(size = 3) +
scale_x_continuous(breaks = c(32, 36, 40)) +
labs(
title = "Interacción entre proteína y lípidos sobre la ganancia de peso",
x = "Proteína cruda (%)",
y = "Ganancia media de peso (g)",
linetype = "Lípidos (%)"
) +
theme_minimal(base_size = 12)
Interpretación. El gráfico permite observar una respuesta no estrictamente lineal. El crecimiento mejora al pasar desde los niveles bajos hacia la región central-alta, pero la magnitud del efecto de la proteína cambia según el nivel de lípidos. La falta de paralelismo perfecto entre las líneas anticipa una posible interacción proteína × lípidos, que será evaluada formalmente mediante el modelo cuadrático.
ggplot(
resumen_tratamientos,
aes(
x = factor(proteina_pct),
y = ganancia_media,
group = factor(lipidos_pct),
fill = factor(lipidos_pct)
)
) +
geom_col(
position = position_dodge(width = 0.8),
width = 0.7
) +
geom_errorbar(
aes(
ymin = ganancia_media - sd_ganancia,
ymax = ganancia_media + sd_ganancia
),
position = position_dodge(width = 0.8),
width = 0.2
) +
labs(
title = "Ganancia de peso por combinación nutricional",
subtitle = "Media ± desviación estándar",
x = "Proteína cruda (%)",
y = "Ganancia de peso (g)",
fill = "Lípidos (%)"
) +
theme_minimal(base_size = 12)
ggplot(
resumen_tratamientos,
aes(
x = factor(proteina_pct),
y = FCR_medio,
group = factor(lipidos_pct),
fill = factor(lipidos_pct)
)
) +
geom_col(
position = position_dodge(width = 0.8),
width = 0.7
) +
labs(
title = "Factor de conversión alimenticia por tratamiento",
subtitle = "Valores menores representan mayor eficiencia",
x = "Proteína cruda (%)",
y = "FCR medio",
fill = "Lípidos (%)"
) +
theme_minimal(base_size = 12)
Interpretación. Los tratamientos de la región central-alta muestran una conversión alimenticia más eficiente que la combinación de niveles nutricionales bajos. Sin embargo, elevar simultáneamente ambos nutrientes hasta sus máximos no implica necesariamente el FCR mínimo, lo que justifica buscar un punto de optimización mediante una función cuadrática.
Para cada respuesta se ajusta el modelo cuadrático de segundo orden:
\[ Y=\beta_0+\beta_1x_1+\beta_2x_2+\beta_{12}x_1x_2+ \beta_{11}x_1^2+\beta_{22}x_2^2+\varepsilon \]
donde:
modelo_peso <- lm(
ganancia_peso_g ~ x1 + x2 + x1:x2 + I(x1^2) + I(x2^2),
data = datos
)
modelo_fcr <- lm(
FCR ~ x1 + x2 + x1:x2 + I(x1^2) + I(x2^2),
data = datos
)
modelo_surv <- lm(
supervivencia_pct ~ x1 + x2 + x1:x2 + I(x1^2) + I(x2^2),
data = datos
)
modelo_sgr <- lm(
SGR_pct_dia ~ x1 + x2 + x1:x2 + I(x1^2) + I(x2^2),
data = datos
)
# Modelo equivalente con el paquete rsm, útil para análisis canónico.
modelo_peso_rsm <- rsm(
ganancia_peso_g ~ SO(x1, x2),
data = datos
)
p_global_modelo <- function(modelo) {
f <- summary(modelo)$fstatistic
unname(pf(f[1], f[2], f[3], lower.tail = FALSE))
}
metricas_modelo <- function(modelo, respuesta) {
s <- summary(modelo)
tibble(
Respuesta = respuesta,
R2 = s$r.squared,
R2_ajustado = s$adj.r.squared,
F_global = unname(s$fstatistic[1]),
p_global = p_global_modelo(modelo)
)
}
tabla_metricas <- bind_rows(
metricas_modelo(modelo_peso, "Ganancia de peso"),
metricas_modelo(modelo_fcr, "FCR"),
metricas_modelo(modelo_surv, "Supervivencia"),
metricas_modelo(modelo_sgr, "SGR")
)
kable(
tabla_metricas,
digits = 4,
caption = "Desempeño global de los modelos cuadráticos"
)
| Respuesta | R2 | R2_ajustado | F_global | p_global |
|---|---|---|---|---|
| Ganancia de peso | 0.9608 | 0.9514 | 102.8345 | 0.0000 |
| FCR | 0.9834 | 0.9794 | 248.8329 | 0.0000 |
| Supervivencia | 0.5089 | 0.3919 | 4.3515 | 0.0071 |
| SGR | 0.9338 | 0.9180 | 59.2363 | 0.0000 |
coef_peso <- as.data.frame(summary(modelo_peso)$coefficients)
coef_peso$Termino <- rownames(coef_peso)
rownames(coef_peso) <- NULL
coef_peso <- coef_peso %>%
select(Termino, everything())
kable(
coef_peso,
digits = 5,
caption = "Coeficientes del modelo cuadrático para ganancia de peso"
)
| Termino | Estimate | Std. Error | t value | Pr(>|t|) |
|---|---|---|---|---|
| (Intercept) | 18.25322 | 0.16481 | 110.75110 | 0.00000 |
| x1 | 1.38478 | 0.09027 | 15.34009 | 0.00000 |
| x2 | 1.00661 | 0.09027 | 11.15089 | 0.00000 |
| I(x1^2) | -1.55967 | 0.15636 | -9.97514 | 0.00000 |
| I(x2^2) | -0.90183 | 0.15636 | -5.76784 | 0.00001 |
| x1:x2 | 0.51550 | 0.11056 | 4.66263 | 0.00013 |
anova_peso <- as.data.frame(anova(modelo_peso))
anova_peso$Fuente <- rownames(anova_peso)
rownames(anova_peso) <- NULL
anova_peso <- anova_peso %>% select(Fuente, everything())
kable(
anova_peso,
digits = 5,
caption = "ANOVA del modelo de ganancia de peso"
)
| Fuente | Df | Sum Sq | Mean Sq | F value | Pr(>F) |
|---|---|---|---|---|---|
| x1 | 1 | 34.51697 | 34.51697 | 235.31844 | 0.00000 |
| x2 | 1 | 18.23879 | 18.23879 | 124.34240 | 0.00000 |
| I(x1^2) | 1 | 14.59536 | 14.59536 | 99.50345 | 0.00000 |
| I(x2^2) | 1 | 4.87982 | 4.87982 | 33.26803 | 0.00001 |
| x1:x2 | 1 | 3.18888 | 3.18888 | 21.74012 | 0.00013 |
| Residuals | 21 | 3.08032 | 0.14668 | NA | NA |
Interpretación técnica. El modelo explica aproximadamente 96.1% de la variabilidad observada en la ganancia de peso, con un \(R^2\) ajustado de 0.951. La prueba global del modelo presenta \(p=\) <0.001, lo que evidencia una relación estadísticamente relevante entre la formulación nutricional y el crecimiento simulado.
Los efectos lineales de proteína (\(p=\) <0.001) y lípidos (\(p=\) <0.001) son significativos. La interacción proteína × lípidos también resulta significativa (\(p=\) <0.001), indicando que el efecto de un nutriente depende del nivel del otro. Los términos cuadráticos de proteína y lípidos presentan evidencia de curvatura, condición fundamental para que la RSM pueda identificar una región óptima interior.
coef_fcr <- as.data.frame(summary(modelo_fcr)$coefficients)
coef_fcr$Termino <- rownames(coef_fcr)
rownames(coef_fcr) <- NULL
coef_fcr <- coef_fcr %>%
select(Termino, everything())
kable(
coef_fcr,
digits = 5,
caption = "Coeficientes del modelo cuadrático para FCR"
)
| Termino | Estimate | Std. Error | t value | Pr(>|t|) |
|---|---|---|---|---|
| (Intercept) | 1.29204 | 0.00663 | 194.94709 | 0.0000 |
| x1 | -0.08422 | 0.00363 | -23.20106 | 0.0000 |
| x2 | -0.06728 | 0.00363 | -18.53330 | 0.0000 |
| I(x1^2) | 0.08644 | 0.00629 | 13.74857 | 0.0000 |
| I(x2^2) | 0.08161 | 0.00629 | 12.97985 | 0.0000 |
| x1:x2 | -0.00983 | 0.00445 | -2.21175 | 0.0382 |
Interpretación técnica. El modelo de FCR alcanza un \(R^2\) de 0.983 y un \(R^2\) ajustado de 0.979, mostrando un ajuste muy alto para estos datos simulados. Los coeficientes lineales negativos indican que, dentro de la región inicial del experimento, el incremento de proteína y lípidos reduce el FCR. Los términos cuadráticos positivos muestran posteriormente un cambio de pendiente, generando un mínimo en lugar de una reducción indefinida. La interacción presenta \(p=\) 0.0382.
Desde una perspectiva productiva, este resultado es relevante porque el punto de máximo crecimiento no necesariamente coincide exactamente con el punto de mínimo consumo de alimento por unidad de biomasa producida.
metricas_secundarias <- bind_rows(
metricas_modelo(modelo_surv, "Supervivencia"),
metricas_modelo(modelo_sgr, "SGR")
)
kable(
metricas_secundarias,
digits = 4,
caption = "Modelos de respuestas productivas complementarias"
)
| Respuesta | R2 | R2_ajustado | F_global | p_global |
|---|---|---|---|---|
| Supervivencia | 0.5089 | 0.3919 | 4.3515 | 0.0071 |
| SGR | 0.9338 | 0.9180 | 59.2363 | 0.0000 |
Interpretación. La supervivencia presenta un ajuste más moderado que las variables de crecimiento y eficiencia alimenticia, lo que indica una mayor proporción de variabilidad experimental no explicada por proteína y lípidos. En contraste, el SGR presenta un ajuste elevado y una estructura similar a la ganancia de peso. En un ensayo real sería recomendable registrar variables ambientales —oxígeno disuelto, temperatura, salinidad, pH, amonio y otros parámetros de calidad de agua— para evaluar fuentes adicionales de variabilidad en supervivencia.
La existencia de réplicas permite separar el error residual en error puro y falta de ajuste. Una falta de ajuste no significativa es deseable, porque indica que el modelo cuadrático representa adecuadamente la tendencia de los datos dentro de la región experimental.
prueba_lof <- function(modelo, datos, respuesta) {
y <- datos[[respuesta]]
grupo <- interaction(datos$x1, datos$x2, drop = TRUE)
media_grupo <- ave(y, grupo, FUN = mean)
ss_error_puro <- sum((y - media_grupo)^2)
gl_error_puro <- length(y) - nlevels(grupo)
ss_residual <- sum(residuals(modelo)^2)
gl_residual <- df.residual(modelo)
ss_lof <- ss_residual - ss_error_puro
gl_lof <- gl_residual - gl_error_puro
cm_error_puro <- ss_error_puro / gl_error_puro
cm_lof <- ss_lof / gl_lof
F_lof <- cm_lof / cm_error_puro
p_lof <- pf(F_lof, gl_lof, gl_error_puro, lower.tail = FALSE)
tibble(
Fuente = c("Falta de ajuste", "Error puro"),
SC = c(ss_lof, ss_error_puro),
gl = c(gl_lof, gl_error_puro),
CM = c(cm_lof, cm_error_puro),
F = c(F_lof, NA),
p = c(p_lof, NA)
)
}
lof_peso <- prueba_lof(
modelo_peso, datos, "ganancia_peso_g"
)
lof_fcr <- prueba_lof(
modelo_fcr, datos, "FCR"
)
lof_surv <- prueba_lof(
modelo_surv, datos, "supervivencia_pct"
)
lof_sgr <- prueba_lof(
modelo_sgr, datos, "SGR_pct_dia"
)
tabla_lof <- tibble(
Respuesta = c(
"Ganancia de peso",
"FCR",
"Supervivencia",
"SGR"
),
p_falta_ajuste = c(
lof_peso$p[1],
lof_fcr$p[1],
lof_surv$p[1],
lof_sgr$p[1]
)
)
kable(
tabla_lof,
digits = 4,
caption = "Prueba de falta de ajuste de los modelos"
)
| Respuesta | p_falta_ajuste |
|---|---|
| Ganancia de peso | 0.1969 |
| FCR | 0.4514 |
| Supervivencia | 0.6322 |
| SGR | 0.8023 |
Interpretación. Para la ganancia de peso, la prueba de falta de ajuste presenta \(p=\) 0.197; para FCR, \(p=\) 0.451. Al ser superiores a 0,05, no existe evidencia suficiente para afirmar que el modelo cuadrático presente una falta de ajuste sistemática en estas respuestas. Esto respalda el uso del modelo de segundo orden dentro de la región experimental evaluada.
diag_peso <- tibble(
ajustado = fitted(modelo_peso),
residuo = residuals(modelo_peso),
residuo_est = rstandard(modelo_peso)
)
ggplot(diag_peso, aes(ajustado, residuo_est)) +
geom_point(size = 2.7) +
geom_hline(yintercept = 0, linetype = 2) +
labs(
title = "Diagnóstico del modelo: residuos vs valores ajustados",
x = "Valor ajustado de ganancia de peso",
y = "Residuo estandarizado"
) +
theme_minimal(base_size = 12)
ggplot(diag_peso, aes(sample = residuo_est)) +
stat_qq(size = 2.5) +
stat_qq_line() +
labs(
title = "Gráfico Q-Q de residuos - ganancia de peso"
) +
theme_minimal(base_size = 12)
shapiro_peso <- shapiro.test(residuals(modelo_peso))
tibble(
Prueba = "Shapiro-Wilk",
W = unname(shapiro_peso$statistic),
p = shapiro_peso$p.value
) %>%
kable(
digits = 4,
caption = "Normalidad de los residuos del modelo de ganancia"
)
| Prueba | W | p |
|---|---|---|
| Shapiro-Wilk | 0.9823 | 0.9107 |
Interpretación. El gráfico de residuos debe mostrar dispersión aproximadamente aleatoria alrededor de cero, sin una forma de embudo ni patrón curvilíneo evidente. El gráfico Q-Q permite evaluar normalidad aproximada. La prueba de Shapiro-Wilk presenta \(p=\) 0.911; un valor superior a 0,05 es compatible con el supuesto de normalidad de los residuos en este ejercicio.
diag_fcr <- tibble(
ajustado = fitted(modelo_fcr),
residuo_est = rstandard(modelo_fcr)
)
ggplot(diag_fcr, aes(ajustado, residuo_est)) +
geom_point(size = 2.7) +
geom_hline(yintercept = 0, linetype = 2) +
labs(
title = "Diagnóstico del modelo FCR",
x = "FCR ajustado",
y = "Residuo estandarizado"
) +
theme_minimal(base_size = 12)
rejilla <- tidyr::expand_grid(
x1 = seq(-1, 1, length.out = 151),
x2 = seq(-1, 1, length.out = 151)
) %>%
mutate(
proteina_pct = 36 + 4*x1,
lipidos_pct = 8 + 2*x2,
pred_ganancia = predict(modelo_peso, newdata = .),
pred_FCR = predict(modelo_fcr, newdata = .),
pred_supervivencia = predict(modelo_surv, newdata = .),
pred_SGR = predict(modelo_sgr, newdata = .)
)
ggplot(
rejilla,
aes(
x = proteina_pct,
y = lipidos_pct,
z = pred_ganancia
)
) +
geom_contour_filled(bins = 14) +
geom_point(
data = datos,
aes(proteina_pct, lipidos_pct),
inherit.aes = FALSE,
size = 1.6
) +
labs(
title = "Superficie de respuesta de la ganancia de peso",
subtitle = "Mapa de contornos del modelo cuadrático",
x = "Proteína cruda (%)",
y = "Lípidos (%)",
fill = "Ganancia\nde peso (g)"
) +
theme_minimal(base_size = 12)
Interpretación. Las curvas de nivel representan combinaciones de proteína y lípidos con igual ganancia de peso predicha. La región de mayor respuesta se ubica dentro de la zona central-alta del espacio experimental, en lugar de encontrarse necesariamente en una esquina del diseño. Esto refleja la presencia de curvatura y constituye una de las principales ventajas de la RSM frente a seleccionar simplemente el tratamiento con la media observada más alta.
ggplot(
rejilla,
aes(
x = proteina_pct,
y = lipidos_pct,
z = pred_FCR
)
) +
geom_contour_filled(bins = 14) +
geom_point(
data = datos,
aes(proteina_pct, lipidos_pct),
inherit.aes = FALSE,
size = 1.6
) +
labs(
title = "Superficie de respuesta del FCR",
subtitle = "En esta respuesta se busca el mínimo",
x = "Proteína cruda (%)",
y = "Lípidos (%)",
fill = "FCR"
) +
theme_minimal(base_size = 12)
xseq <- seq(-1, 1, length.out = 45)
yseq <- seq(-1, 1, length.out = 45)
z <- outer(
xseq,
yseq,
Vectorize(function(a, b) {
predict(
modelo_peso,
newdata = data.frame(x1 = a, x2 = b)
)
})
)
persp(
x = 36 + 4*xseq,
y = 8 + 2*yseq,
z = z,
theta = 40,
phi = 25,
expand = 0.75,
ticktype = "detailed",
xlab = "Proteína (%)",
ylab = "Lípidos (%)",
zlab = "Ganancia (g)",
main = "Superficie de respuesta 3D - ganancia de peso"
)
Se determina el punto donde la derivada de la superficie respecto de ambos factores es igual a cero. Según los signos de los autovalores de la matriz Hessiana, este punto puede clasificarse como máximo, mínimo o punto silla.
punto_estacionario <- function(modelo, objetivo = "max") {
b <- coef(modelo)
H <- matrix(
c(
2*b["I(x1^2)"], b["x1:x2"],
b["x1:x2"], 2*b["I(x2^2)"]
),
nrow = 2,
byrow = TRUE
)
lineales <- c(b["x1"], b["x2"])
x <- solve(H, -lineales)
autovalores <- eigen(H, symmetric = TRUE)$values
tipo <- if (all(autovalores < 0)) {
"Máximo"
} else if (all(autovalores > 0)) {
"Mínimo"
} else {
"Punto silla"
}
pred <- predict(
modelo,
newdata = data.frame(
x1 = x[1],
x2 = x[2]
)
)
tibble(
x1 = x[1],
x2 = x[2],
proteina_pct = 36 + 4*x[1],
lipidos_pct = 8 + 2*x[2],
respuesta_predicha = unname(pred),
clasificacion = tipo
)
}
opt_peso <- punto_estacionario(modelo_peso)
opt_fcr <- punto_estacionario(modelo_fcr)
opt_surv <- punto_estacionario(modelo_surv)
opt_sgr <- punto_estacionario(modelo_sgr)
tabla_optimos <- bind_rows(
opt_peso %>% mutate(Respuesta = "Ganancia de peso"),
opt_fcr %>% mutate(Respuesta = "FCR"),
opt_surv %>% mutate(Respuesta = "Supervivencia"),
opt_sgr %>% mutate(Respuesta = "SGR")
) %>%
select(
Respuesta,
proteina_pct,
lipidos_pct,
respuesta_predicha,
clasificacion
)
kable(
tabla_optimos,
digits = 3,
caption = "Puntos estacionarios estimados para cada respuesta"
)
| Respuesta | proteina_pct | lipidos_pct | respuesta_predicha | clasificacion |
|---|---|---|---|---|
| Ganancia de peso | 38.251 | 9.438 | 19.005 | Máximo |
| FCR | 38.049 | 8.886 | 1.256 | Mínimo |
| Supervivencia | 37.128 | 8.914 | 95.229 | Máximo |
| SGR | 38.049 | 9.040 | 4.233 | Máximo |
Interpretación. Para maximizar la ganancia de peso, el modelo estima una combinación cercana a 38.25% de proteína y 9.44% de lípidos, con una ganancia predicha aproximada de 19.00 g. El punto se clasifica como máximo dentro de la superficie cuadrática.
Para FCR, el mínimo estimado se localiza alrededor de 38.05% de proteína y 8.89% de lípidos, con FCR predicho de 1.256.
La cercanía entre las regiones óptimas de crecimiento y eficiencia alimenticia sugiere que es posible buscar una solución de compromiso con buen desempeño simultáneo.
rsmcanonical(modelo_peso_rsm)
## $xs
## x1 x2
## 0.5627436 0.7189274
##
## $eigen
## eigen() decomposition
## $values
## [1] -0.8128729 -1.6486271
##
## $vectors
## [,1] [,2]
## x1 -0.3262564 -0.9452813
## x2 -0.9452813 0.3262564
El análisis canónico es una forma formal de estudiar la geometría de la superficie de respuesta. Los signos de sus raíces o autovalores permiten identificar si la región estacionaria corresponde a un máximo, mínimo o punto silla.
Una formulación técnicamente atractiva no debería evaluarse únicamente por el peso. Se plantea una optimización simultánea con tres objetivos:
Se asigna doble ponderación a ganancia de peso y FCR y una ponderación simple a supervivencia.
mayor_mejor <- function(y) {
(y - min(y)) / (max(y) - min(y))
}
menor_mejor <- function(y) {
(max(y) - y) / (max(y) - min(y))
}
rejilla_opt <- rejilla %>%
mutate(
d_peso = mayor_mejor(pred_ganancia),
d_fcr = menor_mejor(pred_FCR),
d_surv = mayor_mejor(pred_supervivencia),
deseabilidad = (
d_peso^2 *
d_fcr^2 *
d_surv
)^(1/5)
)
optimo_multi <- rejilla_opt %>%
slice_max(
deseabilidad,
n = 1,
with_ties = FALSE
) %>%
select(
proteina_pct,
lipidos_pct,
pred_ganancia,
pred_FCR,
pred_supervivencia,
pred_SGR,
deseabilidad
)
kable(
optimo_multi,
digits = 3,
caption = "Óptimo multirrespuesta estimado"
)
| proteina_pct | lipidos_pct | pred_ganancia | pred_FCR | pred_supervivencia | pred_SGR | deseabilidad |
|---|---|---|---|---|---|---|
| 37.867 | 9.067 | 18.968 | 1.256 | 95.193 | 4.233 | 0.993 |
ggplot(
rejilla_opt,
aes(
x = proteina_pct,
y = lipidos_pct,
z = deseabilidad
)
) +
geom_contour_filled(bins = 14) +
geom_point(
data = optimo_multi,
aes(proteina_pct, lipidos_pct),
inherit.aes = FALSE,
size = 4,
shape = 8
) +
labs(
title = "Optimización multirrespuesta por deseabilidad",
subtitle = "Maximizar crecimiento y supervivencia; minimizar FCR",
x = "Proteína cruda (%)",
y = "Lípidos (%)",
fill = "Deseabilidad"
) +
theme_minimal(base_size = 12)
Interpretación. La optimización simultánea localiza una solución de compromiso alrededor de 37.87% de proteína y 9.07% de lípidos. En ese punto el modelo predice aproximadamente 18.97 g de ganancia de peso, FCR = 1.256, supervivencia de 95.19% y una deseabilidad global de 0.993.
Esta solución es más útil para toma de decisiones que optimizar una sola variable, porque integra simultáneamente crecimiento, eficiencia alimenticia y supervivencia.
comparacion <- tibble(
Criterio = c(
"Mayor ganancia observada",
"Máximo de ganancia según RSM",
"Mínimo FCR según RSM",
"Óptimo multirrespuesta"
),
Proteina_pct = c(
mejor_peso_obs$proteina_pct,
opt_peso$proteina_pct,
opt_fcr$proteina_pct,
optimo_multi$proteina_pct
),
Lipidos_pct = c(
mejor_peso_obs$lipidos_pct,
opt_peso$lipidos_pct,
opt_fcr$lipidos_pct,
optimo_multi$lipidos_pct
)
)
kable(
comparacion,
digits = 2,
caption = "Comparación de regiones de interés"
)
| Criterio | Proteina_pct | Lipidos_pct |
|---|---|---|
| Mayor ganancia observada | 40.00 | 10.00 |
| Máximo de ganancia según RSM | 38.25 | 9.44 |
| Mínimo FCR según RSM | 38.05 | 8.89 |
| Óptimo multirrespuesta | 37.87 | 9.07 |
La comparación permite distinguir entre selección del mejor tratamiento ensayado y optimización mediante una superficie continua. Esa diferencia es central en RSM: el mejor tratamiento observado se limita a las combinaciones ejecutadas, mientras que el modelo permite estimar una combinación intermedia potencialmente superior.
Los resultados simulados sugieren una región central-alta de proteína y lípidos como zona de interés. Sin embargo, una decisión real de formulación debería incorporar variables adicionales, entre ellas:
Además, si los tanques estuvieran distribuidos espacialmente en diferentes áreas del centro experimental, podría ser conveniente aplicar bloqueo para controlar gradientes ambientales.
La optimización estadística no debería considerarse el punto final del proceso. La formulación óptima predicha debe validarse experimentalmente.
Un ensayo confirmatorio podría incluir:
La hipótesis de validación sería que la respuesta observada con la dieta óptima se encuentre dentro del intervalo de predicción generado por el modelo y mantenga ventajas productivas frente al control.
En un proyecto de I+D, el flujo completo sería:
\[ \text{Diseño} \rightarrow \text{Ejecución} \rightarrow \text{Captura de datos} \rightarrow \text{Modelado} \rightarrow \text{Optimización} \rightarrow \text{Validación} \rightarrow \text{Escalamiento} \]
El diseño permite evaluar simultáneamente los efectos lineales, la interacción proteína × lípidos y la curvatura de la respuesta.
La ganancia de peso y el FCR presentan una estructura cuadrática marcada en los datos simulados, lo que justifica el uso de Metodología de Superficie de Respuesta.
El modelo de ganancia de peso explica aproximadamente 96.1% de la variabilidad, mientras que el modelo de FCR explica aproximadamente 98.3%.
La falta de ajuste no significativa en las respuestas principales respalda el uso de la forma cuadrática dentro de la región experimental.
El óptimo individual de crecimiento se estima alrededor de 38.25% de proteína y 9.44% de lípidos.
La optimización multirrespuesta propone aproximadamente 37.87% de proteína y 9.07% de lípidos, buscando simultáneamente mayor crecimiento, menor FCR y alta supervivencia.
En una aplicación comercial, la recomendación final debe complementarse con un ensayo confirmatorio y un análisis económico de la formulación.
Este documento constituye un ejercicio técnico demostrativo basado en datos simulados. Su objetivo es mostrar competencias en diseño experimental, estadística aplicada, R, R Markdown, modelado de superficies de respuesta y comunicación de resultados técnicos en un contexto de investigación nutricional para acuicultura.