C2MV commited on
Commit
4b95828
·
verified ·
1 Parent(s): c65663f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +270 -153
app.py CHANGED
@@ -9,11 +9,10 @@ import plotly.express as px
9
  from scipy.stats import t, f
10
  import gradio as gr
11
  import io
12
- import os
13
  import zipfile
 
14
 
15
  class RSM_BoxBehnken:
16
- # ... (La clase RSM_BoxBehnken se mantiene igual que en la respuesta anterior)
17
  def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels):
18
  self.data = data.copy()
19
  self.model = None
@@ -30,8 +29,25 @@ class RSM_BoxBehnken:
30
  self.x1_levels = x1_levels
31
  self.x2_levels = x2_levels
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,6 +58,9 @@ class RSM_BoxBehnken:
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,6 +70,9 @@ class RSM_BoxBehnken:
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,6 +81,12 @@ class RSM_BoxBehnken:
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,11 +100,13 @@ class RSM_BoxBehnken:
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,
@@ -86,46 +116,69 @@ class RSM_BoxBehnken:
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,6 +189,7 @@ class RSM_BoxBehnken:
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,29 +201,38 @@ class RSM_BoxBehnken:
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():
153
- point_labels.append(f"{row[self.y_name]:.2f}")
154
 
155
  fig.add_trace(go.Scatter3d(
156
  x=experiments_x_natural,
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,
174
  width=1000,
175
  showlegend=True
@@ -177,44 +240,61 @@ class RSM_BoxBehnken:
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,13 +304,17 @@ class RSM_BoxBehnken:
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")
230
 
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
@@ -256,34 +340,40 @@ class RSM_BoxBehnken:
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
262
 
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
@@ -295,69 +385,89 @@ class RSM_BoxBehnken:
295
  factor_name = f'{self.x3_name}^2'
296
 
297
  ss_factor = row['sum_sq']
298
- contribution_percentage = (ss_factor / ss_total) * 100
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,19 +475,39 @@ class RSM_BoxBehnken:
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
 
@@ -388,7 +518,7 @@ def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x
388
 
389
  def fit_and_optimize_model():
390
  if 'rsm' not in globals():
391
- return None, None, None, None, None, None, "Error: Carga los datos primero."
392
 
393
  model_completo, pareto_completo = rsm.fit_model()
394
  model_simplificado, pareto_simplificado = rsm.fit_simplified_model()
@@ -397,101 +527,73 @@ def fit_and_optimize_model():
397
  prediction_table = rsm.generate_prediction_table()
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):
411
- global current_plot_index, plot_images
412
-
413
- if 'rsm' not in globals():
414
- return None, "Error: Carga los datos primero.", 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
421
-
422
- current_plot_index = (current_plot_index) % len(plot_images)
423
- fig = plot_images[current_plot_index]
424
-
425
- # Convertir la figura a bytes de imagen
426
- img_bytes = fig.to_image(format="png")
427
-
428
- return fig, "", img_bytes
429
 
430
  def download_excel():
431
  if 'rsm' not in globals():
432
- return None, "Error: Carga los datos primero."
433
 
434
- # Create a temporary file
435
- temp_file = zipfile.NamedTemporaryFile(delete=False, suffix='.xlsx')
436
-
437
- with pd.ExcelWriter(temp_file.name, engine='xlsxwriter') as writer:
438
  rsm.data.to_excel(writer, sheet_name='Datos', index=False)
439
  rsm.generate_prediction_table().to_excel(writer, sheet_name='Predicciones', index=False)
440
- rsm.optimize().to_excel(writer, sheet_name='Optimizacion', index=False)
441
  rsm.calculate_contribution_percentage().to_excel(writer, sheet_name='Contribucion', index=False)
442
  rsm.calculate_detailed_anova().to_excel(writer, sheet_name='ANOVA', index=False)
443
-
444
- return temp_file.name
445
-
446
- def download_images():
447
- global plot_images
448
- if 'rsm' not in globals():
449
- return None, "Error: Carga los datos primero."
450
-
451
- if not plot_images:
452
- return None, "Error: No se han generado gráficos."
453
-
454
- # Create a temporary file
455
- temp_file = zipfile.NamedTemporaryFile(delete=False, suffix='.zip')
456
-
457
- with zipfile.ZipFile(temp_file.name, 'w', zipfile.ZIP_DEFLATED) as zipf:
458
- for i, fig in enumerate(plot_images):
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  img_bytes = fig.to_image(format="png")
460
- zipf.writestr(f"plot_{i}.png", img_bytes)
461
-
462
- return temp_file.name
463
 
464
- def generate_rsm_plot(fixed_variable, fixed_level):
465
- global current_plot_index, plot_images
466
-
467
- if 'rsm' not in globals():
468
- return None, "Error: Carga los datos primero.", None
469
-
470
- if not plot_images:
471
- plot_images = rsm.generate_all_plots()
472
-
473
- if not plot_images:
474
- return None, "Error: No se pudieron generar los gráficos.", None
475
-
476
- current_plot_index = (current_plot_index) % len(plot_images)
477
- fig = plot_images[current_plot_index]
478
-
479
- # Create a temporary file for the image
480
- temp_file = zipfile.NamedTemporaryFile(delete=False, suffix='.png')
481
- fig.write_image(temp_file.name)
482
-
483
- return fig, "", temp_file.name
484
-
485
- def next_plot():
486
- global current_plot_index
487
- current_plot_index += 1
488
- return current_plot_index
489
-
490
- def prev_plot():
491
- global current_plot_index
492
- current_plot_index -= 1
493
-
494
- return current_plot_index
495
 
496
  # --- Crear la interfaz de Gradio ---
497
 
@@ -530,54 +632,71 @@ with gr.Blocks() as demo:
530
  gr.Markdown("## Datos Cargados")
531
  data_output = gr.Dataframe(label="Tabla de Datos")
532
 
533
- # Hacer que la sección de análisis sea visible solo después de cargar los datos
534
  with gr.Row(visible=False) as analysis_row:
535
  with gr.Column():
536
  fit_button = gr.Button("Ajustar Modelo y Optimizar")
537
- download_excel_button = gr.Button("Descargar Tablas en Excel")
538
  gr.Markdown("**Modelo Completo**")
539
- model_completo_output = gr.HTML()
540
- pareto_completo_output = gr.Plot()
 
 
541
  gr.Markdown("**Modelo Simplificado**")
542
- model_simplificado_output = gr.HTML()
543
- pareto_simplificado_output = gr.Plot()
544
- equation_output = gr.HTML()
545
- optimization_table_output = gr.Dataframe(label="Tabla de Optimización", headers=["Variable", "Nivel Óptimo (Natural)", "Nivel Óptimo (Codificado)"])
 
546
  prediction_table_output = gr.Dataframe(label="Tabla de Predicciones")
547
  contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución")
548
  anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada")
 
 
 
 
 
549
  with gr.Column():
550
- gr.Markdown("## Generar Gráficos de Superficie de Respuesta")
551
- fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa", "Extracto_de_Levadura", "Triptofano"], value="Glucosa")
552
- fixed_level_input = gr.Slider(label="Nivel de Variable Fija", minimum=0, maximum=1, step=0.01, value=0.5)
553
  with gr.Row():
554
- plot_button = gr.Button("Generar Gráfico")
555
- download_images_button = gr.Button("Descargar Gráficos en ZIP")
556
-
557
- prev_plot_button = gr.Button("<")
558
- next_plot_button = gr.Button(">")
559
- rsm_plot_output = gr.Plot()
560
- plot_image_output = gr.File(label="Descargar Gráfico Actual")
561
 
 
562
  load_button.click(
563
  load_data,
564
  inputs=[x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, data_input],
565
  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]
566
  )
567
 
568
- 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])
 
 
 
569
 
570
- plot_button.click(generate_rsm_plot,
571
- inputs=[fixed_variable_input, fixed_level_input],
572
- outputs=[rsm_plot_output, equation_output, plot_image_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
 
574
  download_excel_button.click(download_excel, outputs=[download_excel_button])
575
-
576
- download_images_button.click(download_images, outputs=[download_images_button])
577
-
578
- 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])
579
-
580
- 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])
581
 
582
  # Ejemplo de uso
583
  gr.Markdown("## Ejemplo de uso")
@@ -585,11 +704,9 @@ with gr.Blocks() as demo:
585
  gr.Markdown("2. Copia y pega los datos del experimento en la caja de texto 'Datos del Experimento'.")
586
  gr.Markdown("3. Haz clic en 'Cargar Datos' para cargar los datos en la tabla.")
587
  gr.Markdown("4. Haz clic en 'Ajustar Modelo y Optimizar' para ajustar el modelo y encontrar los niveles óptimos de los factores.")
588
- gr.Markdown("5. Selecciona una variable fija y su nivel en los controles deslizantes.")
589
- gr.Markdown("6. Haz clic en 'Generar Gráfico' para generar un gráfico de superficie de respuesta.")
590
- gr.Markdown("7. Usa '<' y '>' para navegar entre los gráficos generados.")
591
- gr.Markdown("8. Haz clic en 'Descargar Tablas en Excel' para obtener un archivo Excel con todas las tablas generadas.")
592
- gr.Markdown("9. Haz clic en 'Descargar Gráfico Actual' para descargar la imagen del gráfico actual en formato PNG.")
593
- 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.")
594
 
595
  demo.launch()
 
9
  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
 
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
+ """
43
+ Obtiene los niveles para una variable específica.
44
+
45
+ Args:
46
+ variable_name (str): Nombre de la variable.
47
+
48
+ Returns:
49
+ list: Niveles de la variable.
50
+ """
51
  if variable_name == self.x1_name:
52
  return self.x1_levels
53
  elif variable_name == self.x2_name:
 
58
  raise ValueError(f"Variable desconocida: {variable_name}")
59
 
60
  def fit_model(self):
61
+ """
62
+ Ajusta el modelo de segundo orden completo a los datos.
63
+ """
64
  formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
65
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + ' \
66
  f'{self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}'
 
70
  return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo")
71
 
72
  def fit_simplified_model(self):
73
+ """
74
+ Ajusta el modelo de segundo orden a los datos, eliminando términos no significativos.
75
+ """
76
  formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + ' \
77
  f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)'
78
  self.model_simplified = smf.ols(formula, data=self.data).fit()
 
81
  return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado")
82
 
83
  def optimize(self, method='Nelder-Mead'):
84
+ """
85
+ Encuentra los niveles óptimos de los factores para maximizar la respuesta usando el modelo simplificado.
86
+
87
+ Args:
88
+ method (str): Método de optimización a utilizar (por defecto, 'Nelder-Mead').
89
+ """
90
  if self.model_simplified is None:
91
  print("Error: Ajusta el modelo simplificado primero.")
92
  return
 
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,
 
116
  return optimization_table
117
 
118
  def plot_rsm_individual(self, fixed_variable, fixed_level):
119
+ """
120
+ Genera un gráfico de superficie de respuesta (RSM) individual para una configuración específica.
121
+
122
+ Args:
123
+ fixed_variable (str): Nombre de la variable a mantener fija.
124
+ fixed_level (float): Nivel al que se fija la variable (en unidades naturales).
125
+
126
+ Returns:
127
+ go.Figure: Objeto de figura de Plotly.
128
+ """
129
  if self.model_simplified is None:
130
  print("Error: Ajusta el modelo simplificado primero.")
131
  return None
132
 
133
+ # Determinar las variables que varían y sus niveles naturales
134
  varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
135
 
136
+ # Establecer los niveles naturales para las variables que varían
137
  x_natural_levels = self.get_levels(varying_variables[0])
138
  y_natural_levels = self.get_levels(varying_variables[1])
139
 
140
+ # Crear una malla de puntos para las variables que varían (en unidades naturales)
141
  x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
142
  y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
143
  x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
144
 
145
+ # Convertir la malla de variables naturales a codificadas
146
  x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
147
  y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
148
 
149
+ # Crear un DataFrame para la predicción con variables codificadas
150
  prediction_data = pd.DataFrame({
151
  varying_variables[0]: x_grid_coded.flatten(),
152
  varying_variables[1]: y_grid_coded.flatten(),
153
  })
154
  prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable)
155
 
156
+ # Calcular los valores predichos
157
  z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape)
158
 
159
+ # 1. Identificar los dos factores que varían
160
  varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
161
 
162
+ # 2. Filtrar por el nivel de la variable fija (en codificado)
163
  fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
164
  subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
165
 
166
+ # 3. Filtrar por niveles válidos en las variables que varían
167
  valid_levels = [-1, 0, 1]
168
  experiments_data = subset_data[
169
  subset_data[varying_variables[0]].isin(valid_levels) &
170
  subset_data[varying_variables[1]].isin(valid_levels)
171
  ]
172
 
173
+ # Convertir coordenadas de experimentos a naturales
174
  experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
175
  experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
176
 
177
+ # Crear el gráfico de superficie con variables naturales en los ejes y transparencia
178
  fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)])
179
 
180
+ # --- Añadir cuadrícula a la superficie ---
181
+ # Líneas en la dirección x
182
  for i in range(x_grid_natural.shape[0]):
183
  fig.add_trace(go.Scatter3d(
184
  x=x_grid_natural[i, :],
 
189
  showlegend=False,
190
  hoverinfo='skip'
191
  ))
192
+ # Líneas en la dirección y
193
  for j in range(x_grid_natural.shape[1]):
194
  fig.add_trace(go.Scatter3d(
195
  x=x_grid_natural[:, j],
 
201
  hoverinfo='skip'
202
  ))
203
 
204
+ # --- Fin de la adición de la cuadrícula ---
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
220
  textposition='top center',
221
  name='Experimentos'
222
  ))
223
 
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,
237
  width=1000,
238
  showlegend=True
 
240
  return fig
241
 
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 = {
252
  self.x1_name: self.x1_levels,
253
  self.x2_name: self.x2_levels,
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."""
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
  )
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:.3f}",
310
  annotation_position="bottom right")
311
 
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
 
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
 
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
+ """
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': [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
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', 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])
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
  # --- 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
 
 
518
 
519
  def fit_and_optimize_model():
520
  if 'rsm' not in globals():
521
+ return 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()
 
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 model_completo.summary().tables[0].as_html(), pareto_completo, model_completo.summary().tables[1].as_html(), model_simplificado.summary().as_html(), pareto_simplificado, equation_formatted, optimization_table, prediction_table, contribution_table, anova_table, rsm_plots, gr.update(visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
539
 
540
+ def generate_rsm_plot(plot_index):
541
+ if 'rsm_plots' not in globals() or not rsm_plots:
542
+ return None, gr.update(visible=False), "Error: Genera los gráficos primero."
543
+
544
+ plot_index = int(plot_index)
545
+ if 0 <= plot_index < len(rsm_plots):
546
+ selected_plot = rsm_plots[plot_index]
547
+ return selected_plot, gr.update(visible=True, value=plot_index)
548
+ else:
549
+ return None, gr.update(visible=False), "Error: Índice de gráfico fuera de rango."
550
 
551
  def download_excel():
552
  if 'rsm' not in globals():
553
+ return None, "Error: Carga los datos y ajusta el modelo primero."
554
 
555
+ output = io.BytesIO()
556
+ with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
 
 
557
  rsm.data.to_excel(writer, sheet_name='Datos', index=False)
558
  rsm.generate_prediction_table().to_excel(writer, sheet_name='Predicciones', index=False)
 
559
  rsm.calculate_contribution_percentage().to_excel(writer, sheet_name='Contribucion', index=False)
560
  rsm.calculate_detailed_anova().to_excel(writer, sheet_name='ANOVA', index=False)
561
+ rsm.optimize().to_excel(writer, sheet_name='Optimizacion', index=False)
562
+ # Aquí puedes agregar más tablas a diferentes hojas si es necesario
563
+
564
+ excel_data = output.getvalue()
565
+ b64 = b64encode(excel_data).decode('utf-8')
566
+ href = f'<a download="resultados_rsm.xlsx" href="data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,{b64}">Descargar Excel</a>'
567
+ return href
568
+
569
+ def download_selected_image(plot_index):
570
+ if 'rsm_plots' not in globals() or not rsm_plots:
571
+ return None, "Error: Genera los gráficos primero."
572
+
573
+ plot_index = int(plot_index)
574
+ if 0 <= plot_index < len(rsm_plots):
575
+ selected_plot = rsm_plots[plot_index]
576
+ img_bytes = selected_plot.to_image(format="png")
577
+ b64 = b64encode(img_bytes).decode('utf-8')
578
+ href = f'<a download="grafico_rsm_{plot_index}.png" href="data:image/png;base64,{b64}">Descargar Gráfico {plot_index}</a>'
579
+ return href
580
+ else:
581
+ return None, "Error: Índice de gráfico fuera de rango."
582
+
583
+ def download_all_images():
584
+ if 'rsm_plots' not in globals() or not rsm_plots:
585
+ return None, "Error: Genera los gráficos primero."
586
+
587
+ zip_output = io.BytesIO()
588
+ with zipfile.ZipFile(zip_output, 'w') as zipf:
589
+ for i, fig in enumerate(rsm_plots):
590
  img_bytes = fig.to_image(format="png")
591
+ zipf.writestr(f"grafico_rsm_{i}.png", img_bytes)
 
 
592
 
593
+ zip_data = zip_output.getvalue()
594
+ b64 = b64encode(zip_data).decode('utf-8')
595
+ href = f'<a download="graficos_rsm.zip" href="data:application/zip;base64,{b64}">Descargar Todos los Gráficos</a>'
596
+ return href
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
  # --- Crear la interfaz de Gradio ---
599
 
 
632
  gr.Markdown("## Datos Cargados")
633
  data_output = gr.Dataframe(label="Tabla de Datos")
634
 
635
+ # Hacer que la sección de análisis y gráficos sea visible solo después de cargar los datos
636
  with gr.Row(visible=False) as analysis_row:
637
  with gr.Column():
638
  fit_button = gr.Button("Ajustar Modelo y Optimizar")
 
639
  gr.Markdown("**Modelo Completo**")
640
+ with gr.Row():
641
+ model_completo_output1 = gr.HTML(label="Tabla de Coeficientes")
642
+ pareto_completo_output = gr.Plot(label="Pareto Modelo Completo")
643
+ model_completo_output2 = gr.HTML(label="Tabla de ANOVA")
644
  gr.Markdown("**Modelo Simplificado**")
645
+ with gr.Row():
646
+ model_simplificado_output = gr.HTML(label="Tabla de Coeficientes")
647
+ pareto_simplificado_output = gr.Plot(label="Pareto Modelo Simplificado")
648
+ equation_output = gr.HTML(label="Ecuación del Modelo Simplificado")
649
+ optimization_table_output = gr.Dataframe(label="Tabla de Optimización")
650
  prediction_table_output = gr.Dataframe(label="Tabla de Predicciones")
651
  contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución")
652
  anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada")
653
+
654
+ # Botones de descarga
655
+ download_excel_button = gr.HTML("Descargar Excel")
656
+ download_all_images_button = gr.HTML("Descargar Todos los Gráficos")
657
+
658
  with gr.Column():
659
+ gr.Markdown("## Gráficos de Superficie de Respuesta")
 
 
660
  with gr.Row():
661
+ plot_index_slider = gr.Number(label="Índice del Gráfico", value=0, step=1, minimum=0, maximum=8)
662
+ previous_plot_button = gr.Button("Anterior")
663
+ next_plot_button = gr.Button("Siguiente")
664
+ rsm_plot_output = gr.Plot(label="Superficie de Respuesta")
665
+ download_image_button = gr.HTML("Descargar Gráfico")
 
 
666
 
667
+ # Funcionalidad de los botones
668
  load_button.click(
669
  load_data,
670
  inputs=[x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, data_input],
671
  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]
672
  )
673
 
674
+ fit_button.click(
675
+ fit_and_optimize_model,
676
+ outputs=[model_completo_output1, pareto_completo_output, model_completo_output2, model_simplificado_output, pareto_simplificado_output, equation_output, optimization_table_output, prediction_table_output, contribution_table_output, anova_table_output, rsm_plots, plot_index_slider]
677
+ )
678
 
679
+ previous_plot_button.click(
680
+ generate_rsm_plot,
681
+ inputs=[plot_index_slider],
682
+ outputs=[rsm_plot_output, plot_index_slider]
683
+ ).then(lambda x: x - 1 if x > 0 else x, plot_index_slider, plot_index_slider)
684
+
685
+ next_plot_button.click(
686
+ generate_rsm_plot,
687
+ inputs=[plot_index_slider],
688
+ outputs=[rsm_plot_output, plot_index_slider]
689
+ ).then(lambda x: x + 1 if x < 8 else x, plot_index_slider, plot_index_slider)
690
+
691
+ plot_index_slider.change(
692
+ generate_rsm_plot,
693
+ inputs=[plot_index_slider],
694
+ outputs=[rsm_plot_output, plot_index_slider]
695
+ )
696
 
697
  download_excel_button.click(download_excel, outputs=[download_excel_button])
698
+ download_image_button.click(download_selected_image, inputs=[plot_index_slider], outputs=[download_image_button])
699
+ download_all_images_button.click(download_all_images, outputs=[download_all_images_button])
 
 
 
 
700
 
701
  # Ejemplo de uso
702
  gr.Markdown("## Ejemplo de uso")
 
704
  gr.Markdown("2. Copia y pega los datos del experimento en la caja de texto 'Datos del Experimento'.")
705
  gr.Markdown("3. Haz clic en 'Cargar Datos' para cargar los datos en la tabla.")
706
  gr.Markdown("4. Haz clic en 'Ajustar Modelo y Optimizar' para ajustar el modelo y encontrar los niveles óptimos de los factores.")
707
+ gr.Markdown("5. Navega por los gráficos de superficie de respuesta usando los botones 'Anterior' y 'Siguiente' o el control deslizante.")
708
+ gr.Markdown("6. Haz clic en 'Descargar Excel' para descargar un archivo Excel con todas las tablas.")
709
+ gr.Markdown("7. Haz clic en 'Descargar Gráfico' para descargar la imagen del gráfico actual.")
710
+ 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.")
 
 
711
 
712
  demo.launch()