El propósito de este documento es comprender la lógica del método DIFFIT en el contexto del modelo Tucker3. Más que profundizar en todos los aspectos del algoritmo de estimación, el interés principal es mostrar cómo DIFFIT permite seleccionar una dimensionalidad razonable a partir de una colección de modelos estimados.
En un modelo Tucker3, el investigador debe decidir cuántos componentes conservar en cada uno de los tres modos. Es decir, debe elegir una terna:
\[\begin{equation} (P,Q,R), \end{equation}\]
donde:
A medida que estos valores aumentan, el ajuste del modelo generalmente mejora. Sin embargo, una mejora en ajuste no siempre justifica una solución más compleja. Por ello, se necesita un criterio que permita identificar el punto en el que seguir aumentando componentes deja de aportar una mejora sustancial. Ese es precisamente el papel de DIFFIT.
El objetivo de este documento es entender la lógica y la utilidad del método DIFFIT mediante un ejemplo con datos simulados.
En particular, se busca:
DIFFIT se apoya en la siguiente lógica:
En términos intuitivos, DIFFIT busca el punto donde la curva de ajuste comienza a mostrar rendimientos decrecientes.
rm(list = ls())
Se considera un arreglo de dimensión \(I\times J \times K = 30 \times 8 \times 6\), de manera que se tienen \(30\) unidade en la vía A, \(8\) unidades en la vía B y \(6\) unidades en la C. Además, se establece como estructura verdadera \((P, Q, R) = (2, 2, 2)\). Es decir, el arreglo \(\mathbf {\underline{X}}\), será generado a partir de dos componentes en cada vía.
El parámetro ruido_sd = \(0.25\) controla la magnitud del ruido aleatorio. Mientras mayor sea este valor, más difícil será recuperar la estructura verdadera.
Ejemplo numérico simple
Si en lugar de \(30 \times 8\times 6\) se trabajara con un arreglo pequeño \(2\times 3\times 2\), entonces cada observación \(X_{IJK}\) estaría indexada por: \(i=1, 2\); \(j=1,2,3\); \(k=1,2\). Y si el modelo verdadero fuera \((1,2,1)\), significaría que la estructura latente se resume con: 1 componente en la primera vía, 2 en la segunda, 1 en la tercera.
# Dimensiones del arreglo
I <- 30
J <- 8
K <- 6
# Modelo verdadero
P_true <- 2
Q_true <- 2
R_true <- 2
# Nivel de ruido
ruido_sd <- 0.25
# Semilla
semilla <- 2026
set.seed(semilla)
Se general las matrices verdaderas de los componentes:
\(\mathbf{A} \in \mathbb{R}^{I\times P}\), \(\mathbf{B} \in \mathbb{R}^{J\times Q}\), y \(\mathbf{C} \in \mathbb{R}^{K\times R}\).
En este caso\(\mathbf{A} \in \mathbb{R}^{30\times 2}\), \(\mathbf{B} \in \mathbb{R}^{8\times 2}\), y \(\mathbf{C} \in \mathbb{R}^{6\times 2}\).
A_true <- qr.Q(qr(matrix(rnorm(I * P_true), I, P_true)))
B_true <- qr.Q(qr(matrix(rnorm(J * Q_true), J, Q_true)))
C_true <- qr.Q(qr(matrix(rnorm(K * R_true), K, R_true)))
La expresión: qr.Q(qr(…)) se usa para ortonormalizar las columnas. Esto implica que, por ejemplo, en \(\mathbf{A}\):
\[\begin{equation} \mathbf{A'A} = \mathbf{I}_p \end{equation}\]
lo mismo ocurre con \(\mathbf{B}\) y \(\mathbf{C}\).
Ejemplo numérico
Si una matriz inicial fuera:
\[\begin{equation} M= \begin{pmatrix} 1 & 2\\ 2 & 1\\ 1 & 1 \end{pmatrix}. \end{equation}\]
la descomposición QR produce una matriz \(Q\) con columnas ortogonales y de norma \(1\). Esa será la base utilizada como matriz de componentes.
Aquí se genera el núcleo:
\[\begin{equation} \mathbf{G} \in \mathbb{R}^{p\times Q \times R} \end{equation}\]
Como en este caso \(P=Q=R=2\), entonces:
\[\begin{equation} \mathbf{G} \in \mathbb{R}^{2\times 2 \times 2} \end{equation}\]
El núcleo contiene la relación entre los componentes de las tres vías.
Ejemplo numérico
Si \(P=Q=R=2\), el núcleo tiene 8 elementos:
\[\begin{equation} g_{111}, g_{112}, g_{121}, g_{122}, g_{211}, g_{212}, g_{221}, g_{222} \end{equation}\]
Cada uno de ellos pondera la interacción entre un componente de , uno de y uno de .
G_true <- array(rnorm(P_true * Q_true * R_true, mean = 0, sd = 1),
dim = c(P_true, Q_true, R_true))
Este bloque implementa directamente el modelo Tucker3:
\[\begin{equation} x_{ijk} = \sum_{p=1}^P\sum_{q=1}^Q \sum_{r=1}^R a_{ip}b_{jq}c_{kr}g_{pqr} \end{equation}\]
Cada elemento del arreglo estructural \(\mathbf {\underline{X}}\)_true es una suma ponderada de productos de componentes.
Ejemplo numérico
Supongamos un caso extremadamente simple con: \(P=Q=R=1\)
Entonces el mdoelo se reduce a:
\[\begin{equation} x_{ijk} = a_{i1}b_{j1}c_{k1}g_{111} \end{equation}\]
Si \(a_{i1}= 1, b_{j1}= 3, c_{k1}= 4, g_{111} = 5\)
entonces:
\[\begin{equation} x_{111} = 2\cdot 3 \cdot4 \cdot5 = 120 \end{equation}\]
X_true <- array(0, dim = c(I, J, K))
for (i in 1:I) {
for (j in 1:J) {
for (k in 1:K) {
suma <- 0
for (p in 1:P_true) {
for (q in 1:Q_true) {
for (r in 1:R_true) {
suma <- suma +
A_true[i, p] *
B_true[j, q] *
C_true[k, r] *
G_true[p, q, r]
}
}
}
X_true[i, j, k] <- suma
}
}
}
Los datos observados se generan como:
\[\begin{equation} \mathbf {\underline{X}} = \mathbf {\underline{X}}_{true} + \mathbf {\underline{E}} \end{equation}\]
donde es un arreglo de errores aleatorios.
Luego se centra globalmente el arreglo restando su media:
Esto desplaza el arreglo para que su promedio total sea cero.
\[\begin{equation} X \leftarrow X - \bar{X}. \end{equation}\]
E <- array(rnorm(I * J * K, mean = 0, sd = ruido_sd), dim = c(I, J, K))
X <- X_true + E
# Centrado global
X <- X - mean(X)
cat("Modelo verdadero simulado: (",
P_true, ", ", Q_true, ", ", R_true, ")\n", sep = "")
## Modelo verdadero simulado: (2, 2, 2)
No todas las ternas \((P,Q,R)\) son útiles. Se usan las restricciones:
\[\begin{equation} PQ \geq R, PR \geq Q, QR \geq P \end{equation}\]
Estas evitan modelos redundantes o combinaciones cuya complejidad excede lo que las otras dimensiones pueden representar.
Ejemplo numérico
Si \(P=1\), \(Q=1\), \(R=4\), entonces:
\[\begin{equation} PQ = 1 \cdot 1 = 1 < 4 = R \end{equation}\]
Esa terna no se considera válida.
Pmax <- 4
Qmax <- 4
Rmax <- 4
modelos <- NULL
for (P in 1:Pmax) {
for (Q in 1:Qmax) {
for (R in 1:Rmax) {
if (P * Q >= R && P * R >= Q && Q * R >= P) {
modelos <- rbind(modelos, c(P, Q, R))
}
}
}
}
modelos <- as.data.frame(modelos)
colnames(modelos) <- c("P", "Q", "R")
modelos
## P Q R
## 1 1 1 1
## 2 1 2 2
## 3 1 3 3
## 4 1 4 4
## 5 2 1 2
## 6 2 2 1
## 7 2 2 2
## 8 2 2 3
## 9 2 2 4
## 10 2 3 2
## 11 2 3 3
## 12 2 3 4
## 13 2 4 2
## 14 2 4 3
## 15 2 4 4
## 16 3 1 3
## 17 3 2 2
## 18 3 2 3
## 19 3 2 4
## 20 3 3 1
## 21 3 3 2
## 22 3 3 3
## 23 3 3 4
## 24 3 4 2
## 25 3 4 3
## 26 3 4 4
## 27 4 1 4
## 28 4 2 2
## 29 4 2 3
## 30 4 2 4
## 31 4 3 2
## 32 4 3 3
## 33 4 3 4
## 34 4 4 1
## 35 4 4 2
## 36 4 4 3
## 37 4 4 4
En esta sección se estima cada modelo válido. Aunque el objetivo principal no es estudiar el algoritmo ALS, sí se necesita obtener el ajuste de cada terna para poder aplicar DIFFIT.
Se construyen tres versiones matricizadas de \(X\):
En cada iteración ALS se actualizan \(A\), \(B\) y \(C\) a partir de descomposiciones SVD. Por ejemplo, para \(A\):
\[\begin{equation} M_A = X_A (C \otimes B), \end{equation}\]
y luego se toman las primeras \(P\) columnas ortonormales de la matriz izquierda de la SVD.
Se calcula mediante:
\[\begin{equation} G = A^\top X_A (C \otimes B). \end{equation}\]
Se usa la suma de cuadrados del error:
\[\begin{equation} SSE = \sum_{i,j,k} (x_{ijk} - \hat{x}_{ijk})^2. \end{equation}\]
El ajuste porcentual se calcula como:
\[\begin{equation} Fit = 100 \left( 1 - \frac{SSE}{\sum x_{ijk}^2} \right). \end{equation}\]
Si la suma de cuadrados total es 200 y el error residual es 50, entonces:
\[\begin{equation} Fit = 100 \left( 1 - \frac{50}{200} \right) = 75\%. \end{equation}\]
resultados <- data.frame()
cat("\nEstimando modelos...\n")
##
## Estimando modelos...
for (m in 1:nrow(modelos)) {
P <- modelos$P[m]
Q <- modelos$Q[m]
R <- modelos$R[m]
cat("Modelo: (", P, ", ", Q, ", ", R, ")\n", sep = "")
XA <- matrix(0, nrow = I, ncol = J * K)
col_ini <- 1
for (k in 1:K) {
XA[, col_ini:(col_ini + J - 1)] <- X[, , k]
col_ini <- col_ini + J
}
XB <- matrix(0, nrow = J, ncol = I * K)
col_ini <- 1
for (k in 1:K) {
XB[, col_ini:(col_ini + I - 1)] <- t(X[, , k])
col_ini <- col_ini + I
}
XC <- matrix(0, nrow = K, ncol = I * J)
for (k in 1:K) {
XC[k, ] <- as.vector(X[, , k])
}
mejor_sse <- Inf
mejor_A <- NULL
mejor_B <- NULL
mejor_C <- NULL
mejor_G <- NULL
mejor_Xhat <- NULL
mejor_iter <- NA
nstart <- 3
tol <- 1e-7
max_iter <- 5
for (inicio in 1:nstart) {
A <- qr.Q(qr(matrix(rnorm(I * P), I, P)))
B <- qr.Q(qr(matrix(rnorm(J * Q), J, Q)))
C <- qr.Q(qr(matrix(rnorm(K * R), K, R)))
sse_old <- Inf
for (iter in 1:max_iter) {
M_A <- XA %*% kronecker(C, B)
sv_A <- svd(M_A)
A <- sv_A$u[, 1:P, drop = FALSE]
M_B <- XB %*% kronecker(C, A)
sv_B <- svd(M_B)
B <- sv_B$u[, 1:Q, drop = FALSE]
M_C <- XC %*% kronecker(B, A)
sv_C <- svd(M_C)
C <- sv_C$u[, 1:R, drop = FALSE]
Gmat <- t(A) %*% XA %*% kronecker(C, B)
G <- array(0, dim = c(P, Q, R))
col_ini <- 1
for (r in 1:R) {
G[, , r] <- Gmat[, col_ini:(col_ini + Q - 1)]
col_ini <- col_ini + Q
}
Xhat <- array(0, dim = c(I, J, K))
for (i in 1:I) {
for (j in 1:J) {
for (k in 1:K) {
suma <- 0
for (p in 1:P) {
for (q in 1:Q) {
for (r in 1:R) {
suma <- suma +
A[i, p] *
B[j, q] *
C[k, r] *
G[p, q, r]
}
}
}
Xhat[i, j, k] <- suma
}
}
}
sse <- sum((X - Xhat)^2)
if (abs(sse_old - sse) < tol) {
break
}
sse_old <- sse
}
if (sse < mejor_sse) {
mejor_sse <- sse
mejor_A <- A
mejor_B <- B
mejor_C <- C
mejor_G <- G
mejor_Xhat <- Xhat
mejor_iter <- iter
}
}
ss_total <- sum(X^2)
fit <- 100 * (1 - mejor_sse / ss_total)
resultados <- rbind(
resultados,
data.frame(
P = P,
Q = Q,
R = R,
S = P + Q + R,
Fit = fit,
SSE = mejor_sse,
Iter = mejor_iter
)
)
}
## Modelo: (1, 1, 1)
## Modelo: (1, 2, 2)
## Modelo: (1, 3, 3)
## Modelo: (1, 4, 4)
## Modelo: (2, 1, 2)
## Modelo: (2, 2, 1)
## Modelo: (2, 2, 2)
## Modelo: (2, 2, 3)
## Modelo: (2, 2, 4)
## Modelo: (2, 3, 2)
## Modelo: (2, 3, 3)
## Modelo: (2, 3, 4)
## Modelo: (2, 4, 2)
## Modelo: (2, 4, 3)
## Modelo: (2, 4, 4)
## Modelo: (3, 1, 3)
## Modelo: (3, 2, 2)
## Modelo: (3, 2, 3)
## Modelo: (3, 2, 4)
## Modelo: (3, 3, 1)
## Modelo: (3, 3, 2)
## Modelo: (3, 3, 3)
## Modelo: (3, 3, 4)
## Modelo: (3, 4, 2)
## Modelo: (3, 4, 3)
## Modelo: (3, 4, 4)
## Modelo: (4, 1, 4)
## Modelo: (4, 2, 2)
## Modelo: (4, 2, 3)
## Modelo: (4, 2, 4)
## Modelo: (4, 3, 2)
## Modelo: (4, 3, 3)
## Modelo: (4, 3, 4)
## Modelo: (4, 4, 1)
## Modelo: (4, 4, 2)
## Modelo: (4, 4, 3)
## Modelo: (4, 4, 4)
resultados <- resultados[order(resultados$S, -resultados$Fit), ]
rownames(resultados) <- NULL
resultados
## P Q R S Fit SSE Iter
## 1 1 1 1 3 11.39860 96.37621 5
## 2 2 2 1 5 19.03203 88.07294 5
## 3 2 1 2 5 15.99182 91.37993 5
## 4 1 2 2 5 12.68992 94.97157 5
## 5 2 2 2 6 22.41652 84.39147 5
## 6 3 2 2 7 24.44519 82.18477 5
## 7 2 3 2 7 23.05179 83.70044 5
## 8 2 2 3 7 22.76124 84.01649 5
## 9 3 3 1 7 21.87077 84.98510 5
## 10 3 1 3 7 18.49484 88.65728 5
## 11 1 3 3 7 13.48567 94.10600 5
## 12 4 2 2 8 25.77844 80.73453 5
## 13 3 3 2 8 25.48422 81.05457 5
## 14 3 2 3 8 25.10779 81.46403 5
## 15 2 3 3 8 23.71989 82.97372 5
## 16 2 4 2 8 23.13279 83.61234 5
## 17 2 2 4 8 22.79789 83.97663 5
## 18 3 3 3 9 27.81601 78.51817 5
## 19 4 2 3 9 27.39862 78.97218 5
## 20 4 3 2 9 27.30676 79.07211 5
## 21 3 4 2 9 27.03230 79.37065 5
## 22 3 2 4 9 26.12848 80.35377 5
## 23 4 4 1 9 24.48828 82.13790 5
## 24 2 4 3 9 24.25846 82.38789 5
## 25 2 3 4 9 24.05744 82.60655 5
## 26 4 1 4 9 20.52694 86.44685 5
## 27 1 4 4 9 13.80791 93.75548 5
## 28 4 3 3 10 30.91305 75.14936 5
## 29 4 4 2 10 29.71202 76.45578 5
## 30 3 4 3 10 28.91667 77.32092 5
## 31 3 3 4 10 28.62903 77.63381 5
## 32 4 2 4 10 28.22889 78.06906 5
## 33 2 4 4 10 24.79477 81.80452 5
## 34 4 4 3 11 32.67800 73.22953 5
## 35 4 3 4 11 31.59967 74.40249 5
## 36 3 4 4 11 30.07567 76.06022 5
## 37 4 4 4 12 34.33618 71.42585 5
DIFFIT no compara directamente todas las ternas. Primero agrupa por:
\[\begin{equation} S = P + Q + R \end{equation}\]
Luego, dentro de cada valor de \(S\), conserva solamente la solución con mayor ajuste.
Ejemplo numérico
Supongamos que para \(S=5\) aparecen tres ternas:\((1, 2, 2)\) con ajuste \(52\), \((2, 1, 2)\) con ajuste \(49\), y \((2, 2, 1)\) con ajuste \(68\).
Entonces, DIFFIT conserva para \(S=5\) únicamente la terna \((2,2,1)\), porque tiene el mayor ajuste.
valores_S <- sort(unique(resultados$S))
mejores_por_S <- data.frame()
for (s in valores_S) {
sub <- resultados[resultados$S == s, ]
fila_mejor <- sub[which.max(sub$Fit), ]
mejores_por_S <- rbind(mejores_por_S, fila_mejor)
}
rownames(mejores_por_S) <- NULL
mejores_por_S
## P Q R S Fit SSE Iter
## 1 1 1 1 3 11.39860 96.37621 5
## 2 2 2 1 5 19.03203 88.07294 5
## 3 2 2 2 6 22.41652 84.39147 5
## 4 3 2 2 7 24.44519 82.18477 5
## 5 4 2 2 8 25.77844 80.73453 5
## 6 3 3 3 9 27.81601 78.51817 5
## 7 4 3 3 10 30.91305 75.14936 5
## 8 4 4 3 11 32.67800 73.22953 5
## 9 4 4 4 12 34.33618 71.42585 5
Para cada valor de \(S\), se mide cuánto mejora el ajuste respecto a la mejor solución del valor anterior:
\[\begin{equation} dif(s) = Fit(s)- Fit(s-1) \end{equation}\]
Para la primera solución se toma:
\[\begin{equation} dif(s_1) = Fit(s_1) \end{equation}\]
pues no hay una solución anterior con la cual compararla.
Si los mejores ajustes son:
library(knitr)
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.5.3
tabla <- data.frame(
S = c(3, 5, 6, 7),
Fit = c(40, 68, 74, 77)
)
kable(tabla) %>%
kable_styling(full_width = FALSE, position = "center")
| S | Fit |
|---|---|
| 3 | 40 |
| 5 | 68 |
| 6 | 74 |
| 7 | 77 |
Entonces:
\[\begin{equation} dif(3) = 40, \qquad dif(5) = 68 - 40 = 28, \qquad dif(6) = 74 - 68 = 6, \qquad dif(7) = 77 - 74 = 3. \end{equation}\]
La lógica aquí es que el paso de \(S = 3\) a \(S = 5\) produjo una mejora muy grande, mientras que después los incrementos son menores.
mejores_por_S$dif <- NA_real_
mejores_por_S$dif[1] <- mejores_por_S$Fit[1]
if (nrow(mejores_por_S) >= 2) {
for (i in 2:nrow(mejores_por_S)) {
mejores_por_S$dif[i] <- mejores_por_S$Fit[i] - mejores_por_S$Fit[i - 1]
}
}
mejores_por_S
## P Q R S Fit SSE Iter dif
## 1 1 1 1 3 11.39860 96.37621 5 11.398598
## 2 2 2 1 5 19.03203 88.07294 5 7.633437
## 3 2 2 2 6 22.41652 84.39147 5 3.384482
## 4 3 2 2 7 24.44519 82.18477 5 2.028676
## 5 4 2 2 8 25.77844 80.73453 5 1.333250
## 6 3 3 3 9 27.81601 78.51817 5 2.037567
## 7 4 3 3 10 30.91305 75.14936 5 3.097039
## 8 4 4 3 11 32.67800 73.22953 5 1.764956
## 9 4 4 4 12 34.33618 71.42585 5 1.658174
Un valor \(dif(s)\) es considerado máximo secuencial si es mayor que todos los incrementos que le siguen.
Formalmente, \(dif(s\) es máximo secuencial si:
\[\begin{equation} dif(s) > dif(t) ~~ para ~ todo ~ t > s \end{equation}\]
Esto permite identificar los saltos importantes que siguen dominando a todos los incrementos posteriores.
Ejemplo numérico
Si:
\[\begin{equation} dif = (40, 28, 6, 3, 1) \end{equation}\]
entonces los máximos secuenciales serían:
En cambio, si una secuencia fuera:
\[\begin{equation} dif = (40, 5, 8, 2) \end{equation}\]
entonces 5 no sería máximo secuencial, porque más adelante aparece un valor mayor, 8.
es_max_secuencial <- rep(FALSE, nrow(mejores_por_S))
for (i in 1:nrow(mejores_por_S)) {
if (i == nrow(mejores_por_S)) {
es_max_secuencial[i] <- TRUE
} else {
es_max_secuencial[i] <- all(mejores_por_S$dif[i] > mejores_por_S$dif[(i + 1):nrow(mejores_por_S)])
}
}
secuenciales <- mejores_por_S[es_max_secuencial, ]
rownames(secuenciales) <- NULL
secuenciales
## P Q R S Fit SSE Iter dif
## 1 1 1 1 3 11.39860 96.37621 5 11.398598
## 2 2 2 1 5 19.03203 88.07294 5 7.633437
## 3 2 2 2 6 22.41652 84.39147 5 3.384482
## 4 4 3 3 10 30.91305 75.14936 5 3.097039
## 5 4 4 3 11 32.67800 73.22953 5 1.764956
## 6 4 4 4 12 34.33618 71.42585 5 1.658174
DIFFIT compara los máximos secuenciales mediante:
\[\begin{equation} b_t(m) = \frac{dif_t(m)}{dif_t(m+1)}. \end{equation}\]
Si esta razón es grande, significa que el incremento actual es mucho mayor que el siguiente incremento relevante. Eso sugiere una caída fuerte en la ganancia marginal del ajuste.
Si dos máximos secuenciales consecutivos son:
\[\begin{equation} dif_t(1) = 28, \qquad dif_t(2) = 6, \end{equation}\]
entonces:
\[\begin{equation} b_t(1) = \frac{28}{6} = 4.67. \end{equation}\]
Una razón alta como esta indica que el salto asociado al primer valor fue mucho más importante que el siguiente.
secuenciales$bt <- NA_real_
if (nrow(secuenciales) >= 2) {
for (i in 1:(nrow(secuenciales) - 1)) {
if (!is.na(secuenciales$dif[i + 1]) && secuenciales$dif[i + 1] != 0) {
secuenciales$bt[i] <- secuenciales$dif[i] / secuenciales$dif[i + 1]
}
}
}
secuenciales
## P Q R S Fit SSE Iter dif bt
## 1 1 1 1 3 11.39860 96.37621 5 11.398598 1.493246
## 2 2 2 1 5 19.03203 88.07294 5 7.633437 2.255422
## 3 2 2 2 6 22.41652 84.39147 5 3.384482 1.092812
## 4 4 3 3 10 30.91305 75.14936 5 3.097039 1.754740
## 5 4 4 3 11 32.67800 73.22953 5 1.764956 1.064397
## 6 4 4 4 12 34.33618 71.42585 5 1.658174 NA
El umbral se define como:
\[\begin{equation} T = \frac{100}{s_{\max} - 3}, \end{equation}\]
donde:
\[\begin{equation} s_{\max} = \min(I, JK) + \min(J, IK) + \min(K, IJ). \end{equation}\]
Este umbral evita seleccionar un incremento demasiado pequeño, incluso si su razón \(b_t\) fuera alta.
No basta con que un salto sea grande comparado con el siguiente: también debe ser sustancial en términos absolutos.
smax <- min(I, J * K) + min(J, I * K) + min(K, I * J)
T_porcentaje <- 100 / (smax - 3)
T_porcentaje
## [1] 2.439024
DIFFIT selecciona la solución cuya razón \(b_t\) es máxima entre aquellas que además cumplen:
\[\begin{equation} dif_t(m) > T. \end{equation}\]
Es decir, la solución elegida debe:
candidatos <- secuenciales[!is.na(secuenciales$bt) & secuenciales$dif > T_porcentaje, ]
if (nrow(candidatos) == 0) {
candidatos <- secuenciales[!is.na(secuenciales$bt), ]
}
if (nrow(candidatos) == 0) {
stop("No se pudo seleccionar una solución con DIFFIT.")
}
seleccionado <- candidatos[which.max(candidatos$bt), ]
seleccionado
## P Q R S Fit SSE Iter dif bt
## 2 2 2 1 5 19.03203 88.07294 5 7.633437 2.255422
tabla_resumen <- data.frame(
Concepto = c(
"Modelo verdadero",
"Modelo seleccionado por DIFFIT",
"S verdadero",
"S seleccionado",
"Fit del modelo seleccionado (%)",
"SSE del modelo seleccionado",
"dif(s) seleccionado",
"bt seleccionado",
"Umbral T (%)"
),
Valor = c(
paste0("(", P_true, ", ", Q_true, ", ", R_true, ")"),
paste0("(", seleccionado$P, ", ", seleccionado$Q, ", ", seleccionado$R, ")"),
P_true + Q_true + R_true,
seleccionado$S,
round(seleccionado$Fit, 4),
round(seleccionado$SSE, 6),
round(seleccionado$dif, 6),
round(seleccionado$bt, 6),
round(T_porcentaje, 6)
),
stringsAsFactors = FALSE
)
tabla_resumen
## Concepto Valor
## 1 Modelo verdadero (2, 2, 2)
## 2 Modelo seleccionado por DIFFIT (2, 2, 1)
## 3 S verdadero 6
## 4 S seleccionado 5
## 5 Fit del modelo seleccionado (%) 19.032
## 6 SSE del modelo seleccionado 88.072938
## 7 dif(s) seleccionado 7.633437
## 8 bt seleccionado 2.255422
## 9 Umbral T (%) 2.439024
op <- par(no.readonly = TRUE)
par(mfrow = c(1, 2))
plot(
mejores_por_S$S, mejores_por_S$Fit,
type = "b", pch = 19,
xlab = "S = P + Q + R",
ylab = "Fit (%)",
main = "Mejor ajuste por S"
)
points(seleccionado$S, seleccionado$Fit, pch = 19, cex = 1.5)
text(
seleccionado$S, seleccionado$Fit,
labels = paste0(" (", seleccionado$P, ",", seleccionado$Q, ",", seleccionado$R, ")"),
pos = 4
)
plot(
mejores_por_S$S, mejores_por_S$dif,
type = "b", pch = 19,
xlab = "S = P + Q + R",
ylab = "dif(s)",
main = "DIFFIT con datos simulados"
)
abline(h = T_porcentaje, lty = 2)
points(seleccionado$S, seleccionado$dif, pch = 19, cex = 1.5)
text(
seleccionado$S, seleccionado$dif,
labels = paste0(" (", seleccionado$P, ",", seleccionado$Q, ",", seleccionado$R, ")"),
pos = 4
)
par(op)
Esta es la tabla que resume los elementos centrales del criterio.
tabla_maximos_secuenciales <- secuenciales[, c("P", "Q", "R", "S", "Fit", "dif", "bt")]
tabla_maximos_secuenciales
## P Q R S Fit dif bt
## 1 1 1 1 3 11.39860 11.398598 1.493246
## 2 2 2 1 5 19.03203 7.633437 2.255422
## 3 2 2 2 6 22.41652 3.384482 1.092812
## 4 4 3 3 10 30.91305 3.097039 1.754740
## 5 4 4 3 11 32.67800 1.764956 1.064397
## 6 4 4 4 12 34.33618 1.658174 NA