C2MV commited on
Commit
30bca94
verified
1 Parent(s): c121bf6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +427 -363
app.py CHANGED
@@ -16,312 +16,410 @@ 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 ---
20
- data_dict = {
21
- 'Tratamiento': ['T1', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7', 'T8', 'T9', 'T10', 'T11', 'T12', 'T13', 'T14', 'T15'] * 3,
22
- 'Tiempo_fermentacion_h': [16] * 15 + [23] * 15 + [40] * 15,
23
- 'pH': [6.02, 5.39, 6.27, 4.82, 6.25, 4.87, 4.76, 4.68, 4.64, 6.35, 4.67, 6.43, 4.58, 4.60, 6.96,
24
- 5.17, 5.95, 6.90, 5.50, 5.08, 4.95, 5.41, 5.52, 4.98, 7.10, 5.36, 6.91, 5.21, 4.66, 7.10,
25
- 5.42, 5.60, 7.36, 5.36, 4.66, 4.93, 5.18, 5.26, 4.92, 7.28, 5.26, 6.84, 5.19, 4.58, 7.07],
26
- 'Abs_600nm': [1.576, 1.474, 1.293, 1.446, 1.537, 1.415, 1.481, 1.419, 1.321, 1.224, 1.459, 0.345, 1.279, 1.181, 0.662,
27
- 1.760, 1.690, 1.485, 1.658, 1.728, 1.594, 1.673, 1.607, 1.531, 1.424, 1.595, 0.344, 1.477, 1.257, 0.660,
28
- 1.932, 1.780, 1.689, 1.876, 1.885, 1.824, 1.913, 1.810, 1.852, 1.694, 1.831, 0.347, 1.752, 1.367, 0.656],
29
- 'Glucosa_g_L': [5,10,0,5,10,5,10,5,10,0,5,0,5,5,0] * 3,
30
- 'Proteina_Pescado_g_L': [1.4,1.4,3.2,3.2,3.2,3.2,3.2,5,5,5,5,0,5,5,0] * 3,
31
- 'Sulfato_Manganeso_g_L': [0.75,0.5,0.75,0.5,0.75,0.5,0.75,0.5,0.25,0.75,0.5,0.25,0.5,0.25,0.5] * 3
32
- }
33
- data = pd.DataFrame(data_dict)
34
- # --- End of data definition in global scope ---
 
 
 
 
 
35
 
36
 
37
  # --- Clase RSM_BoxBehnken ---
38
  class RSM_BoxBehnken:
39
  def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels):
40
- """
41
- Inicializa la clase con los datos del dise帽o Box-Behnken.
42
- """
43
  self.data = data.copy()
44
  self.model = None
45
  self.model_simplified = None
46
- self.model_personalized = None # For personalized model
47
  self.optimized_results = None
48
  self.optimal_levels = None
49
- self.all_figures_full = [] # Separate lists for different model plots
50
  self.all_figures_simplified = []
51
  self.all_figures_personalized = []
52
  self.x1_name = x1_name
53
  self.x2_name = x2_name
54
  self.x3_name = x3_name
55
  self.y_name = y_name
56
-
57
- # Niveles originales de las variables
58
  self.x1_levels = x1_levels
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
- """
136
- Ajusta un modelo personalizado de segundo orden a los datos, usando la formula dada.
137
- """
138
  self.model_personalized = smf.ols(formula, data=self.data).fit()
139
- print("\nModelo Personalizado:")
140
- print(self.model_personalized.summary())
141
  return self.model_personalized, self.pareto_chart(self.model_personalized, "Pareto - Modelo Personalizado")
142
 
143
  def generate_all_plots(self):
144
- """
145
- Genera todas las gr谩ficas de RSM para todos los modelos.
146
- """
147
- if self.model_simplified is None:
148
- print("Error: Ajusta el modelo simplificado primero.")
149
- return
150
-
151
- self.all_figures_full = [] # Reset lists for each model type
152
  self.all_figures_simplified = []
153
  self.all_figures_personalized = []
154
-
155
- levels_to_plot_natural = { # Levels from data, as before
156
- self.x1_name: sorted(list(set(self.data[self.x1_name]))),
157
- self.x2_name: sorted(list(set(self.data[self.x2_name]))),
158
- self.x3_name: sorted(list(set(self.data[self.x3_name])))
159
- }
160
-
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
175
- """
176
- Genera un gr谩fico de superficie de respuesta (RSM) individual para una configuraci贸n espec铆fica y modelo.
177
- """
178
- model_to_use = self.model_simplified # Default to simplified model
179
- model_title_suffix = "(Modelo Simplificado)"
180
- if model_type == 'full':
181
- model_to_use = self.model
182
- model_title_suffix = "(Modelo Completo)"
183
- elif model_type == 'personalized':
184
- if self.model_personalized is None:
185
- print("Error: Modelo personalizado no ajustado.")
186
- return None
187
- model_to_use = self.model_personalized
188
- model_title_suffix = "(Modelo Personalizado)"
189
-
190
- if model_to_use is None: # Use model_to_use instead of self.model_simplified
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_range_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])})",
292
- yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})",
293
- zaxis_title=self.y_name,
294
- ),
295
- title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.3f} ({self.get_units(fixed_variable)}) {model_title_suffix}</sup>", # Updated title
296
- height=800,
297
- width=1000,
298
- showlegend=True
299
- )
 
300
  return fig
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
  # --- Funciones para la Interfaz de Gradio ---
304
 
305
- model_completo_output = gr.HTML()
306
- pareto_completo_output = gr.Plot()
307
- model_simplificado_output = gr.HTML()
308
- pareto_simplificado_output = gr.Plot()
309
- equation_output = gr.HTML()
310
- optimization_table_output = gr.Dataframe(label="Tabla de Optimizaci贸n", interactive=False)
311
- prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False)
312
- contribution_table_output = gr.Dataframe(label="Tabla de % de Contribuci贸n", interactive=False)
313
- anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False)
314
- download_all_plots_button = gr.DownloadButton("Descargar Todos los Gr谩ficos (ZIP)")
315
- download_excel_button = gr.DownloadButton("Descargar Tablas en Excel")
316
- rsm_plot_output = gr.Plot()
317
- plot_info = gr.Textbox(label="Informaci贸n del Gr谩fico", value="Gr谩fico 1 de 9", interactive=False)
318
- current_index_state = gr.State(0)
319
- all_figures_state = gr.State([])
320
- current_model_type_state = gr.State('simplified')
321
- model_personalized_output = gr.HTML() # Output for personalized model summary
322
- pareto_personalized_output = gr.Plot() # Pareto for personalized model
323
- factor_checkboxes = gr.CheckboxGroup(["factors", "x1_sq", "x2_sq", "x3_sq"], label="T茅rminos de Factores", value=["factors", "x1_sq", "x2_sq", "x3_sq"]) # Factor Checkboxes
324
- interaction_checkboxes = gr.CheckboxGroup(["x1x2", "x1x3", "x2x3"], label="T茅rminos de Interacci贸n") # Interaction Checkboxes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
 
326
 
327
  def create_gradio_interface():
@@ -332,10 +430,62 @@ def create_gradio_interface():
332
 
333
  with gr.Row():
334
  with gr.Column():
335
- gr.Markdown("## Configuraci贸n del An谩lisis")
336
- data_input = gr.Textbox(label="Datos del Experimento (formato CSV - Ignored, Data is Hardcoded)", lines=5, interactive=False, value="Data is pre-loaded, ignore input.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  load_button = gr.Button("Cargar Datos")
338
- data_dropdown = gr.Dropdown(["All Data"], value="All Data", label="Seleccionar Datos") # Data Selection Dropdown - currently only 'All Data'
339
 
340
  with gr.Column():
341
  gr.Markdown("## Datos Cargados")
@@ -343,151 +493,65 @@ def create_gradio_interface():
343
 
344
  with gr.Row(visible=False) as analysis_row:
345
  with gr.Column():
346
- fit_button = gr.Button("Ajustar Modelo Simplificado y Completo") # Button label changed
347
  gr.Markdown("**Modelo Completo**")
348
- model_completo_output = model_completo_output # gr.HTML() # output_components are now global
349
- pareto_completo_output = pareto_completo_output # gr.Plot()
350
  gr.Markdown("**Modelo Simplificado**")
351
- model_simplificado_output = model_simplificado_output # gr.HTML()
352
- pareto_simplificado_output = pareto_simplificado_output # gr.Plot()
353
 
354
- gr.Markdown("## Modelo Personalizado") # Personalized Model Section
355
- factor_checkboxes_comp = factor_checkboxes # gr.CheckboxGroup(["factors", "x1_sq", "x2_sq", "x3_sq"], label="T茅rminos de Factores", value=["factors", "x1_sq", "x2_sq", "x3_sq"]) # Factor Checkboxes
356
- interaction_checkboxes_comp = interaction_checkboxes # gr.CheckboxGroup(["x1x2", "x1x3", "x2x3"], label="T茅rminos de Interacci贸n") # Interaction Checkboxes
357
- custom_model_button = gr.Button("Ajustar Modelo Personalizado") # Fit Custom Model Button
358
- model_personalized_output_comp = model_personalized_output # gr.HTML() # Output for personalized model summary
359
- pareto_personalized_output_comp = pareto_personalized_output # gr.Plot() # Pareto for personalized model
360
 
361
  gr.Markdown("**Ecuaci贸n del Modelo Simplificado**")
362
- equation_output = equation_output # gr.HTML()
363
- optimization_table_output = optimization_table_output # gr.Dataframe(label="Tabla de Optimizaci贸n", interactive=False)
364
- prediction_table_output = prediction_table_output # gr.Dataframe(label="Tabla de Predicciones", interactive=False)
365
- contribution_table_output = contribution_table_output # gr.Dataframe(label="Tabla de % de Contribuci贸n", interactive=False)
366
- anova_table_output = anova_table_output # gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False)
367
 
368
  gr.Markdown("## Descargar Todas las Tablas")
369
- download_excel_button_comp = download_excel_button # gr.DownloadButton("Descargar Tablas en Excel")
370
  download_word_button = gr.DownloadButton("Descargar Tablas en Word")
371
 
372
  with gr.Column():
373
  gr.Markdown("## Gr谩ficos de Superficie de Respuesta")
374
- model_type_radio = gr.Radio(["simplified", "full", "personalized"], value="simplified", label="Tipo de Modelo para Gr谩ficos") # Model Type Radio
375
  fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa_g_L", "Proteina_Pescado_g_L", "Sulfato_Manganeso_g_L"], value="Glucosa_g_L")
376
- fixed_level_input = gr.Slider(label="Nivel de Variable Fija (Natural Units)", minimum=min(data['Glucosa_g_L']), maximum=max(data['Glucosa_g_L']), step=0.1, value=5.0)
377
  plot_button = gr.Button("Generar Gr谩ficos")
378
  with gr.Row():
379
  left_button = gr.Button("<")
380
  right_button = gr.Button(">")
381
- rsm_plot_output = rsm_plot_output # gr.Plot()
382
- plot_info = plot_info # gr.Textbox(label="Informaci贸n del Gr谩fico", value="Gr谩fico 1 de 9", interactive=False)
383
  with gr.Row():
384
- download_plot_button_comp = download_plot_button # gr.DownloadButton("Descargar Gr谩fico Actual (PNG)")
385
- download_all_plots_button_comp = download_all_plots_button # gr.DownloadButton("Descargar Todos los Gr谩ficos (ZIP)")
386
- current_index_state = current_index_state # gr.State(0)
387
- all_figures_state = all_figures_state # gr.State([])
388
- current_model_type_state = current_model_type_state # gr.State('simplified')
389
-
390
-
391
- # Cargar datos
392
- load_button.click(
393
- load_data,
394
- inputs=[data_input],
395
- outputs=[data_output, analysis_row]
396
- )
397
-
398
- # Ajustar modelo y optimizar (Simplified and Full)
399
- fit_button.click(
400
- fit_and_optimize_model,
401
- inputs=[],
402
- outputs=[
403
- model_completo_output,
404
- pareto_completo_output,
405
- model_simplificado_output,
406
- pareto_simplificado_output,
407
- equation_output,
408
- optimization_table_output,
409
- prediction_table_output,
410
- contribution_table_output,
411
- anova_table_output,
412
- download_all_plots_button,
413
- download_excel_button
414
- ]
415
- )
416
-
417
- # Ajustar modelo personalizado
418
- custom_model_button.click( # New event for custom model fitting
419
- fit_custom_model,
420
- inputs=[factor_checkboxes_comp, interaction_checkboxes_comp],
421
- outputs=[model_personalized_output_comp, pareto_personalized_output_comp] # pass output components
422
- )
423
-
424
-
425
- # Generar y mostrar los gr谩ficos
426
- plot_button.click(
427
- 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),
428
- inputs=[fixed_variable_input, fixed_level_input, model_type_radio],
429
- outputs=[rsm_plot_output, plot_info, current_index_state, current_model_type_state]
430
- )
431
-
432
-
433
- # Navegaci贸n de gr谩ficos
434
- left_button.click(
435
- lambda current_index, all_figures, model_type: navigate_plot('left', current_index, all_figures, model_type),
436
- inputs=[current_index_state, all_figures_state, current_model_type_state],
437
- outputs=[rsm_plot_output, plot_info, current_index_state]
438
- )
439
- right_button.click(
440
- lambda current_index, all_figures, model_type: navigate_plot('right', current_index, all_figures, model_type),
441
- inputs=[current_index_state, all_figures_state, current_model_type_state],
442
- outputs=[rsm_plot_output, plot_info, current_index_state]
443
- )
444
-
445
- # Descargar gr谩fico actual
446
- download_plot_button.click(
447
- download_current_plot,
448
- inputs=[all_figures_state, current_index_state, current_model_type_state],
449
- outputs=download_plot_button
450
- )
451
-
452
- # Descargar todos los gr谩ficos en ZIP
453
- download_all_plots_button.click(
454
- lambda model_type: download_all_plots_zip(model_type),
455
- inputs=[current_model_type_state],
456
- outputs=download_all_plots_button
457
- )
458
-
459
- # Descargar todas las tablas en Excel y Word
460
- download_excel_button.click(
461
- fn=lambda: download_all_tables_excel(),
462
- inputs=[],
463
- outputs=download_excel_button
464
- )
465
-
466
- download_word_button.click(
467
- fn=lambda: exportar_word(rsm, rsm.get_all_tables()),
468
- inputs=[],
469
- outputs=download_word_button
470
- )
471
-
472
-
473
- # Ejemplo de uso
474
- gr.Markdown("## Instrucciones:")
475
- gr.Markdown("""
476
- 1. Click 'Cargar Datos' para usar los datos precargados.
477
- 2. Click 'Ajustar Modelo Simplificado y Completo'.
478
- 3. Opcional: Define un Modelo Personalizado seleccionando t茅rminos y haz clic en 'Ajustar Modelo Personalizado'.
479
- 4. Selecciona el 'Tipo de Modelo para Gr谩ficos' (Simplificado, Completo o Personalizado).
480
- 5. Select 'Variable Fija' and 'Nivel de Variable Fija'.
481
- 6. Click 'Generar Gr谩ficos'.
482
- 7. Navega entre los gr谩ficos usando los botones '<' y '>'.
483
- 8. Descarga el gr谩fico actual en PNG o descarga todos los gr谩ficos en un ZIP.
484
- 9. Descarga todas las tablas en un archivo Excel o Word con los botones correspondientes.
485
- """)
486
 
487
  return demo
488
 
489
  # --- Funci贸n Principal ---
490
-
491
  def main():
492
  interface = create_gradio_interface()
493
  interface.launch(share=True)
 
16
  from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
17
  import os
18
 
19
+ # --- Global output components ---
20
+ model_completo_output = gr.HTML()
21
+ pareto_completo_output = gr.Plot()
22
+ model_simplificado_output = gr.HTML()
23
+ pareto_simplificado_output = gr.Plot()
24
+ equation_output = gr.HTML()
25
+ optimization_table_output = gr.Dataframe(label="Tabla de Optimizaci贸n", interactive=False)
26
+ prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False)
27
+ contribution_table_output = gr.Dataframe(label="Tabla de % de Contribuci贸n", interactive=False)
28
+ anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False)
29
+ download_all_plots_button = gr.DownloadButton("Descargar Todos los Gr谩ficos (ZIP)")
30
+ download_excel_button = gr.DownloadButton("Descargar Tablas en Excel")
31
+ rsm_plot_output = gr.Plot()
32
+ plot_info = gr.Textbox(label="Informaci贸n del Gr谩fico", value="Gr谩fico 1 de 9", interactive=False)
33
+ current_index_state = gr.State(0)
34
+ all_figures_state = gr.State([])
35
+ current_model_type_state = gr.State('simplified')
36
+ model_personalized_output = gr.HTML()
37
+ pareto_personalized_output = gr.Plot()
38
+ factor_checkboxes = gr.CheckboxGroup(["factors", "x1_sq", "x2_sq", "x3_sq"], label="T茅rminos de Factores", value=["factors", "x1_sq", "x2_sq", "x3_sq"])
39
+ interaction_checkboxes = gr.CheckboxGroup(["x1x2", "x1x3", "x2x3"], label="T茅rminos de Interacci贸n")
40
 
41
 
42
  # --- Clase RSM_BoxBehnken ---
43
  class RSM_BoxBehnken:
44
  def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels):
 
 
 
45
  self.data = data.copy()
46
  self.model = None
47
  self.model_simplified = None
48
+ self.model_personalized = None
49
  self.optimized_results = None
50
  self.optimal_levels = None
51
+ self.all_figures_full = []
52
  self.all_figures_simplified = []
53
  self.all_figures_personalized = []
54
  self.x1_name = x1_name
55
  self.x2_name = x2_name
56
  self.x3_name = x3_name
57
  self.y_name = y_name
 
 
58
  self.x1_levels = x1_levels
59
  self.x2_levels = x2_levels
60
  self.x3_levels = x3_levels
61
 
62
  def get_levels(self, variable_name):
63
+ levels = {self.x1_name: self.x1_levels, self.x2_name: self.x2_levels, self.x3_name: self.x3_levels}
64
+ return levels.get(variable_name)
 
 
 
 
 
 
 
 
 
65
 
66
  def fit_model(self):
67
+ formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + {self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}'
 
 
 
 
 
68
  self.model = smf.ols(formula, data=self.data).fit()
 
 
69
  return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo")
70
 
71
  def fit_simplified_model(self):
72
+ formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)'
 
 
 
 
73
  self.model_simplified = smf.ols(formula, data=self.data).fit()
 
 
74
  return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado")
75
 
76
  def optimize(self, method='Nelder-Mead'):
77
+ if self.model_simplified is None: return
 
 
 
 
 
 
78
  def objective_function(x):
79
+ return -self.model_simplified.predict(pd.DataFrame({self.x1_name: [x[0]], self.x2_name: [x[1]], self.x3_name: [x[2]]})).values[0]
 
 
 
 
 
80
  bounds = [(-1, 1), (-1, 1), (-1, 1)]
81
  x0 = [0, 0, 0]
 
82
  self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds)
83
+ optimal_levels_natural = [self.coded_to_natural(self.optimal_levels[0], self.x1_name), self.coded_to_natural(self.optimal_levels[1], self.x2_name), self.coded_to_natural(self.optimal_levels[2], self.x3_name)]
84
+ optimization_table = pd.DataFrame({'Variable': [self.x1_name, self.x2_name, self.x3_name], 'Nivel 脫ptimo (Natural)': optimal_levels_natural, 'Nivel 脫ptimo (Codificado)': self.optimal_levels})
85
+ return optimization_table.round(3)
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  def fit_personalized_model(self, formula):
 
 
 
88
  self.model_personalized = smf.ols(formula, data=self.data).fit()
 
 
89
  return self.model_personalized, self.pareto_chart(self.model_personalized, "Pareto - Modelo Personalizado")
90
 
91
  def generate_all_plots(self):
92
+ if self.model_simplified is None: return
93
+ self.all_figures_full = []
 
 
 
 
 
 
94
  self.all_figures_simplified = []
95
  self.all_figures_personalized = []
96
+ levels_to_plot_natural = {self.x1_name: self.x1_levels, self.x2_name: self.x2_levels, self.x3_name: self.x3_levels}
 
 
 
 
 
 
97
  for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]:
98
  for level in levels_to_plot_natural[fixed_variable]:
99
+ fig_full = self.plot_rsm_individual(fixed_variable, level, model_type='full')
100
+ if fig_full: self.all_figures_full.append(fig_full)
101
+ fig_simplified = self.plot_rsm_individual(fixed_variable, level, model_type='simplified')
102
+ if fig_simplified: self.all_figures_simplified.append(fig_simplified)
103
+ if self.model_personalized is not None:
104
+ fig_personalized = self.plot_rsm_individual(fixed_variable, level, model_type='personalized')
105
+ if fig_personalized: self.all_figures_personalized.append(fig_personalized)
106
+
107
+ def plot_rsm_individual(self, fixed_variable, fixed_level, model_type='simplified'):
108
+ model_to_use = self.model_simplified if model_type == 'simplified' else self.model if model_type == 'full' else self.model_personalized
109
+ if model_to_use is None: return None
110
+ model_title_suffix = "(Modelo Simplificado)" if model_type == 'simplified' else "(Modelo Completo)" if model_type == 'full' else "(Modelo Personalizado)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
 
 
112
  x_natural_levels = self.get_levels(varying_variables[0])
113
  y_natural_levels = self.get_levels(varying_variables[1])
 
 
114
  x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100)
115
  y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100)
116
  x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural)
 
 
117
  x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0])
118
+ y_grid_coded = self.natural_to_coded(y_grid_natural, varying_variables[1])
119
+ prediction_data = pd.DataFrame({varying_variables[0]: x_grid_coded.flatten(), varying_variables[1]: y_grid_coded.flatten()})
 
 
 
 
 
120
  prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable)
121
+ z_pred = model_to_use.predict(prediction_data).values.reshape(x_grid_coded.shape)
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable)
123
  subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)]
 
 
124
  valid_levels = [-1, 0, 1]
125
+ experiments_data = subset_data[subset_data[varying_variables[0]].isin(valid_levels) & subset_data[varying_variables[1]].isin(valid_levels)]
 
 
 
 
 
126
  experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0]))
127
  experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1]))
128
 
 
129
  fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)])
 
 
 
130
  for i in range(x_grid_natural.shape[0]):
131
+ fig.add_trace(go.Scatter3d(x=x_grid_natural[i, :], y=y_grid_natural[i, :], z=z_pred[i, :], mode='lines', line=dict(color='gray', width=2), showlegend=False, hoverinfo='skip'))
 
 
 
 
 
 
 
 
 
132
  for j in range(x_grid_natural.shape[1]):
133
+ fig.add_trace(go.Scatter3d(x=x_grid_natural[:, j], y=y_grid_natural[:, j], z=z_pred[:, j], mode='lines', line=dict(color='gray', width=2), showlegend=False, hoverinfo='skip'))
134
+
 
 
 
 
 
 
 
 
 
 
 
135
  colors = px.colors.qualitative.Safe
136
  point_labels = [f"{row[self.y_name]:.3f}" for _, row in experiments_data.iterrows()]
137
+ fig.add_trace(go.Scatter3d(x=experiments_x_natural, y=experiments_y_natural, z=experiments_data[self.y_name].round(3), mode='markers+text', marker=dict(size=4, color=colors[:len(experiments_x_natural)]), text=point_labels, textposition='top center', name='Experimentos'))
138
+ fig.update_layout(scene=dict(xaxis_title=f"{varying_variables[0]} ({self.get_units(varying_variables[0])})", yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})", zaxis_title=self.y_name), title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}<br><sup>{fixed_variable} fijo en {fixed_level:.3f} ({self.get_units(fixed_variable)}) {model_title_suffix}</sup>", height=800, width=1000, showlegend=True)
139
+ return fig
140
 
141
+ def get_units(self, variable_name):
142
+ units = {'Glucosa_g_L': 'g/L', 'Proteina_Pescado_g_L': 'g/L', 'Sulfato_Manganeso_g_L': 'g/L', 'Abs_600nm': ''}
143
+ return units.get(variable_name, '')
144
+
145
+ def coded_to_natural(self, coded_value, variable_name):
146
+ levels = self.get_levels(variable_name)
147
+ return levels[0] + (coded_value + 1) * (levels[-1] - levels[0]) / 2
148
+
149
+ def natural_to_coded(self, natural_value, variable_name):
150
+ levels = self.get_levels(variable_name)
151
+ return -1 + 2 * (natural_value - levels[0]) / (levels[-1] - levels[0])
152
+
153
+ def pareto_chart(self, model, title):
154
+ tvalues = model.tvalues[1:]
155
+ abs_tvalues = np.abs(tvalues)
156
+ sorted_idx = np.argsort(abs_tvalues)[::-1]
157
+ sorted_tvalues = abs_tvalues[sorted_idx]
158
+ sorted_names = tvalues.index[sorted_idx]
159
+ alpha = 0.05
160
+ dof = model.df_resid
161
+ t_critical = t.ppf(1 - alpha / 2, dof)
162
+ fig = px.bar(x=sorted_tvalues.round(3), y=sorted_names, orientation='h', labels={'x': 'Efecto Estandarizado', 'y': 'T茅rmino'}, title=title)
163
+ fig.update_yaxes(autorange="reversed")
164
+ fig.add_vline(x=t_critical, line_dash="dot", annotation_text=f"t cr铆tico = {t_critical:.3f}", annotation_position="bottom right")
165
  return fig
166
 
167
+ def get_simplified_equation(self):
168
+ if self.model_simplified is None: return None
169
+ coefficients = self.model_simplified.params
170
+ equation = f"{self.y_name} = {coefficients['Intercept']:.3f}"
171
+ for term, coef in coefficients.items():
172
+ if term != 'Intercept':
173
+ if term == f'{self.x1_name}': equation += f" + {coef:.3f}*{self.x1_name}"
174
+ elif term == f'{self.x2_name}': equation += f" + {coef:.3f}*{self.x2_name}"
175
+ elif term == f'{self.x3_name}': equation += f" + {coef:.3f}*{self.x3_name}"
176
+ elif term == f'I({self.x1_name} ** 2)': equation += f" + {coef:.3f}*{self.x1_name}^2"
177
+ elif term == f'I({self.x2_name} ** 2)': equation += f" + {coef:.3f}*{self.x2_name}^2"
178
+ elif term == f'I({self.x3_name} ** 2)': equation += f" + {coef:.3f}*{self.x3_name}^2"
179
+ return equation
180
+
181
+ def generate_prediction_table(self):
182
+ if self.model_simplified is None: return None
183
+ self.data['Predicho'] = self.model_simplified.predict(self.data)
184
+ self.data['Residual'] = self.data[self.y_name] - self.data['Predicho']
185
+ return self.data[[self.y_name, 'Predicho', 'Residual']].round(3)
186
+
187
+ def calculate_contribution_percentage(self):
188
+ if self.model_simplified is None: return None
189
+ anova_table = sm.stats.anova_lm(self.model_simplified, typ=2)
190
+ ss_total = anova_table['sum_sq'].sum()
191
+ contribution_table = pd.DataFrame({'Factor': [], 'Suma de Cuadrados': [], '% Contribuci贸n': []})
192
+ for index, row in anova_table.iterrows():
193
+ if index != 'Residual':
194
+ factor_name = index
195
+ if factor_name == f'I({self.x1_name} ** 2)': factor_name = f'{self.x1_name}^2'
196
+ elif factor_name == f'I({self.x2_name} ** 2)': factor_name = f'{self.x2_name}^2'
197
+ elif factor_name == f'I({self.x3_name} ** 2)': factor_name = f'{self.x3_name}^2'
198
+ ss_factor = row['sum_sq']
199
+ contribution_percentage = (ss_factor / ss_total) * 100
200
+ contribution_table = pd.concat([contribution_table, pd.DataFrame({'Factor': [factor_name], 'Suma de Cuadrados': [ss_factor], '% Contribuci贸n': [contribution_percentage]})], ignore_index=True)
201
+ return contribution_table.round(3)
202
+
203
+ def calculate_detailed_anova(self):
204
+ if self.model_simplified is None: return None
205
+ formula_reduced = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)'
206
+ model_reduced = smf.ols(formula_reduced, data=self.data).fit()
207
+ anova_reduced = sm.stats.anova_lm(model_reduced, typ=2)
208
+ ss_total = np.sum((self.data[self.y_name] - self.data[self.y_name].mean())**2)
209
+ df_total = len(self.data) - 1
210
+ ss_regression = anova_reduced['sum_sq'][:-1].sum()
211
+ df_regression = len(anova_reduced) - 1
212
+ ss_residual = self.model_simplified.ssr
213
+ df_residual = self.model_simplified.df_resid
214
+ replicas = self.data[self.data.duplicated(subset=[self.x1_name, self.x2_name, self.x3_name], keep=False)]
215
+ 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 if not replicas.empty else np.nan
216
+ df_pure_error = len(replicas) - replicas.groupby([self.x1_name, self.x2_name, self.x3_name]).ngroups if not replicas.empty else np.nan
217
+ ss_lack_of_fit = ss_residual - ss_pure_error if not np.isnan(ss_pure_error) else np.nan
218
+ df_lack_of_fit = df_residual - df_pure_error if not np.isnan(df_pure_error) else np.nan
219
+ ms_regression = ss_regression / df_regression
220
+ ms_residual = ss_residual / df_residual
221
+ ms_lack_of_fit = np.nan
222
+ if not np.isnan(df_lack_of_fit) and df_lack_of_fit != 0:
223
+ ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit
224
+ ms_pure_error = ss_pure_error / df_pure_error if not np.isnan(df_pure_error) else np.nan
225
+ f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) and not np.isnan(ms_pure_error) and ms_pure_error != 0 else np.nan
226
+ 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) and not np.isnan(df_lack_of_fit) and not np.isnan(df_pure_error) else np.nan
227
+
228
+ detailed_anova_table = pd.DataFrame({
229
+ 'Fuente de Variaci贸n': ['Regresi贸n', 'Curvatura', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'], # Curvature added here
230
+ 'Suma de Cuadrados': [ss_regression, np.nan, ss_residual, ss_lack_of_fit, ss_pure_error, ss_total], # ss_curvature removed from here
231
+ 'Grados de Libertad': [df_regression, np.nan, df_residual, df_lack_of_fit, df_pure_error, df_total], # df_curvature removed from here
232
+ 'Cuadrado Medio': [ms_regression, np.nan, ms_residual, ms_lack_of_fit, ms_pure_error, np.nan],
233
+ 'F': [np.nan, np.nan, np.nan, f_lack_of_fit, np.nan, np.nan],
234
+ 'Valor p': [np.nan, np.nan, np.nan, p_lack_of_fit, np.nan, np.nan]
235
+ })
236
+ 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)']
237
+ df_curvature = 3
238
+ detailed_anova_table.loc[1, ['Fuente de Variaci贸n', 'Suma de Cuadrados', 'Grados de Libertad', 'Cuadrado Medio']] = ['Curvatura', ss_curvature, df_curvature, ss_curvature / df_curvature] # Curvature row added here
239
+
240
+ return detailed_anova_table.round(3)
241
+
242
+ def get_all_tables(self):
243
+ prediction_table = self.generate_prediction_table()
244
+ contribution_table = self.calculate_contribution_percentage()
245
+ detailed_anova_table = self.calculate_detailed_anova()
246
+ return {'Predicciones': prediction_table, '% Contribuci贸n': contribution_table, 'ANOVA Detallada': detailed_anova_table}
247
+
248
+ def save_figures_to_zip(self):
249
+ if not self.all_figures_simplified and not self.all_figures_full and not self.all_figures_personalized: return None
250
+ zip_buffer = io.BytesIO()
251
+ with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
252
+ for idx, fig in enumerate(self.all_figures_simplified, start=1):
253
+ img_bytes = fig.to_image(format="png")
254
+ zip_file.writestr(f'Grafico_Simplificado_{idx}.png', img_bytes)
255
+ for idx, fig in enumerate(self.all_figures_full, start=1):
256
+ img_bytes = fig.to_image(format="png")
257
+ zip_file.writestr(f'Grafico_Completo_{idx}.png', img_bytes)
258
+ for idx, fig in enumerate(self.all_figures_personalized, start=1):
259
+ img_bytes = fig.to_image(format="png")
260
+ zip_file.writestr(f'Grafico_Personalizado_{idx}.png', img_bytes)
261
+ zip_buffer.seek(0)
262
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as temp_file:
263
+ temp_file.write(zip_buffer.read())
264
+ temp_path = temp_file.name
265
+ return temp_path
266
+
267
+ def save_fig_to_bytes(self, fig):
268
+ return fig.to_image(format="png")
269
+
270
+ def save_all_figures_png(self):
271
+ png_paths = []
272
+ for idx, fig in enumerate(self.all_figures_simplified, start=1):
273
+ img_bytes = fig.to_image(format="png")
274
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
275
+ temp_file.write(img_bytes)
276
+ png_paths.append(temp_file.name)
277
+ for idx, fig in enumerate(self.all_figures_full, start=1):
278
+ img_bytes = fig.to_image(format="png")
279
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
280
+ temp_file.write(img_bytes)
281
+ png_paths.append(temp_file.name)
282
+ for idx, fig in enumerate(self.all_figures_personalized, start=1):
283
+ img_bytes = fig.to_image(format="png")
284
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
285
+ temp_file.write(img_bytes)
286
+ png_paths.append(temp_file.name)
287
+ return png_paths
288
+
289
+ def save_tables_to_excel(self):
290
+ tables = self.get_all_tables()
291
+ excel_buffer = io.BytesIO()
292
+ with pd.ExcelWriter(excel_buffer, engine='xlsxwriter') as writer:
293
+ for sheet_name, table in tables.items():
294
+ table.to_excel(writer, sheet_name=sheet_name, index=False)
295
+ excel_buffer.seek(0)
296
+ excel_bytes = excel_buffer.read()
297
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as temp_file:
298
+ temp_file.write(excel_bytes)
299
+ temp_path = temp_file.name
300
+ return temp_path
301
+
302
+ def export_tables_to_word(self, tables_dict):
303
+ if not tables_dict: return None
304
+ doc = docx.Document()
305
+ style = doc.styles['Normal']
306
+ font = style.font
307
+ font.name = 'Times New Roman'
308
+ font.size = Pt(12)
309
+ titulo = doc.add_heading('Informe de Optimizaci贸n de Producci贸n de Absorbancia', 0)
310
+ titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
311
+ doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}").alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
312
+ doc.add_paragraph('\n')
313
+ for sheet_name, table in tables_dict.items():
314
+ doc.add_heading(sheet_name, level=1)
315
+ if table.empty:
316
+ doc.add_paragraph("No hay datos disponibles para esta tabla.")
317
+ continue
318
+ table_doc = doc.add_table(rows=1, cols=len(table.columns))
319
+ table_doc.style = 'Light List Accent 1'
320
+ hdr_cells = table_doc.rows[0].cells
321
+ for idx, col_name in enumerate(table.columns):
322
+ hdr_cells[idx].text = col_name
323
+ for _, row in table.iterrows():
324
+ row_cells = table_doc.add_row().cells
325
+ for idx, item in enumerate(row):
326
+ row_cells[idx].text = str(item)
327
+ doc.add_paragraph('\n')
328
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp:
329
+ doc.save(tmp.name)
330
+ tmp_path = tmp.name
331
+ return tmp_path
332
+
333
 
334
  # --- Funciones para la Interfaz de Gradio ---
335
 
336
+ def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str):
337
+ try:
338
+ x1_levels = [float(x.strip()) for x in x1_levels_str.split(',')]
339
+ x2_levels = [float(x.strip()) for x in x2_levels_str.split(',')]
340
+ x3_levels = [float(x.strip()) for x in x3_levels_str.split(',')]
341
+ data_list = [row.split(',') for row in data_str.strip().split('\n')]
342
+ column_names = ['Exp.', x1_name, x2_name, x3_name, y_name]
343
+ data = pd.DataFrame(data_list, columns=column_names).apply(pd.to_numeric, errors='coerce')
344
+ if not all(col in data.columns for col in column_names): raise ValueError("Data format incorrect.")
345
+ global rsm
346
+ rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels)
347
+ return data.round(3), gr.update(visible=True)
348
+ except Exception as e:
349
+ error_message = f"Error loading data: {str(e)}"
350
+ print(error_message)
351
+ return None, gr.update(visible=False)
352
+
353
+ def fit_and_optimize_model():
354
+ if 'rsm' not in globals(): return [None]*11
355
+ model_completo, pareto_completo = rsm.fit_model()
356
+ model_simplificado, pareto_simplificado = rsm.fit_simplified_model()
357
+ optimization_table = rsm.optimize()
358
+ equation = rsm.get_simplified_equation()
359
+ prediction_table = rsm.generate_prediction_table()
360
+ contribution_table = rsm.calculate_contribution_percentage()
361
+ anova_table = rsm.calculate_detailed_anova()
362
+ rsm.generate_all_plots()
363
+ equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " 脳 ")
364
+ equation_formatted = f"### Ecuaci贸n del Modelo Simplificado:<br>{equation_formatted}"
365
+ excel_path = rsm.save_tables_to_excel()
366
+ zip_path = rsm.save_figures_to_zip()
367
+ 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, zip_path, excel_path)
368
+
369
+ def fit_custom_model(factor_checkboxes, interaction_checkboxes, model_personalized_output_component, pareto_personalized_output_component):
370
+ if 'rsm' not in globals(): return [None]*2
371
+ formula_parts = [rsm.x1_name, rsm.x2_name, rsm.x3_name] if "factors" in factor_checkboxes else []
372
+ if "x1_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x1_name}**2)')
373
+ if "x2_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x2_name}**2)')
374
+ if "x3_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x3_name}**2)')
375
+ if "x1x2" in interaction_checkboxes: formula_parts.append(f'{rsm.x1_name}:{rsm.x2_name}')
376
+ if "x1x3" in interaction_checkboxes: formula_parts.append(f'{rsm.x1_name}:{rsm.x3_name}')
377
+ if "x2x3" in interaction_checkboxes: formula_parts.append(f'{rsm.x2_name}:{rsm.x3_name}')
378
+ formula = f'{rsm.y_name} ~ ' + ' + '.join(formula_parts) if formula_parts else f'{rsm.y_name} ~ 1'
379
+ custom_model, pareto_custom = rsm.fit_personalized_model(formula)
380
+ rsm.generate_all_plots()
381
+ return custom_model.summary().as_html(), pareto_custom
382
+
383
+ def show_plot(current_index, all_figures, model_type):
384
+ figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized
385
+ if not figure_list: return None, f"No graphs for {model_type}.", current_index
386
+ selected_fig = figure_list[current_index]
387
+ plot_info_text = f"Gr谩fico {current_index + 1} de {len(figure_list)} (Modelo {model_type.capitalize()})"
388
+ return selected_fig, plot_info_text, current_index
389
+
390
+ def navigate_plot(direction, current_index, all_figures, model_type):
391
+ figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized
392
+ if not figure_list: return None, f"No graphs for {model_type}.", current_index
393
+ new_index = (current_index - 1) % len(figure_list) if direction == 'left' else (current_index + 1) % len(figure_list)
394
+ selected_fig = figure_list[new_index]
395
+ plot_info_text = f"Gr谩fico {new_index + 1} de {len(figure_list)} (Modelo {model_type.capitalize()})"
396
+ return selected_fig, plot_info_text, new_index
397
+
398
+ def download_current_plot(all_figures, current_index, model_type):
399
+ figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized
400
+ if not figure_list: return None
401
+ fig = figure_list[current_index]
402
+ img_bytes = rsm.save_fig_to_bytes(fig)
403
+ filename = f"Grafico_RSM_{model_type}_{current_index + 1}.png"
404
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
405
+ temp_file.write(img_bytes)
406
+ return temp_file.name
407
+
408
+ def download_all_plots_zip(model_type):
409
+ if 'rsm' not in globals(): return None
410
+ if model_type == 'full': rsm.all_figures = rsm.all_figures_full
411
+ elif model_type == 'simplified': rsm.all_figures = rsm.all_figures_simplified
412
+ elif model_type == 'personalized': rsm.all_figures = rsm.all_figures_personalized
413
+ zip_path = rsm.save_figures_to_zip()
414
+ filename = f"Graficos_RSM_{model_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
415
+ return zip_path
416
+
417
+ def download_all_tables_excel():
418
+ if 'rsm' not in globals(): return None
419
+ return rsm.save_tables_to_excel()
420
+
421
+ def exportar_word(rsm_instance, tables_dict):
422
+ return rsm_instance.export_tables_to_word(tables_dict)
423
 
424
 
425
  def create_gradio_interface():
 
430
 
431
  with gr.Row():
432
  with gr.Column():
433
+ gr.Markdown("## Configuraci贸n del Dise帽o")
434
+ x1_name_input = gr.Textbox(label="Nombre de la Variable X1 (ej. Glucosa)", value="Glucosa_g_L")
435
+ x2_name_input = gr.Textbox(label="Nombre de la Variable X2 (ej. Proteina_Pescado)", value="Proteina_Pescado_g_L")
436
+ x3_name_input = gr.Textbox(label="Nombre de la Variable X3 (ej. Sulfato_Manganeso)", value="Sulfato_Manganeso_g_L")
437
+ y_name_input = gr.Textbox(label="Nombre de la Variable Dependiente (ej. Absorbancia)", value="Abs_600nm")
438
+ x1_levels_input = gr.Textbox(label="Niveles de X1 (separados por comas)", value="0, 5, 10")
439
+ x2_levels_input = gr.Textbox(label="Niveles de X2 (separados por comas)", value="0, 1.4, 3.2, 5")
440
+ x3_levels_input = gr.Textbox(label="Niveles de X3 (separados por comas)", value="0.25, 0.5, 0.75")
441
+ data_input = gr.Textbox(label="Datos del Experimento (formato CSV)", lines=10, value="""Exp.,Glucosa_g_L,Proteina_Pescado_g_L,Sulfato_Manganeso_g_L,Abs_600nm
442
+ 1,-1,-1,0,1.576
443
+ 2,1,-1,0,1.474
444
+ 3,-1,1,0,1.293
445
+ 4,1,1,0,1.446
446
+ 5,-1,0,-1,1.537
447
+ 6,1,0,-1,1.415
448
+ 7,-1,0,1,1.481
449
+ 8,1,0,1,1.419
450
+ 9,0,-1,-1,1.321
451
+ 10,0,1,-1,1.224
452
+ 11,0,-1,1,1.459
453
+ 12,0,1,1,0.345
454
+ 13,0,0,0,1.279
455
+ 14,0,0,0,1.181
456
+ 15,0,0,0,0.662,
457
+ 16,-1,-1,0,1.760
458
+ 17,1,-1,0,1.690
459
+ 18,-1,1,0,1.485
460
+ 19,1,1,0,1.658
461
+ 20,-1,0,-1,1.728
462
+ 21,1,0,-1,1.594
463
+ 22,-1,0,1,1.673
464
+ 23,1,0,1,1.607
465
+ 24,0,-1,-1,1.531
466
+ 25,0,1,-1,1.424
467
+ 26,0,-1,1,1.595
468
+ 27,0,1,1,0.344
469
+ 28,0,0,0,1.477
470
+ 29,0,0,0,1.257
471
+ 30,0,0,0,0.660,
472
+ 31,-1,-1,0,1.932
473
+ 32,1,-1,0,1.780
474
+ 33,-1,1,0,1.689
475
+ 34,1,1,0,1.876
476
+ 35,-1,0,-1,1.885
477
+ 36,1,0,-1,1.824
478
+ 37,-1,0,1,1.913
479
+ 38,1,0,1,1.810
480
+ 39,0,-1,-1,1.852
481
+ 40,0,1,-1,1.694
482
+ 41,0,-1,1,1.831
483
+ 42,0,1,1,0.347
484
+ 43,0,0,0,1.752
485
+ 44,0,0,0,1.367
486
+ 45,0,0,0,0.656""")
487
  load_button = gr.Button("Cargar Datos")
488
+ data_dropdown = gr.Dropdown(["All Data"], value="All Data", label="Seleccionar Datos")
489
 
490
  with gr.Column():
491
  gr.Markdown("## Datos Cargados")
 
493
 
494
  with gr.Row(visible=False) as analysis_row:
495
  with gr.Column():
496
+ fit_button = gr.Button("Ajustar Modelo Simplificado y Completo")
497
  gr.Markdown("**Modelo Completo**")
498
+ model_completo_output_comp = model_completo_output # Use global output_components
499
+ pareto_completo_output_comp = pareto_completo_output
500
  gr.Markdown("**Modelo Simplificado**")
501
+ model_simplificado_output_comp = model_simplificado_output
502
+ pareto_simplificado_output_comp = pareto_simplificado_output
503
 
504
+ gr.Markdown("## Modelo Personalizado")
505
+ factor_checkboxes_comp = factor_checkboxes
506
+ interaction_checkboxes_comp = interaction_checkboxes
507
+ custom_model_button = gr.Button("Ajustar Modelo Personalizado")
508
+ model_personalized_output_comp = model_personalized_output
509
+ pareto_personalized_output_comp = pareto_personalized_output
510
 
511
  gr.Markdown("**Ecuaci贸n del Modelo Simplificado**")
512
+ equation_output_comp = equation_output
513
+ optimization_table_output_comp = optimization_table_output
514
+ prediction_table_output_comp = prediction_table_output
515
+ contribution_table_output_comp = contribution_table_output
516
+ anova_table_output_comp = anova_table_output
517
 
518
  gr.Markdown("## Descargar Todas las Tablas")
519
+ download_excel_button_comp = download_excel_button
520
  download_word_button = gr.DownloadButton("Descargar Tablas en Word")
521
 
522
  with gr.Column():
523
  gr.Markdown("## Gr谩ficos de Superficie de Respuesta")
524
+ model_type_radio = gr.Radio(["simplified", "full", "personalized"], value="simplified", label="Tipo de Modelo para Gr谩ficos")
525
  fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa_g_L", "Proteina_Pescado_g_L", "Sulfato_Manganeso_g_L"], value="Glucosa_g_L")
526
+ fixed_level_input = gr.Slider(label="Nivel de Variable Fija (Natural Units)", minimum=0, maximum=10, step=0.1, value=5.0)
527
  plot_button = gr.Button("Generar Gr谩ficos")
528
  with gr.Row():
529
  left_button = gr.Button("<")
530
  right_button = gr.Button(">")
531
+ rsm_plot_output_comp = rsm_plot_output
532
+ plot_info_comp = plot_info
533
  with gr.Row():
534
+ download_plot_button_comp = download_plot_button
535
+ download_all_plots_button_comp = download_all_plots_button
536
+ current_index_state_comp = current_index_state
537
+ all_figures_state_comp = all_figures_state
538
+ current_model_type_state_comp = current_model_type_state
539
+
540
+
541
+ load_button.click(load_data, inputs=[x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, data_input], outputs=[data_output, analysis_row])
542
+ fit_button.click(fit_and_optimize_model, inputs=[], outputs=[model_completo_output_comp, pareto_completo_output_comp, model_simplificado_output_comp, pareto_simplificado_output_comp, equation_output_comp, optimization_table_output_comp, prediction_table_output_comp, contribution_table_output_comp, anova_table_output_comp, download_all_plots_button_comp, download_excel_button_comp])
543
+ custom_model_button.click(fit_custom_model, inputs=[factor_checkboxes_comp, interaction_checkboxes_comp, model_personalized_output_comp, pareto_personalized_output_comp], outputs=[model_personalized_output_comp, pareto_personalized_output_comp]) # Pass output components as input and output
544
+ plot_button.click(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), inputs=[fixed_variable_input, fixed_level_input, model_type_radio], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp, current_model_type_state_comp])
545
+ left_button.click(lambda current_index, all_figures, model_type: navigate_plot('left', current_index, all_figures, model_type), inputs=[current_index_state_comp, all_figures_state_comp, current_model_type_state_comp], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp])
546
+ right_button.click(lambda current_index, all_figures, model_type: navigate_plot('right', current_index, all_figures, model_type), inputs=[current_index_state_comp, all_figures_state_comp, current_model_type_state_comp], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp])
547
+ download_plot_button.click(download_current_plot, inputs=[all_figures_state_comp, current_index_state_comp, current_model_type_state_comp], outputs=download_plot_button_comp)
548
+ download_all_plots_button.click(lambda model_type: download_all_plots_zip(model_type), inputs=[current_model_type_state_comp], outputs=download_all_plots_button_comp)
549
+ download_excel_button.click(fn=lambda: download_all_tables_excel(), inputs=[], outputs=download_excel_button_comp)
550
+ download_word_button.click(exportar_word, inputs=[gr.State(rsm), gr.State(rsm.get_all_tables())], outputs=download_word_button) # Pass rsm instance and tables as state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
 
552
  return demo
553
 
554
  # --- Funci贸n Principal ---
 
555
  def main():
556
  interface = create_gradio_interface()
557
  interface.launch(share=True)