C2MV commited on
Commit
58ad40d
·
verified ·
1 Parent(s): c281624

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +224 -28
app.py CHANGED
@@ -14,7 +14,6 @@ from datetime import datetime
14
  import docx
15
  from docx.shared import Inches, Pt
16
  from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
17
- from matplotlib.colors import to_hex
18
  import os
19
 
20
  # --- Data definition in global scope ---
@@ -60,7 +59,77 @@ class RSM_BoxBehnken:
60
  self.x2_levels = x2_levels
61
  self.x3_levels = x3_levels
62
 
63
- # ... (previous methods like get_levels, get_units, coded_to_natural, natural_to_coded, pareto_chart, get_simplified_equation, generate_prediction_table, calculate_contribution_percentage, calculate_detailed_anova, get_all_tables, save_figures_to_zip, save_fig_to_bytes, save_all_figures_png, save_tables_to_excel, export_tables_to_word remain mostly the same)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  def fit_personalized_model(self, formula):
66
  """
@@ -92,14 +161,14 @@ class RSM_BoxBehnken:
92
  for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
93
  for level in levels_to_plot_natural[fixed_variable]:
94
  fig_full = self.plot_rsm_individual(fixed_variable, level, model_type='full') # Pass model_type
95
- if fig_full:
96
  self.all_figures_full.append(fig_full)
97
  fig_simplified = self.plot_rsm_individual(fixed_variable, level, model_type='simplified') # Pass model_type
98
- if fig_simplified:
99
  self.all_figures_simplified.append(fig_simplified)
100
  if self.model_personalized is not None: # Generate personalized plots only if model exists
101
  fig_personalized = self.plot_rsm_individual(fixed_variable, level, model_type='personalized') # Pass model_type
102
- if fig_personalized:
103
  self.all_figures_personalized.append(fig_personalized)
104
 
105
  def plot_rsm_individual(self, fixed_variable, fixed_level, model_type='simplified'): # Added model_type parameter
@@ -122,8 +191,101 @@ class RSM_BoxBehnken:
122
  print(f"Error: Ajusta el modelo {model_type} primero.") # More informative error message
123
  return None
124
 
125
- # ... (rest of the plot_rsm_individual method remains similar, but use model_to_use and model_title_suffix)
126
- # ... (Make sure to update title to include model_title_suffix)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  fig.update_layout(
128
  scene=dict(
129
  xaxis_title=f"{varying_variables[0]} ({self.get_units(varying_variables[0])})",
@@ -141,9 +303,20 @@ class RSM_BoxBehnken:
141
  # --- Funciones para la Interfaz de Gradio ---
142
 
143
  def load_data(data_str):
144
- # ... (load_data function remains the same)
 
 
 
 
 
 
 
 
 
 
145
  return data.round(3), gr.update(visible=True)
146
 
 
147
  def fit_and_optimize_model():
148
  if 'rsm' not in globals():
149
  return [None]*11
@@ -168,7 +341,7 @@ def fit_and_optimize_model():
168
  return (
169
  model_completo.summary().as_html(),
170
  pareto_completo,
171
- model_simplificado.summary().as_html(),
172
  pareto_simplificado,
173
  equation_formatted,
174
  optimization_table,
@@ -179,9 +352,9 @@ def fit_and_optimize_model():
179
  excel_path
180
  )
181
 
182
- def fit_custom_model(factor_checkboxes, interaction_checkboxes): # New function for custom model
183
  if 'rsm' not in globals():
184
- return [None]*3 # Adjust output number
185
 
186
  formula_parts = [rsm.x1_name, rsm.x2_name, rsm.x3_name] if "factors" in factor_checkboxes else []
187
  if "x1_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x1_name}**2)')
@@ -199,7 +372,8 @@ def fit_custom_model(factor_checkboxes, interaction_checkboxes): # New function
199
  custom_model, pareto_custom = rsm.fit_personalized_model(formula) # Fit personalized model
200
  rsm.generate_all_plots() # Regenerate plots to include personalized model plots
201
 
202
- return custom_model.summary().as_html(), pareto_custom, rsm.all_figures_personalized # Return custom model summary, pareto, and personalized plots
 
203
 
204
  def show_plot(current_index, all_figures, model_type): # Modified to accept model_type
205
  figure_list = []
@@ -275,6 +449,29 @@ def download_all_plots_zip(model_type): # Modified to accept model_type
275
  return zip_path
276
  return None
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  # --- Crear la interfaz de Gradio ---
279
 
280
  def create_gradio_interface():
@@ -338,6 +535,7 @@ def create_gradio_interface():
338
  all_figures_state = gr.State([])
339
  current_model_type_state = gr.State('simplified') # State to track selected model type for plots
340
 
 
341
  # Cargar datos
342
  load_button.click(
343
  load_data,
@@ -367,45 +565,42 @@ def create_gradio_interface():
367
  # Ajustar modelo personalizado
368
  custom_model_button.click( # New event for custom model fitting
369
  fit_custom_model,
370
- inputs=[factor_checkboxes, interaction_checkboxes],
371
- outputs=[model_personalized_output, pareto_personalized_output, all_figures_state] # Output personalized plots to state
372
  )
373
 
 
374
  # Generar y mostrar los gráficos
375
  plot_button.click(
376
- lambda fixed_var, fixed_lvl, model_type: ( # Added model_type input
377
- show_plot(0, [], model_type) if not hasattr(rsm, 'all_figures_full') or not rsm.all_figures_full else show_plot(0, rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized if model_type == 'personalized' else [], model_type) , # Conditional plot selection
378
- 0,
379
- model_type # Update model_type state
380
- ),
381
- inputs=[fixed_variable_input, fixed_level_input, model_type_radio], # Added model_type_radio input
382
- outputs=[rsm_plot_output, plot_info, current_index_state, current_model_type_state] # Output model_type to state
383
  )
384
 
385
 
386
  # Navegación de gráficos
387
  left_button.click(
388
- lambda current_index, all_figures, model_type: navigate_plot('left', current_index, all_figures, model_type), # Pass model_type
389
- inputs=[current_index_state, all_figures_state, current_model_type_state], # Input model_type state
390
  outputs=[rsm_plot_output, plot_info, current_index_state]
391
  )
392
  right_button.click(
393
- lambda current_index, all_figures, model_type: navigate_plot('right', current_index, all_figures, model_type), # Pass model_type
394
- inputs=[current_index_state, all_figures_state, current_model_type_state], # Input model_type state
395
  outputs=[rsm_plot_output, plot_info, current_index_state]
396
  )
397
 
398
  # Descargar gráfico actual
399
  download_plot_button.click(
400
  download_current_plot,
401
- inputs=[all_figures_state, current_index_state, current_model_type_state], # Pass model_type state
402
  outputs=download_plot_button
403
  )
404
 
405
  # Descargar todos los gráficos en ZIP
406
  download_all_plots_button.click(
407
- lambda model_type: download_all_plots_zip(model_type), # Pass model_type
408
- inputs=[current_model_type_state], # Input model_type state
409
  outputs=download_all_plots_button
410
  )
411
 
@@ -422,6 +617,7 @@ def create_gradio_interface():
422
  outputs=download_word_button
423
  )
424
 
 
425
  # Ejemplo de uso
426
  gr.Markdown("## Instrucciones:")
427
  gr.Markdown("""
 
14
  import docx
15
  from docx.shared import Inches, Pt
16
  from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
 
17
  import os
18
 
19
  # --- Data definition in global scope ---
 
59
  self.x2_levels = x2_levels
60
  self.x3_levels = x3_levels
61
 
62
+ def get_levels(self, variable_name):
63
+ """
64
+ Obtiene los niveles para una variable específica.
65
+ """
66
+ if variable_name == self.x1_name:
67
+ return self.x1_levels
68
+ elif variable_name == self.x2_name:
69
+ return self.x2_levels
70
+ elif variable_name == self.x3_name:
71
+ return self.x3_levels
72
+ else:
73
+ raise ValueError(f"Variable desconocida: {variable_name}")
74
+
75
+ def fit_model(self):
76
+ """
77
+ Ajusta el modelo de segundo orden completo a los datos.
78
+ """
79
+ formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
80
+ f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + ' \
81
+ f'{self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}'
82
+ self.model = smf.ols(formula, data=self.data).fit()
83
+ print("Modelo Completo:")
84
+ print(self.model.summary())
85
+ return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo")
86
+
87
+ def fit_simplified_model(self):
88
+ """
89
+ Ajusta el modelo de segundo orden a los datos, eliminando términos no significativos.
90
+ """
91
+ formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + ' \
92
+ f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)' # Adjusted formula to include x3^2
93
+ self.model_simplified = smf.ols(formula, data=self.data).fit()
94
+ print("\nModelo Simplificado:")
95
+ print(self.model_simplified.summary())
96
+ return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado")
97
+
98
+ def optimize(self, method='Nelder-Mead'):
99
+ """
100
+ Encuentra los niveles óptimos de los factores para maximizar la respuesta usando el modelo simplificado.
101
+ """
102
+ if self.model_simplified is None:
103
+ print("Error: Ajusta el modelo simplificado primero.")
104
+ return
105
+
106
+ def objective_function(x):
107
+ return -self.model_simplified.predict(pd.DataFrame({
108
+ self.x1_name: [x[0]],
109
+ self.x2_name: [x[1]],
110
+ self.x3_name: [x[2]]
111
+ })).values[0]
112
+
113
+ bounds = [(-1, 1), (-1, 1), (-1, 1)]
114
+ x0 = [0, 0, 0]
115
+
116
+ self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
117
+ self.optimal_levels = self.optimized_results.x
118
+
119
+ # Convertir niveles óptimos de codificados a naturales
120
+ optimal_levels_natural = [
121
+ self.coded_to_natural(self.optimal_levels[0], self.x1_name),
122
+ self.coded_to_natural(self.optimal_levels[1], self.x2_name),
123
+ self.coded_to_natural(self.optimal_levels[2], self.x3_name)
124
+ ]
125
+ # Crear la tabla de optimización
126
+ optimization_table = pd.DataFrame({
127
+ 'Variable': [self.x1_name, self.x2_name, self.x3_name],
128
+ 'Nivel Óptimo (Natural)': optimal_levels_natural,
129
+ 'Nivel Óptimo (Codificado)': self.optimal_levels
130
+ })
131
+
132
+ return optimization_table.round(3) # Redondear a 3 decimales
133
 
134
  def fit_personalized_model(self, formula):
135
  """
 
161
  for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
162
  for level in levels_to_plot_natural[fixed_variable]:
163
  fig_full = self.plot_rsm_individual(fixed_variable, level, model_type='full') # Pass model_type
164
+ if fig_full is not None:
165
  self.all_figures_full.append(fig_full)
166
  fig_simplified = self.plot_rsm_individual(fixed_variable, level, model_type='simplified') # Pass model_type
167
+ if fig_simplified is not None:
168
  self.all_figures_simplified.append(fig_simplified)
169
  if self.model_personalized is not None: # Generate personalized plots only if model exists
170
  fig_personalized = self.plot_rsm_individual(fixed_variable, level, model_type='personalized') # Pass model_type
171
+ if fig_personalized is not None:
172
  self.all_figures_personalized.append(fig_personalized)
173
 
174
  def plot_rsm_individual(self, fixed_variable, fixed_level, model_type='simplified'): # Added model_type parameter
 
191
  print(f"Error: Ajusta el modelo {model_type} primero.") # More informative error message
192
  return None
193
 
194
+ # Determinar las variables que varían y sus niveles naturales
195
+ varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
196
+
197
+ # Establecer los niveles naturales para las variables que varían
198
+ x_natural_levels = self.get_levels(varying_variables[0])
199
+ y_natural_levels = self.get_levels(varying_variables[1])
200
+
201
+ # Crear una malla de puntos para las variables que varían (en unidades naturales)
202
+ x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
203
+ y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
204
+ x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
205
+
206
+ # Convertir la malla de variables naturales a codificadas
207
+ x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
208
+ y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
209
+
210
+ # Crear un DataFrame para la predicción con variables codificadas
211
+ prediction_data = pd.DataFrame({
212
+ varying_variables[0]: x_grid_coded.flatten(),
213
+ varying_variables[1]: y_grid_coded.flatten(),
214
+ })
215
+ prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable)
216
+
217
+ # Fijar la variable fija en el DataFrame de predicción
218
+ fixed_var_levels = self.get_levels(fixed_variable)
219
+ if len(fixed_var_levels) == 3: # Box-Behnken design levels
220
+ prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable)
221
+ elif len(fixed_var_levels) > 0: # Use the closest level if not Box-Behnken
222
+ closest_level_coded = self.natural_to_coded(min(fixed_var_levels, key=lambda x:abs(x-fixed_level)), fixed_variable)
223
+ prediction_data[fixed_variable] = closest_level_coded
224
+
225
+
226
+ # Calcular los valores predichos
227
+ z_pred = model_to_use.predict(prediction_data).values.reshape(x_grid_coded.shape) # Use model_to_use here
228
+
229
+ # Filtrar por el nivel de la variable fija (en codificado)
230
+ fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
231
+ subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
232
+
233
+ # Filtrar por niveles válidos en las variables que varían
234
+ valid_levels = [-1, 0, 1]
235
+ experiments_data = subset_data[
236
+ subset_data[varying_variables[0]].isin(valid_levels) &
237
+ subset_data[varying_variables[1]].isin(valid_levels)
238
+ ]
239
+
240
+ # Convertir coordenadas de experimentos a naturales
241
+ experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
242
+ experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
243
+
244
+ # Crear el gráfico de superficie con variables naturales en los ejes y transparencia
245
+ fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)])
246
+
247
+ # --- Añadir cuadrícula a la superficie ---
248
+ # Líneas en la dirección x
249
+ for i in range(x_grid_natural.shape[0]):
250
+ fig.add_trace(go.Scatter3d(
251
+ x=x_grid_natural[i, :],
252
+ y=y_grid_natural[i, :],
253
+ z=z_pred[i, :],
254
+ mode='lines',
255
+ line=dict(color='gray', width=2),
256
+ showlegend=False,
257
+ hoverinfo='skip'
258
+ ))
259
+ # Líneas en la dirección y
260
+ for j in range(x_grid_natural.shape[1]):
261
+ fig.add_trace(go.Scatter3d(
262
+ x=x_grid_natural[:, j],
263
+ y=y_grid_natural[:, j],
264
+ z=z_pred[:, j],
265
+ mode='lines',
266
+ line=dict(color='gray', width=2),
267
+ showlegend=False,
268
+ hoverinfo='skip'
269
+ ))
270
+
271
+ # --- Fin de la adición de la cuadrícula ---
272
+
273
+ # Añadir los puntos de los experimentos en la superficie de respuesta con diferentes colores y etiquetas
274
+ colors = px.colors.qualitative.Safe
275
+ point_labels = [f"{row[self.y_name]:.3f}" for _, row in experiments_data.iterrows()]
276
+
277
+ fig.add_trace(go.Scatter3d(
278
+ x=experiments_x_natural,
279
+ y=experiments_y_natural,
280
+ z=experiments_data[self.y_name].round(3),
281
+ mode='markers+text',
282
+ marker=dict(size=4, color=colors[:len(experiments_x_natural)]),
283
+ text=point_labels,
284
+ textposition='top center',
285
+ name='Experimentos'
286
+ ))
287
+
288
+ # Añadir etiquetas y título con variables naturales
289
  fig.update_layout(
290
  scene=dict(
291
  xaxis_title=f"{varying_variables[0]} ({self.get_units(varying_variables[0])})",
 
303
  # --- Funciones para la Interfaz de Gradio ---
304
 
305
  def load_data(data_str):
306
+ global rsm, data
307
+
308
+ x1_name = "Glucosa_g_L"
309
+ x2_name = "Proteina_Pescado_g_L"
310
+ x3_name = "Sulfato_Manganeso_g_L"
311
+ y_name = "Abs_600nm"
312
+ x1_levels = sorted(list(set(data[x1_name])))
313
+ x2_levels = sorted(list(set(data[x2_name])))
314
+ x3_levels = sorted(list(set(data[x3_name])))
315
+
316
+ rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
317
  return data.round(3), gr.update(visible=True)
318
 
319
+
320
  def fit_and_optimize_model():
321
  if 'rsm' not in globals():
322
  return [None]*11
 
341
  return (
342
  model_completo.summary().as_html(),
343
  pareto_completo,
344
+ model_simplificado_output, # output_components are correctly referenced now
345
  pareto_simplificado,
346
  equation_formatted,
347
  optimization_table,
 
352
  excel_path
353
  )
354
 
355
+ def fit_custom_model(factor_checkboxes, interaction_checkboxes, model_personalized_output_component, pareto_personalized_output_component):
356
  if 'rsm' not in globals():
357
+ return [None]*2 # adjust output number
358
 
359
  formula_parts = [rsm.x1_name, rsm.x2_name, rsm.x3_name] if "factors" in factor_checkboxes else []
360
  if "x1_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x1_name}**2)')
 
372
  custom_model, pareto_custom = rsm.fit_personalized_model(formula) # Fit personalized model
373
  rsm.generate_all_plots() # Regenerate plots to include personalized model plots
374
 
375
+ return custom_model.summary().as_html(), pareto_custom # return values for outputs
376
+
377
 
378
  def show_plot(current_index, all_figures, model_type): # Modified to accept model_type
379
  figure_list = []
 
449
  return zip_path
450
  return None
451
 
452
+ def download_all_tables_excel():
453
+ """
454
+ Descarga todas las tablas en un archivo Excel con múltiples hojas.
455
+ """
456
+ if 'rsm' not in globals():
457
+ return None
458
+ excel_path = rsm.save_tables_to_excel()
459
+ if excel_path:
460
+ filename = f"Tablas_RSM_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
461
+ # Gradio no permite renombrar directamente, por lo que retornamos la ruta del archivo
462
+ return excel_path
463
+ return None
464
+
465
+ def exportar_word(rsm_instance, tables_dict):
466
+ """
467
+ Función para exportar las tablas a un documento de Word.
468
+ """
469
+ word_path = rsm_instance.export_tables_to_word(tables_dict)
470
+ if word_path and os.path.exists(word_path):
471
+ return word_path
472
+ return None
473
+
474
+
475
  # --- Crear la interfaz de Gradio ---
476
 
477
  def create_gradio_interface():
 
535
  all_figures_state = gr.State([])
536
  current_model_type_state = gr.State('simplified') # State to track selected model type for plots
537
 
538
+
539
  # Cargar datos
540
  load_button.click(
541
  load_data,
 
565
  # Ajustar modelo personalizado
566
  custom_model_button.click( # New event for custom model fitting
567
  fit_custom_model,
568
+ inputs=[factor_checkboxes, interaction_checkboxes, model_personalized_output, pareto_personalized_output], # pass output components
569
+ outputs=[model_personalized_output, pareto_personalized_output] # return values for outputs
570
  )
571
 
572
+
573
  # Generar y mostrar los gráficos
574
  plot_button.click(
575
+ lambda fixed_var, fixed_lvl, model_type: show_plot(0, [], model_type) if not hasattr(rsm, 'all_figures_full') or not rsm.all_figures_full else show_plot(0, [], model_type) if model_type == 'full' and not rsm.all_figures_full else show_plot(0, [], model_type) if model_type == 'simplified' and not rsm.all_figures_simplified else show_plot(0, [], model_type) if model_type == 'personalized' and not rsm.all_figures_personalized else show_plot(0, rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized, model_type),
576
+ inputs=[fixed_variable_input, fixed_level_input, model_type_radio],
577
+ outputs=[rsm_plot_output, plot_info, current_index_state, current_model_type_state]
 
 
 
 
578
  )
579
 
580
 
581
  # Navegación de gráficos
582
  left_button.click(
583
+ lambda current_index, all_figures, model_type: navigate_plot('left', current_index, all_figures, model_type),
584
+ inputs=[current_index_state, all_figures_state, current_model_type_state],
585
  outputs=[rsm_plot_output, plot_info, current_index_state]
586
  )
587
  right_button.click(
588
+ lambda current_index, all_figures, model_type: navigate_plot('right', current_index, all_figures, model_type),
589
+ inputs=[current_index_state, all_figures_state, current_model_type_state],
590
  outputs=[rsm_plot_output, plot_info, current_index_state]
591
  )
592
 
593
  # Descargar gráfico actual
594
  download_plot_button.click(
595
  download_current_plot,
596
+ inputs=[all_figures_state, current_index_state, current_model_type_state],
597
  outputs=download_plot_button
598
  )
599
 
600
  # Descargar todos los gráficos en ZIP
601
  download_all_plots_button.click(
602
+ lambda model_type: download_all_plots_zip(model_type),
603
+ inputs=[current_model_type_state],
604
  outputs=download_all_plots_button
605
  )
606
 
 
617
  outputs=download_word_button
618
  )
619
 
620
+
621
  # Ejemplo de uso
622
  gr.Markdown("## Instrucciones:")
623
  gr.Markdown("""