Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -31,6 +31,9 @@ import matplotlib.pyplot as plt
|
|
| 31 |
from matplotlib.colors import to_hex
|
| 32 |
import seaborn as sns
|
| 33 |
from statsmodels.stats.outliers_influence import variance_inflation_factor
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
# --- Clase RSM_BoxBehnken Mejorada ---
|
| 36 |
class RSM_BoxBehnken:
|
|
@@ -47,7 +50,6 @@ class RSM_BoxBehnken:
|
|
| 47 |
self.data[f'{x1_name}_{x2_name}'] = self.data[x1_name] * self.data[x2_name]
|
| 48 |
self.data[f'{x1_name}_{x3_name}'] = self.data[x1_name] * self.data[x3_name]
|
| 49 |
self.data[f'{x2_name}_{x3_name}'] = self.data[x2_name] * self.data[x3_name]
|
| 50 |
-
|
| 51 |
self.model = None
|
| 52 |
self.model_simplified = None
|
| 53 |
self.optimized_results = None
|
|
@@ -73,19 +75,31 @@ class RSM_BoxBehnken:
|
|
| 73 |
'opacity': 0.7,
|
| 74 |
'show_grid': True,
|
| 75 |
'show_points': True,
|
| 76 |
-
'contour_levels': 10
|
|
|
|
|
|
|
| 77 |
}
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
def update_configuration(self, colorscale, opacity, show_grid, show_points, contour_levels):
|
| 80 |
-
"""Actualiza la configuración de visualización"""
|
| 81 |
self.current_configuration = {
|
| 82 |
'colorscale': colorscale,
|
| 83 |
'opacity': opacity,
|
| 84 |
'show_grid': show_grid,
|
| 85 |
'show_points': show_points,
|
| 86 |
-
'contour_levels': contour_levels
|
|
|
|
|
|
|
| 87 |
}
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
def get_levels(self, variable_name):
|
| 91 |
"""
|
|
@@ -132,20 +146,16 @@ class RSM_BoxBehnken:
|
|
| 132 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 133 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 134 |
y = self.data[self.y_name]
|
| 135 |
-
|
| 136 |
# Ajustar modelo Ridge
|
| 137 |
self.ridge_model = Ridge(alpha=alpha).fit(X, y)
|
| 138 |
-
|
| 139 |
# Calcular métricas
|
| 140 |
r2 = self.ridge_model.score(X, y)
|
| 141 |
mse = np.mean((y - self.ridge_model.predict(X))**2)
|
| 142 |
-
|
| 143 |
# Crear tabla de coeficientes
|
| 144 |
coef_df = pd.DataFrame({
|
| 145 |
'Variable': X.columns,
|
| 146 |
'Coeficiente': self.ridge_model.coef_
|
| 147 |
})
|
| 148 |
-
|
| 149 |
return coef_df, r2, mse
|
| 150 |
|
| 151 |
def fit_lasso_regression(self, alpha=0.1):
|
|
@@ -157,20 +167,16 @@ class RSM_BoxBehnken:
|
|
| 157 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 158 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 159 |
y = self.data[self.y_name]
|
| 160 |
-
|
| 161 |
# Ajustar modelo LASSO
|
| 162 |
self.lasso_model = Lasso(alpha=alpha, max_iter=10000).fit(X, y)
|
| 163 |
-
|
| 164 |
# Calcular métricas
|
| 165 |
r2 = self.lasso_model.score(X, y)
|
| 166 |
mse = np.mean((y - self.lasso_model.predict(X))**2)
|
| 167 |
-
|
| 168 |
# Crear tabla de coeficientes
|
| 169 |
coef_df = pd.DataFrame({
|
| 170 |
'Variable': X.columns,
|
| 171 |
'Coeficiente': self.lasso_model.coef_
|
| 172 |
})
|
| 173 |
-
|
| 174 |
return coef_df, r2, mse
|
| 175 |
|
| 176 |
def fit_elasticnet_regression(self, alpha=0.1, l1_ratio=0.5):
|
|
@@ -182,20 +188,16 @@ class RSM_BoxBehnken:
|
|
| 182 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 183 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 184 |
y = self.data[self.y_name]
|
| 185 |
-
|
| 186 |
# Ajustar modelo ElasticNet
|
| 187 |
self.elasticnet_model = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, max_iter=10000).fit(X, y)
|
| 188 |
-
|
| 189 |
# Calcular métricas
|
| 190 |
r2 = self.elasticnet_model.score(X, y)
|
| 191 |
mse = np.mean((y - self.elasticnet_model.predict(X))**2)
|
| 192 |
-
|
| 193 |
# Crear tabla de coeficientes
|
| 194 |
coef_df = pd.DataFrame({
|
| 195 |
'Variable': X.columns,
|
| 196 |
'Coeficiente': self.elasticnet_model.coef_
|
| 197 |
})
|
| 198 |
-
|
| 199 |
return coef_df, r2, mse
|
| 200 |
|
| 201 |
def perform_pca(self):
|
|
@@ -205,25 +207,20 @@ class RSM_BoxBehnken:
|
|
| 205 |
# Preparar datos
|
| 206 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name]]
|
| 207 |
X_scaled = StandardScaler().fit_transform(X)
|
| 208 |
-
|
| 209 |
# Ajustar PCA
|
| 210 |
self.pca_model = PCA(n_components=2)
|
| 211 |
X_pca = self.pca_model.fit_transform(X_scaled)
|
| 212 |
-
|
| 213 |
# Crear tabla de resultados
|
| 214 |
pca_df = pd.DataFrame(X_pca, columns=['PC1', 'PC2'])
|
| 215 |
pca_df[self.y_name] = self.data[self.y_name].values
|
| 216 |
-
|
| 217 |
# Crear tabla de carga
|
| 218 |
loading_df = pd.DataFrame(
|
| 219 |
self.pca_model.components_.T,
|
| 220 |
columns=['PC1', 'PC2'],
|
| 221 |
index=[self.x1_name, self.x2_name, self.x3_name]
|
| 222 |
)
|
| 223 |
-
|
| 224 |
# Varianza explicada
|
| 225 |
explained_variance = self.pca_model.explained_variance_ratio_
|
| 226 |
-
|
| 227 |
return pca_df, loading_df, explained_variance
|
| 228 |
|
| 229 |
def plot_pca(self):
|
|
@@ -231,10 +228,8 @@ class RSM_BoxBehnken:
|
|
| 231 |
Genera un gráfico de PCA.
|
| 232 |
"""
|
| 233 |
pca_df, loading_df, explained_variance = self.perform_pca()
|
| 234 |
-
|
| 235 |
# Crear el gráfico
|
| 236 |
fig = go.Figure()
|
| 237 |
-
|
| 238 |
# Puntos de datos
|
| 239 |
fig.add_trace(go.Scatter(
|
| 240 |
x=pca_df['PC1'],
|
|
@@ -243,14 +238,13 @@ class RSM_BoxBehnken:
|
|
| 243 |
marker=dict(
|
| 244 |
size=10,
|
| 245 |
color=self.data[self.y_name],
|
| 246 |
-
colorscale='
|
| 247 |
colorbar=dict(title=self.y_name),
|
| 248 |
showscale=True
|
| 249 |
),
|
| 250 |
-
text=[f"{self.y_name}: {y:.
|
| 251 |
hoverinfo='text'
|
| 252 |
))
|
| 253 |
-
|
| 254 |
# Vectores de carga
|
| 255 |
for i, var in enumerate([self.x1_name, self.x2_name, self.x3_name]):
|
| 256 |
fig.add_trace(go.Scatter(
|
|
@@ -260,17 +254,15 @@ class RSM_BoxBehnken:
|
|
| 260 |
line=dict(width=2),
|
| 261 |
name=var
|
| 262 |
))
|
| 263 |
-
|
| 264 |
# Configuración del gráfico
|
| 265 |
fig.update_layout(
|
| 266 |
-
title=f'PCA - Varianza Explicada: {explained_variance[0]*100:.
|
| 267 |
xaxis_title='Componente Principal 1',
|
| 268 |
yaxis_title='Componente Principal 2',
|
| 269 |
height=600,
|
| 270 |
width=800,
|
| 271 |
showlegend=True
|
| 272 |
)
|
| 273 |
-
|
| 274 |
return fig
|
| 275 |
|
| 276 |
def optimize(self, method='Nelder-Mead'):
|
|
@@ -280,7 +272,6 @@ class RSM_BoxBehnken:
|
|
| 280 |
if self.model_simplified is None:
|
| 281 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 282 |
return None
|
| 283 |
-
|
| 284 |
def objective_function(x):
|
| 285 |
return -self.model_simplified.predict(pd.DataFrame({
|
| 286 |
self.x1_name: [x[0]],
|
|
@@ -290,36 +281,31 @@ class RSM_BoxBehnken:
|
|
| 290 |
f'{self.x2_name}_sq': [x[1]**2],
|
| 291 |
f'{self.x3_name}_sq': [x[2]**2]
|
| 292 |
})).values[0]
|
| 293 |
-
|
| 294 |
bounds = [(-1, 1), (-1, 1), (-1, 1)]
|
| 295 |
x0 = [0, 0, 0]
|
| 296 |
self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
|
| 297 |
self.optimal_levels = self.optimized_results.x
|
| 298 |
-
|
| 299 |
# Convertir niveles óptimos de codificados a naturales
|
| 300 |
optimal_levels_natural = [
|
| 301 |
self.coded_to_natural(self.optimal_levels[0], self.x1_name),
|
| 302 |
self.coded_to_natural(self.optimal_levels[1], self.x2_name),
|
| 303 |
self.coded_to_natural(self.optimal_levels[2], self.x3_name)
|
| 304 |
]
|
| 305 |
-
|
| 306 |
# Crear la tabla de optimización
|
| 307 |
optimization_table = pd.DataFrame({
|
| 308 |
'Variable': [self.x1_name, self.x2_name, self.x3_name],
|
| 309 |
'Nivel Óptimo (Natural)': optimal_levels_natural,
|
| 310 |
'Nivel Óptimo (Codificado)': self.optimal_levels
|
| 311 |
})
|
| 312 |
-
|
| 313 |
-
return optimization_table.round(3) # Redondear a 3 decimales
|
| 314 |
|
| 315 |
def optimize_bayesian(self, n_calls=20):
|
| 316 |
"""
|
| 317 |
-
Encuentra los niveles óptimos usando optimización bayesiana.
|
| 318 |
"""
|
| 319 |
if self.model_simplified is None:
|
| 320 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 321 |
return None
|
| 322 |
-
|
| 323 |
# Definir el espacio de búsqueda
|
| 324 |
space = [
|
| 325 |
Real(-1, 1, name=self.x1_name),
|
|
@@ -339,47 +325,45 @@ class RSM_BoxBehnken:
|
|
| 339 |
f'{self.x3_name}_sq': [x[2]**2]
|
| 340 |
})).values[0]
|
| 341 |
|
| 342 |
-
# Ejecutar optimización bayesiana
|
| 343 |
res = gp_minimize(
|
| 344 |
objective,
|
| 345 |
space,
|
| 346 |
n_calls=n_calls,
|
| 347 |
random_state=0,
|
| 348 |
-
n_initial_points=5
|
|
|
|
| 349 |
)
|
| 350 |
|
| 351 |
# Almacenar resultados
|
| 352 |
self.optimization_history = res
|
| 353 |
-
|
| 354 |
# Convertir niveles óptimos de codificados a naturales
|
| 355 |
optimal_levels_natural = [
|
| 356 |
self.coded_to_natural(res.x[0], self.x1_name),
|
| 357 |
self.coded_to_natural(res.x[1], self.x2_name),
|
| 358 |
self.coded_to_natural(res.x[2], self.x3_name)
|
| 359 |
]
|
| 360 |
-
|
| 361 |
# Crear la tabla de optimización
|
| 362 |
optimization_table = pd.DataFrame({
|
| 363 |
'Variable': [self.x1_name, self.x2_name, self.x3_name],
|
| 364 |
'Nivel Óptimo (Natural)': optimal_levels_natural,
|
| 365 |
'Nivel Óptimo (Codificado)': res.x
|
| 366 |
})
|
| 367 |
-
|
| 368 |
-
# Obtener la figura de convergencia como objeto Figure
|
| 369 |
ax = plot_convergence(res)
|
| 370 |
fig = ax.get_figure()
|
| 371 |
-
|
| 372 |
-
return optimization_table.round(
|
| 373 |
|
| 374 |
def optimize_pso(self, n_particles=30, max_iter=50):
|
| 375 |
"""
|
| 376 |
-
Encuentra los niveles óptimos usando Particle Swarm Optimization.
|
| 377 |
"""
|
| 378 |
if self.model_simplified is None:
|
| 379 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 380 |
return None
|
| 381 |
|
| 382 |
-
# Función objetivo
|
| 383 |
def objective(x):
|
| 384 |
return -self.model_simplified.predict(pd.DataFrame({
|
| 385 |
self.x1_name: [x[0]],
|
|
@@ -399,7 +383,11 @@ class RSM_BoxBehnken:
|
|
| 399 |
particles = np.random.uniform(-1, 1, (n_particles, 3))
|
| 400 |
velocities = np.random.uniform(-0.1, 0.1, (n_particles, 3))
|
| 401 |
personal_best = particles.copy()
|
| 402 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
|
| 404 |
# Encontrar la mejor partícula global
|
| 405 |
global_best_idx = np.argmin(personal_best_fitness)
|
|
@@ -411,31 +399,30 @@ class RSM_BoxBehnken:
|
|
| 411 |
|
| 412 |
# Optimización
|
| 413 |
for _ in range(max_iter):
|
|
|
|
| 414 |
for i in range(n_particles):
|
| 415 |
-
# Actualizar velocidad
|
| 416 |
r1, r2 = np.random.rand(2)
|
| 417 |
velocities[i] = (w * velocities[i] +
|
| 418 |
c1 * r1 * (personal_best[i] - particles[i]) +
|
| 419 |
c2 * r2 * (global_best - particles[i]))
|
| 420 |
-
|
| 421 |
-
# Actualizar posición
|
| 422 |
particles[i] += velocities[i]
|
| 423 |
-
|
| 424 |
-
# Asegurar que esté dentro de los límites
|
| 425 |
particles[i] = np.clip(particles[i], -1, 1)
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
|
|
|
|
|
|
|
|
|
| 439 |
|
| 440 |
# Almacenar estado actual para visualización
|
| 441 |
history.append({
|
|
@@ -450,17 +437,14 @@ class RSM_BoxBehnken:
|
|
| 450 |
self.coded_to_natural(global_best[1], self.x2_name),
|
| 451 |
self.coded_to_natural(global_best[2], self.x3_name)
|
| 452 |
]
|
| 453 |
-
|
| 454 |
# Crear la tabla de optimización
|
| 455 |
optimization_table = pd.DataFrame({
|
| 456 |
'Variable': [self.x1_name, self.x2_name, self.x3_name],
|
| 457 |
'Nivel Óptimo (Natural)': optimal_levels_natural,
|
| 458 |
'Nivel Óptimo (Codificado)': global_best
|
| 459 |
})
|
| 460 |
-
|
| 461 |
# Crear gráfico de la evolución
|
| 462 |
fig = go.Figure()
|
| 463 |
-
|
| 464 |
# Añadir trayectorias de partículas
|
| 465 |
for i in range(0, len(history), max(1, len(history)//10)):
|
| 466 |
for j in range(n_particles):
|
|
@@ -474,7 +458,6 @@ class RSM_BoxBehnken:
|
|
| 474 |
showlegend=False,
|
| 475 |
hoverinfo='skip'
|
| 476 |
))
|
| 477 |
-
|
| 478 |
# Añadir posición final de partículas
|
| 479 |
fig.add_trace(go.Scatter3d(
|
| 480 |
x=history[-1]['particles'][:, 0],
|
|
@@ -484,7 +467,6 @@ class RSM_BoxBehnken:
|
|
| 484 |
marker=dict(size=4, color='blue'),
|
| 485 |
name='Partículas finales'
|
| 486 |
))
|
| 487 |
-
|
| 488 |
# Añadir mejor solución global
|
| 489 |
fig.add_trace(go.Scatter3d(
|
| 490 |
x=[global_best[0]],
|
|
@@ -494,7 +476,6 @@ class RSM_BoxBehnken:
|
|
| 494 |
marker=dict(size=6, color='red'),
|
| 495 |
name='Mejor solución'
|
| 496 |
))
|
| 497 |
-
|
| 498 |
fig.update_layout(
|
| 499 |
scene=dict(
|
| 500 |
xaxis_title=self.x1_name,
|
|
@@ -504,8 +485,7 @@ class RSM_BoxBehnken:
|
|
| 504 |
title='Evolución de la Optimización con PSO',
|
| 505 |
height=700
|
| 506 |
)
|
| 507 |
-
|
| 508 |
-
return optimization_table.round(3), fig
|
| 509 |
|
| 510 |
def calculate_vif(self):
|
| 511 |
"""
|
|
@@ -514,16 +494,13 @@ class RSM_BoxBehnken:
|
|
| 514 |
if self.model_simplified is None:
|
| 515 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 516 |
return None
|
| 517 |
-
|
| 518 |
# Variables predictoras
|
| 519 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 520 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 521 |
-
|
| 522 |
# Calcular VIF para cada variable
|
| 523 |
vif_data = pd.DataFrame()
|
| 524 |
vif_data["Variable"] = X.columns
|
| 525 |
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
|
| 526 |
-
|
| 527 |
return vif_data
|
| 528 |
|
| 529 |
def plot_diagnostics(self):
|
|
@@ -533,13 +510,11 @@ class RSM_BoxBehnken:
|
|
| 533 |
if self.model_simplified is None:
|
| 534 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 535 |
return []
|
| 536 |
-
|
| 537 |
# Calcular residuos y valores ajustados
|
| 538 |
fitted = self.model_simplified.fittedvalues
|
| 539 |
residuals = self.model_simplified.resid
|
| 540 |
standardized_residuals = residuals / np.sqrt(self.model_simplified.mse_resid)
|
| 541 |
leverage = self.model_simplified.get_influence().hat_matrix_diag
|
| 542 |
-
|
| 543 |
# 1. Residuos vs Valores Ajustados
|
| 544 |
fig1 = go.Figure()
|
| 545 |
fig1.add_trace(go.Scatter(
|
|
@@ -554,17 +529,15 @@ class RSM_BoxBehnken:
|
|
| 554 |
))
|
| 555 |
fig1.add_hline(y=0, line_dash="dash", line_color="red")
|
| 556 |
fig1.update_layout(
|
| 557 |
-
title='Residuos vs Valores Ajustados',
|
| 558 |
xaxis_title='Valores Ajustados',
|
| 559 |
yaxis_title='Residuos',
|
| 560 |
height=400
|
| 561 |
)
|
| 562 |
-
|
| 563 |
# 2. Q-Q Plot
|
| 564 |
sorted_residuals = np.sort(residuals)
|
| 565 |
n = len(sorted_residuals)
|
| 566 |
theoretical_quantiles = t.ppf(np.arange(0.5, n)/n, df=self.model_simplified.df_resid)
|
| 567 |
-
|
| 568 |
fig2 = go.Figure()
|
| 569 |
fig2.add_trace(go.Scatter(
|
| 570 |
x=theoretical_quantiles,
|
|
@@ -587,12 +560,11 @@ class RSM_BoxBehnken:
|
|
| 587 |
name='Línea de referencia'
|
| 588 |
))
|
| 589 |
fig2.update_layout(
|
| 590 |
-
title='Q-Q Plot de Residuos',
|
| 591 |
xaxis_title='Cuantiles Teóricos',
|
| 592 |
yaxis_title='Cuantiles de Residuos',
|
| 593 |
height=400
|
| 594 |
)
|
| 595 |
-
|
| 596 |
# 3. Gráfico de Influencia (Leverage vs Residuos Estándar)
|
| 597 |
fig3 = go.Figure()
|
| 598 |
fig3.add_trace(go.Scatter(
|
|
@@ -613,15 +585,13 @@ class RSM_BoxBehnken:
|
|
| 613 |
line_dash="dash", line_color="green",
|
| 614 |
annotation_text="Límite Leverage")
|
| 615 |
fig3.update_layout(
|
| 616 |
-
title='Gráfico de Influencia (
|
| 617 |
xaxis_title='Leverage',
|
| 618 |
yaxis_title='Residuos Estándar',
|
| 619 |
height=400
|
| 620 |
)
|
| 621 |
-
|
| 622 |
# 4. Gráfico de Escala-Localización
|
| 623 |
sqrt_abs_standardized_residuals = np.sqrt(np.abs(standardized_residuals))
|
| 624 |
-
|
| 625 |
fig4 = go.Figure()
|
| 626 |
fig4.add_trace(go.Scatter(
|
| 627 |
x=fitted,
|
|
@@ -634,12 +604,11 @@ class RSM_BoxBehnken:
|
|
| 634 |
)
|
| 635 |
))
|
| 636 |
fig4.update_layout(
|
| 637 |
-
title='Gráfico de Escala-Localización',
|
| 638 |
xaxis_title='Valores Ajustados',
|
| 639 |
yaxis_title='√|Residuos Estándar|',
|
| 640 |
height=400
|
| 641 |
)
|
| 642 |
-
|
| 643 |
self.diagnostic_figures = [fig1, fig2, fig3, fig4]
|
| 644 |
return self.diagnostic_figures
|
| 645 |
|
|
@@ -650,59 +619,47 @@ class RSM_BoxBehnken:
|
|
| 650 |
if self.model_simplified is None:
|
| 651 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 652 |
return None
|
| 653 |
-
|
| 654 |
# Preparar datos
|
| 655 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 656 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 657 |
y = self.data[self.y_name]
|
| 658 |
-
|
| 659 |
# Crear los folds
|
| 660 |
kf = KFold(n_splits=cv_folds, shuffle=True, random_state=42)
|
| 661 |
-
|
| 662 |
# Almacenar los resultados
|
| 663 |
r2_scores = []
|
| 664 |
mse_scores = []
|
| 665 |
-
|
| 666 |
# Realizar la validación cruzada
|
| 667 |
for train_index, test_index in kf.split(X):
|
| 668 |
X_train, X_test = X.iloc[train_index], X.iloc[test_index]
|
| 669 |
y_train, y_test = y.iloc[train_index], y.iloc[test_index]
|
| 670 |
-
|
| 671 |
# Crear un DataFrame para el ajuste del modelo
|
| 672 |
train_data = X_train.copy()
|
| 673 |
train_data[self.y_name] = y_train
|
| 674 |
-
|
| 675 |
# Ajustar el modelo en los datos de entrenamiento
|
| 676 |
formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
|
| 677 |
f'{self.x1_name}_sq + {self.x2_name}_sq + {self.x3_name}_sq'
|
| 678 |
model = smf.ols(formula, data=train_data).fit()
|
| 679 |
-
|
| 680 |
# Predecir en los datos de prueba
|
| 681 |
y_pred = model.predict(X_test)
|
| 682 |
-
|
| 683 |
# Calcular R²
|
| 684 |
ss_total = np.sum((y_test - np.mean(y_test))**2)
|
| 685 |
ss_residual = np.sum((y_test - y_pred)**2)
|
| 686 |
r2 = 1 - (ss_residual / ss_total)
|
| 687 |
r2_scores.append(r2)
|
| 688 |
-
|
| 689 |
# Calcular MSE
|
| 690 |
mse = np.mean((y_test - y_pred)**2)
|
| 691 |
mse_scores.append(mse)
|
| 692 |
-
|
| 693 |
# Crear tabla de resultados
|
| 694 |
cv_results = pd.DataFrame({
|
| 695 |
'Fold': range(1, cv_folds+1),
|
| 696 |
'R²': r2_scores,
|
| 697 |
'MSE': mse_scores
|
| 698 |
})
|
| 699 |
-
|
| 700 |
# Agregar promedio
|
| 701 |
cv_results.loc['Promedio'] = ['Promedio', np.mean(r2_scores), np.mean(mse_scores)]
|
| 702 |
-
|
| 703 |
return cv_results
|
| 704 |
|
| 705 |
-
def plot_contour_individual(self, fixed_variable, fixed_level, contour_levels=
|
| 706 |
"""
|
| 707 |
Genera un gráfico de contorno para una configuración específica.
|
| 708 |
"""
|
|
@@ -710,22 +667,21 @@ class RSM_BoxBehnken:
|
|
| 710 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 711 |
return None
|
| 712 |
|
|
|
|
|
|
|
|
|
|
| 713 |
# Determinar las variables que varían y sus niveles naturales
|
| 714 |
varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
|
| 715 |
-
|
| 716 |
# Establecer los niveles naturales para las variables que varían
|
| 717 |
x_natural_levels = self.get_levels(varying_variables[0])
|
| 718 |
y_natural_levels = self.get_levels(varying_variables[1])
|
| 719 |
-
|
| 720 |
# Crear una malla de puntos para las variables que varían (en unidades naturales)
|
| 721 |
x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
|
| 722 |
y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
|
| 723 |
x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
|
| 724 |
-
|
| 725 |
# Convertir la malla de variables naturales a codificadas
|
| 726 |
x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
|
| 727 |
y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
|
| 728 |
-
|
| 729 |
# Crear un DataFrame para la predicción con variables codificadas
|
| 730 |
prediction_data = pd.DataFrame({
|
| 731 |
varying_variables[0]: x_grid_coded.flatten(),
|
|
@@ -736,28 +692,22 @@ class RSM_BoxBehnken:
|
|
| 736 |
prediction_data[f'{varying_variables[0]}_sq'] = prediction_data[varying_variables[0]] ** 2
|
| 737 |
prediction_data[f'{varying_variables[1]}_sq'] = prediction_data[varying_variables[1]] ** 2
|
| 738 |
prediction_data[f'{fixed_variable}_sq'] = prediction_data[fixed_variable] ** 2
|
| 739 |
-
|
| 740 |
# Calcular los valores predichos
|
| 741 |
z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape)
|
| 742 |
-
|
| 743 |
# Filtrar por el nivel de la variable fija (en codificado)
|
| 744 |
fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
|
| 745 |
subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
|
| 746 |
-
|
| 747 |
# Filtrar por niveles válidos en las variables que varían
|
| 748 |
valid_levels = [-1, 0, 1]
|
| 749 |
experiments_data = subset_data[
|
| 750 |
subset_data[varying_variables[0]].isin(valid_levels) &
|
| 751 |
subset_data[varying_variables[1]].isin(valid_levels)
|
| 752 |
]
|
| 753 |
-
|
| 754 |
# Convertir coordenadas de experimentos a naturales
|
| 755 |
experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
|
| 756 |
experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
|
| 757 |
-
|
| 758 |
# Crear el gráfico de contorno
|
| 759 |
fig = go.Figure()
|
| 760 |
-
|
| 761 |
# Añadir contornos
|
| 762 |
fig.add_trace(go.Contour(
|
| 763 |
x=x_grid_natural,
|
|
@@ -773,7 +723,6 @@ class RSM_BoxBehnken:
|
|
| 773 |
),
|
| 774 |
colorbar=dict(title=self.y_name)
|
| 775 |
))
|
| 776 |
-
|
| 777 |
# Añadir puntos de experimentos
|
| 778 |
fig.add_trace(go.Scatter(
|
| 779 |
x=experiments_x_natural,
|
|
@@ -784,19 +733,17 @@ class RSM_BoxBehnken:
|
|
| 784 |
color='red',
|
| 785 |
symbol='circle'
|
| 786 |
),
|
| 787 |
-
text=[f"{y:.
|
| 788 |
hoverinfo='text'
|
| 789 |
))
|
| 790 |
-
|
| 791 |
# Añadir etiquetas y título
|
| 792 |
fig.update_layout(
|
| 793 |
-
title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.
|
| 794 |
xaxis_title=f"{varying_variables[0]} ({self.get_units(varying_variables[0])})",
|
| 795 |
yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})",
|
| 796 |
height=600,
|
| 797 |
width=800
|
| 798 |
)
|
| 799 |
-
|
| 800 |
return fig
|
| 801 |
|
| 802 |
def plot_rsm_individual(self, fixed_variable, fixed_level):
|
|
@@ -806,23 +753,18 @@ class RSM_BoxBehnken:
|
|
| 806 |
if self.model_simplified is None:
|
| 807 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 808 |
return None
|
| 809 |
-
|
| 810 |
# Determinar las variables que varían y sus niveles naturales
|
| 811 |
varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
|
| 812 |
-
|
| 813 |
# Establecer los niveles naturales para las variables que varían
|
| 814 |
x_natural_levels = self.get_levels(varying_variables[0])
|
| 815 |
y_natural_levels = self.get_levels(varying_variables[1])
|
| 816 |
-
|
| 817 |
# Crear una malla de puntos para las variables que varían (en unidades naturales)
|
| 818 |
x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
|
| 819 |
y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
|
| 820 |
x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
|
| 821 |
-
|
| 822 |
# Convertir la malla de variables naturales a codificadas
|
| 823 |
x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
|
| 824 |
y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
|
| 825 |
-
|
| 826 |
# Crear un DataFrame para la predicción con variables codificadas
|
| 827 |
prediction_data = pd.DataFrame({
|
| 828 |
varying_variables[0]: x_grid_coded.flatten(),
|
|
@@ -833,25 +775,20 @@ class RSM_BoxBehnken:
|
|
| 833 |
prediction_data[f'{varying_variables[0]}_sq'] = prediction_data[varying_variables[0]] ** 2
|
| 834 |
prediction_data[f'{varying_variables[1]}_sq'] = prediction_data[varying_variables[1]] ** 2
|
| 835 |
prediction_data[f'{fixed_variable}_sq'] = prediction_data[fixed_variable] ** 2
|
| 836 |
-
|
| 837 |
# Calcular los valores predichos
|
| 838 |
z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape)
|
| 839 |
-
|
| 840 |
# Filtrar por el nivel de la variable fija (en codificado)
|
| 841 |
fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
|
| 842 |
subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
|
| 843 |
-
|
| 844 |
# Filtrar por niveles válidos en las variables que varían
|
| 845 |
valid_levels = [-1, 0, 1]
|
| 846 |
experiments_data = subset_data[
|
| 847 |
subset_data[varying_variables[0]].isin(valid_levels) &
|
| 848 |
subset_data[varying_variables[1]].isin(valid_levels)
|
| 849 |
]
|
| 850 |
-
|
| 851 |
# Convertir coordenadas de experimentos a naturales
|
| 852 |
experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
|
| 853 |
experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
|
| 854 |
-
|
| 855 |
# Crear el gráfico de superficie con variables naturales en los ejes y transparencia
|
| 856 |
fig = go.Figure(data=[go.Surface(
|
| 857 |
z=z_pred,
|
|
@@ -861,7 +798,6 @@ class RSM_BoxBehnken:
|
|
| 861 |
opacity=self.current_configuration['opacity'],
|
| 862 |
showscale=True
|
| 863 |
)])
|
| 864 |
-
|
| 865 |
# --- Añadir cuadrícula a la superficie si está habilitado ---
|
| 866 |
if self.current_configuration['show_grid']:
|
| 867 |
# Líneas en la dirección x
|
|
@@ -887,22 +823,20 @@ class RSM_BoxBehnken:
|
|
| 887 |
hoverinfo='skip'
|
| 888 |
))
|
| 889 |
# --- Fin de la adición de la cuadrícula ---
|
| 890 |
-
|
| 891 |
# Añadir los puntos de los experimentos en la superficie de respuesta con diferentes colores y etiquetas
|
| 892 |
if self.current_configuration['show_points']:
|
| 893 |
colors = px.colors.qualitative.Safe
|
| 894 |
-
point_labels = [f"{row[self.y_name]:.
|
| 895 |
fig.add_trace(go.Scatter3d(
|
| 896 |
x=experiments_x_natural,
|
| 897 |
y=experiments_y_natural,
|
| 898 |
-
z=experiments_data[self.y_name].round(
|
| 899 |
mode='markers+text',
|
| 900 |
marker=dict(size=4, color=colors[:len(experiments_x_natural)]),
|
| 901 |
text=point_labels,
|
| 902 |
textposition='top center',
|
| 903 |
name='Experimentos'
|
| 904 |
))
|
| 905 |
-
|
| 906 |
# Añadir etiquetas y título con variables naturales
|
| 907 |
fig.update_layout(
|
| 908 |
scene=dict(
|
|
@@ -910,12 +844,11 @@ class RSM_BoxBehnken:
|
|
| 910 |
yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})",
|
| 911 |
zaxis_title=self.y_name,
|
| 912 |
),
|
| 913 |
-
title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.
|
| 914 |
height=800,
|
| 915 |
width=1000,
|
| 916 |
showlegend=True
|
| 917 |
)
|
| 918 |
-
|
| 919 |
return fig
|
| 920 |
|
| 921 |
def get_units(self, variable_name):
|
|
@@ -939,16 +872,13 @@ class RSM_BoxBehnken:
|
|
| 939 |
if self.model_simplified is None:
|
| 940 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 941 |
return
|
| 942 |
-
|
| 943 |
self.all_figures = [] # Resetear la lista de figuras
|
| 944 |
-
|
| 945 |
# Niveles naturales para graficar
|
| 946 |
levels_to_plot_natural = {
|
| 947 |
self.x1_name: self.x1_levels,
|
| 948 |
self.x2_name: self.x2_levels,
|
| 949 |
self.x3_name: self.x3_levels
|
| 950 |
}
|
| 951 |
-
|
| 952 |
# Generar y almacenar gráficos individuales
|
| 953 |
for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
|
| 954 |
for level in levels_to_plot_natural[fixed_variable]:
|
|
@@ -968,38 +898,50 @@ class RSM_BoxBehnken:
|
|
| 968 |
|
| 969 |
def pareto_chart(self, model, title):
|
| 970 |
"""
|
| 971 |
-
Genera un diagrama de Pareto para los efectos usando estadísticos F,
|
| 972 |
incluyendo la línea de significancia.
|
| 973 |
"""
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 981 |
|
| 982 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 983 |
alpha = 0.05 # Nivel de significancia
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 987 |
|
| 988 |
# Crear el diagrama de Pareto
|
| 989 |
fig = px.bar(
|
| 990 |
-
x=
|
| 991 |
y=sorted_names,
|
| 992 |
orientation='h',
|
| 993 |
-
labels={'x':
|
| 994 |
title=title
|
| 995 |
)
|
| 996 |
fig.update_yaxes(autorange="reversed")
|
| 997 |
-
|
| 998 |
# Agregar la línea de significancia
|
| 999 |
-
fig.add_vline(x=
|
| 1000 |
-
annotation_text=f"
|
| 1001 |
annotation_position="bottom right")
|
| 1002 |
-
|
| 1003 |
return fig
|
| 1004 |
|
| 1005 |
def get_simplified_equation(self):
|
|
@@ -1009,24 +951,22 @@ class RSM_BoxBehnken:
|
|
| 1009 |
if self.model_simplified is None:
|
| 1010 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 1011 |
return None
|
| 1012 |
-
|
| 1013 |
coefficients = self.model_simplified.params
|
| 1014 |
-
equation = f"{self.y_name} = {coefficients['Intercept']:.
|
| 1015 |
for term, coef in coefficients.items():
|
| 1016 |
if term != 'Intercept':
|
| 1017 |
if term == f'{self.x1_name}':
|
| 1018 |
-
equation += f" + {coef:.
|
| 1019 |
elif term == f'{self.x2_name}':
|
| 1020 |
-
equation += f" + {coef:.
|
| 1021 |
elif term == f'{self.x3_name}':
|
| 1022 |
-
equation += f" + {coef:.
|
| 1023 |
elif term == f'{self.x1_name}_sq':
|
| 1024 |
-
equation += f" + {coef:.
|
| 1025 |
elif term == f'{self.x2_name}_sq':
|
| 1026 |
-
equation += f" + {coef:.
|
| 1027 |
elif term == f'{self.x3_name}_sq':
|
| 1028 |
-
equation += f" + {coef:.
|
| 1029 |
-
|
| 1030 |
return equation
|
| 1031 |
|
| 1032 |
def generate_prediction_table(self):
|
|
@@ -1036,11 +976,9 @@ class RSM_BoxBehnken:
|
|
| 1036 |
if self.model_simplified is None:
|
| 1037 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 1038 |
return None
|
| 1039 |
-
|
| 1040 |
self.data['Predicho'] = self.model_simplified.predict(self.data)
|
| 1041 |
self.data['Residual'] = self.data[self.y_name] - self.data['Predicho']
|
| 1042 |
-
|
| 1043 |
-
return self.data[[self.y_name, 'Predicho', 'Residual']].round(3)
|
| 1044 |
|
| 1045 |
def calculate_contribution_percentage(self):
|
| 1046 |
"""
|
|
@@ -1049,13 +987,10 @@ class RSM_BoxBehnken:
|
|
| 1049 |
if self.model_simplified is None:
|
| 1050 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 1051 |
return None
|
| 1052 |
-
|
| 1053 |
# ANOVA del modelo simplificado
|
| 1054 |
anova_table = sm.stats.anova_lm(self.model_simplified, typ=2)
|
| 1055 |
-
|
| 1056 |
# Suma de cuadrados total
|
| 1057 |
ss_total = anova_table['sum_sq'].sum()
|
| 1058 |
-
|
| 1059 |
# Crear tabla de contribución
|
| 1060 |
contribution_table = pd.DataFrame({
|
| 1061 |
'Fuente de Variación': [],
|
|
@@ -1066,10 +1001,8 @@ class RSM_BoxBehnken:
|
|
| 1066 |
'Valor p': [],
|
| 1067 |
'% Contribución': []
|
| 1068 |
})
|
| 1069 |
-
|
| 1070 |
# Calcular estadísticos F y porcentaje de contribución para cada factor
|
| 1071 |
ms_error = anova_table.loc['Residual', 'sum_sq'] / anova_table.loc['Residual', 'df']
|
| 1072 |
-
|
| 1073 |
# Agregar fila para el bloque
|
| 1074 |
block_ss = self.data.groupby('Exp.')[self.y_name].sum().var() * len(self.data)
|
| 1075 |
block_df = 1
|
|
@@ -1086,7 +1019,6 @@ class RSM_BoxBehnken:
|
|
| 1086 |
'Valor p': [block_p],
|
| 1087 |
'% Contribución': [block_contribution]
|
| 1088 |
})], ignore_index=True)
|
| 1089 |
-
|
| 1090 |
# Agregar fila para el modelo
|
| 1091 |
model_ss = anova_table['sum_sq'][:-1].sum() # Suma todo excepto residual
|
| 1092 |
model_df = anova_table['df'][:-1].sum()
|
|
@@ -1103,7 +1035,6 @@ class RSM_BoxBehnken:
|
|
| 1103 |
'Valor p': [model_p],
|
| 1104 |
'% Contribución': [model_contribution]
|
| 1105 |
})], ignore_index=True)
|
| 1106 |
-
|
| 1107 |
# Agregar filas para cada término del modelo
|
| 1108 |
for index, row in anova_table.iterrows():
|
| 1109 |
if index != 'Residual':
|
|
@@ -1129,7 +1060,6 @@ class RSM_BoxBehnken:
|
|
| 1129 |
'Valor p': [p_value],
|
| 1130 |
'% Contribución': [contribution_percentage]
|
| 1131 |
})], ignore_index=True)
|
| 1132 |
-
|
| 1133 |
# Agregar fila para Cor Total
|
| 1134 |
cor_total_ss = ss_total
|
| 1135 |
cor_total_df = len(self.data) - 1
|
|
@@ -1142,8 +1072,7 @@ class RSM_BoxBehnken:
|
|
| 1142 |
'Valor p': [np.nan],
|
| 1143 |
'% Contribución': [100]
|
| 1144 |
})], ignore_index=True)
|
| 1145 |
-
|
| 1146 |
-
return contribution_table.round(3)
|
| 1147 |
|
| 1148 |
def calculate_detailed_anova(self):
|
| 1149 |
"""
|
|
@@ -1152,32 +1081,24 @@ class RSM_BoxBehnken:
|
|
| 1152 |
if self.model_simplified is None:
|
| 1153 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 1154 |
return None
|
| 1155 |
-
|
| 1156 |
# --- ANOVA detallada ---
|
| 1157 |
# 1. Ajustar un modelo solo con los términos de primer orden y cuadráticos
|
| 1158 |
formula_reduced = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
|
| 1159 |
f'{self.x1_name}_sq + {self.x2_name}_sq + {self.x3_name}_sq'
|
| 1160 |
model_reduced = smf.ols(formula_reduced, data=self.data).fit()
|
| 1161 |
-
|
| 1162 |
# 2. ANOVA del modelo reducido
|
| 1163 |
anova_reduced = sm.stats.anova_lm(model_reduced, typ=2)
|
| 1164 |
-
|
| 1165 |
# 3. Suma de cuadrados total
|
| 1166 |
ss_total = np.sum((self.data[self.y_name] - self.data[self.y_name].mean())**2)
|
| 1167 |
-
|
| 1168 |
# 4. Grados de libertad totales
|
| 1169 |
df_total = len(self.data) - 1
|
| 1170 |
-
|
| 1171 |
# 5. Suma de cuadrados de la regresión
|
| 1172 |
ss_regression = anova_reduced['sum_sq'][:-1].sum() # Sumar todo excepto 'Residual'
|
| 1173 |
-
|
| 1174 |
# 6. Grados de libertad de la regresión
|
| 1175 |
df_regression = len(anova_reduced) - 1
|
| 1176 |
-
|
| 1177 |
# 7. Suma de cuadrados del error residual
|
| 1178 |
ss_residual = self.model_simplified.ssr
|
| 1179 |
df_residual = self.model_simplified.df_resid
|
| 1180 |
-
|
| 1181 |
# 8. Suma de cuadrados del error puro (se calcula a partir de las réplicas)
|
| 1182 |
replicas = self.data[self.data.duplicated(subset=[self.x1_name, self.x2_name, self.x3_name], keep=False)]
|
| 1183 |
if not replicas.empty:
|
|
@@ -1186,23 +1107,19 @@ class RSM_BoxBehnken:
|
|
| 1186 |
else:
|
| 1187 |
ss_pure_error = np.nan
|
| 1188 |
df_pure_error = np.nan
|
| 1189 |
-
|
| 1190 |
# 9. Suma de cuadrados de la falta de ajuste
|
| 1191 |
ss_lack_of_fit = ss_residual - ss_pure_error if not np.isnan(ss_pure_error) else np.nan
|
| 1192 |
df_lack_of_fit = df_residual - df_pure_error if not np.isnan(df_pure_error) else np.nan
|
| 1193 |
-
|
| 1194 |
# 10. Cuadrados medios
|
| 1195 |
ms_regression = ss_regression / df_regression
|
| 1196 |
ms_residual = ss_residual / df_residual
|
| 1197 |
ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit if not np.isnan(ss_lack_of_fit) else np.nan
|
| 1198 |
ms_pure_error = ss_pure_error / df_pure_error if not np.isnan(ss_pure_error) else np.nan
|
| 1199 |
-
|
| 1200 |
# 11. Estadísticos F y valores p
|
| 1201 |
f_regression = ms_regression / ms_residual
|
| 1202 |
p_regression = 1 - f.cdf(f_regression, df_regression, df_residual)
|
| 1203 |
f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) else np.nan
|
| 1204 |
p_lack_of_fit = 1 - f.cdf(f_lack_of_fit, df_lack_of_fit, df_pure_error) if not np.isnan(f_lack_of_fit) else np.nan
|
| 1205 |
-
|
| 1206 |
# 12. Crear la tabla ANOVA detallada
|
| 1207 |
detailed_anova_table = pd.DataFrame({
|
| 1208 |
'Fuente de Variación': ['Regresión', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'],
|
|
@@ -1212,7 +1129,6 @@ class RSM_BoxBehnken:
|
|
| 1212 |
'F': [f_regression, np.nan, f_lack_of_fit, np.nan, np.nan],
|
| 1213 |
'Valor p': [p_regression, np.nan, p_lack_of_fit, np.nan, np.nan]
|
| 1214 |
})
|
| 1215 |
-
|
| 1216 |
# Calcular la suma de cuadrados y estadísticos F para la curvatura
|
| 1217 |
ss_curvature = anova_reduced['sum_sq'][f'{self.x1_name}_sq'] + \
|
| 1218 |
anova_reduced['sum_sq'][f'{self.x2_name}_sq'] + \
|
|
@@ -1221,7 +1137,6 @@ class RSM_BoxBehnken:
|
|
| 1221 |
ms_curvature = ss_curvature / df_curvature
|
| 1222 |
f_curvature = ms_curvature / ms_residual
|
| 1223 |
p_curvature = 1 - f.cdf(f_curvature, df_curvature, df_residual)
|
| 1224 |
-
|
| 1225 |
# Añadir la fila de curvatura a la tabla ANOVA
|
| 1226 |
detailed_anova_table.loc[len(detailed_anova_table)] = [
|
| 1227 |
'Curvatura',
|
|
@@ -1231,11 +1146,9 @@ class RSM_BoxBehnken:
|
|
| 1231 |
f_curvature,
|
| 1232 |
p_curvature
|
| 1233 |
]
|
| 1234 |
-
|
| 1235 |
# Reorganizar las filas y resetear el índice
|
| 1236 |
detailed_anova_table = detailed_anova_table.reindex([0, 5, 1, 2, 3, 4]).reset_index(drop=True)
|
| 1237 |
-
|
| 1238 |
-
return detailed_anova_table.round(3)
|
| 1239 |
|
| 1240 |
def get_all_tables(self):
|
| 1241 |
"""
|
|
@@ -1354,7 +1267,6 @@ class RSM_BoxBehnken:
|
|
| 1354 |
doc.save(tmp.name)
|
| 1355 |
tmp_path = tmp.name
|
| 1356 |
return tmp_path
|
| 1357 |
-
|
| 1358 |
# --- Funciones para la Interfaz de Gradio Mejorada ---
|
| 1359 |
def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str):
|
| 1360 |
"""
|
|
@@ -1376,14 +1288,14 @@ def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x
|
|
| 1376 |
# Crear la instancia de RSM_BoxBehnken
|
| 1377 |
global rsm
|
| 1378 |
rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
|
| 1379 |
-
return data.round(
|
| 1380 |
except Exception as e:
|
| 1381 |
# Mostrar mensaje de error
|
| 1382 |
error_message = f"Error al cargar los datos: {str(e)}"
|
| 1383 |
print(error_message)
|
| 1384 |
return None, "", "", "", "", [], [], [], False
|
| 1385 |
|
| 1386 |
-
def fit_and_optimize_model(regularization_alpha=1.0, regularization_l1_ratio=0.5):
|
| 1387 |
if 'rsm' not in globals():
|
| 1388 |
# Devolver None para todos los componentes de salida
|
| 1389 |
return (
|
|
@@ -1392,10 +1304,13 @@ def fit_and_optimize_model(regularization_alpha=1.0, regularization_l1_ratio=0.5
|
|
| 1392 |
None, None, None, None, None
|
| 1393 |
)
|
| 1394 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1395 |
# Ajustar modelos y optimizar
|
| 1396 |
model_completo, pareto_completo = rsm.fit_model()
|
| 1397 |
model_simplificado, pareto_simplificado = rsm.fit_simplified_model()
|
| 1398 |
-
|
| 1399 |
# Ajustar modelos de regularización
|
| 1400 |
ridge_coef, ridge_r2, ridge_mse = rsm.fit_ridge_regression(alpha=regularization_alpha)
|
| 1401 |
lasso_coef, lasso_r2, lasso_mse = rsm.fit_lasso_regression(alpha=regularization_alpha)
|
|
@@ -1403,39 +1318,32 @@ def fit_and_optimize_model(regularization_alpha=1.0, regularization_l1_ratio=0.5
|
|
| 1403 |
alpha=regularization_alpha,
|
| 1404 |
l1_ratio=regularization_l1_ratio
|
| 1405 |
)
|
| 1406 |
-
|
| 1407 |
optimization_table = rsm.optimize()
|
| 1408 |
bayesian_opt_table, bayesian_plot = rsm.optimize_bayesian()
|
| 1409 |
pso_opt_table, pso_plot = rsm.optimize_pso()
|
| 1410 |
-
|
| 1411 |
equation = rsm.get_simplified_equation()
|
| 1412 |
prediction_table = rsm.generate_prediction_table()
|
| 1413 |
contribution_table = rsm.calculate_contribution_percentage()
|
| 1414 |
anova_table = rsm.calculate_detailed_anova()
|
| 1415 |
cross_val_table = rsm.calculate_cross_validation()
|
| 1416 |
vif_table = rsm.calculate_vif()
|
| 1417 |
-
|
| 1418 |
# Generar todas las figuras y almacenarlas
|
| 1419 |
rsm.generate_all_plots()
|
| 1420 |
diagnostic_plots = rsm.plot_diagnostics()
|
| 1421 |
-
|
| 1422 |
# Formatear la ecuación para que se vea mejor en Markdown
|
| 1423 |
equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " × ")
|
| 1424 |
equation_formatted = f"### Ecuación del Modelo Simplificado:<br>{equation_formatted}"
|
| 1425 |
-
|
| 1426 |
# Formatear métricas de regularización
|
| 1427 |
regularization_metrics = f"""
|
| 1428 |
### Métricas de Modelos de Regularización:
|
| 1429 |
-
- **Ridge Regression**: R² = {ridge_r2:.
|
| 1430 |
-
- **LASSO Regression**: R² = {lasso_r2:.
|
| 1431 |
-
- **ElasticNet**: R² = {elasticnet_r2:.
|
| 1432 |
"""
|
| 1433 |
-
|
| 1434 |
# Guardar las tablas en Excel temporal
|
| 1435 |
excel_path = rsm.save_tables_to_excel()
|
| 1436 |
# Guardar todas las figuras en un ZIP temporal
|
| 1437 |
zip_path = rsm.save_figures_to_zip()
|
| 1438 |
-
|
| 1439 |
return (
|
| 1440 |
model_completo.summary().as_html(),
|
| 1441 |
pareto_completo,
|
|
@@ -1537,14 +1445,13 @@ def exportar_word(rsm_instance, tables_dict):
|
|
| 1537 |
return word_path
|
| 1538 |
return None
|
| 1539 |
|
| 1540 |
-
def update_visualization_settings(colorscale, opacity, show_grid, show_points, contour_levels):
|
| 1541 |
"""
|
| 1542 |
-
Actualiza la configuración de visualización.
|
| 1543 |
"""
|
| 1544 |
if 'rsm' not in globals():
|
| 1545 |
return "Error: Primero cargue los datos"
|
| 1546 |
-
|
| 1547 |
-
message = rsm.update_configuration(colorscale, opacity, show_grid, show_points, contour_levels)
|
| 1548 |
return message
|
| 1549 |
|
| 1550 |
def generate_contour_plot(fixed_variable, fixed_level, contour_levels):
|
|
@@ -1553,7 +1460,6 @@ def generate_contour_plot(fixed_variable, fixed_level, contour_levels):
|
|
| 1553 |
"""
|
| 1554 |
if 'rsm' not in globals():
|
| 1555 |
return None
|
| 1556 |
-
|
| 1557 |
return rsm.plot_contour_individual(fixed_variable, fixed_level, contour_levels)
|
| 1558 |
|
| 1559 |
# --- Crear la interfaz de Gradio Mejorada ---
|
|
@@ -1568,18 +1474,15 @@ def create_gradio_interface():
|
|
| 1568 |
técnicas avanzadas de optimización.
|
| 1569 |
</div>
|
| 1570 |
""")
|
| 1571 |
-
|
| 1572 |
# Estado para el índice actual de gráficos
|
| 1573 |
current_index_state = gr.State(0)
|
| 1574 |
all_figures_state = gr.State([])
|
| 1575 |
diagnostic_figures_state = gr.State([])
|
| 1576 |
-
|
| 1577 |
# Pestañas principales
|
| 1578 |
with gr.Tabs():
|
| 1579 |
# Pestaña 1: Configuración y Carga de Datos
|
| 1580 |
with gr.Tab("1. Configuración y Carga de Datos", id="tab1"):
|
| 1581 |
gr.Markdown("### 📥 Configuración del Diseño Experimental")
|
| 1582 |
-
|
| 1583 |
# Asistente de progreso
|
| 1584 |
with gr.Row():
|
| 1585 |
progress = gr.Markdown("""
|
|
@@ -1590,7 +1493,6 @@ def create_gradio_interface():
|
|
| 1590 |
<span style="min-width: 70px; text-align: right; color: #666;">20% Completado</span>
|
| 1591 |
</div>
|
| 1592 |
""")
|
| 1593 |
-
|
| 1594 |
with gr.Row():
|
| 1595 |
with gr.Column(scale=2):
|
| 1596 |
gr.Markdown("#### 🔤 Nombres de Variables")
|
|
@@ -1600,7 +1502,6 @@ def create_gradio_interface():
|
|
| 1600 |
- **Variable Dependiente**: Variable de respuesta que desea optimizar
|
| 1601 |
- Use nombres sin espacios o guiones bajos en su lugar (ej: 'Temperatura', 'pH')
|
| 1602 |
""")
|
| 1603 |
-
|
| 1604 |
x1_name_input = gr.Textbox(label="Variable X1 (ej. Glucosa)", value="Glucosa",
|
| 1605 |
info="Nombre de la primera variable independiente")
|
| 1606 |
x2_name_input = gr.Textbox(label="Variable X2 (ej. Extracto_de_Levadura)", value="Extracto_de_Levadura",
|
|
@@ -1609,7 +1510,6 @@ def create_gradio_interface():
|
|
| 1609 |
info="Nombre de la tercera variable independiente")
|
| 1610 |
y_name_input = gr.Textbox(label="Variable Dependiente (ej. AIA_ppm)", value="AIA_ppm",
|
| 1611 |
info="Nombre de la variable de respuesta")
|
| 1612 |
-
|
| 1613 |
with gr.Column(scale=3):
|
| 1614 |
gr.Markdown("#### 📊 Niveles de Factores")
|
| 1615 |
with gr.Accordion("Ayuda para niveles de factores", open=False):
|
|
@@ -1618,7 +1518,6 @@ def create_gradio_interface():
|
|
| 1618 |
- El orden debe ser: nivel bajo, nivel central, nivel alto
|
| 1619 |
- Ejemplo: 1, 3.5, 5.5
|
| 1620 |
""")
|
| 1621 |
-
|
| 1622 |
with gr.Row():
|
| 1623 |
x1_levels_input = gr.Textbox(label="Niveles de X1", value="1, 3.5, 5.5",
|
| 1624 |
info="Niveles para la variable X1 (bajo, central, alto)")
|
|
@@ -1626,7 +1525,6 @@ def create_gradio_interface():
|
|
| 1626 |
info="Niveles para la variable X2 (bajo, central, alto)")
|
| 1627 |
x3_levels_input = gr.Textbox(label="Niveles de X3", value="0.4, 0.65, 0.9",
|
| 1628 |
info="Niveles para la variable X3 (bajo, central, alto)")
|
| 1629 |
-
|
| 1630 |
gr.Markdown("#### 📤 Datos del Experimento")
|
| 1631 |
with gr.Accordion("Formato de datos requerido", open=False):
|
| 1632 |
gr.Markdown("""
|
|
@@ -1640,7 +1538,6 @@ def create_gradio_interface():
|
|
| 1640 |
...
|
| 1641 |
```
|
| 1642 |
""")
|
| 1643 |
-
|
| 1644 |
data_input = gr.Textbox(label="Datos del Experimento (formato CSV)", lines=10,
|
| 1645 |
value="""1,-1,-1,0,166.594
|
| 1646 |
2,1,-1,0,177.557
|
|
@@ -1657,18 +1554,14 @@ def create_gradio_interface():
|
|
| 1657 |
13,0,0,0,278.951
|
| 1658 |
14,0,0,0,297.238
|
| 1659 |
15,0,0,0,280.896""")
|
| 1660 |
-
|
| 1661 |
with gr.Row():
|
| 1662 |
load_button = gr.Button("Cargar Datos 📂", variant="primary")
|
| 1663 |
data_output = gr.Dataframe(label="Datos Cargados", interactive=False)
|
| 1664 |
-
|
| 1665 |
gr.Markdown("#### ✅ Estado de Carga")
|
| 1666 |
status_output = gr.Textbox(label="Estado", value="Esperando para cargar datos...", interactive=False)
|
| 1667 |
-
|
| 1668 |
# Pestaña 2: Análisis Estadístico
|
| 1669 |
with gr.Tab("2. Análisis Estadístico", id="tab2"):
|
| 1670 |
gr.Markdown("### 📈 Análisis Estadístico del Modelo")
|
| 1671 |
-
|
| 1672 |
# Asistente de progreso
|
| 1673 |
with gr.Row():
|
| 1674 |
progress2 = gr.Markdown("""
|
|
@@ -1679,12 +1572,21 @@ def create_gradio_interface():
|
|
| 1679 |
<span style="min-width: 70px; text-align: right; color: #666;">40% Completado</span>
|
| 1680 |
</div>
|
| 1681 |
""")
|
| 1682 |
-
|
| 1683 |
with gr.Row():
|
| 1684 |
with gr.Column(scale=2):
|
| 1685 |
gr.Markdown("#### ⚙️ Configuración del Análisis")
|
| 1686 |
-
|
| 1687 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1688 |
regularization_alpha = gr.Slider(
|
| 1689 |
minimum=0.001, maximum=10.0, value=1.0, step=0.1,
|
| 1690 |
label="Parámetro Alpha (Regularización)",
|
|
@@ -1695,9 +1597,7 @@ def create_gradio_interface():
|
|
| 1695 |
label="Proporción L1 (ElasticNet)",
|
| 1696 |
info="0 = Ridge, 1 = LASSO, 0.5 = ElasticNet"
|
| 1697 |
)
|
| 1698 |
-
|
| 1699 |
fit_button = gr.Button("Ejecutar Análisis Estadístico 📊", variant="primary")
|
| 1700 |
-
|
| 1701 |
gr.Markdown("#### 📊 Resultados Básicos")
|
| 1702 |
model_completo_output = gr.HTML(label="Modelo Completo")
|
| 1703 |
pareto_completo_output = gr.Plot(label="Diagrama de Pareto - Modelo Completo")
|
|
@@ -1705,10 +1605,8 @@ def create_gradio_interface():
|
|
| 1705 |
pareto_simplificado_output = gr.Plot(label="Diagrama de Pareto - Modelo Simplificado")
|
| 1706 |
equation_output = gr.HTML(label="Ecuación del Modelo Simplificado")
|
| 1707 |
prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False)
|
| 1708 |
-
|
| 1709 |
with gr.Column(scale=3):
|
| 1710 |
gr.Markdown("#### 📐 Modelos Avanzados")
|
| 1711 |
-
|
| 1712 |
with gr.Tabs():
|
| 1713 |
with gr.Tab("Regularización"):
|
| 1714 |
gr.Markdown("### Resultados de Modelos de Regularización")
|
|
@@ -1720,7 +1618,6 @@ def create_gradio_interface():
|
|
| 1720 |
lasso_coef_output = gr.Dataframe(label="Coeficientes LASSO")
|
| 1721 |
with gr.Column():
|
| 1722 |
elasticnet_coef_output = gr.Dataframe(label="Coeficientes ElasticNet")
|
| 1723 |
-
|
| 1724 |
with gr.Tab("Diagnóstico"):
|
| 1725 |
gr.Markdown("### Gráficos de Diagnóstico del Modelo")
|
| 1726 |
with gr.Row():
|
|
@@ -1729,17 +1626,14 @@ def create_gradio_interface():
|
|
| 1729 |
with gr.Row():
|
| 1730 |
diagnostic_plot3 = gr.Plot(label="Gráfico de Influencia")
|
| 1731 |
diagnostic_plot4 = gr.Plot(label="Gráfico de Escala-Localización")
|
| 1732 |
-
|
| 1733 |
with gr.Tab("ANOVA y Contribución"):
|
| 1734 |
gr.Markdown("### Análisis de Varianza y Contribución")
|
| 1735 |
anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False)
|
| 1736 |
contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución", interactive=False)
|
| 1737 |
vif_table_output = gr.Dataframe(label="Factor de Inflación de Varianza (VIF)", interactive=False)
|
| 1738 |
-
|
| 1739 |
# Pestaña 3: Visualización
|
| 1740 |
with gr.Tab("3. Visualización", id="tab3"):
|
| 1741 |
gr.Markdown("### 🖼️ Visualización de Superficies de Respuesta")
|
| 1742 |
-
|
| 1743 |
# Asistente de progreso
|
| 1744 |
with gr.Row():
|
| 1745 |
progress3 = gr.Markdown("""
|
|
@@ -1750,11 +1644,9 @@ def create_gradio_interface():
|
|
| 1750 |
<span style="min-width: 70px; text-align: right; color: #666;">60% Completado</span>
|
| 1751 |
</div>
|
| 1752 |
""")
|
| 1753 |
-
|
| 1754 |
with gr.Row():
|
| 1755 |
with gr.Column(scale=2):
|
| 1756 |
gr.Markdown("#### ⚙️ Configuración de Visualización")
|
| 1757 |
-
|
| 1758 |
with gr.Accordion("Personalización de Gráficos", open=True):
|
| 1759 |
colorscale = gr.Dropdown(
|
| 1760 |
choices=['Viridis', 'Plasma', 'Inferno', 'Magma', 'Cividis', 'Turbo', 'Rainbow', 'Portland'],
|
|
@@ -1765,10 +1657,19 @@ def create_gradio_interface():
|
|
| 1765 |
show_grid = gr.Checkbox(value=True, label="Mostrar Cuadrícula")
|
| 1766 |
show_points = gr.Checkbox(value=True, label="Mostrar Puntos Experimentales")
|
| 1767 |
contour_levels = gr.Slider(minimum=5, maximum=20, value=10, step=1, label="Niveles de Contorno")
|
| 1768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1769 |
update_vis_btn = gr.Button("Actualizar Configuración de Visualización", variant="secondary")
|
| 1770 |
vis_status = gr.Textbox(label="Estado", value="Configuración predeterminada", interactive=False)
|
| 1771 |
-
|
| 1772 |
gr.Markdown("#### 🔍 Selección de Gráfico")
|
| 1773 |
with gr.Accordion("Ayuda para selección de gráficos", open=False):
|
| 1774 |
gr.Markdown("""
|
|
@@ -1776,7 +1677,6 @@ def create_gradio_interface():
|
|
| 1776 |
- **Nivel de Variable Fija**: Valor específico de la variable fija
|
| 1777 |
- Puede generar múltiples gráficos para diferentes combinaciones
|
| 1778 |
""")
|
| 1779 |
-
|
| 1780 |
fixed_variable_input = gr.Dropdown(
|
| 1781 |
label="Variable Fija",
|
| 1782 |
choices=["Glucosa", "Extracto_de_Levadura", "Triptofano"],
|
|
@@ -1791,35 +1691,27 @@ def create_gradio_interface():
|
|
| 1791 |
value="Superficie 3D",
|
| 1792 |
label="Tipo de Gráfico"
|
| 1793 |
)
|
| 1794 |
-
|
| 1795 |
with gr.Row():
|
| 1796 |
plot_button = gr.Button("Generar Gráficos 📊", variant="primary")
|
| 1797 |
contour_button = gr.Button("Generar Contorno 📈", variant="secondary")
|
| 1798 |
-
|
| 1799 |
with gr.Column(scale=3):
|
| 1800 |
gr.Markdown("#### 📊 Visualización de Resultados")
|
| 1801 |
-
|
| 1802 |
with gr.Tabs():
|
| 1803 |
with gr.Tab("Superficie 3D"):
|
| 1804 |
with gr.Row():
|
| 1805 |
left_button = gr.Button("❮", variant="secondary")
|
| 1806 |
right_button = gr.Button("❯", variant="secondary")
|
| 1807 |
-
|
| 1808 |
rsm_plot_output = gr.Plot(label="Superficie de Respuesta")
|
| 1809 |
plot_info = gr.Textbox(label="Información del Gráfico", value="Gráfico 1 de 9", interactive=False)
|
| 1810 |
-
|
| 1811 |
with gr.Row():
|
| 1812 |
download_plot_button = gr.DownloadButton("Descargar Gráfico Actual (PNG) 📥")
|
| 1813 |
download_all_plots_button = gr.DownloadButton("Descargar Todos los Gráficos (ZIP) 📦")
|
| 1814 |
-
|
| 1815 |
with gr.Tab("Contorno"):
|
| 1816 |
contour_plot_output = gr.Plot(label="Gráfico de Contorno")
|
| 1817 |
download_contour_button = gr.DownloadButton("Descargar Contorno (PNG) 📥")
|
| 1818 |
-
|
| 1819 |
# Pestaña 4: Optimización
|
| 1820 |
with gr.Tab("4. Optimización", id="tab4"):
|
| 1821 |
gr.Markdown("### 🎯 Optimización de la Respuesta")
|
| 1822 |
-
|
| 1823 |
# Asistente de progreso
|
| 1824 |
with gr.Row():
|
| 1825 |
progress4 = gr.Markdown("""
|
|
@@ -1830,11 +1722,9 @@ def create_gradio_interface():
|
|
| 1830 |
<span style="min-width: 70px; text-align: right; color: #666;">80% Completado</span>
|
| 1831 |
</div>
|
| 1832 |
""")
|
| 1833 |
-
|
| 1834 |
with gr.Row():
|
| 1835 |
with gr.Column(scale=2):
|
| 1836 |
gr.Markdown("#### ⚙️ Configuración de Optimización")
|
| 1837 |
-
|
| 1838 |
with gr.Accordion("Métodos de Optimización", open=True):
|
| 1839 |
optimization_method = gr.Radio(
|
| 1840 |
["Nelder-Mead", "Bayesiana", "PSO", "Todos"],
|
|
@@ -1844,39 +1734,31 @@ def create_gradio_interface():
|
|
| 1844 |
n_calls = gr.Slider(minimum=10, maximum=50, value=20, step=5, label="Llamadas (Opt. Bayesiana)")
|
| 1845 |
n_particles = gr.Slider(minimum=10, maximum=50, value=30, step=5, label="Partículas (PSO)")
|
| 1846 |
max_iter = gr.Slider(minimum=10, maximum=100, value=50, step=10, label="Iteraciones (PSO)")
|
| 1847 |
-
|
| 1848 |
optimize_button = gr.Button("Ejecutar Optimización 🔍", variant="primary")
|
| 1849 |
-
|
| 1850 |
gr.Markdown("#### 📊 Resultados de Optimización")
|
| 1851 |
optimization_table_output = gr.Dataframe(label="Tabla de Optimización (Nelder-Mead)", interactive=False)
|
| 1852 |
bayesian_opt_table_output = gr.Dataframe(label="Tabla de Optimización (Bayesiana)", interactive=False)
|
| 1853 |
pso_opt_table_output = gr.Dataframe(label="Tabla de Optimización (PSO)", interactive=False)
|
| 1854 |
bayesian_plot_output = gr.Plot(label="Convergencia de Optimización Bayesiana")
|
| 1855 |
pso_plot_output = gr.Plot(label="Visualización de Optimización PSO")
|
| 1856 |
-
|
| 1857 |
with gr.Column(scale=3):
|
| 1858 |
gr.Markdown("#### 📈 Análisis Adicional de Optimización")
|
| 1859 |
-
|
| 1860 |
with gr.Tabs():
|
| 1861 |
with gr.Tab("Análisis de Sensibilidad"):
|
| 1862 |
gr.Markdown("### Análisis de Sensibilidad de los Factores")
|
| 1863 |
# Aquí se podrían agregar gráficos de sensibilidad
|
| 1864 |
gr.Plot(label="Gráfico de Sensibilidad")
|
| 1865 |
gr.Dataframe(label="Tabla de Sensibilidad")
|
| 1866 |
-
|
| 1867 |
with gr.Tab("Intervalos de Confianza"):
|
| 1868 |
gr.Markdown("### Intervalos de Confianza para la Predicción Óptima")
|
| 1869 |
gr.Plot(label="Intervalos de Confianza")
|
| 1870 |
gr.Dataframe(label="Valores de Confianza")
|
| 1871 |
-
|
| 1872 |
with gr.Tab("Validación Cruzada"):
|
| 1873 |
gr.Markdown("### Resultados de Validación Cruzada")
|
| 1874 |
cross_val_table_output = gr.Dataframe(label="Resultados de Validación Cruzada", interactive=False)
|
| 1875 |
-
|
| 1876 |
# Pestaña 5: Reporte y Exportación
|
| 1877 |
with gr.Tab("5. Reporte y Exportación", id="tab5"):
|
| 1878 |
gr.Markdown("### 📑 Generación de Reportes y Exportación de Resultados")
|
| 1879 |
-
|
| 1880 |
# Asistente de progreso
|
| 1881 |
with gr.Row():
|
| 1882 |
progress5 = gr.Markdown("""
|
|
@@ -1887,11 +1769,9 @@ def create_gradio_interface():
|
|
| 1887 |
<span style="min-width: 70px; text-align: right; color: #666;">100% Completado</span>
|
| 1888 |
</div>
|
| 1889 |
""")
|
| 1890 |
-
|
| 1891 |
with gr.Row():
|
| 1892 |
with gr.Column(scale=2):
|
| 1893 |
gr.Markdown("#### 📤 Opciones de Exportación")
|
| 1894 |
-
|
| 1895 |
with gr.Accordion("Configuración del Reporte", open=True):
|
| 1896 |
report_title = gr.Textbox(
|
| 1897 |
label="Título del Reporte",
|
|
@@ -1908,13 +1788,11 @@ def create_gradio_interface():
|
|
| 1908 |
label="Incluir Tablas en el Reporte",
|
| 1909 |
info="Incluirá todas las tablas estadísticas en el documento"
|
| 1910 |
)
|
| 1911 |
-
|
| 1912 |
gr.Markdown("#### 📎 Formatos de Exportación")
|
| 1913 |
with gr.Row():
|
| 1914 |
download_excel_button = gr.DownloadButton("Descargar Tablas en Excel 📊", variant="secondary")
|
| 1915 |
download_word_button = gr.DownloadButton("Descargar Reporte en Word 📝", variant="primary")
|
| 1916 |
download_ppt_button = gr.DownloadButton("Descargar Presentación en PowerPoint 💼", variant="secondary")
|
| 1917 |
-
|
| 1918 |
gr.Markdown("#### 📁 Exportación Personalizada")
|
| 1919 |
with gr.Accordion("Selección de elementos para exportar", open=False):
|
| 1920 |
export_options = gr.CheckboxGroup(
|
|
@@ -1933,36 +1811,28 @@ def create_gradio_interface():
|
|
| 1933 |
],
|
| 1934 |
label="Elementos a incluir en la exportación"
|
| 1935 |
)
|
| 1936 |
-
|
| 1937 |
export_custom_button = gr.Button("Exportar Selección Personalizada 📤", variant="secondary")
|
| 1938 |
-
|
| 1939 |
with gr.Column(scale=3):
|
| 1940 |
gr.Markdown("#### 📄 Vista Previa del Reporte")
|
| 1941 |
-
|
| 1942 |
with gr.Tabs():
|
| 1943 |
with gr.Tab("Vista General"):
|
| 1944 |
gr.Markdown("### Vista Previa del Reporte")
|
| 1945 |
report_preview = gr.Markdown("""
|
| 1946 |
# Informe de Optimización de Producción de AIA
|
| 1947 |
-
|
| 1948 |
_Fecha: 15/05/2023_
|
| 1949 |
-
|
| 1950 |
## Resumen Ejecutivo
|
| 1951 |
Este informe presenta los resultados del análisis de superficie de respuesta
|
| 1952 |
para la optimización del proceso de producción de AIA utilizando un diseño Box-Behnken.
|
| 1953 |
-
|
| 1954 |
## Hallazgos Clave
|
| 1955 |
- El modelo simplificado explica el 92.3% de la variabilidad observada
|
| 1956 |
- Los factores más influyentes fueron [X1] y [X2]
|
| 1957 |
- El punto óptimo identificado predice un valor de [Y] = 295.7 ppm
|
| 1958 |
-
|
| 1959 |
## Recomendaciones
|
| 1960 |
Se recomienda operar en los siguientes niveles:
|
| 1961 |
- [X1]: 3.5 g/L
|
| 1962 |
- [X2]: 0.2 g/L
|
| 1963 |
- [X3]: 0.65 g/L
|
| 1964 |
""")
|
| 1965 |
-
|
| 1966 |
with gr.Tab("Estructura del Reporte"):
|
| 1967 |
report_structure = gr.JSON(value={
|
| 1968 |
"Título": "Informe de Optimización de Producción de AIA",
|
|
@@ -1980,7 +1850,6 @@ def create_gradio_interface():
|
|
| 1980 |
"Gráficos de Superficie"
|
| 1981 |
]
|
| 1982 |
})
|
| 1983 |
-
|
| 1984 |
gr.Markdown("#### 📤 Historial de Exportaciones")
|
| 1985 |
export_history = gr.Dataframe(
|
| 1986 |
headers=["Fecha", "Tipo", "Elementos Exportados", "Tamaño"],
|
|
@@ -1990,7 +1859,6 @@ def create_gradio_interface():
|
|
| 1990 |
],
|
| 1991 |
interactive=False
|
| 1992 |
)
|
| 1993 |
-
|
| 1994 |
# Configuración de eventos
|
| 1995 |
load_button.click(
|
| 1996 |
load_data,
|
|
@@ -2003,18 +1871,16 @@ def create_gradio_interface():
|
|
| 2003 |
lambda: "Datos cargados correctamente. Puede avanzar a la pestaña de Análisis Estadístico.",
|
| 2004 |
outputs=status_output
|
| 2005 |
)
|
| 2006 |
-
|
| 2007 |
# Actualizar configuración de visualización
|
| 2008 |
update_vis_btn.click(
|
| 2009 |
update_visualization_settings,
|
| 2010 |
-
inputs=[colorscale, opacity, show_grid, show_points, contour_levels],
|
| 2011 |
outputs=vis_status
|
| 2012 |
)
|
| 2013 |
-
|
| 2014 |
# Ajustar modelo y optimizar
|
| 2015 |
fit_button.click(
|
| 2016 |
fit_and_optimize_model,
|
| 2017 |
-
inputs=[regularization_alpha, regularization_l1_ratio],
|
| 2018 |
outputs=[
|
| 2019 |
model_completo_output,
|
| 2020 |
pareto_completo_output,
|
|
@@ -2043,7 +1909,6 @@ def create_gradio_interface():
|
|
| 2043 |
download_excel_button
|
| 2044 |
]
|
| 2045 |
)
|
| 2046 |
-
|
| 2047 |
# Generar y mostrar los gráficos
|
| 2048 |
plot_button.click(
|
| 2049 |
lambda fixed_var, fixed_lvl: (
|
|
@@ -2055,58 +1920,50 @@ def create_gradio_interface():
|
|
| 2055 |
inputs=[fixed_variable_input, fixed_level_input],
|
| 2056 |
outputs=[rsm_plot_output, plot_info, current_index_state, all_figures_state]
|
| 2057 |
)
|
| 2058 |
-
|
| 2059 |
# Generar contorno
|
| 2060 |
contour_button.click(
|
| 2061 |
generate_contour_plot,
|
| 2062 |
inputs=[fixed_variable_input, fixed_level_input, contour_levels],
|
| 2063 |
outputs=contour_plot_output
|
| 2064 |
)
|
| 2065 |
-
|
| 2066 |
# Navegación de gráficos
|
| 2067 |
left_button.click(
|
| 2068 |
lambda current_index, all_figures: navigate_plot('left', current_index, all_figures),
|
| 2069 |
inputs=[current_index_state, all_figures_state],
|
| 2070 |
outputs=[rsm_plot_output, plot_info, current_index_state]
|
| 2071 |
)
|
| 2072 |
-
|
| 2073 |
right_button.click(
|
| 2074 |
lambda current_index, all_figures: navigate_plot('right', current_index, all_figures),
|
| 2075 |
inputs=[current_index_state, all_figures_state],
|
| 2076 |
outputs=[rsm_plot_output, plot_info, current_index_state]
|
| 2077 |
)
|
| 2078 |
-
|
| 2079 |
# Descargar gráfico actual
|
| 2080 |
download_plot_button.click(
|
| 2081 |
download_current_plot,
|
| 2082 |
inputs=[all_figures_state, current_index_state],
|
| 2083 |
outputs=download_plot_button
|
| 2084 |
)
|
| 2085 |
-
|
| 2086 |
# Descargar todos los gráficos en ZIP
|
| 2087 |
download_all_plots_button.click(
|
| 2088 |
download_all_plots_zip,
|
| 2089 |
inputs=[],
|
| 2090 |
outputs=download_all_plots_button
|
| 2091 |
)
|
| 2092 |
-
|
| 2093 |
# Descargar todas las tablas en Excel y Word
|
| 2094 |
download_excel_button.click(
|
| 2095 |
fn=lambda: download_all_tables_excel(),
|
| 2096 |
inputs=[],
|
| 2097 |
outputs=download_excel_button
|
| 2098 |
)
|
| 2099 |
-
|
| 2100 |
download_word_button.click(
|
| 2101 |
fn=lambda: exportar_word(rsm, rsm.get_all_tables()),
|
| 2102 |
inputs=[],
|
| 2103 |
outputs=download_word_button
|
| 2104 |
)
|
| 2105 |
-
|
| 2106 |
# Optimización
|
| 2107 |
optimize_button.click(
|
| 2108 |
fit_and_optimize_model,
|
| 2109 |
-
inputs=[regularization_alpha, regularization_l1_ratio],
|
| 2110 |
outputs=[
|
| 2111 |
model_completo_output,
|
| 2112 |
pareto_completo_output,
|
|
@@ -2135,7 +1992,6 @@ def create_gradio_interface():
|
|
| 2135 |
download_excel_button
|
| 2136 |
]
|
| 2137 |
)
|
| 2138 |
-
|
| 2139 |
# Ejemplo de uso
|
| 2140 |
gr.Markdown("## 📌 Guía Rápida de Uso")
|
| 2141 |
gr.Markdown("""
|
|
@@ -2144,27 +2000,22 @@ def create_gradio_interface():
|
|
| 2144 |
- Especifique los niveles de cada factor
|
| 2145 |
- Cargue sus datos experimentales
|
| 2146 |
- Haga clic en "Cargar Datos"
|
| 2147 |
-
|
| 2148 |
2. **Análisis Estadístico**:
|
| 2149 |
- Ajuste los parámetros de regularización si es necesario
|
| 2150 |
- Haga clic en "Ejecutar Análisis Estadístico"
|
| 2151 |
- Revise los resultados de los modelos y diagnósticos
|
| 2152 |
-
|
| 2153 |
3. **Visualización**:
|
| 2154 |
- Personalice la apariencia de los gráficos
|
| 2155 |
- Seleccione qué variable mantener fija y en qué nivel
|
| 2156 |
- Genere y navegue entre los gráficos de superficie
|
| 2157 |
-
|
| 2158 |
4. **Optimización**:
|
| 2159 |
- Seleccione el método de optimización deseado
|
| 2160 |
- Haga clic en "Ejecutar Optimización"
|
| 2161 |
- Analice los resultados y recomendaciones
|
| 2162 |
-
|
| 2163 |
5. **Reporte y Exportación**:
|
| 2164 |
- Personalice y genere su reporte final
|
| 2165 |
- Exporte los resultados en el formato deseado
|
| 2166 |
""")
|
| 2167 |
-
|
| 2168 |
return demo
|
| 2169 |
|
| 2170 |
# --- Función Principal ---
|
|
|
|
| 31 |
from matplotlib.colors import to_hex
|
| 32 |
import seaborn as sns
|
| 33 |
from statsmodels.stats.outliers_influence import variance_inflation_factor
|
| 34 |
+
import concurrent.futures
|
| 35 |
+
from joblib import Parallel, delayed
|
| 36 |
+
import multiprocessing
|
| 37 |
|
| 38 |
# --- Clase RSM_BoxBehnken Mejorada ---
|
| 39 |
class RSM_BoxBehnken:
|
|
|
|
| 50 |
self.data[f'{x1_name}_{x2_name}'] = self.data[x1_name] * self.data[x2_name]
|
| 51 |
self.data[f'{x1_name}_{x3_name}'] = self.data[x1_name] * self.data[x3_name]
|
| 52 |
self.data[f'{x2_name}_{x3_name}'] = self.data[x2_name] * self.data[x3_name]
|
|
|
|
| 53 |
self.model = None
|
| 54 |
self.model_simplified = None
|
| 55 |
self.optimized_results = None
|
|
|
|
| 75 |
'opacity': 0.7,
|
| 76 |
'show_grid': True,
|
| 77 |
'show_points': True,
|
| 78 |
+
'contour_levels': 10,
|
| 79 |
+
'decimal_places': 3,
|
| 80 |
+
'pareto_statistic': 'F' # 't' o 'F'
|
| 81 |
}
|
| 82 |
+
# Para cómputo paralelo
|
| 83 |
+
self.n_jobs = max(1, multiprocessing.cpu_count() - 1)
|
| 84 |
|
| 85 |
+
def update_configuration(self, colorscale, opacity, show_grid, show_points, contour_levels, decimal_places, pareto_statistic):
|
| 86 |
+
"""Actualiza la configuración de visualización y regenera todos los gráficos"""
|
| 87 |
self.current_configuration = {
|
| 88 |
'colorscale': colorscale,
|
| 89 |
'opacity': opacity,
|
| 90 |
'show_grid': show_grid,
|
| 91 |
'show_points': show_points,
|
| 92 |
+
'contour_levels': contour_levels,
|
| 93 |
+
'decimal_places': int(decimal_places),
|
| 94 |
+
'pareto_statistic': pareto_statistic
|
| 95 |
}
|
| 96 |
+
|
| 97 |
+
# Regenerar todos los gráficos con la nueva configuración
|
| 98 |
+
if self.model_simplified is not None:
|
| 99 |
+
self.generate_all_plots()
|
| 100 |
+
self.plot_diagnostics()
|
| 101 |
+
|
| 102 |
+
return "Configuración actualizada correctamente. Todos los gráficos han sido regenerados."
|
| 103 |
|
| 104 |
def get_levels(self, variable_name):
|
| 105 |
"""
|
|
|
|
| 146 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 147 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 148 |
y = self.data[self.y_name]
|
|
|
|
| 149 |
# Ajustar modelo Ridge
|
| 150 |
self.ridge_model = Ridge(alpha=alpha).fit(X, y)
|
|
|
|
| 151 |
# Calcular métricas
|
| 152 |
r2 = self.ridge_model.score(X, y)
|
| 153 |
mse = np.mean((y - self.ridge_model.predict(X))**2)
|
|
|
|
| 154 |
# Crear tabla de coeficientes
|
| 155 |
coef_df = pd.DataFrame({
|
| 156 |
'Variable': X.columns,
|
| 157 |
'Coeficiente': self.ridge_model.coef_
|
| 158 |
})
|
|
|
|
| 159 |
return coef_df, r2, mse
|
| 160 |
|
| 161 |
def fit_lasso_regression(self, alpha=0.1):
|
|
|
|
| 167 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 168 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 169 |
y = self.data[self.y_name]
|
|
|
|
| 170 |
# Ajustar modelo LASSO
|
| 171 |
self.lasso_model = Lasso(alpha=alpha, max_iter=10000).fit(X, y)
|
|
|
|
| 172 |
# Calcular métricas
|
| 173 |
r2 = self.lasso_model.score(X, y)
|
| 174 |
mse = np.mean((y - self.lasso_model.predict(X))**2)
|
|
|
|
| 175 |
# Crear tabla de coeficientes
|
| 176 |
coef_df = pd.DataFrame({
|
| 177 |
'Variable': X.columns,
|
| 178 |
'Coeficiente': self.lasso_model.coef_
|
| 179 |
})
|
|
|
|
| 180 |
return coef_df, r2, mse
|
| 181 |
|
| 182 |
def fit_elasticnet_regression(self, alpha=0.1, l1_ratio=0.5):
|
|
|
|
| 188 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 189 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 190 |
y = self.data[self.y_name]
|
|
|
|
| 191 |
# Ajustar modelo ElasticNet
|
| 192 |
self.elasticnet_model = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, max_iter=10000).fit(X, y)
|
|
|
|
| 193 |
# Calcular métricas
|
| 194 |
r2 = self.elasticnet_model.score(X, y)
|
| 195 |
mse = np.mean((y - self.elasticnet_model.predict(X))**2)
|
|
|
|
| 196 |
# Crear tabla de coeficientes
|
| 197 |
coef_df = pd.DataFrame({
|
| 198 |
'Variable': X.columns,
|
| 199 |
'Coeficiente': self.elasticnet_model.coef_
|
| 200 |
})
|
|
|
|
| 201 |
return coef_df, r2, mse
|
| 202 |
|
| 203 |
def perform_pca(self):
|
|
|
|
| 207 |
# Preparar datos
|
| 208 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name]]
|
| 209 |
X_scaled = StandardScaler().fit_transform(X)
|
|
|
|
| 210 |
# Ajustar PCA
|
| 211 |
self.pca_model = PCA(n_components=2)
|
| 212 |
X_pca = self.pca_model.fit_transform(X_scaled)
|
|
|
|
| 213 |
# Crear tabla de resultados
|
| 214 |
pca_df = pd.DataFrame(X_pca, columns=['PC1', 'PC2'])
|
| 215 |
pca_df[self.y_name] = self.data[self.y_name].values
|
|
|
|
| 216 |
# Crear tabla de carga
|
| 217 |
loading_df = pd.DataFrame(
|
| 218 |
self.pca_model.components_.T,
|
| 219 |
columns=['PC1', 'PC2'],
|
| 220 |
index=[self.x1_name, self.x2_name, self.x3_name]
|
| 221 |
)
|
|
|
|
| 222 |
# Varianza explicada
|
| 223 |
explained_variance = self.pca_model.explained_variance_ratio_
|
|
|
|
| 224 |
return pca_df, loading_df, explained_variance
|
| 225 |
|
| 226 |
def plot_pca(self):
|
|
|
|
| 228 |
Genera un gráfico de PCA.
|
| 229 |
"""
|
| 230 |
pca_df, loading_df, explained_variance = self.perform_pca()
|
|
|
|
| 231 |
# Crear el gráfico
|
| 232 |
fig = go.Figure()
|
|
|
|
| 233 |
# Puntos de datos
|
| 234 |
fig.add_trace(go.Scatter(
|
| 235 |
x=pca_df['PC1'],
|
|
|
|
| 238 |
marker=dict(
|
| 239 |
size=10,
|
| 240 |
color=self.data[self.y_name],
|
| 241 |
+
colorscale=self.current_configuration['colorscale'],
|
| 242 |
colorbar=dict(title=self.y_name),
|
| 243 |
showscale=True
|
| 244 |
),
|
| 245 |
+
text=[f"{self.y_name}: {y:.{self.current_configuration['decimal_places']}f}" for y in self.data[self.y_name]],
|
| 246 |
hoverinfo='text'
|
| 247 |
))
|
|
|
|
| 248 |
# Vectores de carga
|
| 249 |
for i, var in enumerate([self.x1_name, self.x2_name, self.x3_name]):
|
| 250 |
fig.add_trace(go.Scatter(
|
|
|
|
| 254 |
line=dict(width=2),
|
| 255 |
name=var
|
| 256 |
))
|
|
|
|
| 257 |
# Configuración del gráfico
|
| 258 |
fig.update_layout(
|
| 259 |
+
title=f'PCA - Varianza Explicada: {explained_variance[0]*100:.{self.current_configuration["decimal_places"]}f}% (PC1), {explained_variance[1]*100:.{self.current_configuration["decimal_places"]}f}% (PC2)',
|
| 260 |
xaxis_title='Componente Principal 1',
|
| 261 |
yaxis_title='Componente Principal 2',
|
| 262 |
height=600,
|
| 263 |
width=800,
|
| 264 |
showlegend=True
|
| 265 |
)
|
|
|
|
| 266 |
return fig
|
| 267 |
|
| 268 |
def optimize(self, method='Nelder-Mead'):
|
|
|
|
| 272 |
if self.model_simplified is None:
|
| 273 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 274 |
return None
|
|
|
|
| 275 |
def objective_function(x):
|
| 276 |
return -self.model_simplified.predict(pd.DataFrame({
|
| 277 |
self.x1_name: [x[0]],
|
|
|
|
| 281 |
f'{self.x2_name}_sq': [x[1]**2],
|
| 282 |
f'{self.x3_name}_sq': [x[2]**2]
|
| 283 |
})).values[0]
|
|
|
|
| 284 |
bounds = [(-1, 1), (-1, 1), (-1, 1)]
|
| 285 |
x0 = [0, 0, 0]
|
| 286 |
self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
|
| 287 |
self.optimal_levels = self.optimized_results.x
|
|
|
|
| 288 |
# Convertir niveles óptimos de codificados a naturales
|
| 289 |
optimal_levels_natural = [
|
| 290 |
self.coded_to_natural(self.optimal_levels[0], self.x1_name),
|
| 291 |
self.coded_to_natural(self.optimal_levels[1], self.x2_name),
|
| 292 |
self.coded_to_natural(self.optimal_levels[2], self.x3_name)
|
| 293 |
]
|
|
|
|
| 294 |
# Crear la tabla de optimización
|
| 295 |
optimization_table = pd.DataFrame({
|
| 296 |
'Variable': [self.x1_name, self.x2_name, self.x3_name],
|
| 297 |
'Nivel Óptimo (Natural)': optimal_levels_natural,
|
| 298 |
'Nivel Óptimo (Codificado)': self.optimal_levels
|
| 299 |
})
|
| 300 |
+
return optimization_table.round(self.current_configuration['decimal_places'])
|
|
|
|
| 301 |
|
| 302 |
def optimize_bayesian(self, n_calls=20):
|
| 303 |
"""
|
| 304 |
+
Encuentra los niveles óptimos usando optimización bayesiana con paralelización.
|
| 305 |
"""
|
| 306 |
if self.model_simplified is None:
|
| 307 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 308 |
return None
|
|
|
|
| 309 |
# Definir el espacio de búsqueda
|
| 310 |
space = [
|
| 311 |
Real(-1, 1, name=self.x1_name),
|
|
|
|
| 325 |
f'{self.x3_name}_sq': [x[2]**2]
|
| 326 |
})).values[0]
|
| 327 |
|
| 328 |
+
# Ejecutar optimización bayesiana con paralelización
|
| 329 |
res = gp_minimize(
|
| 330 |
objective,
|
| 331 |
space,
|
| 332 |
n_calls=n_calls,
|
| 333 |
random_state=0,
|
| 334 |
+
n_initial_points=5,
|
| 335 |
+
n_jobs=self.n_jobs # Paralelización
|
| 336 |
)
|
| 337 |
|
| 338 |
# Almacenar resultados
|
| 339 |
self.optimization_history = res
|
|
|
|
| 340 |
# Convertir niveles óptimos de codificados a naturales
|
| 341 |
optimal_levels_natural = [
|
| 342 |
self.coded_to_natural(res.x[0], self.x1_name),
|
| 343 |
self.coded_to_natural(res.x[1], self.x2_name),
|
| 344 |
self.coded_to_natural(res.x[2], self.x3_name)
|
| 345 |
]
|
|
|
|
| 346 |
# Crear la tabla de optimización
|
| 347 |
optimization_table = pd.DataFrame({
|
| 348 |
'Variable': [self.x1_name, self.x2_name, self.x3_name],
|
| 349 |
'Nivel Óptimo (Natural)': optimal_levels_natural,
|
| 350 |
'Nivel Óptimo (Codificado)': res.x
|
| 351 |
})
|
| 352 |
+
# Obtener la figura de convergencia
|
|
|
|
| 353 |
ax = plot_convergence(res)
|
| 354 |
fig = ax.get_figure()
|
| 355 |
+
plt.close(fig) # Cerrar la figura para liberar memoria
|
| 356 |
+
return optimization_table.round(self.current_configuration['decimal_places']), fig
|
| 357 |
|
| 358 |
def optimize_pso(self, n_particles=30, max_iter=50):
|
| 359 |
"""
|
| 360 |
+
Encuentra los niveles óptimos usando Particle Swarm Optimization con evaluación paralela.
|
| 361 |
"""
|
| 362 |
if self.model_simplified is None:
|
| 363 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 364 |
return None
|
| 365 |
|
| 366 |
+
# Función objetivo para una partícula
|
| 367 |
def objective(x):
|
| 368 |
return -self.model_simplified.predict(pd.DataFrame({
|
| 369 |
self.x1_name: [x[0]],
|
|
|
|
| 383 |
particles = np.random.uniform(-1, 1, (n_particles, 3))
|
| 384 |
velocities = np.random.uniform(-0.1, 0.1, (n_particles, 3))
|
| 385 |
personal_best = particles.copy()
|
| 386 |
+
|
| 387 |
+
# Evaluar partículas inicialmente con paralelización
|
| 388 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=self.n_jobs) as executor:
|
| 389 |
+
personal_best_fitness = list(executor.map(objective, particles))
|
| 390 |
+
personal_best_fitness = np.array(personal_best_fitness)
|
| 391 |
|
| 392 |
# Encontrar la mejor partícula global
|
| 393 |
global_best_idx = np.argmin(personal_best_fitness)
|
|
|
|
| 399 |
|
| 400 |
# Optimización
|
| 401 |
for _ in range(max_iter):
|
| 402 |
+
# Actualizar velocidades y posiciones
|
| 403 |
for i in range(n_particles):
|
|
|
|
| 404 |
r1, r2 = np.random.rand(2)
|
| 405 |
velocities[i] = (w * velocities[i] +
|
| 406 |
c1 * r1 * (personal_best[i] - particles[i]) +
|
| 407 |
c2 * r2 * (global_best - particles[i]))
|
|
|
|
|
|
|
| 408 |
particles[i] += velocities[i]
|
|
|
|
|
|
|
| 409 |
particles[i] = np.clip(particles[i], -1, 1)
|
| 410 |
+
|
| 411 |
+
# Evaluar nuevas posiciones con paralelización
|
| 412 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=self.n_jobs) as executor:
|
| 413 |
+
fitness_values = list(executor.map(objective, particles))
|
| 414 |
+
fitness_values = np.array(fitness_values)
|
| 415 |
+
|
| 416 |
+
# Actualizar mejores valores
|
| 417 |
+
improved = fitness_values < personal_best_fitness
|
| 418 |
+
personal_best[improved] = particles[improved]
|
| 419 |
+
personal_best_fitness[improved] = fitness_values[improved]
|
| 420 |
+
|
| 421 |
+
# Actualizar mejor global
|
| 422 |
+
if np.min(fitness_values) < global_best_fitness:
|
| 423 |
+
global_best_idx = np.argmin(fitness_values)
|
| 424 |
+
global_best = particles[global_best_idx].copy()
|
| 425 |
+
global_best_fitness = fitness_values[global_best_idx]
|
| 426 |
|
| 427 |
# Almacenar estado actual para visualización
|
| 428 |
history.append({
|
|
|
|
| 437 |
self.coded_to_natural(global_best[1], self.x2_name),
|
| 438 |
self.coded_to_natural(global_best[2], self.x3_name)
|
| 439 |
]
|
|
|
|
| 440 |
# Crear la tabla de optimización
|
| 441 |
optimization_table = pd.DataFrame({
|
| 442 |
'Variable': [self.x1_name, self.x2_name, self.x3_name],
|
| 443 |
'Nivel Óptimo (Natural)': optimal_levels_natural,
|
| 444 |
'Nivel Óptimo (Codificado)': global_best
|
| 445 |
})
|
|
|
|
| 446 |
# Crear gráfico de la evolución
|
| 447 |
fig = go.Figure()
|
|
|
|
| 448 |
# Añadir trayectorias de partículas
|
| 449 |
for i in range(0, len(history), max(1, len(history)//10)):
|
| 450 |
for j in range(n_particles):
|
|
|
|
| 458 |
showlegend=False,
|
| 459 |
hoverinfo='skip'
|
| 460 |
))
|
|
|
|
| 461 |
# Añadir posición final de partículas
|
| 462 |
fig.add_trace(go.Scatter3d(
|
| 463 |
x=history[-1]['particles'][:, 0],
|
|
|
|
| 467 |
marker=dict(size=4, color='blue'),
|
| 468 |
name='Partículas finales'
|
| 469 |
))
|
|
|
|
| 470 |
# Añadir mejor solución global
|
| 471 |
fig.add_trace(go.Scatter3d(
|
| 472 |
x=[global_best[0]],
|
|
|
|
| 476 |
marker=dict(size=6, color='red'),
|
| 477 |
name='Mejor solución'
|
| 478 |
))
|
|
|
|
| 479 |
fig.update_layout(
|
| 480 |
scene=dict(
|
| 481 |
xaxis_title=self.x1_name,
|
|
|
|
| 485 |
title='Evolución de la Optimización con PSO',
|
| 486 |
height=700
|
| 487 |
)
|
| 488 |
+
return optimization_table.round(self.current_configuration['decimal_places']), fig
|
|
|
|
| 489 |
|
| 490 |
def calculate_vif(self):
|
| 491 |
"""
|
|
|
|
| 494 |
if self.model_simplified is None:
|
| 495 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 496 |
return None
|
|
|
|
| 497 |
# Variables predictoras
|
| 498 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 499 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
|
|
|
| 500 |
# Calcular VIF para cada variable
|
| 501 |
vif_data = pd.DataFrame()
|
| 502 |
vif_data["Variable"] = X.columns
|
| 503 |
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
|
|
|
|
| 504 |
return vif_data
|
| 505 |
|
| 506 |
def plot_diagnostics(self):
|
|
|
|
| 510 |
if self.model_simplified is None:
|
| 511 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 512 |
return []
|
|
|
|
| 513 |
# Calcular residuos y valores ajustados
|
| 514 |
fitted = self.model_simplified.fittedvalues
|
| 515 |
residuals = self.model_simplified.resid
|
| 516 |
standardized_residuals = residuals / np.sqrt(self.model_simplified.mse_resid)
|
| 517 |
leverage = self.model_simplified.get_influence().hat_matrix_diag
|
|
|
|
| 518 |
# 1. Residuos vs Valores Ajustados
|
| 519 |
fig1 = go.Figure()
|
| 520 |
fig1.add_trace(go.Scatter(
|
|
|
|
| 529 |
))
|
| 530 |
fig1.add_hline(y=0, line_dash="dash", line_color="red")
|
| 531 |
fig1.update_layout(
|
| 532 |
+
title=f'Residuos vs Valores Ajustados ({self.current_configuration["decimal_places"]} decimales)',
|
| 533 |
xaxis_title='Valores Ajustados',
|
| 534 |
yaxis_title='Residuos',
|
| 535 |
height=400
|
| 536 |
)
|
|
|
|
| 537 |
# 2. Q-Q Plot
|
| 538 |
sorted_residuals = np.sort(residuals)
|
| 539 |
n = len(sorted_residuals)
|
| 540 |
theoretical_quantiles = t.ppf(np.arange(0.5, n)/n, df=self.model_simplified.df_resid)
|
|
|
|
| 541 |
fig2 = go.Figure()
|
| 542 |
fig2.add_trace(go.Scatter(
|
| 543 |
x=theoretical_quantiles,
|
|
|
|
| 560 |
name='Línea de referencia'
|
| 561 |
))
|
| 562 |
fig2.update_layout(
|
| 563 |
+
title=f'Q-Q Plot de Residuos ({self.current_configuration["decimal_places"]} decimales)',
|
| 564 |
xaxis_title='Cuantiles Teóricos',
|
| 565 |
yaxis_title='Cuantiles de Residuos',
|
| 566 |
height=400
|
| 567 |
)
|
|
|
|
| 568 |
# 3. Gráfico de Influencia (Leverage vs Residuos Estándar)
|
| 569 |
fig3 = go.Figure()
|
| 570 |
fig3.add_trace(go.Scatter(
|
|
|
|
| 585 |
line_dash="dash", line_color="green",
|
| 586 |
annotation_text="Límite Leverage")
|
| 587 |
fig3.update_layout(
|
| 588 |
+
title=f'Gráfico de Influencia ({self.current_configuration["decimal_places"]} decimales)',
|
| 589 |
xaxis_title='Leverage',
|
| 590 |
yaxis_title='Residuos Estándar',
|
| 591 |
height=400
|
| 592 |
)
|
|
|
|
| 593 |
# 4. Gráfico de Escala-Localización
|
| 594 |
sqrt_abs_standardized_residuals = np.sqrt(np.abs(standardized_residuals))
|
|
|
|
| 595 |
fig4 = go.Figure()
|
| 596 |
fig4.add_trace(go.Scatter(
|
| 597 |
x=fitted,
|
|
|
|
| 604 |
)
|
| 605 |
))
|
| 606 |
fig4.update_layout(
|
| 607 |
+
title=f'Gráfico de Escala-Localización ({self.current_configuration["decimal_places"]} decimales)',
|
| 608 |
xaxis_title='Valores Ajustados',
|
| 609 |
yaxis_title='√|Residuos Estándar|',
|
| 610 |
height=400
|
| 611 |
)
|
|
|
|
| 612 |
self.diagnostic_figures = [fig1, fig2, fig3, fig4]
|
| 613 |
return self.diagnostic_figures
|
| 614 |
|
|
|
|
| 619 |
if self.model_simplified is None:
|
| 620 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 621 |
return None
|
|
|
|
| 622 |
# Preparar datos
|
| 623 |
X = self.data[[self.x1_name, self.x2_name, self.x3_name,
|
| 624 |
f'{self.x1_name}_sq', f'{self.x2_name}_sq', f'{self.x3_name}_sq']]
|
| 625 |
y = self.data[self.y_name]
|
|
|
|
| 626 |
# Crear los folds
|
| 627 |
kf = KFold(n_splits=cv_folds, shuffle=True, random_state=42)
|
|
|
|
| 628 |
# Almacenar los resultados
|
| 629 |
r2_scores = []
|
| 630 |
mse_scores = []
|
|
|
|
| 631 |
# Realizar la validación cruzada
|
| 632 |
for train_index, test_index in kf.split(X):
|
| 633 |
X_train, X_test = X.iloc[train_index], X.iloc[test_index]
|
| 634 |
y_train, y_test = y.iloc[train_index], y.iloc[test_index]
|
|
|
|
| 635 |
# Crear un DataFrame para el ajuste del modelo
|
| 636 |
train_data = X_train.copy()
|
| 637 |
train_data[self.y_name] = y_train
|
|
|
|
| 638 |
# Ajustar el modelo en los datos de entrenamiento
|
| 639 |
formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
|
| 640 |
f'{self.x1_name}_sq + {self.x2_name}_sq + {self.x3_name}_sq'
|
| 641 |
model = smf.ols(formula, data=train_data).fit()
|
|
|
|
| 642 |
# Predecir en los datos de prueba
|
| 643 |
y_pred = model.predict(X_test)
|
|
|
|
| 644 |
# Calcular R²
|
| 645 |
ss_total = np.sum((y_test - np.mean(y_test))**2)
|
| 646 |
ss_residual = np.sum((y_test - y_pred)**2)
|
| 647 |
r2 = 1 - (ss_residual / ss_total)
|
| 648 |
r2_scores.append(r2)
|
|
|
|
| 649 |
# Calcular MSE
|
| 650 |
mse = np.mean((y_test - y_pred)**2)
|
| 651 |
mse_scores.append(mse)
|
|
|
|
| 652 |
# Crear tabla de resultados
|
| 653 |
cv_results = pd.DataFrame({
|
| 654 |
'Fold': range(1, cv_folds+1),
|
| 655 |
'R²': r2_scores,
|
| 656 |
'MSE': mse_scores
|
| 657 |
})
|
|
|
|
| 658 |
# Agregar promedio
|
| 659 |
cv_results.loc['Promedio'] = ['Promedio', np.mean(r2_scores), np.mean(mse_scores)]
|
|
|
|
| 660 |
return cv_results
|
| 661 |
|
| 662 |
+
def plot_contour_individual(self, fixed_variable, fixed_level, contour_levels=None):
|
| 663 |
"""
|
| 664 |
Genera un gráfico de contorno para una configuración específica.
|
| 665 |
"""
|
|
|
|
| 667 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 668 |
return None
|
| 669 |
|
| 670 |
+
if contour_levels is None:
|
| 671 |
+
contour_levels = self.current_configuration['contour_levels']
|
| 672 |
+
|
| 673 |
# Determinar las variables que varían y sus niveles naturales
|
| 674 |
varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
|
|
|
|
| 675 |
# Establecer los niveles naturales para las variables que varían
|
| 676 |
x_natural_levels = self.get_levels(varying_variables[0])
|
| 677 |
y_natural_levels = self.get_levels(varying_variables[1])
|
|
|
|
| 678 |
# Crear una malla de puntos para las variables que varían (en unidades naturales)
|
| 679 |
x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
|
| 680 |
y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
|
| 681 |
x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
|
|
|
|
| 682 |
# Convertir la malla de variables naturales a codificadas
|
| 683 |
x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
|
| 684 |
y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
|
|
|
|
| 685 |
# Crear un DataFrame para la predicción con variables codificadas
|
| 686 |
prediction_data = pd.DataFrame({
|
| 687 |
varying_variables[0]: x_grid_coded.flatten(),
|
|
|
|
| 692 |
prediction_data[f'{varying_variables[0]}_sq'] = prediction_data[varying_variables[0]] ** 2
|
| 693 |
prediction_data[f'{varying_variables[1]}_sq'] = prediction_data[varying_variables[1]] ** 2
|
| 694 |
prediction_data[f'{fixed_variable}_sq'] = prediction_data[fixed_variable] ** 2
|
|
|
|
| 695 |
# Calcular los valores predichos
|
| 696 |
z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape)
|
|
|
|
| 697 |
# Filtrar por el nivel de la variable fija (en codificado)
|
| 698 |
fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
|
| 699 |
subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
|
|
|
|
| 700 |
# Filtrar por niveles válidos en las variables que varían
|
| 701 |
valid_levels = [-1, 0, 1]
|
| 702 |
experiments_data = subset_data[
|
| 703 |
subset_data[varying_variables[0]].isin(valid_levels) &
|
| 704 |
subset_data[varying_variables[1]].isin(valid_levels)
|
| 705 |
]
|
|
|
|
| 706 |
# Convertir coordenadas de experimentos a naturales
|
| 707 |
experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
|
| 708 |
experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
|
|
|
|
| 709 |
# Crear el gráfico de contorno
|
| 710 |
fig = go.Figure()
|
|
|
|
| 711 |
# Añadir contornos
|
| 712 |
fig.add_trace(go.Contour(
|
| 713 |
x=x_grid_natural,
|
|
|
|
| 723 |
),
|
| 724 |
colorbar=dict(title=self.y_name)
|
| 725 |
))
|
|
|
|
| 726 |
# Añadir puntos de experimentos
|
| 727 |
fig.add_trace(go.Scatter(
|
| 728 |
x=experiments_x_natural,
|
|
|
|
| 733 |
color='red',
|
| 734 |
symbol='circle'
|
| 735 |
),
|
| 736 |
+
text=[f"{y:.{self.current_configuration['decimal_places']}f}" for y in experiments_data[self.y_name]],
|
| 737 |
hoverinfo='text'
|
| 738 |
))
|
|
|
|
| 739 |
# Añadir etiquetas y título
|
| 740 |
fig.update_layout(
|
| 741 |
+
title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.{self.current_configuration['decimal_places']}f} ({self.get_units(fixed_variable)}) (Modelo Simplificado)</sup>",
|
| 742 |
xaxis_title=f"{varying_variables[0]} ({self.get_units(varying_variables[0])})",
|
| 743 |
yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})",
|
| 744 |
height=600,
|
| 745 |
width=800
|
| 746 |
)
|
|
|
|
| 747 |
return fig
|
| 748 |
|
| 749 |
def plot_rsm_individual(self, fixed_variable, fixed_level):
|
|
|
|
| 753 |
if self.model_simplified is None:
|
| 754 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 755 |
return None
|
|
|
|
| 756 |
# Determinar las variables que varían y sus niveles naturales
|
| 757 |
varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
|
|
|
|
| 758 |
# Establecer los niveles naturales para las variables que varían
|
| 759 |
x_natural_levels = self.get_levels(varying_variables[0])
|
| 760 |
y_natural_levels = self.get_levels(varying_variables[1])
|
|
|
|
| 761 |
# Crear una malla de puntos para las variables que varían (en unidades naturales)
|
| 762 |
x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
|
| 763 |
y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
|
| 764 |
x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
|
|
|
|
| 765 |
# Convertir la malla de variables naturales a codificadas
|
| 766 |
x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
|
| 767 |
y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
|
|
|
|
| 768 |
# Crear un DataFrame para la predicción con variables codificadas
|
| 769 |
prediction_data = pd.DataFrame({
|
| 770 |
varying_variables[0]: x_grid_coded.flatten(),
|
|
|
|
| 775 |
prediction_data[f'{varying_variables[0]}_sq'] = prediction_data[varying_variables[0]] ** 2
|
| 776 |
prediction_data[f'{varying_variables[1]}_sq'] = prediction_data[varying_variables[1]] ** 2
|
| 777 |
prediction_data[f'{fixed_variable}_sq'] = prediction_data[fixed_variable] ** 2
|
|
|
|
| 778 |
# Calcular los valores predichos
|
| 779 |
z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape)
|
|
|
|
| 780 |
# Filtrar por el nivel de la variable fija (en codificado)
|
| 781 |
fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
|
| 782 |
subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
|
|
|
|
| 783 |
# Filtrar por niveles válidos en las variables que varían
|
| 784 |
valid_levels = [-1, 0, 1]
|
| 785 |
experiments_data = subset_data[
|
| 786 |
subset_data[varying_variables[0]].isin(valid_levels) &
|
| 787 |
subset_data[varying_variables[1]].isin(valid_levels)
|
| 788 |
]
|
|
|
|
| 789 |
# Convertir coordenadas de experimentos a naturales
|
| 790 |
experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
|
| 791 |
experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
|
|
|
|
| 792 |
# Crear el gráfico de superficie con variables naturales en los ejes y transparencia
|
| 793 |
fig = go.Figure(data=[go.Surface(
|
| 794 |
z=z_pred,
|
|
|
|
| 798 |
opacity=self.current_configuration['opacity'],
|
| 799 |
showscale=True
|
| 800 |
)])
|
|
|
|
| 801 |
# --- Añadir cuadrícula a la superficie si está habilitado ---
|
| 802 |
if self.current_configuration['show_grid']:
|
| 803 |
# Líneas en la dirección x
|
|
|
|
| 823 |
hoverinfo='skip'
|
| 824 |
))
|
| 825 |
# --- Fin de la adición de la cuadrícula ---
|
|
|
|
| 826 |
# Añadir los puntos de los experimentos en la superficie de respuesta con diferentes colores y etiquetas
|
| 827 |
if self.current_configuration['show_points']:
|
| 828 |
colors = px.colors.qualitative.Safe
|
| 829 |
+
point_labels = [f"{row[self.y_name]:.{self.current_configuration['decimal_places']}f}" for _, row in experiments_data.iterrows()]
|
| 830 |
fig.add_trace(go.Scatter3d(
|
| 831 |
x=experiments_x_natural,
|
| 832 |
y=experiments_y_natural,
|
| 833 |
+
z=experiments_data[self.y_name].round(self.current_configuration['decimal_places']),
|
| 834 |
mode='markers+text',
|
| 835 |
marker=dict(size=4, color=colors[:len(experiments_x_natural)]),
|
| 836 |
text=point_labels,
|
| 837 |
textposition='top center',
|
| 838 |
name='Experimentos'
|
| 839 |
))
|
|
|
|
| 840 |
# Añadir etiquetas y título con variables naturales
|
| 841 |
fig.update_layout(
|
| 842 |
scene=dict(
|
|
|
|
| 844 |
yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})",
|
| 845 |
zaxis_title=self.y_name,
|
| 846 |
),
|
| 847 |
+
title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.{self.current_configuration['decimal_places']}f} ({self.get_units(fixed_variable)}) (Modelo Simplificado)</sup>",
|
| 848 |
height=800,
|
| 849 |
width=1000,
|
| 850 |
showlegend=True
|
| 851 |
)
|
|
|
|
| 852 |
return fig
|
| 853 |
|
| 854 |
def get_units(self, variable_name):
|
|
|
|
| 872 |
if self.model_simplified is None:
|
| 873 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 874 |
return
|
|
|
|
| 875 |
self.all_figures = [] # Resetear la lista de figuras
|
|
|
|
| 876 |
# Niveles naturales para graficar
|
| 877 |
levels_to_plot_natural = {
|
| 878 |
self.x1_name: self.x1_levels,
|
| 879 |
self.x2_name: self.x2_levels,
|
| 880 |
self.x3_name: self.x3_levels
|
| 881 |
}
|
|
|
|
| 882 |
# Generar y almacenar gráficos individuales
|
| 883 |
for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
|
| 884 |
for level in levels_to_plot_natural[fixed_variable]:
|
|
|
|
| 898 |
|
| 899 |
def pareto_chart(self, model, title):
|
| 900 |
"""
|
| 901 |
+
Genera un diagrama de Pareto para los efectos usando estadísticos t o F,
|
| 902 |
incluyendo la línea de significancia.
|
| 903 |
"""
|
| 904 |
+
statistic_type = self.current_configuration['pareto_statistic']
|
| 905 |
+
|
| 906 |
+
# Calcular los estadísticos según la selección
|
| 907 |
+
if statistic_type == 'F':
|
| 908 |
+
# Usar estadístico F (t^2)
|
| 909 |
+
fvalues = model.tvalues[1:]**2 # Excluir la Intercept y convertir t a F
|
| 910 |
+
values = np.abs(fvalues)
|
| 911 |
+
y_label = 'Estadístico F'
|
| 912 |
+
else:
|
| 913 |
+
# Usar estadístico t directamente
|
| 914 |
+
tvalues = model.tvalues[1:] # Excluir la Intercept
|
| 915 |
+
values = np.abs(tvalues)
|
| 916 |
+
y_label = 'Estadístico t'
|
| 917 |
|
| 918 |
+
sorted_idx = np.argsort(values)[::-1]
|
| 919 |
+
sorted_values = values[sorted_idx]
|
| 920 |
+
sorted_names = values.index[sorted_idx]
|
| 921 |
+
|
| 922 |
+
# Calcular el valor crítico para la línea de significancia
|
| 923 |
alpha = 0.05 # Nivel de significancia
|
| 924 |
+
if statistic_type == 'F':
|
| 925 |
+
dof_num = 1 # Grados de libertad del numerador (cada término)
|
| 926 |
+
dof_den = model.df_resid # Grados de libertad residuales
|
| 927 |
+
critical_value = f.ppf(1 - alpha, dof_num, dof_den)
|
| 928 |
+
else:
|
| 929 |
+
dof = model.df_resid
|
| 930 |
+
critical_value = t.ppf(1 - alpha/2, dof) # Valor crítico para prueba de dos colas
|
| 931 |
|
| 932 |
# Crear el diagrama de Pareto
|
| 933 |
fig = px.bar(
|
| 934 |
+
x=sorted_values.round(self.current_configuration['decimal_places']),
|
| 935 |
y=sorted_names,
|
| 936 |
orientation='h',
|
| 937 |
+
labels={'x': y_label, 'y': 'Término'},
|
| 938 |
title=title
|
| 939 |
)
|
| 940 |
fig.update_yaxes(autorange="reversed")
|
|
|
|
| 941 |
# Agregar la línea de significancia
|
| 942 |
+
fig.add_vline(x=critical_value, line_dash="dot",
|
| 943 |
+
annotation_text=f"{statistic_type.upper()} crítico = {critical_value:.{self.current_configuration['decimal_places']}f}",
|
| 944 |
annotation_position="bottom right")
|
|
|
|
| 945 |
return fig
|
| 946 |
|
| 947 |
def get_simplified_equation(self):
|
|
|
|
| 951 |
if self.model_simplified is None:
|
| 952 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 953 |
return None
|
|
|
|
| 954 |
coefficients = self.model_simplified.params
|
| 955 |
+
equation = f"{self.y_name} = {coefficients['Intercept']:.{self.current_configuration['decimal_places']}f}"
|
| 956 |
for term, coef in coefficients.items():
|
| 957 |
if term != 'Intercept':
|
| 958 |
if term == f'{self.x1_name}':
|
| 959 |
+
equation += f" + {coef:.{self.current_configuration['decimal_places']}f}*{self.x1_name}"
|
| 960 |
elif term == f'{self.x2_name}':
|
| 961 |
+
equation += f" + {coef:.{self.current_configuration['decimal_places']}f}*{self.x2_name}"
|
| 962 |
elif term == f'{self.x3_name}':
|
| 963 |
+
equation += f" + {coef:.{self.current_configuration['decimal_places']}f}*{self.x3_name}"
|
| 964 |
elif term == f'{self.x1_name}_sq':
|
| 965 |
+
equation += f" + {coef:.{self.current_configuration['decimal_places']}f}*{self.x1_name}^2"
|
| 966 |
elif term == f'{self.x2_name}_sq':
|
| 967 |
+
equation += f" + {coef:.{self.current_configuration['decimal_places']}f}*{self.x2_name}^2"
|
| 968 |
elif term == f'{self.x3_name}_sq':
|
| 969 |
+
equation += f" + {coef:.{self.current_configuration['decimal_places']}f}*{self.x3_name}^2"
|
|
|
|
| 970 |
return equation
|
| 971 |
|
| 972 |
def generate_prediction_table(self):
|
|
|
|
| 976 |
if self.model_simplified is None:
|
| 977 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 978 |
return None
|
|
|
|
| 979 |
self.data['Predicho'] = self.model_simplified.predict(self.data)
|
| 980 |
self.data['Residual'] = self.data[self.y_name] - self.data['Predicho']
|
| 981 |
+
return self.data[[self.y_name, 'Predicho', 'Residual']].round(self.current_configuration['decimal_places'])
|
|
|
|
| 982 |
|
| 983 |
def calculate_contribution_percentage(self):
|
| 984 |
"""
|
|
|
|
| 987 |
if self.model_simplified is None:
|
| 988 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 989 |
return None
|
|
|
|
| 990 |
# ANOVA del modelo simplificado
|
| 991 |
anova_table = sm.stats.anova_lm(self.model_simplified, typ=2)
|
|
|
|
| 992 |
# Suma de cuadrados total
|
| 993 |
ss_total = anova_table['sum_sq'].sum()
|
|
|
|
| 994 |
# Crear tabla de contribución
|
| 995 |
contribution_table = pd.DataFrame({
|
| 996 |
'Fuente de Variación': [],
|
|
|
|
| 1001 |
'Valor p': [],
|
| 1002 |
'% Contribución': []
|
| 1003 |
})
|
|
|
|
| 1004 |
# Calcular estadísticos F y porcentaje de contribución para cada factor
|
| 1005 |
ms_error = anova_table.loc['Residual', 'sum_sq'] / anova_table.loc['Residual', 'df']
|
|
|
|
| 1006 |
# Agregar fila para el bloque
|
| 1007 |
block_ss = self.data.groupby('Exp.')[self.y_name].sum().var() * len(self.data)
|
| 1008 |
block_df = 1
|
|
|
|
| 1019 |
'Valor p': [block_p],
|
| 1020 |
'% Contribución': [block_contribution]
|
| 1021 |
})], ignore_index=True)
|
|
|
|
| 1022 |
# Agregar fila para el modelo
|
| 1023 |
model_ss = anova_table['sum_sq'][:-1].sum() # Suma todo excepto residual
|
| 1024 |
model_df = anova_table['df'][:-1].sum()
|
|
|
|
| 1035 |
'Valor p': [model_p],
|
| 1036 |
'% Contribución': [model_contribution]
|
| 1037 |
})], ignore_index=True)
|
|
|
|
| 1038 |
# Agregar filas para cada término del modelo
|
| 1039 |
for index, row in anova_table.iterrows():
|
| 1040 |
if index != 'Residual':
|
|
|
|
| 1060 |
'Valor p': [p_value],
|
| 1061 |
'% Contribución': [contribution_percentage]
|
| 1062 |
})], ignore_index=True)
|
|
|
|
| 1063 |
# Agregar fila para Cor Total
|
| 1064 |
cor_total_ss = ss_total
|
| 1065 |
cor_total_df = len(self.data) - 1
|
|
|
|
| 1072 |
'Valor p': [np.nan],
|
| 1073 |
'% Contribución': [100]
|
| 1074 |
})], ignore_index=True)
|
| 1075 |
+
return contribution_table.round(self.current_configuration['decimal_places'])
|
|
|
|
| 1076 |
|
| 1077 |
def calculate_detailed_anova(self):
|
| 1078 |
"""
|
|
|
|
| 1081 |
if self.model_simplified is None:
|
| 1082 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 1083 |
return None
|
|
|
|
| 1084 |
# --- ANOVA detallada ---
|
| 1085 |
# 1. Ajustar un modelo solo con los términos de primer orden y cuadráticos
|
| 1086 |
formula_reduced = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
|
| 1087 |
f'{self.x1_name}_sq + {self.x2_name}_sq + {self.x3_name}_sq'
|
| 1088 |
model_reduced = smf.ols(formula_reduced, data=self.data).fit()
|
|
|
|
| 1089 |
# 2. ANOVA del modelo reducido
|
| 1090 |
anova_reduced = sm.stats.anova_lm(model_reduced, typ=2)
|
|
|
|
| 1091 |
# 3. Suma de cuadrados total
|
| 1092 |
ss_total = np.sum((self.data[self.y_name] - self.data[self.y_name].mean())**2)
|
|
|
|
| 1093 |
# 4. Grados de libertad totales
|
| 1094 |
df_total = len(self.data) - 1
|
|
|
|
| 1095 |
# 5. Suma de cuadrados de la regresión
|
| 1096 |
ss_regression = anova_reduced['sum_sq'][:-1].sum() # Sumar todo excepto 'Residual'
|
|
|
|
| 1097 |
# 6. Grados de libertad de la regresión
|
| 1098 |
df_regression = len(anova_reduced) - 1
|
|
|
|
| 1099 |
# 7. Suma de cuadrados del error residual
|
| 1100 |
ss_residual = self.model_simplified.ssr
|
| 1101 |
df_residual = self.model_simplified.df_resid
|
|
|
|
| 1102 |
# 8. Suma de cuadrados del error puro (se calcula a partir de las réplicas)
|
| 1103 |
replicas = self.data[self.data.duplicated(subset=[self.x1_name, self.x2_name, self.x3_name], keep=False)]
|
| 1104 |
if not replicas.empty:
|
|
|
|
| 1107 |
else:
|
| 1108 |
ss_pure_error = np.nan
|
| 1109 |
df_pure_error = np.nan
|
|
|
|
| 1110 |
# 9. Suma de cuadrados de la falta de ajuste
|
| 1111 |
ss_lack_of_fit = ss_residual - ss_pure_error if not np.isnan(ss_pure_error) else np.nan
|
| 1112 |
df_lack_of_fit = df_residual - df_pure_error if not np.isnan(df_pure_error) else np.nan
|
|
|
|
| 1113 |
# 10. Cuadrados medios
|
| 1114 |
ms_regression = ss_regression / df_regression
|
| 1115 |
ms_residual = ss_residual / df_residual
|
| 1116 |
ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit if not np.isnan(ss_lack_of_fit) else np.nan
|
| 1117 |
ms_pure_error = ss_pure_error / df_pure_error if not np.isnan(ss_pure_error) else np.nan
|
|
|
|
| 1118 |
# 11. Estadísticos F y valores p
|
| 1119 |
f_regression = ms_regression / ms_residual
|
| 1120 |
p_regression = 1 - f.cdf(f_regression, df_regression, df_residual)
|
| 1121 |
f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) else np.nan
|
| 1122 |
p_lack_of_fit = 1 - f.cdf(f_lack_of_fit, df_lack_of_fit, df_pure_error) if not np.isnan(f_lack_of_fit) else np.nan
|
|
|
|
| 1123 |
# 12. Crear la tabla ANOVA detallada
|
| 1124 |
detailed_anova_table = pd.DataFrame({
|
| 1125 |
'Fuente de Variación': ['Regresión', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'],
|
|
|
|
| 1129 |
'F': [f_regression, np.nan, f_lack_of_fit, np.nan, np.nan],
|
| 1130 |
'Valor p': [p_regression, np.nan, p_lack_of_fit, np.nan, np.nan]
|
| 1131 |
})
|
|
|
|
| 1132 |
# Calcular la suma de cuadrados y estadísticos F para la curvatura
|
| 1133 |
ss_curvature = anova_reduced['sum_sq'][f'{self.x1_name}_sq'] + \
|
| 1134 |
anova_reduced['sum_sq'][f'{self.x2_name}_sq'] + \
|
|
|
|
| 1137 |
ms_curvature = ss_curvature / df_curvature
|
| 1138 |
f_curvature = ms_curvature / ms_residual
|
| 1139 |
p_curvature = 1 - f.cdf(f_curvature, df_curvature, df_residual)
|
|
|
|
| 1140 |
# Añadir la fila de curvatura a la tabla ANOVA
|
| 1141 |
detailed_anova_table.loc[len(detailed_anova_table)] = [
|
| 1142 |
'Curvatura',
|
|
|
|
| 1146 |
f_curvature,
|
| 1147 |
p_curvature
|
| 1148 |
]
|
|
|
|
| 1149 |
# Reorganizar las filas y resetear el índice
|
| 1150 |
detailed_anova_table = detailed_anova_table.reindex([0, 5, 1, 2, 3, 4]).reset_index(drop=True)
|
| 1151 |
+
return detailed_anova_table.round(self.current_configuration['decimal_places'])
|
|
|
|
| 1152 |
|
| 1153 |
def get_all_tables(self):
|
| 1154 |
"""
|
|
|
|
| 1267 |
doc.save(tmp.name)
|
| 1268 |
tmp_path = tmp.name
|
| 1269 |
return tmp_path
|
|
|
|
| 1270 |
# --- Funciones para la Interfaz de Gradio Mejorada ---
|
| 1271 |
def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str):
|
| 1272 |
"""
|
|
|
|
| 1288 |
# Crear la instancia de RSM_BoxBehnken
|
| 1289 |
global rsm
|
| 1290 |
rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
|
| 1291 |
+
return data.round(rsm.current_configuration['decimal_places']), x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels, True
|
| 1292 |
except Exception as e:
|
| 1293 |
# Mostrar mensaje de error
|
| 1294 |
error_message = f"Error al cargar los datos: {str(e)}"
|
| 1295 |
print(error_message)
|
| 1296 |
return None, "", "", "", "", [], [], [], False
|
| 1297 |
|
| 1298 |
+
def fit_and_optimize_model(regularization_alpha=1.0, regularization_l1_ratio=0.5, decimal_places=3, pareto_statistic='F'):
|
| 1299 |
if 'rsm' not in globals():
|
| 1300 |
# Devolver None para todos los componentes de salida
|
| 1301 |
return (
|
|
|
|
| 1304 |
None, None, None, None, None
|
| 1305 |
)
|
| 1306 |
|
| 1307 |
+
# Actualizar configuración de decimales y estadístico de Pareto
|
| 1308 |
+
rsm.current_configuration['decimal_places'] = int(decimal_places)
|
| 1309 |
+
rsm.current_configuration['pareto_statistic'] = pareto_statistic
|
| 1310 |
+
|
| 1311 |
# Ajustar modelos y optimizar
|
| 1312 |
model_completo, pareto_completo = rsm.fit_model()
|
| 1313 |
model_simplificado, pareto_simplificado = rsm.fit_simplified_model()
|
|
|
|
| 1314 |
# Ajustar modelos de regularización
|
| 1315 |
ridge_coef, ridge_r2, ridge_mse = rsm.fit_ridge_regression(alpha=regularization_alpha)
|
| 1316 |
lasso_coef, lasso_r2, lasso_mse = rsm.fit_lasso_regression(alpha=regularization_alpha)
|
|
|
|
| 1318 |
alpha=regularization_alpha,
|
| 1319 |
l1_ratio=regularization_l1_ratio
|
| 1320 |
)
|
|
|
|
| 1321 |
optimization_table = rsm.optimize()
|
| 1322 |
bayesian_opt_table, bayesian_plot = rsm.optimize_bayesian()
|
| 1323 |
pso_opt_table, pso_plot = rsm.optimize_pso()
|
|
|
|
| 1324 |
equation = rsm.get_simplified_equation()
|
| 1325 |
prediction_table = rsm.generate_prediction_table()
|
| 1326 |
contribution_table = rsm.calculate_contribution_percentage()
|
| 1327 |
anova_table = rsm.calculate_detailed_anova()
|
| 1328 |
cross_val_table = rsm.calculate_cross_validation()
|
| 1329 |
vif_table = rsm.calculate_vif()
|
|
|
|
| 1330 |
# Generar todas las figuras y almacenarlas
|
| 1331 |
rsm.generate_all_plots()
|
| 1332 |
diagnostic_plots = rsm.plot_diagnostics()
|
|
|
|
| 1333 |
# Formatear la ecuación para que se vea mejor en Markdown
|
| 1334 |
equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " × ")
|
| 1335 |
equation_formatted = f"### Ecuación del Modelo Simplificado:<br>{equation_formatted}"
|
|
|
|
| 1336 |
# Formatear métricas de regularización
|
| 1337 |
regularization_metrics = f"""
|
| 1338 |
### Métricas de Modelos de Regularización:
|
| 1339 |
+
- **Ridge Regression**: R² = {ridge_r2:.{decimal_places}f}, MSE = {ridge_mse:.{decimal_places}f}
|
| 1340 |
+
- **LASSO Regression**: R² = {lasso_r2:.{decimal_places}f}, MSE = {lasso_mse:.{decimal_places}f}
|
| 1341 |
+
- **ElasticNet**: R² = {elasticnet_r2:.{decimal_places}f}, MSE = {elasticnet_mse:.{decimal_places}f}
|
| 1342 |
"""
|
|
|
|
| 1343 |
# Guardar las tablas en Excel temporal
|
| 1344 |
excel_path = rsm.save_tables_to_excel()
|
| 1345 |
# Guardar todas las figuras en un ZIP temporal
|
| 1346 |
zip_path = rsm.save_figures_to_zip()
|
|
|
|
| 1347 |
return (
|
| 1348 |
model_completo.summary().as_html(),
|
| 1349 |
pareto_completo,
|
|
|
|
| 1445 |
return word_path
|
| 1446 |
return None
|
| 1447 |
|
| 1448 |
+
def update_visualization_settings(colorscale, opacity, show_grid, show_points, contour_levels, decimal_places, pareto_statistic):
|
| 1449 |
"""
|
| 1450 |
+
Actualiza la configuración de visualización y regenera todos los gráficos.
|
| 1451 |
"""
|
| 1452 |
if 'rsm' not in globals():
|
| 1453 |
return "Error: Primero cargue los datos"
|
| 1454 |
+
message = rsm.update_configuration(colorscale, opacity, show_grid, show_points, contour_levels, decimal_places, pareto_statistic)
|
|
|
|
| 1455 |
return message
|
| 1456 |
|
| 1457 |
def generate_contour_plot(fixed_variable, fixed_level, contour_levels):
|
|
|
|
| 1460 |
"""
|
| 1461 |
if 'rsm' not in globals():
|
| 1462 |
return None
|
|
|
|
| 1463 |
return rsm.plot_contour_individual(fixed_variable, fixed_level, contour_levels)
|
| 1464 |
|
| 1465 |
# --- Crear la interfaz de Gradio Mejorada ---
|
|
|
|
| 1474 |
técnicas avanzadas de optimización.
|
| 1475 |
</div>
|
| 1476 |
""")
|
|
|
|
| 1477 |
# Estado para el índice actual de gráficos
|
| 1478 |
current_index_state = gr.State(0)
|
| 1479 |
all_figures_state = gr.State([])
|
| 1480 |
diagnostic_figures_state = gr.State([])
|
|
|
|
| 1481 |
# Pestañas principales
|
| 1482 |
with gr.Tabs():
|
| 1483 |
# Pestaña 1: Configuración y Carga de Datos
|
| 1484 |
with gr.Tab("1. Configuración y Carga de Datos", id="tab1"):
|
| 1485 |
gr.Markdown("### 📥 Configuración del Diseño Experimental")
|
|
|
|
| 1486 |
# Asistente de progreso
|
| 1487 |
with gr.Row():
|
| 1488 |
progress = gr.Markdown("""
|
|
|
|
| 1493 |
<span style="min-width: 70px; text-align: right; color: #666;">20% Completado</span>
|
| 1494 |
</div>
|
| 1495 |
""")
|
|
|
|
| 1496 |
with gr.Row():
|
| 1497 |
with gr.Column(scale=2):
|
| 1498 |
gr.Markdown("#### 🔤 Nombres de Variables")
|
|
|
|
| 1502 |
- **Variable Dependiente**: Variable de respuesta que desea optimizar
|
| 1503 |
- Use nombres sin espacios o guiones bajos en su lugar (ej: 'Temperatura', 'pH')
|
| 1504 |
""")
|
|
|
|
| 1505 |
x1_name_input = gr.Textbox(label="Variable X1 (ej. Glucosa)", value="Glucosa",
|
| 1506 |
info="Nombre de la primera variable independiente")
|
| 1507 |
x2_name_input = gr.Textbox(label="Variable X2 (ej. Extracto_de_Levadura)", value="Extracto_de_Levadura",
|
|
|
|
| 1510 |
info="Nombre de la tercera variable independiente")
|
| 1511 |
y_name_input = gr.Textbox(label="Variable Dependiente (ej. AIA_ppm)", value="AIA_ppm",
|
| 1512 |
info="Nombre de la variable de respuesta")
|
|
|
|
| 1513 |
with gr.Column(scale=3):
|
| 1514 |
gr.Markdown("#### 📊 Niveles de Factores")
|
| 1515 |
with gr.Accordion("Ayuda para niveles de factores", open=False):
|
|
|
|
| 1518 |
- El orden debe ser: nivel bajo, nivel central, nivel alto
|
| 1519 |
- Ejemplo: 1, 3.5, 5.5
|
| 1520 |
""")
|
|
|
|
| 1521 |
with gr.Row():
|
| 1522 |
x1_levels_input = gr.Textbox(label="Niveles de X1", value="1, 3.5, 5.5",
|
| 1523 |
info="Niveles para la variable X1 (bajo, central, alto)")
|
|
|
|
| 1525 |
info="Niveles para la variable X2 (bajo, central, alto)")
|
| 1526 |
x3_levels_input = gr.Textbox(label="Niveles de X3", value="0.4, 0.65, 0.9",
|
| 1527 |
info="Niveles para la variable X3 (bajo, central, alto)")
|
|
|
|
| 1528 |
gr.Markdown("#### 📤 Datos del Experimento")
|
| 1529 |
with gr.Accordion("Formato de datos requerido", open=False):
|
| 1530 |
gr.Markdown("""
|
|
|
|
| 1538 |
...
|
| 1539 |
```
|
| 1540 |
""")
|
|
|
|
| 1541 |
data_input = gr.Textbox(label="Datos del Experimento (formato CSV)", lines=10,
|
| 1542 |
value="""1,-1,-1,0,166.594
|
| 1543 |
2,1,-1,0,177.557
|
|
|
|
| 1554 |
13,0,0,0,278.951
|
| 1555 |
14,0,0,0,297.238
|
| 1556 |
15,0,0,0,280.896""")
|
|
|
|
| 1557 |
with gr.Row():
|
| 1558 |
load_button = gr.Button("Cargar Datos 📂", variant="primary")
|
| 1559 |
data_output = gr.Dataframe(label="Datos Cargados", interactive=False)
|
|
|
|
| 1560 |
gr.Markdown("#### ✅ Estado de Carga")
|
| 1561 |
status_output = gr.Textbox(label="Estado", value="Esperando para cargar datos...", interactive=False)
|
|
|
|
| 1562 |
# Pestaña 2: Análisis Estadístico
|
| 1563 |
with gr.Tab("2. Análisis Estadístico", id="tab2"):
|
| 1564 |
gr.Markdown("### 📈 Análisis Estadístico del Modelo")
|
|
|
|
| 1565 |
# Asistente de progreso
|
| 1566 |
with gr.Row():
|
| 1567 |
progress2 = gr.Markdown("""
|
|
|
|
| 1572 |
<span style="min-width: 70px; text-align: right; color: #666;">40% Completado</span>
|
| 1573 |
</div>
|
| 1574 |
""")
|
|
|
|
| 1575 |
with gr.Row():
|
| 1576 |
with gr.Column(scale=2):
|
| 1577 |
gr.Markdown("#### ⚙️ Configuración del Análisis")
|
| 1578 |
+
with gr.Accordion("Parámetros de análisis", open=True):
|
| 1579 |
+
decimal_places = gr.Slider(
|
| 1580 |
+
minimum=0, maximum=5, value=3, step=1,
|
| 1581 |
+
label="Número de decimales",
|
| 1582 |
+
info="Número de decimales a mostrar en los resultados"
|
| 1583 |
+
)
|
| 1584 |
+
pareto_statistic = gr.Radio(
|
| 1585 |
+
["t", "F"],
|
| 1586 |
+
value="F",
|
| 1587 |
+
label="Estadístico para diagrama de Pareto",
|
| 1588 |
+
info="Seleccionar el estadístico a utilizar en los diagramas de Pareto"
|
| 1589 |
+
)
|
| 1590 |
regularization_alpha = gr.Slider(
|
| 1591 |
minimum=0.001, maximum=10.0, value=1.0, step=0.1,
|
| 1592 |
label="Parámetro Alpha (Regularización)",
|
|
|
|
| 1597 |
label="Proporción L1 (ElasticNet)",
|
| 1598 |
info="0 = Ridge, 1 = LASSO, 0.5 = ElasticNet"
|
| 1599 |
)
|
|
|
|
| 1600 |
fit_button = gr.Button("Ejecutar Análisis Estadístico 📊", variant="primary")
|
|
|
|
| 1601 |
gr.Markdown("#### 📊 Resultados Básicos")
|
| 1602 |
model_completo_output = gr.HTML(label="Modelo Completo")
|
| 1603 |
pareto_completo_output = gr.Plot(label="Diagrama de Pareto - Modelo Completo")
|
|
|
|
| 1605 |
pareto_simplificado_output = gr.Plot(label="Diagrama de Pareto - Modelo Simplificado")
|
| 1606 |
equation_output = gr.HTML(label="Ecuación del Modelo Simplificado")
|
| 1607 |
prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False)
|
|
|
|
| 1608 |
with gr.Column(scale=3):
|
| 1609 |
gr.Markdown("#### 📐 Modelos Avanzados")
|
|
|
|
| 1610 |
with gr.Tabs():
|
| 1611 |
with gr.Tab("Regularización"):
|
| 1612 |
gr.Markdown("### Resultados de Modelos de Regularización")
|
|
|
|
| 1618 |
lasso_coef_output = gr.Dataframe(label="Coeficientes LASSO")
|
| 1619 |
with gr.Column():
|
| 1620 |
elasticnet_coef_output = gr.Dataframe(label="Coeficientes ElasticNet")
|
|
|
|
| 1621 |
with gr.Tab("Diagnóstico"):
|
| 1622 |
gr.Markdown("### Gráficos de Diagnóstico del Modelo")
|
| 1623 |
with gr.Row():
|
|
|
|
| 1626 |
with gr.Row():
|
| 1627 |
diagnostic_plot3 = gr.Plot(label="Gráfico de Influencia")
|
| 1628 |
diagnostic_plot4 = gr.Plot(label="Gráfico de Escala-Localización")
|
|
|
|
| 1629 |
with gr.Tab("ANOVA y Contribución"):
|
| 1630 |
gr.Markdown("### Análisis de Varianza y Contribución")
|
| 1631 |
anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False)
|
| 1632 |
contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución", interactive=False)
|
| 1633 |
vif_table_output = gr.Dataframe(label="Factor de Inflación de Varianza (VIF)", interactive=False)
|
|
|
|
| 1634 |
# Pestaña 3: Visualización
|
| 1635 |
with gr.Tab("3. Visualización", id="tab3"):
|
| 1636 |
gr.Markdown("### 🖼️ Visualización de Superficies de Respuesta")
|
|
|
|
| 1637 |
# Asistente de progreso
|
| 1638 |
with gr.Row():
|
| 1639 |
progress3 = gr.Markdown("""
|
|
|
|
| 1644 |
<span style="min-width: 70px; text-align: right; color: #666;">60% Completado</span>
|
| 1645 |
</div>
|
| 1646 |
""")
|
|
|
|
| 1647 |
with gr.Row():
|
| 1648 |
with gr.Column(scale=2):
|
| 1649 |
gr.Markdown("#### ⚙️ Configuración de Visualización")
|
|
|
|
| 1650 |
with gr.Accordion("Personalización de Gráficos", open=True):
|
| 1651 |
colorscale = gr.Dropdown(
|
| 1652 |
choices=['Viridis', 'Plasma', 'Inferno', 'Magma', 'Cividis', 'Turbo', 'Rainbow', 'Portland'],
|
|
|
|
| 1657 |
show_grid = gr.Checkbox(value=True, label="Mostrar Cuadrícula")
|
| 1658 |
show_points = gr.Checkbox(value=True, label="Mostrar Puntos Experimentales")
|
| 1659 |
contour_levels = gr.Slider(minimum=5, maximum=20, value=10, step=1, label="Niveles de Contorno")
|
| 1660 |
+
decimal_places_vis = gr.Slider(
|
| 1661 |
+
minimum=0, maximum=5, value=3, step=1,
|
| 1662 |
+
label="Número de decimales en gráficos",
|
| 1663 |
+
info="Número de decimales a mostrar en los gráficos"
|
| 1664 |
+
)
|
| 1665 |
+
pareto_statistic_vis = gr.Radio(
|
| 1666 |
+
["t", "F"],
|
| 1667 |
+
value="F",
|
| 1668 |
+
label="Estadístico para diagrama de Pareto",
|
| 1669 |
+
info="Seleccionar el estadístico a utilizar en los diagramas de Pareto"
|
| 1670 |
+
)
|
| 1671 |
update_vis_btn = gr.Button("Actualizar Configuración de Visualización", variant="secondary")
|
| 1672 |
vis_status = gr.Textbox(label="Estado", value="Configuración predeterminada", interactive=False)
|
|
|
|
| 1673 |
gr.Markdown("#### 🔍 Selección de Gráfico")
|
| 1674 |
with gr.Accordion("Ayuda para selección de gráficos", open=False):
|
| 1675 |
gr.Markdown("""
|
|
|
|
| 1677 |
- **Nivel de Variable Fija**: Valor específico de la variable fija
|
| 1678 |
- Puede generar múltiples gráficos para diferentes combinaciones
|
| 1679 |
""")
|
|
|
|
| 1680 |
fixed_variable_input = gr.Dropdown(
|
| 1681 |
label="Variable Fija",
|
| 1682 |
choices=["Glucosa", "Extracto_de_Levadura", "Triptofano"],
|
|
|
|
| 1691 |
value="Superficie 3D",
|
| 1692 |
label="Tipo de Gráfico"
|
| 1693 |
)
|
|
|
|
| 1694 |
with gr.Row():
|
| 1695 |
plot_button = gr.Button("Generar Gráficos 📊", variant="primary")
|
| 1696 |
contour_button = gr.Button("Generar Contorno 📈", variant="secondary")
|
|
|
|
| 1697 |
with gr.Column(scale=3):
|
| 1698 |
gr.Markdown("#### 📊 Visualización de Resultados")
|
|
|
|
| 1699 |
with gr.Tabs():
|
| 1700 |
with gr.Tab("Superficie 3D"):
|
| 1701 |
with gr.Row():
|
| 1702 |
left_button = gr.Button("❮", variant="secondary")
|
| 1703 |
right_button = gr.Button("❯", variant="secondary")
|
|
|
|
| 1704 |
rsm_plot_output = gr.Plot(label="Superficie de Respuesta")
|
| 1705 |
plot_info = gr.Textbox(label="Información del Gráfico", value="Gráfico 1 de 9", interactive=False)
|
|
|
|
| 1706 |
with gr.Row():
|
| 1707 |
download_plot_button = gr.DownloadButton("Descargar Gráfico Actual (PNG) 📥")
|
| 1708 |
download_all_plots_button = gr.DownloadButton("Descargar Todos los Gráficos (ZIP) 📦")
|
|
|
|
| 1709 |
with gr.Tab("Contorno"):
|
| 1710 |
contour_plot_output = gr.Plot(label="Gráfico de Contorno")
|
| 1711 |
download_contour_button = gr.DownloadButton("Descargar Contorno (PNG) 📥")
|
|
|
|
| 1712 |
# Pestaña 4: Optimización
|
| 1713 |
with gr.Tab("4. Optimización", id="tab4"):
|
| 1714 |
gr.Markdown("### 🎯 Optimización de la Respuesta")
|
|
|
|
| 1715 |
# Asistente de progreso
|
| 1716 |
with gr.Row():
|
| 1717 |
progress4 = gr.Markdown("""
|
|
|
|
| 1722 |
<span style="min-width: 70px; text-align: right; color: #666;">80% Completado</span>
|
| 1723 |
</div>
|
| 1724 |
""")
|
|
|
|
| 1725 |
with gr.Row():
|
| 1726 |
with gr.Column(scale=2):
|
| 1727 |
gr.Markdown("#### ⚙️ Configuración de Optimización")
|
|
|
|
| 1728 |
with gr.Accordion("Métodos de Optimización", open=True):
|
| 1729 |
optimization_method = gr.Radio(
|
| 1730 |
["Nelder-Mead", "Bayesiana", "PSO", "Todos"],
|
|
|
|
| 1734 |
n_calls = gr.Slider(minimum=10, maximum=50, value=20, step=5, label="Llamadas (Opt. Bayesiana)")
|
| 1735 |
n_particles = gr.Slider(minimum=10, maximum=50, value=30, step=5, label="Partículas (PSO)")
|
| 1736 |
max_iter = gr.Slider(minimum=10, maximum=100, value=50, step=10, label="Iteraciones (PSO)")
|
|
|
|
| 1737 |
optimize_button = gr.Button("Ejecutar Optimización 🔍", variant="primary")
|
|
|
|
| 1738 |
gr.Markdown("#### 📊 Resultados de Optimización")
|
| 1739 |
optimization_table_output = gr.Dataframe(label="Tabla de Optimización (Nelder-Mead)", interactive=False)
|
| 1740 |
bayesian_opt_table_output = gr.Dataframe(label="Tabla de Optimización (Bayesiana)", interactive=False)
|
| 1741 |
pso_opt_table_output = gr.Dataframe(label="Tabla de Optimización (PSO)", interactive=False)
|
| 1742 |
bayesian_plot_output = gr.Plot(label="Convergencia de Optimización Bayesiana")
|
| 1743 |
pso_plot_output = gr.Plot(label="Visualización de Optimización PSO")
|
|
|
|
| 1744 |
with gr.Column(scale=3):
|
| 1745 |
gr.Markdown("#### 📈 Análisis Adicional de Optimización")
|
|
|
|
| 1746 |
with gr.Tabs():
|
| 1747 |
with gr.Tab("Análisis de Sensibilidad"):
|
| 1748 |
gr.Markdown("### Análisis de Sensibilidad de los Factores")
|
| 1749 |
# Aquí se podrían agregar gráficos de sensibilidad
|
| 1750 |
gr.Plot(label="Gráfico de Sensibilidad")
|
| 1751 |
gr.Dataframe(label="Tabla de Sensibilidad")
|
|
|
|
| 1752 |
with gr.Tab("Intervalos de Confianza"):
|
| 1753 |
gr.Markdown("### Intervalos de Confianza para la Predicción Óptima")
|
| 1754 |
gr.Plot(label="Intervalos de Confianza")
|
| 1755 |
gr.Dataframe(label="Valores de Confianza")
|
|
|
|
| 1756 |
with gr.Tab("Validación Cruzada"):
|
| 1757 |
gr.Markdown("### Resultados de Validación Cruzada")
|
| 1758 |
cross_val_table_output = gr.Dataframe(label="Resultados de Validación Cruzada", interactive=False)
|
|
|
|
| 1759 |
# Pestaña 5: Reporte y Exportación
|
| 1760 |
with gr.Tab("5. Reporte y Exportación", id="tab5"):
|
| 1761 |
gr.Markdown("### 📑 Generación de Reportes y Exportación de Resultados")
|
|
|
|
| 1762 |
# Asistente de progreso
|
| 1763 |
with gr.Row():
|
| 1764 |
progress5 = gr.Markdown("""
|
|
|
|
| 1769 |
<span style="min-width: 70px; text-align: right; color: #666;">100% Completado</span>
|
| 1770 |
</div>
|
| 1771 |
""")
|
|
|
|
| 1772 |
with gr.Row():
|
| 1773 |
with gr.Column(scale=2):
|
| 1774 |
gr.Markdown("#### 📤 Opciones de Exportación")
|
|
|
|
| 1775 |
with gr.Accordion("Configuración del Reporte", open=True):
|
| 1776 |
report_title = gr.Textbox(
|
| 1777 |
label="Título del Reporte",
|
|
|
|
| 1788 |
label="Incluir Tablas en el Reporte",
|
| 1789 |
info="Incluirá todas las tablas estadísticas en el documento"
|
| 1790 |
)
|
|
|
|
| 1791 |
gr.Markdown("#### 📎 Formatos de Exportación")
|
| 1792 |
with gr.Row():
|
| 1793 |
download_excel_button = gr.DownloadButton("Descargar Tablas en Excel 📊", variant="secondary")
|
| 1794 |
download_word_button = gr.DownloadButton("Descargar Reporte en Word 📝", variant="primary")
|
| 1795 |
download_ppt_button = gr.DownloadButton("Descargar Presentación en PowerPoint 💼", variant="secondary")
|
|
|
|
| 1796 |
gr.Markdown("#### 📁 Exportación Personalizada")
|
| 1797 |
with gr.Accordion("Selección de elementos para exportar", open=False):
|
| 1798 |
export_options = gr.CheckboxGroup(
|
|
|
|
| 1811 |
],
|
| 1812 |
label="Elementos a incluir en la exportación"
|
| 1813 |
)
|
|
|
|
| 1814 |
export_custom_button = gr.Button("Exportar Selección Personalizada 📤", variant="secondary")
|
|
|
|
| 1815 |
with gr.Column(scale=3):
|
| 1816 |
gr.Markdown("#### 📄 Vista Previa del Reporte")
|
|
|
|
| 1817 |
with gr.Tabs():
|
| 1818 |
with gr.Tab("Vista General"):
|
| 1819 |
gr.Markdown("### Vista Previa del Reporte")
|
| 1820 |
report_preview = gr.Markdown("""
|
| 1821 |
# Informe de Optimización de Producción de AIA
|
|
|
|
| 1822 |
_Fecha: 15/05/2023_
|
|
|
|
| 1823 |
## Resumen Ejecutivo
|
| 1824 |
Este informe presenta los resultados del análisis de superficie de respuesta
|
| 1825 |
para la optimización del proceso de producción de AIA utilizando un diseño Box-Behnken.
|
|
|
|
| 1826 |
## Hallazgos Clave
|
| 1827 |
- El modelo simplificado explica el 92.3% de la variabilidad observada
|
| 1828 |
- Los factores más influyentes fueron [X1] y [X2]
|
| 1829 |
- El punto óptimo identificado predice un valor de [Y] = 295.7 ppm
|
|
|
|
| 1830 |
## Recomendaciones
|
| 1831 |
Se recomienda operar en los siguientes niveles:
|
| 1832 |
- [X1]: 3.5 g/L
|
| 1833 |
- [X2]: 0.2 g/L
|
| 1834 |
- [X3]: 0.65 g/L
|
| 1835 |
""")
|
|
|
|
| 1836 |
with gr.Tab("Estructura del Reporte"):
|
| 1837 |
report_structure = gr.JSON(value={
|
| 1838 |
"Título": "Informe de Optimización de Producción de AIA",
|
|
|
|
| 1850 |
"Gráficos de Superficie"
|
| 1851 |
]
|
| 1852 |
})
|
|
|
|
| 1853 |
gr.Markdown("#### 📤 Historial de Exportaciones")
|
| 1854 |
export_history = gr.Dataframe(
|
| 1855 |
headers=["Fecha", "Tipo", "Elementos Exportados", "Tamaño"],
|
|
|
|
| 1859 |
],
|
| 1860 |
interactive=False
|
| 1861 |
)
|
|
|
|
| 1862 |
# Configuración de eventos
|
| 1863 |
load_button.click(
|
| 1864 |
load_data,
|
|
|
|
| 1871 |
lambda: "Datos cargados correctamente. Puede avanzar a la pestaña de Análisis Estadístico.",
|
| 1872 |
outputs=status_output
|
| 1873 |
)
|
|
|
|
| 1874 |
# Actualizar configuración de visualización
|
| 1875 |
update_vis_btn.click(
|
| 1876 |
update_visualization_settings,
|
| 1877 |
+
inputs=[colorscale, opacity, show_grid, show_points, contour_levels, decimal_places_vis, pareto_statistic_vis],
|
| 1878 |
outputs=vis_status
|
| 1879 |
)
|
|
|
|
| 1880 |
# Ajustar modelo y optimizar
|
| 1881 |
fit_button.click(
|
| 1882 |
fit_and_optimize_model,
|
| 1883 |
+
inputs=[regularization_alpha, regularization_l1_ratio, decimal_places, pareto_statistic],
|
| 1884 |
outputs=[
|
| 1885 |
model_completo_output,
|
| 1886 |
pareto_completo_output,
|
|
|
|
| 1909 |
download_excel_button
|
| 1910 |
]
|
| 1911 |
)
|
|
|
|
| 1912 |
# Generar y mostrar los gráficos
|
| 1913 |
plot_button.click(
|
| 1914 |
lambda fixed_var, fixed_lvl: (
|
|
|
|
| 1920 |
inputs=[fixed_variable_input, fixed_level_input],
|
| 1921 |
outputs=[rsm_plot_output, plot_info, current_index_state, all_figures_state]
|
| 1922 |
)
|
|
|
|
| 1923 |
# Generar contorno
|
| 1924 |
contour_button.click(
|
| 1925 |
generate_contour_plot,
|
| 1926 |
inputs=[fixed_variable_input, fixed_level_input, contour_levels],
|
| 1927 |
outputs=contour_plot_output
|
| 1928 |
)
|
|
|
|
| 1929 |
# Navegación de gráficos
|
| 1930 |
left_button.click(
|
| 1931 |
lambda current_index, all_figures: navigate_plot('left', current_index, all_figures),
|
| 1932 |
inputs=[current_index_state, all_figures_state],
|
| 1933 |
outputs=[rsm_plot_output, plot_info, current_index_state]
|
| 1934 |
)
|
|
|
|
| 1935 |
right_button.click(
|
| 1936 |
lambda current_index, all_figures: navigate_plot('right', current_index, all_figures),
|
| 1937 |
inputs=[current_index_state, all_figures_state],
|
| 1938 |
outputs=[rsm_plot_output, plot_info, current_index_state]
|
| 1939 |
)
|
|
|
|
| 1940 |
# Descargar gráfico actual
|
| 1941 |
download_plot_button.click(
|
| 1942 |
download_current_plot,
|
| 1943 |
inputs=[all_figures_state, current_index_state],
|
| 1944 |
outputs=download_plot_button
|
| 1945 |
)
|
|
|
|
| 1946 |
# Descargar todos los gráficos en ZIP
|
| 1947 |
download_all_plots_button.click(
|
| 1948 |
download_all_plots_zip,
|
| 1949 |
inputs=[],
|
| 1950 |
outputs=download_all_plots_button
|
| 1951 |
)
|
|
|
|
| 1952 |
# Descargar todas las tablas en Excel y Word
|
| 1953 |
download_excel_button.click(
|
| 1954 |
fn=lambda: download_all_tables_excel(),
|
| 1955 |
inputs=[],
|
| 1956 |
outputs=download_excel_button
|
| 1957 |
)
|
|
|
|
| 1958 |
download_word_button.click(
|
| 1959 |
fn=lambda: exportar_word(rsm, rsm.get_all_tables()),
|
| 1960 |
inputs=[],
|
| 1961 |
outputs=download_word_button
|
| 1962 |
)
|
|
|
|
| 1963 |
# Optimización
|
| 1964 |
optimize_button.click(
|
| 1965 |
fit_and_optimize_model,
|
| 1966 |
+
inputs=[regularization_alpha, regularization_l1_ratio, decimal_places, pareto_statistic],
|
| 1967 |
outputs=[
|
| 1968 |
model_completo_output,
|
| 1969 |
pareto_completo_output,
|
|
|
|
| 1992 |
download_excel_button
|
| 1993 |
]
|
| 1994 |
)
|
|
|
|
| 1995 |
# Ejemplo de uso
|
| 1996 |
gr.Markdown("## 📌 Guía Rápida de Uso")
|
| 1997 |
gr.Markdown("""
|
|
|
|
| 2000 |
- Especifique los niveles de cada factor
|
| 2001 |
- Cargue sus datos experimentales
|
| 2002 |
- Haga clic en "Cargar Datos"
|
|
|
|
| 2003 |
2. **Análisis Estadístico**:
|
| 2004 |
- Ajuste los parámetros de regularización si es necesario
|
| 2005 |
- Haga clic en "Ejecutar Análisis Estadístico"
|
| 2006 |
- Revise los resultados de los modelos y diagnósticos
|
|
|
|
| 2007 |
3. **Visualización**:
|
| 2008 |
- Personalice la apariencia de los gráficos
|
| 2009 |
- Seleccione qué variable mantener fija y en qué nivel
|
| 2010 |
- Genere y navegue entre los gráficos de superficie
|
|
|
|
| 2011 |
4. **Optimización**:
|
| 2012 |
- Seleccione el método de optimización deseado
|
| 2013 |
- Haga clic en "Ejecutar Optimización"
|
| 2014 |
- Analice los resultados y recomendaciones
|
|
|
|
| 2015 |
5. **Reporte y Exportación**:
|
| 2016 |
- Personalice y genere su reporte final
|
| 2017 |
- Exporte los resultados en el formato deseado
|
| 2018 |
""")
|
|
|
|
| 2019 |
return demo
|
| 2020 |
|
| 2021 |
# --- Función Principal ---
|