C2MV commited on
Commit
65cf050
verified
1 Parent(s): 1c36a23

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +379 -258
app.py CHANGED
@@ -1,4 +1,4 @@
1
- import numpy as np
2
  import pandas as pd
3
  import statsmodels.formula.api as smf
4
  import statsmodels.api as sm
@@ -10,16 +10,29 @@ from scipy.stats import t, f
10
  import gradio as gr
11
  import io
12
  import zipfile
13
- from base64 import b64encode
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
  self.data = data.copy()
18
  self.model = None
19
  self.model_simplified = None
20
  self.optimized_results = None
21
  self.optimal_levels = None
22
-
23
  self.x1_name = x1_name
24
  self.x2_name = x2_name
25
  self.x3_name = x3_name
@@ -29,14 +42,6 @@ class RSM_BoxBehnken:
29
  self.x1_levels = x1_levels
30
  self.x2_levels = x2_levels
31
  self.x3_levels = x3_levels
32
-
33
- # Formatear datos num茅ricos a 3 decimales
34
- self.format_data()
35
-
36
- def format_data(self):
37
- """Formatea los datos num茅ricos a 3 decimales."""
38
- numeric_cols = self.data.select_dtypes(include=np.number).columns
39
- self.data[numeric_cols] = self.data[numeric_cols].round(3)
40
 
41
  def get_levels(self, variable_name):
42
  """
@@ -99,21 +104,21 @@ class RSM_BoxBehnken:
99
 
100
  self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
101
  self.optimal_levels = self.optimized_results.x
102
-
103
  # Convertir niveles 贸ptimos de codificados a naturales
104
  optimal_levels_natural = [
105
- round(self.coded_to_natural(self.optimal_levels[0], self.x1_name),3),
106
- round(self.coded_to_natural(self.optimal_levels[1], self.x2_name),3),
107
- round(self.coded_to_natural(self.optimal_levels[2], self.x3_name),3)
108
  ]
109
  # Crear la tabla de optimizaci贸n
110
  optimization_table = pd.DataFrame({
111
  'Variable': [self.x1_name, self.x2_name, self.x3_name],
112
  'Nivel 脫ptimo (Natural)': optimal_levels_natural,
113
- 'Nivel 脫ptimo (Codificado)': [round(x, 3) for x in self.optimal_levels]
114
  })
115
 
116
- return optimization_table
117
 
118
  def plot_rsm_individual(self, fixed_variable, fixed_level):
119
  """
@@ -205,15 +210,15 @@ class RSM_BoxBehnken:
205
 
206
  # A帽adir los puntos de los experimentos en la superficie de respuesta con diferentes colores y etiquetas
207
  # Crear una lista de colores y etiquetas para los puntos
208
- colors = ['red', 'blue', 'green', 'purple', 'orange', 'yellow', 'cyan', 'magenta']
209
  point_labels = []
210
  for i, row in experiments_data.iterrows():
211
- point_labels.append(f"{row[self.y_name]:.3f}")
212
 
213
  fig.add_trace(go.Scatter3d(
214
  x=experiments_x_natural,
215
  y=experiments_y_natural,
216
- z=experiments_data[self.y_name],
217
  mode='markers+text',
218
  marker=dict(size=4, color=colors[:len(experiments_x_natural)]), # Usar colores de la lista
219
  text=point_labels, # Usar las etiquetas creadas
@@ -224,13 +229,9 @@ class RSM_BoxBehnken:
224
  # A帽adir etiquetas y t铆tulo con variables naturales
225
  fig.update_layout(
226
  scene=dict(
227
- xaxis_title=varying_variables[0] + " (g/L)",
228
- yaxis_title=varying_variables[1] + " (g/L)",
229
  zaxis_title=self.y_name,
230
- # Puedes mantener la configuraci贸n de grid en los planos si lo deseas
231
- # xaxis=dict(showgrid=True, gridwidth=1, gridcolor='lightgray'),
232
- # yaxis=dict(showgrid=True, gridwidth=1, gridcolor='lightgray'),
233
- # zaxis=dict(showgrid=True, gridwidth=1, gridcolor='lightgray')
234
  ),
235
  title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.3f} (g/L) (Modelo Simplificado)</sup>",
236
  height=800,
@@ -242,10 +243,13 @@ class RSM_BoxBehnken:
242
  def generate_all_plots(self):
243
  """
244
  Genera todas las gr谩ficas de RSM, variando la variable fija y sus niveles usando el modelo simplificado.
 
245
  """
246
  if self.model_simplified is None:
247
  print("Error: Ajusta el modelo simplificado primero.")
248
- return []
 
 
249
 
250
  # Niveles naturales para graficar
251
  levels_to_plot_natural = {
@@ -254,14 +258,12 @@ class RSM_BoxBehnken:
254
  self.x3_name: self.x3_levels
255
  }
256
 
257
- # Generar gr谩ficos individuales
258
- figures = []
259
  for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
260
  for level in levels_to_plot_natural[fixed_variable]:
261
  fig = self.plot_rsm_individual(fixed_variable, level)
262
  if fig is not None:
263
- figures.append(fig)
264
- return figures
265
 
266
  def coded_to_natural(self, coded_value, variable_name):
267
  """Convierte un valor codificado a su valor natural."""
@@ -296,7 +298,7 @@ class RSM_BoxBehnken:
296
 
297
  # Crear el diagrama de Pareto
298
  fig = px.bar(
299
- x=sorted_tvalues,
300
  y=sorted_names,
301
  orientation='h',
302
  labels={'x': 'Efecto Estandarizado', 'y': 'T茅rmino'},
@@ -324,76 +326,76 @@ class RSM_BoxBehnken:
324
 
325
  for term, coef in coefficients.items():
326
  if term != 'Intercept':
327
- if term == f'{self.x1_name}':
328
- equation += f" + {coef:.3f}*{self.x1_name}"
329
- elif term == f'{self.x2_name}':
330
- equation += f" + {coef:.3f}*{self.x2_name}"
331
- elif term == f'{self.x3_name}':
332
- equation += f" + {coef:.3f}*{self.x3_name}"
333
- elif term == f'I({self.x1_name} ** 2)':
334
- equation += f" + {coef:.3f}*{self.x1_name}^2"
335
- elif term == f'I({self.x2_name} ** 2)':
336
- equation += f" + {coef:.3f}*{self.x2_name}^2"
337
- elif term == f'I({self.x3_name} ** 2)':
338
- equation += f" + {coef:.3f}*{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
349
 
350
- self.data['Predicho'] = self.model_simplified.predict(self.data).round(3)
351
- self.data['Residual'] = (self.data[self.y_name] - self.data['Predicho']).round(3)
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
380
- if factor_name == f'I({self.x1_name} ** 2)':
381
- factor_name = f'{self.x1_name}^2'
382
- elif factor_name == f'I({self.x2_name} ** 2)':
383
- factor_name = f'{self.x2_name}^2'
384
- elif factor_name == f'I({self.x3_name} ** 2)':
385
- factor_name = f'{self.x3_name}^2'
386
-
387
- ss_factor = row['sum_sq']
388
- contribution_percentage = round((ss_factor / ss_total) * 100, 3)
389
-
390
- contribution_table = pd.concat([contribution_table, pd.DataFrame({
391
- 'Factor': [factor_name],
392
- 'Suma de Cuadrados': [round(ss_factor,3)],
393
- '% Contribuci贸n': [contribution_percentage]
394
- })], ignore_index=True)
395
-
396
- return contribution_table
397
 
398
  def calculate_detailed_anova(self):
399
  """
@@ -419,7 +421,7 @@ class RSM_BoxBehnken:
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
@@ -430,31 +432,35 @@ class RSM_BoxBehnken:
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': [round(ss_regression,3), round(ss_residual,3), round(ss_lack_of_fit,3), round(ss_pure_error,3), round(ss_total,3)],
454
  'Grados de Libertad': [df_regression, df_residual, df_lack_of_fit, df_pure_error, df_total],
455
- 'Cuadrado Medio': [round(ms_regression,3), round(ms_residual,3), round(ms_lack_of_fit,3), round(ms_pure_error,3), np.nan],
456
- 'F': [np.nan, np.nan, round(f_lack_of_fit,3), np.nan, np.nan],
457
- 'Valor p': [np.nan, np.nan, round(p_lack_of_fit,3), np.nan, np.nan]
458
  })
459
 
460
  # Calcular la suma de cuadrados y grados de libertad para la curvatura
@@ -462,7 +468,7 @@ class RSM_BoxBehnken:
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', round(ss_curvature,3), df_curvature, round(ss_curvature / df_curvature,3), 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])
@@ -470,7 +476,51 @@ class RSM_BoxBehnken:
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
  # --- Funciones para la interfaz de Gradio ---
476
 
@@ -489,7 +539,7 @@ def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x
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, list)
493
  """
494
  try:
495
  # Convertir los niveles a listas de n煤meros
@@ -511,15 +561,16 @@ def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x
511
  global rsm
512
  rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
513
 
514
- return data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels, gr.update(visible=True)
515
-
516
  except Exception as e:
517
  return None, "", "", "", "", [], [], [], gr.update(visible=False), f"Error: {e}"
518
 
519
- def fit_and_optimize_model(rsm_plots_state=None):
520
  if 'rsm' not in globals():
521
- return (None, None, None, None, None, None, None, None, None, None, [], "Error: Carga los datos primero.")
522
 
 
523
  model_completo, pareto_completo = rsm.fit_model()
524
  model_simplificado, pareto_simplificado = rsm.fit_simplified_model()
525
  optimization_table = rsm.optimize()
@@ -527,18 +578,17 @@ def fit_and_optimize_model(rsm_plots_state=None):
527
  prediction_table = rsm.generate_prediction_table()
528
  contribution_table = rsm.calculate_contribution_percentage()
529
  anova_table = rsm.calculate_detailed_anova()
530
-
531
- # Generar todos los gr谩ficos de superficie de respuesta
532
- rsm_plots = rsm.generate_all_plots()
533
-
534
  # Formatear la ecuaci贸n para que se vea mejor en Markdown
535
  equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " 脳 ")
536
  equation_formatted = f"### Ecuaci贸n del Modelo Simplificado:<br>{equation_formatted}"
537
 
538
  return (
539
- model_completo.summary().tables[0].as_html(),
540
  pareto_completo,
541
- model_completo.summary().tables[1].as_html(),
542
  model_simplificado.summary().as_html(),
543
  pareto_simplificado,
544
  equation_formatted,
@@ -546,73 +596,106 @@ def fit_and_optimize_model(rsm_plots_state=None):
546
  prediction_table,
547
  contribution_table,
548
  anova_table,
549
- rsm_plots, # Esto se asignar谩 al estado
550
- gr.update(visible=True, maximum=len(rsm_plots) -1) if rsm_plots else gr.update()
551
  )
552
 
553
- def generate_rsm_plot(plot_index, rsm_plots_state):
554
- if not rsm_plots_state:
555
- return None, gr.update(visible=False), "Error: Genera los gr谩ficos primero."
556
 
557
- plot_index = int(plot_index)
558
- if 0 <= plot_index < len(rsm_plots_state):
559
- selected_plot = rsm_plots_state[plot_index]
560
- return selected_plot, gr.update(visible=True, value=plot_index), ""
561
- else:
562
- return None, gr.update(visible=False), "Error: 脥ndice de gr谩fico fuera de rango."
 
 
 
 
 
 
 
 
563
 
564
- def download_excel():
565
- if 'rsm' not in globals():
566
- return "Error: Carga los datos y ajusta el modelo primero."
567
-
568
- output = io.BytesIO()
569
- with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
570
- rsm.data.to_excel(writer, sheet_name='Datos', index=False)
571
- rsm.generate_prediction_table().to_excel(writer, sheet_name='Predicciones', index=False)
572
- rsm.calculate_contribution_percentage().to_excel(writer, sheet_name='Contribucion', index=False)
573
- rsm.calculate_detailed_anova().to_excel(writer, sheet_name='ANOVA', index=False)
574
- rsm.optimize().to_excel(writer, sheet_name='Optimizacion', index=False)
575
- # Aqu铆 puedes agregar m谩s tablas a diferentes hojas si es necesario
576
-
577
- excel_data = output.getvalue()
578
- b64 = b64encode(excel_data).decode('utf-8')
579
- href = f'<a download="resultados_rsm.xlsx" href="data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,{b64}">Descargar Excel</a>'
580
- return href
581
-
582
- def download_selected_image(plot_index, rsm_plots_state):
583
- if not rsm_plots_state:
584
- return "Error: Genera los gr谩ficos primero."
585
-
586
- plot_index = int(plot_index)
587
- if 0 <= plot_index < len(rsm_plots_state):
588
- selected_plot = rsm_plots_state[plot_index]
589
- img_bytes = selected_plot.to_image(format="png")
590
- b64 = b64encode(img_bytes).decode('utf-8')
591
- href = f'<a download="grafico_rsm_{plot_index}.png" href="data:image/png;base64,{b64}">Descargar Gr谩fico {plot_index}</a>'
592
- return href
593
  else:
594
- return "Error: 脥ndice de gr谩fico fuera de rango."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
595
 
596
- def download_all_images(rsm_plots_state):
597
- if not rsm_plots_state:
598
- return "Error: Genera los gr谩ficos primero."
599
 
600
- zip_output = io.BytesIO()
601
- with zipfile.ZipFile(zip_output, 'w') as zipf:
602
- for i, fig in enumerate(rsm_plots_state):
603
- img_bytes = fig.to_image(format="png")
604
- zipf.writestr(f"grafico_rsm_{i}.png", img_bytes)
605
 
606
- zip_data = zip_output.getvalue()
607
- b64 = b64encode(zip_data).decode('utf-8')
608
- href = f'<a download="graficos_rsm.zip" href="data:application/zip;base64,{b64}">Descargar Todos los Gr谩ficos</a>'
609
- return href
 
 
 
610
 
611
  # --- Crear la interfaz de Gradio ---
612
 
613
  with gr.Blocks() as demo:
614
  gr.Markdown("# Optimizaci贸n de la producci贸n de AIA usando RSM Box-Behnken")
615
-
616
  with gr.Row():
617
  with gr.Column():
618
  gr.Markdown("## Configuraci贸n del Dise帽o")
@@ -623,7 +706,7 @@ with gr.Blocks() as demo:
623
  x1_levels_input = gr.Textbox(label="Niveles de X1 (separados por comas)", value="1, 3.5, 5.5")
624
  x2_levels_input = gr.Textbox(label="Niveles de X2 (separados por comas)", value="0.03, 0.2, 0.3")
625
  x3_levels_input = gr.Textbox(label="Niveles de X3 (separados por comas)", value="0.4, 0.65, 0.9")
626
- data_input = gr.Textbox(label="Datos del Experimento (formato CSV)", lines=5, value="""1,-1,-1,0,166.594
627
  2,1,-1,0,177.557
628
  3,-1,1,0,127.261
629
  4,1,1,0,147.573
@@ -639,112 +722,150 @@ with gr.Blocks() as demo:
639
  14,0,0,0,297.238
640
  15,0,0,0,280.896""")
641
  load_button = gr.Button("Cargar Datos")
642
-
643
-
644
  with gr.Column():
645
  gr.Markdown("## Datos Cargados")
646
- data_output = gr.Dataframe(label="Tabla de Datos")
647
-
648
- # Definir el estado para rsm_plots
649
- rsm_plots_state = gr.State([])
650
-
651
- # Hacer que la secci贸n de an谩lisis y gr谩ficos sea visible solo despu茅s de cargar los datos
652
  with gr.Row(visible=False) as analysis_row:
653
  with gr.Column():
654
  fit_button = gr.Button("Ajustar Modelo y Optimizar")
655
  gr.Markdown("**Modelo Completo**")
656
- with gr.Row():
657
- model_completo_output1 = gr.HTML(label="Tabla de Coeficientes")
658
- pareto_completo_output = gr.Plot(label="Pareto Modelo Completo")
659
- model_completo_output2 = gr.HTML(label="Tabla de ANOVA")
660
  gr.Markdown("**Modelo Simplificado**")
661
- with gr.Row():
662
- model_simplificado_output = gr.HTML(label="Tabla de Coeficientes")
663
- pareto_simplificado_output = gr.Plot(label="Pareto Modelo Simplificado")
664
- equation_output = gr.HTML(label="Ecuaci贸n del Modelo Simplificado")
665
- optimization_table_output = gr.Dataframe(label="Tabla de Optimizaci贸n")
666
- prediction_table_output = gr.Dataframe(label="Tabla de Predicciones")
667
- contribution_table_output = gr.Dataframe(label="Tabla de % de Contribuci贸n")
668
- anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada")
669
-
670
- # Botones de descarga
671
- download_excel_button = gr.HTML("Descargar Excel")
672
- download_all_images_button = gr.HTML("Descargar Todos los Gr谩ficos")
673
-
674
  with gr.Column():
675
- gr.Markdown("## Gr谩ficos de Superficie de Respuesta")
 
 
 
676
  with gr.Row():
677
- plot_index_slider = gr.Number(label="脥ndice del Gr谩fico", value=0, step=1, minimum=0, maximum=8)
678
- previous_plot_button = gr.Button("Anterior")
679
- next_plot_button = gr.Button("Siguiente")
680
- rsm_plot_output = gr.Plot(label="Superficie de Respuesta")
681
- download_image_button = gr.HTML("Descargar Gr谩fico")
682
-
683
- # Funcionalidad de los botones
 
684
  load_button.click(
685
  load_data,
686
  inputs=[x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, data_input],
687
  outputs=[data_output, x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, analysis_row]
688
  )
689
-
690
  fit_button.click(
691
- fit_and_optimize_model,
692
- inputs=None,
693
  outputs=[
694
- model_completo_output1,
695
- pareto_completo,
696
- model_completo_output2,
697
- model_simplificado_output,
698
- pareto_simplificado_output,
699
- equation_output,
700
- optimization_table_output,
701
- prediction_table_output,
702
- contribution_table_output,
703
- anova_table_output,
704
- rsm_plots_state,
705
- plot_index_slider
706
  ]
707
  )
708
-
709
- previous_plot_button.click(
710
- lambda x: x - 1 if x > 0 else x,
711
- inputs=plot_index_slider,
712
- outputs=plot_index_slider
713
- ).then(
714
- generate_rsm_plot,
715
- inputs=[plot_index_slider, rsm_plots_state],
716
- outputs=[rsm_plot_output, plot_index_slider, gr.Textbox()]
717
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
718
 
719
- next_plot_button.click(
720
- lambda x: x + 1 if x < 8 else x,
721
- inputs=plot_index_slider,
722
- outputs=plot_index_slider
723
- ).then(
724
- generate_rsm_plot,
725
- inputs=[plot_index_slider, rsm_plots_state],
726
- outputs=[rsm_plot_output, plot_index_slider, gr.Textbox()]
727
  )
728
-
729
- plot_index_slider.change(
730
- generate_rsm_plot,
731
- inputs=[plot_index_slider, rsm_plots_state],
732
- outputs=[rsm_plot_output, plot_index_slider, gr.Textbox()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
733
  )
734
 
735
- download_excel_button.click(download_excel, outputs=download_excel_button)
736
- download_image_button.click(download_selected_image, inputs=[plot_index_slider, rsm_plots_state], outputs=download_image_button)
737
- download_all_images_button.click(download_all_images, inputs=[rsm_plots_state], outputs=download_all_images_button)
738
-
739
  # Ejemplo de uso
740
  gr.Markdown("## Ejemplo de uso")
741
- gr.Markdown("1. Introduce los nombres de las variables y sus niveles en las cajas de texto correspondientes.")
742
- gr.Markdown("2. Copia y pega los datos del experimento en la caja de texto 'Datos del Experimento'.")
743
- gr.Markdown("3. Haz clic en 'Cargar Datos' para cargar los datos en la tabla.")
744
- gr.Markdown("4. Haz clic en 'Ajustar Modelo y Optimizar' para ajustar el modelo y encontrar los niveles 贸ptimos de los factores.")
745
- gr.Markdown("5. Navega por los gr谩ficos de superficie de respuesta usando los botones 'Anterior' y 'Siguiente' o el control deslizante.")
746
- gr.Markdown("6. Haz clic en 'Descargar Excel' para descargar un archivo Excel con todas las tablas.")
747
- gr.Markdown("7. Haz clic en 'Descargar Gr谩fico' para descargar la imagen del gr谩fico actual.")
748
- gr.Markdown("8. Haz clic en 'Descargar Todos los Gr谩ficos' para descargar un archivo zip con todas las im谩genes de los gr谩ficos.")
749
-
 
 
 
750
  demo.launch()
 
1
+ import numpy as np
2
  import pandas as pd
3
  import statsmodels.formula.api as smf
4
  import statsmodels.api as sm
 
10
  import gradio as gr
11
  import io
12
  import zipfile
13
+ from datetime import datetime
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
+ """
18
+ Inicializa la clase con los datos del dise帽o Box-Behnken.
19
+
20
+ Args:
21
+ data (pd.DataFrame): DataFrame con los datos del experimento.
22
+ x1_name (str): Nombre de la primera variable independiente.
23
+ x2_name (str): Nombre de la segunda variable independiente.
24
+ x3_name (str): Nombre de la tercera variable independiente.
25
+ y_name (str): Nombre de la variable dependiente.
26
+ x1_levels (list): Niveles de la primera variable independiente.
27
+ x2_levels (list): Niveles de la segunda variable independiente.
28
+ x3_levels (list): Niveles de la tercera variable independiente.
29
+ """
30
  self.data = data.copy()
31
  self.model = None
32
  self.model_simplified = None
33
  self.optimized_results = None
34
  self.optimal_levels = None
35
+ self.all_figures = [] # Lista para almacenar las 9 figuras
36
  self.x1_name = x1_name
37
  self.x2_name = x2_name
38
  self.x3_name = x3_name
 
42
  self.x1_levels = x1_levels
43
  self.x2_levels = x2_levels
44
  self.x3_levels = x3_levels
 
 
 
 
 
 
 
 
45
 
46
  def get_levels(self, variable_name):
47
  """
 
104
 
105
  self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
106
  self.optimal_levels = self.optimized_results.x
107
+
108
  # Convertir niveles 贸ptimos de codificados a naturales
109
  optimal_levels_natural = [
110
+ self.coded_to_natural(self.optimal_levels[0], self.x1_name),
111
+ self.coded_to_natural(self.optimal_levels[1], self.x2_name),
112
+ self.coded_to_natural(self.optimal_levels[2], self.x3_name)
113
  ]
114
  # Crear la tabla de optimizaci贸n
115
  optimization_table = pd.DataFrame({
116
  'Variable': [self.x1_name, self.x2_name, self.x3_name],
117
  'Nivel 脫ptimo (Natural)': optimal_levels_natural,
118
+ 'Nivel 脫ptimo (Codificado)': self.optimal_levels.round(3) # Redondear a 3 decimales
119
  })
120
 
121
+ return optimization_table.round(3) # Redondear a 3 decimales
122
 
123
  def plot_rsm_individual(self, fixed_variable, fixed_level):
124
  """
 
210
 
211
  # A帽adir los puntos de los experimentos en la superficie de respuesta con diferentes colores y etiquetas
212
  # Crear una lista de colores y etiquetas para los puntos
213
+ colors = px.colors.qualitative.Safe
214
  point_labels = []
215
  for i, row in experiments_data.iterrows():
216
+ point_labels.append(f"{row[self.y_name]:.3f}") # Redondear a 3 decimales
217
 
218
  fig.add_trace(go.Scatter3d(
219
  x=experiments_x_natural,
220
  y=experiments_y_natural,
221
+ z=experiments_data[self.y_name].round(3),
222
  mode='markers+text',
223
  marker=dict(size=4, color=colors[:len(experiments_x_natural)]), # Usar colores de la lista
224
  text=point_labels, # Usar las etiquetas creadas
 
229
  # A帽adir etiquetas y t铆tulo con variables naturales
230
  fig.update_layout(
231
  scene=dict(
232
+ xaxis_title=f"{varying_variables[0]} (g/L)",
233
+ yaxis_title=f"{varying_variables[1]} (g/L)",
234
  zaxis_title=self.y_name,
 
 
 
 
235
  ),
236
  title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.3f} (g/L) (Modelo Simplificado)</sup>",
237
  height=800,
 
243
  def generate_all_plots(self):
244
  """
245
  Genera todas las gr谩ficas de RSM, variando la variable fija y sus niveles usando el modelo simplificado.
246
+ Almacena las figuras en self.all_figures.
247
  """
248
  if self.model_simplified is None:
249
  print("Error: Ajusta el modelo simplificado primero.")
250
+ return
251
+
252
+ self.all_figures = [] # Resetear la lista de figuras
253
 
254
  # Niveles naturales para graficar
255
  levels_to_plot_natural = {
 
258
  self.x3_name: self.x3_levels
259
  }
260
 
261
+ # Generar y almacenar gr谩ficos individuales
 
262
  for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
263
  for level in levels_to_plot_natural[fixed_variable]:
264
  fig = self.plot_rsm_individual(fixed_variable, level)
265
  if fig is not None:
266
+ self.all_figures.append(fig)
 
267
 
268
  def coded_to_natural(self, coded_value, variable_name):
269
  """Convierte un valor codificado a su valor natural."""
 
298
 
299
  # Crear el diagrama de Pareto
300
  fig = px.bar(
301
+ x=sorted_tvalues.round(3),
302
  y=sorted_names,
303
  orientation='h',
304
  labels={'x': 'Efecto Estandarizado', 'y': 'T茅rmino'},
 
326
 
327
  for term, coef in coefficients.items():
328
  if term != 'Intercept':
329
+ if term == f'{self.x1_name}':
330
+ equation += f" + {coef:.3f}*{self.x1_name}"
331
+ elif term == f'{self.x2_name}':
332
+ equation += f" + {coef:.3f}*{self.x2_name}"
333
+ elif term == f'{self.x3_name}':
334
+ equation += f" + {coef:.3f}*{self.x3_name}"
335
+ elif term == f'I({self.x1_name} ** 2)':
336
+ equation += f" + {coef:.3f}*{self.x1_name}^2"
337
+ elif term == f'I({self.x2_name} ** 2)':
338
+ equation += f" + {coef:.3f}*{self.x2_name}^2"
339
+ elif term == f'I({self.x3_name} ** 2)':
340
+ equation += f" + {coef:.3f}*{self.x3_name}^2"
341
 
342
  return equation
343
 
344
  def generate_prediction_table(self):
345
+ """
346
+ Genera una tabla con los valores actuales, predichos y residuales.
347
+ """
348
+ if self.model_simplified is None:
349
+ print("Error: Ajusta el modelo simplificado primero.")
350
+ return None
351
 
352
+ self.data['Predicho'] = self.model_simplified.predict(self.data)
353
+ self.data['Residual'] = self.data[self.y_name] - self.data['Predicho']
354
 
355
+ return self.data[[self.y_name, 'Predicho', 'Residual']].round(3)
356
 
357
  def calculate_contribution_percentage(self):
358
+ """
359
+ Calcula el porcentaje de contribuci贸n de cada factor a la variabilidad de la respuesta (AIA).
360
+ """
361
+ if self.model_simplified is None:
362
+ print("Error: Ajusta el modelo simplificado primero.")
363
+ return None
364
+
365
+ # ANOVA del modelo simplificado
366
+ anova_table = sm.stats.anova_lm(self.model_simplified, typ=2)
367
+
368
+ # Suma de cuadrados total
369
+ ss_total = anova_table['sum_sq'].sum()
370
+
371
+ # Crear tabla de contribuci贸n
372
+ contribution_table = pd.DataFrame({
373
+ 'Factor': [],
374
+ 'Suma de Cuadrados': [],
375
+ '% Contribuci贸n': []
376
+ })
377
+
378
+ # Calcular porcentaje de contribuci贸n para cada factor
379
+ for index, row in anova_table.iterrows():
380
+ if index != 'Residual':
381
+ factor_name = index
382
+ if factor_name == f'I({self.x1_name} ** 2)':
383
+ factor_name = f'{self.x1_name}^2'
384
+ elif factor_name == f'I({self.x2_name} ** 2)':
385
+ factor_name = f'{self.x2_name}^2'
386
+ elif factor_name == f'I({self.x3_name} ** 2)':
387
+ factor_name = f'{self.x3_name}^2'
388
+
389
+ ss_factor = row['sum_sq']
390
+ contribution_percentage = (ss_factor / ss_total) * 100
391
+
392
+ contribution_table = pd.concat([contribution_table, pd.DataFrame({
393
+ 'Factor': [factor_name],
394
+ 'Suma de Cuadrados': [ss_factor],
395
+ '% Contribuci贸n': [contribution_percentage]
396
+ })], ignore_index=True)
397
+
398
+ return contribution_table.round(3)
399
 
400
  def calculate_detailed_anova(self):
401
  """
 
421
  df_total = len(self.data) - 1
422
 
423
  # 5. Suma de cuadrados de la regresi贸n
424
+ ss_regression = anova_reduced['sum_sq'][:-1].sum() # Sumar todo excepto 'Residual'
425
 
426
  # 6. Grados de libertad de la regresi贸n
427
  df_regression = len(anova_reduced) - 1
 
432
 
433
  # 8. Suma de cuadrados del error puro (se calcula a partir de las r茅plicas)
434
  replicas = self.data[self.data.duplicated(subset=[self.x1_name, self.x2_name, self.x3_name], keep=False)]
435
+ if not replicas.empty:
436
+ ss_pure_error = replicas.groupby([self.x1_name, self.x2_name, self.x3_name])[self.y_name].var().sum() * replicas.groupby([self.x1_name, self.x2_name, self.x3_name]).ngroups
437
+ df_pure_error = len(replicas) - replicas.groupby([self.x1_name, self.x2_name, self.x3_name]).ngroups
438
+ else:
439
+ ss_pure_error = np.nan
440
+ df_pure_error = np.nan
441
 
442
  # 9. Suma de cuadrados de la falta de ajuste
443
+ ss_lack_of_fit = ss_residual - ss_pure_error if not np.isnan(ss_pure_error) else np.nan
444
+ df_lack_of_fit = df_residual - df_pure_error if not np.isnan(df_pure_error) else np.nan
445
 
446
  # 10. Cuadrados medios
447
  ms_regression = ss_regression / df_regression
448
  ms_residual = ss_residual / df_residual
449
+ ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit if not np.isnan(ss_lack_of_fit) else np.nan
450
+ ms_pure_error = ss_pure_error / df_pure_error if not np.isnan(ss_pure_error) else np.nan
451
 
452
  # 11. Estad铆stico F y valor p para la falta de ajuste
453
+ f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) else np.nan
454
+ 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
455
 
456
  # 12. Crear la tabla ANOVA detallada
457
  detailed_anova_table = pd.DataFrame({
458
  'Fuente de Variaci贸n': ['Regresi贸n', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'],
459
+ 'Suma de Cuadrados': [ss_regression, ss_residual, ss_lack_of_fit, ss_pure_error, ss_total],
460
  'Grados de Libertad': [df_regression, df_residual, df_lack_of_fit, df_pure_error, df_total],
461
+ 'Cuadrado Medio': [ms_regression, ms_residual, ms_lack_of_fit, ms_pure_error, np.nan],
462
+ 'F': [np.nan, np.nan, f_lack_of_fit, np.nan, np.nan],
463
+ 'Valor p': [np.nan, np.nan, p_lack_of_fit, np.nan, np.nan]
464
  })
465
 
466
  # Calcular la suma de cuadrados y grados de libertad para la curvatura
 
468
  df_curvature = 3
469
 
470
  # A帽adir la fila de curvatura a la tabla ANOVA
471
+ detailed_anova_table.loc[len(detailed_anova_table)] = ['Curvatura', ss_curvature, df_curvature, ss_curvature / df_curvature, np.nan, np.nan]
472
 
473
  # Reorganizar las filas para que la curvatura aparezca despu茅s de la regresi贸n
474
  detailed_anova_table = detailed_anova_table.reindex([0, 5, 1, 2, 3, 4])
 
476
  # Resetear el 铆ndice para que sea consecutivo
477
  detailed_anova_table = detailed_anova_table.reset_index(drop=True)
478
 
479
+ return detailed_anova_table.round(3)
480
+
481
+ def get_all_tables(self):
482
+ """
483
+ Obtiene todas las tablas generadas para ser exportadas a Excel.
484
+ """
485
+ prediction_table = self.generate_prediction_table()
486
+ contribution_table = self.calculate_contribution_percentage()
487
+ detailed_anova_table = self.calculate_detailed_anova()
488
+
489
+ return {
490
+ 'Predicciones': prediction_table,
491
+ '% Contribuci贸n': contribution_table,
492
+ 'ANOVA Detallada': detailed_anova_table
493
+ }
494
+
495
+ def save_figures_to_zip(self):
496
+ """
497
+ Guarda todas las figuras almacenadas en self.all_figures a un archivo ZIP en memoria.
498
+
499
+ Returns:
500
+ bytes: Bytes del archivo ZIP.
501
+ """
502
+ if not self.all_figures:
503
+ return None
504
+
505
+ zip_buffer = io.BytesIO()
506
+ with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
507
+ for idx, fig in enumerate(self.all_figures, start=1):
508
+ img_bytes = fig.to_image(format="png")
509
+ zip_file.writestr(f'Grafico_{idx}.png', img_bytes)
510
+ zip_buffer.seek(0)
511
+ return zip_buffer
512
+
513
+ def save_fig_to_bytes(self, fig):
514
+ """
515
+ Convierte una figura Plotly a bytes en formato PNG.
516
+
517
+ Args:
518
+ fig (go.Figure): Figura de Plotly.
519
+
520
+ Returns:
521
+ bytes: Bytes de la imagen PNG.
522
+ """
523
+ return fig.to_image(format="png")
524
 
525
  # --- Funciones para la interfaz de Gradio ---
526
 
 
539
  data_str (str): Datos del experimento en formato CSV, separados por comas.
540
 
541
  Returns:
542
+ tuple: (pd.DataFrame, str, str, str, str, list, list, list, gr.update)
543
  """
544
  try:
545
  # Convertir los niveles a listas de n煤meros
 
561
  global rsm
562
  rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
563
 
564
+ return data.round(3), x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels, gr.update(visible=True)
565
+
566
  except Exception as e:
567
  return None, "", "", "", "", [], [], [], gr.update(visible=False), f"Error: {e}"
568
 
569
+ def fit_and_optimize_model():
570
  if 'rsm' not in globals():
571
+ return None, None, None, None, None, None, "Error: Carga los datos primero.", None, None
572
 
573
+ # Ajustar modelos y optimizar
574
  model_completo, pareto_completo = rsm.fit_model()
575
  model_simplificado, pareto_simplificado = rsm.fit_simplified_model()
576
  optimization_table = rsm.optimize()
 
578
  prediction_table = rsm.generate_prediction_table()
579
  contribution_table = rsm.calculate_contribution_percentage()
580
  anova_table = rsm.calculate_detailed_anova()
581
+
582
+ # Generar todas las figuras y almacenarlas
583
+ rsm.generate_all_plots()
584
+
585
  # Formatear la ecuaci贸n para que se vea mejor en Markdown
586
  equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " 脳 ")
587
  equation_formatted = f"### Ecuaci贸n del Modelo Simplificado:<br>{equation_formatted}"
588
 
589
  return (
590
+ model_completo.summary().as_html(),
591
  pareto_completo,
 
592
  model_simplificado.summary().as_html(),
593
  pareto_simplificado,
594
  equation_formatted,
 
596
  prediction_table,
597
  contribution_table,
598
  anova_table,
599
+ rsm.all_figures, # Devuelve todas las figuras generadas
600
+ gr.update() # Placeholder para actualizar otros componentes si es necesario
601
  )
602
 
603
+ def generate_rsm_plot(fixed_variable, fixed_level, all_figures, current_index):
604
+ if 'rsm' not in globals():
605
+ return None, "Error: Carga los datos primero.", current_index
606
 
607
+ # Encontrar el 铆ndice correspondiente al gr谩fico seleccionado
608
+ # Asumimos que los gr谩ficos est谩n ordenados por variable fija y nivel
609
+ # Se puede mejorar la l贸gica si es necesario
610
+ selected_fig = None
611
+ for idx, fig in enumerate(all_figures):
612
+ title = fig.layout.title.text
613
+ if fixed_variable in title and f"fijo en {fixed_level:.3f}" in title:
614
+ selected_fig = fig
615
+ current_index = idx
616
+ break
617
+
618
+ if selected_fig is None and all_figures:
619
+ selected_fig = all_figures[0]
620
+ current_index = 0
621
 
622
+ return selected_fig, None, current_index
623
+
624
+ def navigate_plot(direction, current_index, total_plots):
625
+ """
626
+ Navega entre los gr谩ficos.
627
+
628
+ Args:
629
+ direction (str): 'left' o 'right'.
630
+ current_index (int): 脥ndice actual.
631
+ total_plots (int): Total de gr谩ficos.
632
+
633
+ Returns:
634
+ int: Nuevo 铆ndice.
635
+ """
636
+ if direction == 'left':
637
+ new_index = (current_index - 1) % total_plots
638
+ elif direction == 'right':
639
+ new_index = (current_index + 1) % total_plots
 
 
 
 
 
 
 
 
 
 
 
640
  else:
641
+ new_index = current_index
642
+ return new_index
643
+
644
+ def download_current_plot(all_figures, current_index):
645
+ """
646
+ Descarga la figura actual como PNG.
647
+
648
+ Args:
649
+ all_figures (list): Lista de figuras.
650
+ current_index (int): 脥ndice de la figura actual.
651
+
652
+ Returns:
653
+ bytes: Bytes de la imagen PNG.
654
+ """
655
+ if not all_figures:
656
+ return None
657
+ fig = all_figures[current_index]
658
+ img_bytes = rsm.save_fig_to_bytes(fig)
659
+ return img_bytes
660
+
661
+ def download_all_plots_zip(all_figures):
662
+ """
663
+ Descarga todas las figuras en un archivo ZIP.
664
+
665
+ Args:
666
+ all_figures (list): Lista de figuras.
667
+
668
+ Returns:
669
+ bytes: Bytes del archivo ZIP.
670
+ """
671
+ zip_bytes = rsm.save_figures_to_zip()
672
+ if zip_bytes:
673
+ return zip_bytes
674
+ return None
675
 
676
+ def download_all_tables_excel():
677
+ """
678
+ Descarga todas las tablas en un archivo Excel con m煤ltiples hojas.
679
 
680
+ Returns:
681
+ bytes: Bytes del archivo Excel.
682
+ """
683
+ if 'rsm' not in globals():
684
+ return None
685
 
686
+ tables = rsm.get_all_tables()
687
+ excel_buffer = io.BytesIO()
688
+ with pd.ExcelWriter(excel_buffer, engine='xlsxwriter') as writer:
689
+ for sheet_name, table in tables.items():
690
+ table.to_excel(writer, sheet_name=sheet_name, index=False)
691
+ excel_buffer.seek(0)
692
+ return excel_buffer
693
 
694
  # --- Crear la interfaz de Gradio ---
695
 
696
  with gr.Blocks() as demo:
697
  gr.Markdown("# Optimizaci贸n de la producci贸n de AIA usando RSM Box-Behnken")
698
+
699
  with gr.Row():
700
  with gr.Column():
701
  gr.Markdown("## Configuraci贸n del Dise帽o")
 
706
  x1_levels_input = gr.Textbox(label="Niveles de X1 (separados por comas)", value="1, 3.5, 5.5")
707
  x2_levels_input = gr.Textbox(label="Niveles de X2 (separados por comas)", value="0.03, 0.2, 0.3")
708
  x3_levels_input = gr.Textbox(label="Niveles de X3 (separados por comas)", value="0.4, 0.65, 0.9")
709
+ data_input = gr.Textbox(label="Datos del Experimento (formato CSV)", lines=10, value="""1,-1,-1,0,166.594
710
  2,1,-1,0,177.557
711
  3,-1,1,0,127.261
712
  4,1,1,0,147.573
 
722
  14,0,0,0,297.238
723
  15,0,0,0,280.896""")
724
  load_button = gr.Button("Cargar Datos")
725
+
 
726
  with gr.Column():
727
  gr.Markdown("## Datos Cargados")
728
+ data_output = gr.Dataframe(label="Tabla de Datos", interactive=False)
729
+
730
+ # Hacer que la secci贸n de an谩lisis sea visible solo despu茅s de cargar los datos
 
 
 
731
  with gr.Row(visible=False) as analysis_row:
732
  with gr.Column():
733
  fit_button = gr.Button("Ajustar Modelo y Optimizar")
734
  gr.Markdown("**Modelo Completo**")
735
+ model_completo_output = gr.HTML()
736
+ pareto_completo_output = gr.Plot()
 
 
737
  gr.Markdown("**Modelo Simplificado**")
738
+ model_simplificado_output = gr.HTML()
739
+ pareto_simplificado_output = gr.Plot()
740
+ gr.Markdown("**Ecuaci贸n del Modelo Simplificado**")
741
+ equation_output = gr.HTML()
742
+ optimization_table_output = gr.Dataframe(label="Tabla de Optimizaci贸n", interactive=False)
743
+ prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False)
744
+ contribution_table_output = gr.Dataframe(label="Tabla de % de Contribuci贸n", interactive=False)
745
+ anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False)
746
+ gr.Markdown("## Descargar Todas las Tablas")
747
+ download_excel_button = gr.DownloadButton("Descargar Tablas en Excel", file_name=f"Tablas_RSM_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")
748
+
 
 
749
  with gr.Column():
750
+ gr.Markdown("## Generar Gr谩ficos de Superficie de Respuesta")
751
+ fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa", "Extracto_de_Levadura", "Triptofano"], value="Glucosa")
752
+ fixed_level_input = gr.Slider(label="Nivel de Variable Fija", minimum=0, maximum=1, step=0.01, value=0.5)
753
+ plot_button = gr.Button("Generar Gr谩ficos")
754
  with gr.Row():
755
+ left_button = gr.Button("<")
756
+ right_button = gr.Button(">")
757
+ rsm_plot_output = gr.Plot()
758
+ plot_info = gr.Textbox(label="Informaci贸n del Gr谩fico", value="Gr谩fico 1 de 9", interactive=False)
759
+ with gr.Row():
760
+ download_plot_button = gr.DownloadButton("Descargar Gr谩fico Actual (PNG)", file_name="Grafico_RSM.png")
761
+ download_all_plots_button = gr.DownloadButton("Descargar Todos los Gr谩ficos (ZIP)", file_name="Graficos_RSM.zip")
762
+
763
  load_button.click(
764
  load_data,
765
  inputs=[x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, data_input],
766
  outputs=[data_output, x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, analysis_row]
767
  )
768
+
769
  fit_button.click(
770
+ fit_and_optimize_model,
771
+ inputs=[],
772
  outputs=[
773
+ model_completo_output,
774
+ pareto_completo_output,
775
+ model_simplificado_output,
776
+ pareto_simplificado_output,
777
+ equation_output,
778
+ optimization_table_output,
779
+ prediction_table_output,
780
+ contribution_table_output,
781
+ anova_table_output,
782
+ gr.State(), # all_figures
783
+ gr.update()
 
784
  ]
785
  )
786
+
787
+ plot_button.click(
788
+ lambda: (None, 0), # Reset plot index
789
+ inputs=[],
790
+ outputs=[rsm_plot_output, gr.State()]
 
 
 
 
791
  )
792
+
793
+ # Navegaci贸n de gr谩ficos
794
+ with gr.Row():
795
+ left_button.click(
796
+ navigate_plot,
797
+ inputs=["left", gr.State(), gr.State()],
798
+ outputs=gr.State()
799
+ )
800
+ right_button.click(
801
+ navigate_plot,
802
+ inputs=["right", gr.State(), gr.State()],
803
+ outputs=gr.State()
804
+ )
805
+
806
+ # Funciones para manejar la navegaci贸n y actualizaci贸n de gr谩ficos
807
+ def update_plot(direction, current_index, total_plots, all_figures):
808
+ if not all_figures:
809
+ return None, "No hay gr谩ficos disponibles.", current_index
810
+
811
+ if direction == "left":
812
+ new_index = (current_index - 1) % total_plots
813
+ elif direction == "right":
814
+ new_index = (current_index + 1) % total_plots
815
+ else:
816
+ new_index = current_index
817
+
818
+ selected_fig = all_figures[new_index]
819
+ plot_info_text = f"Gr谩fico {new_index + 1} de {total_plots}"
820
+
821
+ return selected_fig, plot_info_text, new_index
822
 
823
+ # Actualizar gr谩ficos al navegar
824
+ left_button.click(
825
+ update_plot,
826
+ inputs=["left", "current_index", "total_plots", "all_figures"],
827
+ outputs=[rsm_plot_output, plot_info, gr.State()]
 
 
 
828
  )
829
+
830
+ right_button.click(
831
+ update_plot,
832
+ inputs=["right", "current_index", "total_plots", "all_figures"],
833
+ outputs=[rsm_plot_output, plot_info, gr.State()]
834
+ )
835
+
836
+ # Descargar gr谩fico actual
837
+ download_plot_button.click(
838
+ download_current_plot,
839
+ inputs=["all_figures", "current_index"],
840
+ outputs=download_plot_button
841
+ )
842
+
843
+ # Descargar todos los gr谩ficos en ZIP
844
+ download_all_plots_button.click(
845
+ download_all_plots_zip,
846
+ inputs=["all_figures"],
847
+ outputs=download_all_plots_button
848
+ )
849
+
850
+ # Descargar todas las tablas en Excel
851
+ download_excel_button.click(
852
+ download_all_tables_excel,
853
+ inputs=[],
854
+ outputs=download_excel_button
855
  )
856
 
 
 
 
 
857
  # Ejemplo de uso
858
  gr.Markdown("## Ejemplo de uso")
859
+ gr.Markdown("""
860
+ 1. Introduce los nombres de las variables y sus niveles en las cajas de texto correspondientes.
861
+ 2. Copia y pega los datos del experimento en la caja de texto 'Datos del Experimento'.
862
+ 3. Haz clic en 'Cargar Datos' para cargar los datos en la tabla.
863
+ 4. Haz clic en 'Ajustar Modelo y Optimizar' para ajustar el modelo y encontrar los niveles 贸ptimos de los factores.
864
+ 5. Selecciona una variable fija y su nivel en los controles deslizantes.
865
+ 6. Haz clic en 'Generar Gr谩ficos' para generar los gr谩ficos de superficie de respuesta.
866
+ 7. Navega entre los gr谩ficos usando los botones '<' y '>'.
867
+ 8. Descarga el gr谩fico actual en PNG o descarga todos los gr谩ficos en un ZIP.
868
+ 9. Descarga todas las tablas en un archivo Excel con el bot贸n correspondiente.
869
+ """)
870
+
871
  demo.launch()