C2MV commited on
Commit
519b16c
·
verified ·
1 Parent(s): 23ece77

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -177
app.py CHANGED
@@ -8,22 +8,13 @@ from scipy.optimize import minimize
8
  import plotly.express as px
9
  from scipy.stats import t, f
10
  import gradio as gr
 
 
 
11
 
12
  class RSM_BoxBehnken:
13
  def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels):
14
- """
15
- Inicializa la clase con los datos del diseño Box-Behnken.
16
-
17
- Args:
18
- data (pd.DataFrame): DataFrame con los datos del experimento.
19
- x1_name (str): Nombre de la primera variable independiente.
20
- x2_name (str): Nombre de la segunda variable independiente.
21
- x3_name (str): Nombre de la tercera variable independiente.
22
- y_name (str): Nombre de la variable dependiente.
23
- x1_levels (list): Niveles de la primera variable independiente.
24
- x2_levels (list): Niveles de la segunda variable independiente.
25
- x3_levels (list): Niveles de la tercera variable independiente.
26
- """
27
  self.data = data.copy()
28
  self.model = None
29
  self.model_simplified = None
@@ -41,15 +32,6 @@ class RSM_BoxBehnken:
41
  self.x3_levels = x3_levels
42
 
43
  def get_levels(self, variable_name):
44
- """
45
- Obtiene los niveles para una variable específica.
46
-
47
- Args:
48
- variable_name (str): Nombre de la variable.
49
-
50
- Returns:
51
- list: Niveles de la variable.
52
- """
53
  if variable_name == self.x1_name:
54
  return self.x1_levels
55
  elif variable_name == self.x2_name:
@@ -60,9 +42,6 @@ class RSM_BoxBehnken:
60
  raise ValueError(f"Variable desconocida: {variable_name}")
61
 
62
  def fit_model(self):
63
- """
64
- Ajusta el modelo de segundo orden completo a los datos.
65
- """
66
  formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
67
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + ' \
68
  f'{self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}'
@@ -72,9 +51,6 @@ class RSM_BoxBehnken:
72
  return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo")
73
 
74
  def fit_simplified_model(self):
75
- """
76
- Ajusta el modelo de segundo orden a los datos, eliminando términos no significativos.
77
- """
78
  formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + ' \
79
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)'
80
  self.model_simplified = smf.ols(formula, data=self.data).fit()
@@ -83,12 +59,6 @@ class RSM_BoxBehnken:
83
  return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado")
84
 
85
  def optimize(self, method='Nelder-Mead'):
86
- """
87
- Encuentra los niveles óptimos de los factores para maximizar la respuesta usando el modelo simplificado.
88
-
89
- Args:
90
- method (str): Método de optimización a utilizar (por defecto, 'Nelder-Mead').
91
- """
92
  if self.model_simplified is None:
93
  print("Error: Ajusta el modelo simplificado primero.")
94
  return
@@ -102,85 +72,60 @@ class RSM_BoxBehnken:
102
  self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
103
  self.optimal_levels = self.optimized_results.x
104
 
105
- # Convertir niveles óptimos de codificados a naturales
106
  optimal_levels_natural = [
107
- self.coded_to_natural(self.optimal_levels[0], self.x1_name),
108
- self.coded_to_natural(self.optimal_levels[1], self.x2_name),
109
- self.coded_to_natural(self.optimal_levels[2], self.x3_name)
110
  ]
111
- # Crear la tabla de optimización
112
  optimization_table = pd.DataFrame({
113
  'Variable': [self.x1_name, self.x2_name, self.x3_name],
114
  'Nivel Óptimo (Natural)': optimal_levels_natural,
115
- 'Nivel Óptimo (Codificado)': self.optimal_levels
116
  })
117
 
118
  return optimization_table
119
 
120
  def plot_rsm_individual(self, fixed_variable, fixed_level):
121
- """
122
- Genera un gráfico de superficie de respuesta (RSM) individual para una configuración específica.
123
-
124
- Args:
125
- fixed_variable (str): Nombre de la variable a mantener fija.
126
- fixed_level (float): Nivel al que se fija la variable (en unidades naturales).
127
-
128
- Returns:
129
- go.Figure: Objeto de figura de Plotly.
130
- """
131
  if self.model_simplified is None:
132
  print("Error: Ajusta el modelo simplificado primero.")
133
  return None
134
 
135
- # Determinar las variables que varían y sus niveles naturales
136
  varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
137
 
138
- # Establecer los niveles naturales para las variables que varían
139
  x_natural_levels = self.get_levels(varying_variables[0])
140
  y_natural_levels = self.get_levels(varying_variables[1])
141
 
142
- # Crear una malla de puntos para las variables que varían (en unidades naturales)
143
  x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
144
  y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
145
  x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
146
 
147
- # Convertir la malla de variables naturales a codificadas
148
  x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
149
  y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
150
 
151
- # Crear un DataFrame para la predicción con variables codificadas
152
  prediction_data = pd.DataFrame({
153
  varying_variables[0]: x_grid_coded.flatten(),
154
  varying_variables[1]: y_grid_coded.flatten(),
155
  })
156
  prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable)
157
 
158
- # Calcular los valores predichos
159
  z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape)
160
 
161
- # 1. Identificar los dos factores que varían
162
  varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
163
 
164
- # 2. Filtrar por el nivel de la variable fija (en codificado)
165
  fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
166
  subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
167
 
168
- # 3. Filtrar por niveles válidos en las variables que varían
169
  valid_levels = [-1, 0, 1]
170
  experiments_data = subset_data[
171
  subset_data[varying_variables[0]].isin(valid_levels) &
172
  subset_data[varying_variables[1]].isin(valid_levels)
173
  ]
174
 
175
- # Convertir coordenadas de experimentos a naturales
176
  experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
177
  experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
178
 
179
- # Crear el gráfico de superficie con variables naturales en los ejes y transparencia
180
  fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)])
181
 
182
- # --- Añadir cuadrícula a la superficie ---
183
- # Líneas en la dirección x
184
  for i in range(x_grid_natural.shape[0]):
185
  fig.add_trace(go.Scatter3d(
186
  x=x_grid_natural[i, :],
@@ -191,7 +136,6 @@ class RSM_BoxBehnken:
191
  showlegend=False,
192
  hoverinfo='skip'
193
  ))
194
- # Líneas en la dirección y
195
  for j in range(x_grid_natural.shape[1]):
196
  fig.add_trace(go.Scatter3d(
197
  x=x_grid_natural[:, j],
@@ -203,10 +147,6 @@ class RSM_BoxBehnken:
203
  hoverinfo='skip'
204
  ))
205
 
206
- # --- Fin de la adición de la cuadrícula ---
207
-
208
- # Añadir los puntos de los experimentos en la superficie de respuesta con diferentes colores y etiquetas
209
- # Crear una lista de colores y etiquetas para los puntos
210
  colors = ['red', 'blue', 'green', 'purple', 'orange', 'yellow', 'cyan', 'magenta']
211
  point_labels = []
212
  for i, row in experiments_data.iterrows():
@@ -217,22 +157,17 @@ class RSM_BoxBehnken:
217
  y=experiments_y_natural,
218
  z=experiments_data[self.y_name],
219
  mode='markers+text',
220
- marker=dict(size=4, color=colors[:len(experiments_x_natural)]), # Usar colores de la lista
221
- text=point_labels, # Usar las etiquetas creadas
222
  textposition='top center',
223
  name='Experimentos'
224
  ))
225
 
226
- # Añadir etiquetas y título con variables naturales
227
  fig.update_layout(
228
  scene=dict(
229
  xaxis_title=varying_variables[0] + " (g/L)",
230
  yaxis_title=varying_variables[1] + " (g/L)",
231
  zaxis_title=self.y_name,
232
- # Puedes mantener la configuración de grid en los planos si lo deseas
233
- # xaxis=dict(showgrid=True, gridwidth=1, gridcolor='lightgray'),
234
- # yaxis=dict(showgrid=True, gridwidth=1, gridcolor='lightgray'),
235
- # zaxis=dict(showgrid=True, gridwidth=1, gridcolor='lightgray')
236
  ),
237
  title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.2f} (g/L) (Modelo Simplificado)</sup>",
238
  height=800,
@@ -242,59 +177,44 @@ class RSM_BoxBehnken:
242
  return fig
243
 
244
  def generate_all_plots(self):
245
- """
246
- Genera todas las gráficas de RSM, variando la variable fija y sus niveles usando el modelo simplificado.
247
- """
248
  if self.model_simplified is None:
249
  print("Error: Ajusta el modelo simplificado primero.")
250
  return
251
 
252
- # Niveles naturales para graficar
253
  levels_to_plot_natural = {
254
  self.x1_name: self.x1_levels,
255
  self.x2_name: self.x2_levels,
256
  self.x3_name: self.x3_levels
257
  }
 
 
258
 
259
- # Generar y mostrar gráficos individuales
260
  for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
261
  for level in levels_to_plot_natural[fixed_variable]:
262
  fig = self.plot_rsm_individual(fixed_variable, level)
263
  if fig is not None:
264
- fig.show()
 
265
 
266
  def coded_to_natural(self, coded_value, variable_name):
267
- """Convierte un valor codificado a su valor natural."""
268
  levels = self.get_levels(variable_name)
269
  return levels[0] + (coded_value + 1) * (levels[-1] - levels[0]) / 2
270
 
271
  def natural_to_coded(self, natural_value, variable_name):
272
- """Convierte un valor natural a su valor codificado."""
273
  levels = self.get_levels(variable_name)
274
  return -1 + 2 * (natural_value - levels[0]) / (levels[-1] - levels[0])
275
 
276
  def pareto_chart(self, model, title):
277
- """
278
- Genera un diagrama de Pareto para los efectos estandarizados de un modelo,
279
- incluyendo la línea de significancia.
280
-
281
- Args:
282
- model: Modelo ajustado de statsmodels.
283
- title (str): Título del gráfico.
284
- """
285
- # Calcular los efectos estandarizados
286
- tvalues = model.tvalues[1:] # Excluir la Intercept
287
  abs_tvalues = np.abs(tvalues)
288
  sorted_idx = np.argsort(abs_tvalues)[::-1]
289
  sorted_tvalues = abs_tvalues[sorted_idx]
290
  sorted_names = tvalues.index[sorted_idx]
291
 
292
- # Calcular el valor crítico de t para la línea de significancia
293
- alpha = 0.05 # Nivel de significancia
294
- dof = model.df_resid # Grados de libertad residuales
295
  t_critical = t.ppf(1 - alpha / 2, dof)
296
 
297
- # Crear el diagrama de Pareto
298
  fig = px.bar(
299
  x=sorted_tvalues,
300
  y=sorted_names,
@@ -304,7 +224,6 @@ class RSM_BoxBehnken:
304
  )
305
  fig.update_yaxes(autorange="reversed")
306
 
307
- # Agregar la línea de significancia
308
  fig.add_vline(x=t_critical, line_dash="dot",
309
  annotation_text=f"t crítico = {t_critical:.2f}",
310
  annotation_position="bottom right")
@@ -312,37 +231,31 @@ class RSM_BoxBehnken:
312
  return fig
313
 
314
  def get_simplified_equation(self):
315
- """
316
- Imprime la ecuación del modelo simplificado.
317
- """
318
  if self.model_simplified is None:
319
  print("Error: Ajusta el modelo simplificado primero.")
320
  return None
321
 
322
  coefficients = self.model_simplified.params
323
- equation = f"{self.y_name} = {coefficients['Intercept']:.4f}"
324
 
325
  for term, coef in coefficients.items():
326
  if term != 'Intercept':
327
  if term == f'{self.x1_name}':
328
- equation += f" + {coef:.4f}*{self.x1_name}"
329
  elif term == f'{self.x2_name}':
330
- equation += f" + {coef:.4f}*{self.x2_name}"
331
  elif term == f'{self.x3_name}':
332
- equation += f" + {coef:.4f}*{self.x3_name}"
333
  elif term == f'I({self.x1_name} ** 2)':
334
- equation += f" + {coef:.4f}*{self.x1_name}^2"
335
  elif term == f'I({self.x2_name} ** 2)':
336
- equation += f" + {coef:.4f}*{self.x2_name}^2"
337
  elif term == f'I({self.x3_name} ** 2)':
338
- equation += f" + {coef:.4f}*{self.x3_name}^2"
339
 
340
  return equation
341
 
342
  def generate_prediction_table(self):
343
- """
344
- Genera una tabla con los valores actuales, predichos y residuales.
345
- """
346
  if self.model_simplified is None:
347
  print("Error: Ajusta el modelo simplificado primero.")
348
  return None
@@ -350,30 +263,27 @@ class RSM_BoxBehnken:
350
  self.data['Predicho'] = self.model_simplified.predict(self.data)
351
  self.data['Residual'] = self.data[self.y_name] - self.data['Predicho']
352
 
353
- return self.data[[self.y_name, 'Predicho', 'Residual']]
 
 
 
 
 
354
 
355
  def calculate_contribution_percentage(self):
356
- """
357
- Calcula el porcentaje de contribución de cada factor a la variabilidad de la respuesta (AIA).
358
- """
359
  if self.model_simplified is None:
360
  print("Error: Ajusta el modelo simplificado primero.")
361
  return None
362
 
363
- # ANOVA del modelo simplificado
364
  anova_table = sm.stats.anova_lm(self.model_simplified, typ=2)
365
-
366
- # Suma de cuadrados total
367
  ss_total = anova_table['sum_sq'].sum()
368
 
369
- # Crear tabla de contribución
370
  contribution_table = pd.DataFrame({
371
  'Factor': [],
372
  'Suma de Cuadrados': [],
373
  '% Contribución': []
374
  })
375
 
376
- # Calcular porcentaje de contribución para cada factor
377
  for index, row in anova_table.iterrows():
378
  if index != 'Residual':
379
  factor_name = index
@@ -389,85 +299,65 @@ class RSM_BoxBehnken:
389
 
390
  contribution_table = pd.concat([contribution_table, pd.DataFrame({
391
  'Factor': [factor_name],
392
- 'Suma de Cuadrados': [ss_factor],
393
- '% Contribución': [contribution_percentage]
394
  })], ignore_index=True)
395
 
396
  return contribution_table
397
 
398
  def calculate_detailed_anova(self):
399
- """
400
- Calcula la tabla ANOVA detallada con la descomposición del error residual.
401
- """
402
  if self.model_simplified is None:
403
  print("Error: Ajusta el modelo simplificado primero.")
404
  return None
405
 
406
- # --- ANOVA detallada ---
407
- # 1. Ajustar un modelo solo con los términos de primer orden y cuadráticos
408
  formula_reduced = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
409
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)'
410
  model_reduced = smf.ols(formula_reduced, data=self.data).fit()
411
 
412
- # 2. ANOVA del modelo reducido (para obtener la suma de cuadrados de la regresión)
413
  anova_reduced = sm.stats.anova_lm(model_reduced, typ=2)
414
 
415
- # 3. Suma de cuadrados total
416
  ss_total = np.sum((self.data[self.y_name] - self.data[self.y_name].mean())**2)
417
 
418
- # 4. Grados de libertad totales
419
  df_total = len(self.data) - 1
420
 
421
- # 5. Suma de cuadrados de la regresión
422
- ss_regression = anova_reduced['sum_sq'][:-1].sum() # Sumar todo excepto 'Residual'
423
 
424
- # 6. Grados de libertad de la regresión
425
  df_regression = len(anova_reduced) - 1
426
 
427
- # 7. Suma de cuadrados del error residual
428
  ss_residual = self.model_simplified.ssr
429
  df_residual = self.model_simplified.df_resid
430
 
431
- # 8. Suma de cuadrados del error puro (se calcula a partir de las réplicas)
432
  replicas = self.data[self.data.duplicated(subset=[self.x1_name, self.x2_name, self.x3_name], keep=False)]
433
  ss_pure_error = replicas.groupby([self.x1_name, self.x2_name, self.x3_name])[self.y_name].var().sum()
434
  df_pure_error = len(replicas) - len(replicas.groupby([self.x1_name, self.x2_name, self.x3_name]))
435
 
436
- # 9. Suma de cuadrados de la falta de ajuste
437
  ss_lack_of_fit = ss_residual - ss_pure_error
438
  df_lack_of_fit = df_residual - df_pure_error
439
 
440
- # 10. Cuadrados medios
441
  ms_regression = ss_regression / df_regression
442
  ms_residual = ss_residual / df_residual
443
  ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit
444
  ms_pure_error = ss_pure_error / df_pure_error
445
 
446
- # 11. Estadístico F y valor p para la falta de ajuste
447
  f_lack_of_fit = ms_lack_of_fit / ms_pure_error
448
- p_lack_of_fit = 1 - f.cdf(f_lack_of_fit, df_lack_of_fit, df_pure_error) # Usar f.cdf de scipy.stats
449
 
450
- # 12. Crear la tabla ANOVA detallada
451
  detailed_anova_table = pd.DataFrame({
452
  'Fuente de Variación': ['Regresión', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'],
453
- 'Suma de Cuadrados': [ss_regression, ss_residual, ss_lack_of_fit, ss_pure_error, ss_total],
454
  'Grados de Libertad': [df_regression, df_residual, df_lack_of_fit, df_pure_error, df_total],
455
- 'Cuadrado Medio': [ms_regression, ms_residual, ms_lack_of_fit, ms_pure_error, np.nan],
456
- 'F': [np.nan, np.nan, f_lack_of_fit, np.nan, np.nan],
457
- 'Valor p': [np.nan, np.nan, p_lack_of_fit, np.nan, np.nan]
458
  })
459
 
460
- # Calcular la suma de cuadrados y grados de libertad para la curvatura
461
  ss_curvature = anova_reduced['sum_sq'][f'I({self.x1_name} ** 2)'] + anova_reduced['sum_sq'][f'I({self.x2_name} ** 2)'] + anova_reduced['sum_sq'][f'I({self.x3_name} ** 2)']
462
  df_curvature = 3
463
 
464
- # Añadir la fila de curvatura a la tabla ANOVA
465
- detailed_anova_table.loc[len(detailed_anova_table)] = ['Curvatura', ss_curvature, df_curvature, ss_curvature / df_curvature, np.nan, np.nan]
466
 
467
- # Reorganizar las filas para que la curvatura aparezca después de la regresión
468
  detailed_anova_table = detailed_anova_table.reindex([0, 5, 1, 2, 3, 4])
469
 
470
- # Resetear el índice para que sea consecutivo
471
  detailed_anova_table = detailed_anova_table.reset_index(drop=True)
472
 
473
  return detailed_anova_table
@@ -475,39 +365,19 @@ class RSM_BoxBehnken:
475
  # --- Funciones para la interfaz de Gradio ---
476
 
477
  def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str):
478
- """
479
- Carga los datos del diseño Box-Behnken desde cajas de texto y crea la instancia de RSM_BoxBehnken.
480
-
481
- Args:
482
- x1_name (str): Nombre de la primera variable independiente.
483
- x2_name (str): Nombre de la segunda variable independiente.
484
- x3_name (str): Nombre de la tercera variable independiente.
485
- y_name (str): Nombre de la variable dependiente.
486
- x1_levels_str (str): Niveles de la primera variable, separados por comas.
487
- x2_levels_str (str): Niveles de la segunda variable, separados por comas.
488
- x3_levels_str (str): Niveles de la tercera variable, separados por comas.
489
- data_str (str): Datos del experimento en formato CSV, separados por comas.
490
-
491
- Returns:
492
- tuple: (pd.DataFrame, str, str, str, str, list, list, list, gr.update)
493
- """
494
  try:
495
- # Convertir los niveles a listas de números
496
  x1_levels = [float(x.strip()) for x in x1_levels_str.split(',')]
497
  x2_levels = [float(x.strip()) for x in x2_levels_str.split(',')]
498
  x3_levels = [float(x.strip()) for x in x3_levels_str.split(',')]
499
 
500
- # Crear DataFrame a partir de la cadena de datos
501
  data_list = [row.split(',') for row in data_str.strip().split('\n')]
502
  column_names = ['Exp.', x1_name, x2_name, x3_name, y_name]
503
  data = pd.DataFrame(data_list, columns=column_names)
504
- data = data.apply(pd.to_numeric, errors='coerce') # Convertir a numérico
505
 
506
- # Validar que el DataFrame tenga las columnas correctas
507
  if not all(col in data.columns for col in column_names):
508
  raise ValueError("El formato de los datos no es correcto.")
509
 
510
- # Crear la instancia de RSM_BoxBehnken
511
  global rsm
512
  rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
513
 
@@ -528,18 +398,85 @@ def fit_and_optimize_model():
528
  contribution_table = rsm.calculate_contribution_percentage()
529
  anova_table = rsm.calculate_detailed_anova()
530
 
531
- # Formatear la ecuación para que se vea mejor en Markdown
532
  equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " × ")
533
  equation_formatted = f"### Ecuación del Modelo Simplificado:<br>{equation_formatted}"
534
 
535
 
536
  return model_completo.summary().as_html(), pareto_completo, model_simplificado.summary().as_html(), pareto_simplificado, equation_formatted, optimization_table, prediction_table, contribution_table, anova_table
537
 
538
- def generate_rsm_plot(fixed_variable, fixed_level):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  if 'rsm' not in globals():
540
  return None, "Error: Carga los datos primero."
541
- fig = rsm.plot_rsm_individual(fixed_variable, fixed_level)
542
- return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
  # --- Crear la interfaz de Gradio ---
545
 
@@ -582,6 +519,7 @@ with gr.Blocks() as demo:
582
  with gr.Row(visible=False) as analysis_row:
583
  with gr.Column():
584
  fit_button = gr.Button("Ajustar Modelo y Optimizar")
 
585
  gr.Markdown("**Modelo Completo**")
586
  model_completo_output = gr.HTML()
587
  pareto_completo_output = gr.Plot()
@@ -589,7 +527,7 @@ with gr.Blocks() as demo:
589
  model_simplificado_output = gr.HTML()
590
  pareto_simplificado_output = gr.Plot()
591
  equation_output = gr.HTML()
592
- optimization_table_output = gr.Dataframe(label="Tabla de Optimización")
593
  prediction_table_output = gr.Dataframe(label="Tabla de Predicciones")
594
  contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución")
595
  anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada")
@@ -597,8 +535,16 @@ with gr.Blocks() as demo:
597
  gr.Markdown("## Generar Gráficos de Superficie de Respuesta")
598
  fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa", "Extracto_de_Levadura", "Triptofano"], value="Glucosa")
599
  fixed_level_input = gr.Slider(label="Nivel de Variable Fija", minimum=0, maximum=1, step=0.01, value=0.5)
600
- plot_button = gr.Button("Generar Gráfico")
 
 
 
 
 
601
  rsm_plot_output = gr.Plot()
 
 
 
602
 
603
  load_button.click(
604
  load_data,
@@ -607,7 +553,18 @@ with gr.Blocks() as demo:
607
  )
608
 
609
  fit_button.click(fit_and_optimize_model, outputs=[model_completo_output, pareto_completo_output, model_simplificado_output, pareto_simplificado_output, equation_output, optimization_table_output, prediction_table_output, contribution_table_output, anova_table_output])
610
- plot_button.click(generate_rsm_plot, inputs=[fixed_variable_input, fixed_level_input], outputs=[rsm_plot_output])
 
 
 
 
 
 
 
 
 
 
 
611
 
612
  # Ejemplo de uso
613
  gr.Markdown("## Ejemplo de uso")
@@ -617,5 +574,9 @@ with gr.Blocks() as demo:
617
  gr.Markdown("4. Haz clic en 'Ajustar Modelo y Optimizar' para ajustar el modelo y encontrar los niveles óptimos de los factores.")
618
  gr.Markdown("5. Selecciona una variable fija y su nivel en los controles deslizantes.")
619
  gr.Markdown("6. Haz clic en 'Generar Gráfico' para generar un gráfico de superficie de respuesta.")
 
 
 
 
620
 
621
  demo.launch()
 
8
  import plotly.express as px
9
  from scipy.stats import t, f
10
  import gradio as gr
11
+ import io
12
+ import os
13
+ from zipfile import ZipFile
14
 
15
  class RSM_BoxBehnken:
16
  def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels):
17
+ # ... (El código de la clase RSM_BoxBehnken se mantiene igual, solo se modifican las funciones que generan dataframes o strings)
 
 
 
 
 
 
 
 
 
 
 
 
18
  self.data = data.copy()
19
  self.model = None
20
  self.model_simplified = None
 
32
  self.x3_levels = x3_levels
33
 
34
  def get_levels(self, variable_name):
 
 
 
 
 
 
 
 
 
35
  if variable_name == self.x1_name:
36
  return self.x1_levels
37
  elif variable_name == self.x2_name:
 
42
  raise ValueError(f"Variable desconocida: {variable_name}")
43
 
44
  def fit_model(self):
 
 
 
45
  formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
46
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + ' \
47
  f'{self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}'
 
51
  return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo")
52
 
53
  def fit_simplified_model(self):
 
 
 
54
  formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + ' \
55
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)'
56
  self.model_simplified = smf.ols(formula, data=self.data).fit()
 
59
  return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado")
60
 
61
  def optimize(self, method='Nelder-Mead'):
 
 
 
 
 
 
62
  if self.model_simplified is None:
63
  print("Error: Ajusta el modelo simplificado primero.")
64
  return
 
72
  self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
73
  self.optimal_levels = self.optimized_results.x
74
 
 
75
  optimal_levels_natural = [
76
+ round(self.coded_to_natural(self.optimal_levels[0], self.x1_name), 3),
77
+ round(self.coded_to_natural(self.optimal_levels[1], self.x2_name), 3),
78
+ round(self.coded_to_natural(self.optimal_levels[2], self.x3_name), 3)
79
  ]
 
80
  optimization_table = pd.DataFrame({
81
  'Variable': [self.x1_name, self.x2_name, self.x3_name],
82
  'Nivel Óptimo (Natural)': optimal_levels_natural,
83
+ 'Nivel Óptimo (Codificado)': [round(x, 3) for x in self.optimal_levels]
84
  })
85
 
86
  return optimization_table
87
 
88
  def plot_rsm_individual(self, fixed_variable, fixed_level):
 
 
 
 
 
 
 
 
 
 
89
  if self.model_simplified is None:
90
  print("Error: Ajusta el modelo simplificado primero.")
91
  return None
92
 
 
93
  varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
94
 
 
95
  x_natural_levels = self.get_levels(varying_variables[0])
96
  y_natural_levels = self.get_levels(varying_variables[1])
97
 
 
98
  x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
99
  y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
100
  x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
101
 
 
102
  x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
103
  y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
104
 
 
105
  prediction_data = pd.DataFrame({
106
  varying_variables[0]: x_grid_coded.flatten(),
107
  varying_variables[1]: y_grid_coded.flatten(),
108
  })
109
  prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable)
110
 
 
111
  z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape)
112
 
 
113
  varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
114
 
 
115
  fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
116
  subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
117
 
 
118
  valid_levels = [-1, 0, 1]
119
  experiments_data = subset_data[
120
  subset_data[varying_variables[0]].isin(valid_levels) &
121
  subset_data[varying_variables[1]].isin(valid_levels)
122
  ]
123
 
 
124
  experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
125
  experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
126
 
 
127
  fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)])
128
 
 
 
129
  for i in range(x_grid_natural.shape[0]):
130
  fig.add_trace(go.Scatter3d(
131
  x=x_grid_natural[i, :],
 
136
  showlegend=False,
137
  hoverinfo='skip'
138
  ))
 
139
  for j in range(x_grid_natural.shape[1]):
140
  fig.add_trace(go.Scatter3d(
141
  x=x_grid_natural[:, j],
 
147
  hoverinfo='skip'
148
  ))
149
 
 
 
 
 
150
  colors = ['red', 'blue', 'green', 'purple', 'orange', 'yellow', 'cyan', 'magenta']
151
  point_labels = []
152
  for i, row in experiments_data.iterrows():
 
157
  y=experiments_y_natural,
158
  z=experiments_data[self.y_name],
159
  mode='markers+text',
160
+ marker=dict(size=4, color=colors[:len(experiments_x_natural)]),
161
+ text=point_labels,
162
  textposition='top center',
163
  name='Experimentos'
164
  ))
165
 
 
166
  fig.update_layout(
167
  scene=dict(
168
  xaxis_title=varying_variables[0] + " (g/L)",
169
  yaxis_title=varying_variables[1] + " (g/L)",
170
  zaxis_title=self.y_name,
 
 
 
 
171
  ),
172
  title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.2f} (g/L) (Modelo Simplificado)</sup>",
173
  height=800,
 
177
  return fig
178
 
179
  def generate_all_plots(self):
 
 
 
180
  if self.model_simplified is None:
181
  print("Error: Ajusta el modelo simplificado primero.")
182
  return
183
 
 
184
  levels_to_plot_natural = {
185
  self.x1_name: self.x1_levels,
186
  self.x2_name: self.x2_levels,
187
  self.x3_name: self.x3_levels
188
  }
189
+
190
+ figs = []
191
 
 
192
  for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
193
  for level in levels_to_plot_natural[fixed_variable]:
194
  fig = self.plot_rsm_individual(fixed_variable, level)
195
  if fig is not None:
196
+ figs.append(fig)
197
+ return figs
198
 
199
  def coded_to_natural(self, coded_value, variable_name):
 
200
  levels = self.get_levels(variable_name)
201
  return levels[0] + (coded_value + 1) * (levels[-1] - levels[0]) / 2
202
 
203
  def natural_to_coded(self, natural_value, variable_name):
 
204
  levels = self.get_levels(variable_name)
205
  return -1 + 2 * (natural_value - levels[0]) / (levels[-1] - levels[0])
206
 
207
  def pareto_chart(self, model, title):
208
+ tvalues = model.tvalues[1:]
 
 
 
 
 
 
 
 
 
209
  abs_tvalues = np.abs(tvalues)
210
  sorted_idx = np.argsort(abs_tvalues)[::-1]
211
  sorted_tvalues = abs_tvalues[sorted_idx]
212
  sorted_names = tvalues.index[sorted_idx]
213
 
214
+ alpha = 0.05
215
+ dof = model.df_resid
 
216
  t_critical = t.ppf(1 - alpha / 2, dof)
217
 
 
218
  fig = px.bar(
219
  x=sorted_tvalues,
220
  y=sorted_names,
 
224
  )
225
  fig.update_yaxes(autorange="reversed")
226
 
 
227
  fig.add_vline(x=t_critical, line_dash="dot",
228
  annotation_text=f"t crítico = {t_critical:.2f}",
229
  annotation_position="bottom right")
 
231
  return fig
232
 
233
  def get_simplified_equation(self):
 
 
 
234
  if self.model_simplified is None:
235
  print("Error: Ajusta el modelo simplificado primero.")
236
  return None
237
 
238
  coefficients = self.model_simplified.params
239
+ equation = f"{self.y_name} = {coefficients['Intercept']:.3f}"
240
 
241
  for term, coef in coefficients.items():
242
  if term != 'Intercept':
243
  if term == f'{self.x1_name}':
244
+ equation += f" + {coef:.3f}*{self.x1_name}"
245
  elif term == f'{self.x2_name}':
246
+ equation += f" + {coef:.3f}*{self.x2_name}"
247
  elif term == f'{self.x3_name}':
248
+ equation += f" + {coef:.3f}*{self.x3_name}"
249
  elif term == f'I({self.x1_name} ** 2)':
250
+ equation += f" + {coef:.3f}*{self.x1_name}^2"
251
  elif term == f'I({self.x2_name} ** 2)':
252
+ equation += f" + {coef:.3f}*{self.x2_name}^2"
253
  elif term == f'I({self.x3_name} ** 2)':
254
+ equation += f" + {coef:.3f}*{self.x3_name}^2"
255
 
256
  return equation
257
 
258
  def generate_prediction_table(self):
 
 
 
259
  if self.model_simplified is None:
260
  print("Error: Ajusta el modelo simplificado primero.")
261
  return None
 
263
  self.data['Predicho'] = self.model_simplified.predict(self.data)
264
  self.data['Residual'] = self.data[self.y_name] - self.data['Predicho']
265
 
266
+ prediction_table = self.data[[self.y_name, 'Predicho', 'Residual']].copy()
267
+ prediction_table[self.y_name] = prediction_table[self.y_name].round(3)
268
+ prediction_table['Predicho'] = prediction_table['Predicho'].round(3)
269
+ prediction_table['Residual'] = prediction_table['Residual'].round(3)
270
+
271
+ return prediction_table
272
 
273
  def calculate_contribution_percentage(self):
 
 
 
274
  if self.model_simplified is None:
275
  print("Error: Ajusta el modelo simplificado primero.")
276
  return None
277
 
 
278
  anova_table = sm.stats.anova_lm(self.model_simplified, typ=2)
 
 
279
  ss_total = anova_table['sum_sq'].sum()
280
 
 
281
  contribution_table = pd.DataFrame({
282
  'Factor': [],
283
  'Suma de Cuadrados': [],
284
  '% Contribución': []
285
  })
286
 
 
287
  for index, row in anova_table.iterrows():
288
  if index != 'Residual':
289
  factor_name = index
 
299
 
300
  contribution_table = pd.concat([contribution_table, pd.DataFrame({
301
  'Factor': [factor_name],
302
+ 'Suma de Cuadrados': [round(ss_factor, 3)],
303
+ '% Contribución': [round(contribution_percentage, 3)]
304
  })], ignore_index=True)
305
 
306
  return contribution_table
307
 
308
  def calculate_detailed_anova(self):
 
 
 
309
  if self.model_simplified is None:
310
  print("Error: Ajusta el modelo simplificado primero.")
311
  return None
312
 
 
 
313
  formula_reduced = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
314
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)'
315
  model_reduced = smf.ols(formula_reduced, data=self.data).fit()
316
 
 
317
  anova_reduced = sm.stats.anova_lm(model_reduced, typ=2)
318
 
 
319
  ss_total = np.sum((self.data[self.y_name] - self.data[self.y_name].mean())**2)
320
 
 
321
  df_total = len(self.data) - 1
322
 
323
+ ss_regression = anova_reduced['sum_sq'][:-1].sum()
 
324
 
 
325
  df_regression = len(anova_reduced) - 1
326
 
 
327
  ss_residual = self.model_simplified.ssr
328
  df_residual = self.model_simplified.df_resid
329
 
 
330
  replicas = self.data[self.data.duplicated(subset=[self.x1_name, self.x2_name, self.x3_name], keep=False)]
331
  ss_pure_error = replicas.groupby([self.x1_name, self.x2_name, self.x3_name])[self.y_name].var().sum()
332
  df_pure_error = len(replicas) - len(replicas.groupby([self.x1_name, self.x2_name, self.x3_name]))
333
 
 
334
  ss_lack_of_fit = ss_residual - ss_pure_error
335
  df_lack_of_fit = df_residual - df_pure_error
336
 
 
337
  ms_regression = ss_regression / df_regression
338
  ms_residual = ss_residual / df_residual
339
  ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit
340
  ms_pure_error = ss_pure_error / df_pure_error
341
 
 
342
  f_lack_of_fit = ms_lack_of_fit / ms_pure_error
343
+ p_lack_of_fit = 1 - f.cdf(f_lack_of_fit, df_lack_of_fit, df_pure_error)
344
 
 
345
  detailed_anova_table = pd.DataFrame({
346
  'Fuente de Variación': ['Regresión', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'],
347
+ 'Suma de Cuadrados': [round(ss_regression, 3), round(ss_residual, 3), round(ss_lack_of_fit, 3), round(ss_pure_error, 3), round(ss_total, 3)],
348
  'Grados de Libertad': [df_regression, df_residual, df_lack_of_fit, df_pure_error, df_total],
349
+ 'Cuadrado Medio': [round(ms_regression, 3), round(ms_residual, 3), round(ms_lack_of_fit, 3), round(ms_pure_error, 3), np.nan],
350
+ 'F': [np.nan, np.nan, round(f_lack_of_fit, 3), np.nan, np.nan],
351
+ 'Valor p': [np.nan, np.nan, round(p_lack_of_fit, 3), np.nan, np.nan]
352
  })
353
 
 
354
  ss_curvature = anova_reduced['sum_sq'][f'I({self.x1_name} ** 2)'] + anova_reduced['sum_sq'][f'I({self.x2_name} ** 2)'] + anova_reduced['sum_sq'][f'I({self.x3_name} ** 2)']
355
  df_curvature = 3
356
 
357
+ detailed_anova_table.loc[len(detailed_anova_table)] = ['Curvatura', round(ss_curvature, 3), df_curvature, round(ss_curvature / df_curvature, 3), np.nan, np.nan]
 
358
 
 
359
  detailed_anova_table = detailed_anova_table.reindex([0, 5, 1, 2, 3, 4])
360
 
 
361
  detailed_anova_table = detailed_anova_table.reset_index(drop=True)
362
 
363
  return detailed_anova_table
 
365
  # --- Funciones para la interfaz de Gradio ---
366
 
367
  def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  try:
 
369
  x1_levels = [float(x.strip()) for x in x1_levels_str.split(',')]
370
  x2_levels = [float(x.strip()) for x in x2_levels_str.split(',')]
371
  x3_levels = [float(x.strip()) for x in x3_levels_str.split(',')]
372
 
 
373
  data_list = [row.split(',') for row in data_str.strip().split('\n')]
374
  column_names = ['Exp.', x1_name, x2_name, x3_name, y_name]
375
  data = pd.DataFrame(data_list, columns=column_names)
376
+ data = data.apply(pd.to_numeric, errors='coerce')
377
 
 
378
  if not all(col in data.columns for col in column_names):
379
  raise ValueError("El formato de los datos no es correcto.")
380
 
 
381
  global rsm
382
  rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
383
 
 
398
  contribution_table = rsm.calculate_contribution_percentage()
399
  anova_table = rsm.calculate_detailed_anova()
400
 
 
401
  equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " × ")
402
  equation_formatted = f"### Ecuación del Modelo Simplificado:<br>{equation_formatted}"
403
 
404
 
405
  return model_completo.summary().as_html(), pareto_completo, model_simplificado.summary().as_html(), pareto_simplificado, equation_formatted, optimization_table, prediction_table, contribution_table, anova_table
406
 
407
+ current_plot_index = 0
408
+ plot_images = []
409
+
410
+ def generate_rsm_plot(fixed_variable, fixed_level, request: gr.Request):
411
+ global current_plot_index, plot_images
412
+
413
+ if 'rsm' not in globals():
414
+ return None, "Error: Carga los datos primero.", None, None
415
+
416
+ if not plot_images:
417
+ plot_images = rsm.generate_all_plots()
418
+
419
+ if not plot_images:
420
+ return None, "Error: No se pudieron generar los gráficos.", None, None
421
+
422
+ current_plot_index = (current_plot_index) % len(plot_images)
423
+ fig = plot_images[current_plot_index]
424
+
425
+ img_bytes = fig.to_image(format="png")
426
+
427
+ # Crear un archivo temporal para guardar la imagen
428
+ temp_file = os.path.join(request.kwargs['temp_dir'], f"plot_{current_plot_index}.png")
429
+ with open(temp_file, "wb") as f:
430
+ f.write(img_bytes)
431
+
432
+ return fig, "", temp_file, gr.update(visible=True)
433
+
434
+ def download_excel():
435
  if 'rsm' not in globals():
436
  return None, "Error: Carga los datos primero."
437
+
438
+ output = io.BytesIO()
439
+ with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
440
+ rsm.data.to_excel(writer, sheet_name='Datos', index=False)
441
+ rsm.generate_prediction_table().to_excel(writer, sheet_name='Predicciones', index=False)
442
+ rsm.optimize().to_excel(writer, sheet_name='Optimizacion', index=False)
443
+ rsm.calculate_contribution_percentage().to_excel(writer, sheet_name='Contribucion', index=False)
444
+ rsm.calculate_detailed_anova().to_excel(writer, sheet_name='ANOVA', index=False)
445
+
446
+ output.seek(0)
447
+
448
+ return gr.File(value=output, visible=True, filename="resultados_rsm.xlsx")
449
+
450
+ def download_images(request: gr.Request):
451
+ global plot_images
452
+ if 'rsm' not in globals():
453
+ return None, "Error: Carga los datos primero."
454
+
455
+ if not plot_images:
456
+ return None, "Error: No se han generado gráficos."
457
+
458
+ zip_filename = "graficos_rsm.zip"
459
+ zip_path = os.path.join(request.kwargs['temp_dir'], zip_filename)
460
+
461
+ with ZipFile(zip_path, 'w') as zipf:
462
+ for i, fig in enumerate(plot_images):
463
+ img_bytes = fig.to_image(format="png")
464
+ img_path = os.path.join(request.kwargs['temp_dir'], f"plot_{i}.png")
465
+ with open(img_path, "wb") as f:
466
+ f.write(img_bytes)
467
+ zipf.write(img_path, f"plot_{i}.png")
468
+
469
+ return gr.File(value=zip_path, visible=True, filename=zip_filename)
470
+
471
+ def next_plot():
472
+ global current_plot_index
473
+ current_plot_index += 1
474
+ return current_plot_index
475
+
476
+ def prev_plot():
477
+ global current_plot_index
478
+ current_plot_index -= 1
479
+ return current_plot_index
480
 
481
  # --- Crear la interfaz de Gradio ---
482
 
 
519
  with gr.Row(visible=False) as analysis_row:
520
  with gr.Column():
521
  fit_button = gr.Button("Ajustar Modelo y Optimizar")
522
+ download_excel_button = gr.Button("Descargar Tablas en Excel")
523
  gr.Markdown("**Modelo Completo**")
524
  model_completo_output = gr.HTML()
525
  pareto_completo_output = gr.Plot()
 
527
  model_simplificado_output = gr.HTML()
528
  pareto_simplificado_output = gr.Plot()
529
  equation_output = gr.HTML()
530
+ optimization_table_output = gr.Dataframe(label="Tabla de Optimización", headers=["Variable", "Nivel Óptimo (Natural)", "Nivel Óptimo (Codificado)"])
531
  prediction_table_output = gr.Dataframe(label="Tabla de Predicciones")
532
  contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución")
533
  anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada")
 
535
  gr.Markdown("## Generar Gráficos de Superficie de Respuesta")
536
  fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa", "Extracto_de_Levadura", "Triptofano"], value="Glucosa")
537
  fixed_level_input = gr.Slider(label="Nivel de Variable Fija", minimum=0, maximum=1, step=0.01, value=0.5)
538
+ with gr.Row():
539
+ plot_button = gr.Button("Generar Gráfico")
540
+ download_images_button = gr.Button("Descargar Gráficos en ZIP")
541
+
542
+ prev_plot_button = gr.Button("<")
543
+ next_plot_button = gr.Button(">")
544
  rsm_plot_output = gr.Plot()
545
+ download_plot_button = gr.Button("Descargar Gráfico Actual")
546
+ plot_image_output = gr.File(label="Gráfico Actual", visible=False)
547
+
548
 
549
  load_button.click(
550
  load_data,
 
553
  )
554
 
555
  fit_button.click(fit_and_optimize_model, outputs=[model_completo_output, pareto_completo_output, model_simplificado_output, pareto_simplificado_output, equation_output, optimization_table_output, prediction_table_output, contribution_table_output, anova_table_output])
556
+
557
+ plot_button.click(generate_rsm_plot, inputs=[fixed_variable_input, fixed_level_input], outputs=[rsm_plot_output, equation_output, plot_image_output, download_plot_button])
558
+
559
+ download_excel_button.click(download_excel, outputs=download_excel_button, api_name="download_excel")
560
+
561
+ download_images_button.click(download_images, outputs=download_images_button, api_name="download_images")
562
+
563
+ download_plot_button.click(lambda x: x, inputs=[plot_image_output], outputs=[plot_image_output], api_name="download_plot")
564
+
565
+ prev_plot_button.click(prev_plot, outputs=prev_plot_button).then(generate_rsm_plot, inputs=[fixed_variable_input, fixed_level_input], outputs=[rsm_plot_output, equation_output, plot_image_output, download_plot_button])
566
+
567
+ next_plot_button.click(next_plot, outputs=next_plot_button).then(generate_rsm_plot, inputs=[fixed_variable_input, fixed_level_input], outputs=[rsm_plot_output, equation_output, plot_image_output, download_plot_button])
568
 
569
  # Ejemplo de uso
570
  gr.Markdown("## Ejemplo de uso")
 
574
  gr.Markdown("4. Haz clic en 'Ajustar Modelo y Optimizar' para ajustar el modelo y encontrar los niveles óptimos de los factores.")
575
  gr.Markdown("5. Selecciona una variable fija y su nivel en los controles deslizantes.")
576
  gr.Markdown("6. Haz clic en 'Generar Gráfico' para generar un gráfico de superficie de respuesta.")
577
+ gr.Markdown("7. Usa '<' y '>' para navegar entre los gráficos generados.")
578
+ gr.Markdown("8. Haz clic en 'Descargar Tablas en Excel' para obtener un archivo Excel con todas las tablas generadas.")
579
+ gr.Markdown("9. Haz clic en 'Descargar Gráfico Actual' para descargar la imagen del gráfico actual en formato PNG.")
580
+ gr.Markdown("10. Haz clic en 'Descargar Gráficos en ZIP' para descargar todas las imágenes de los gráficos en un archivo ZIP.")
581
 
582
  demo.launch()