Update app.py
Browse files
app.py
CHANGED
|
@@ -21,8 +21,8 @@ import os
|
|
| 21 |
class RSM_BoxBehnken:
|
| 22 |
def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels):
|
| 23 |
"""
|
| 24 |
-
|
| 25 |
-
|
| 26 |
self.data = data.copy()
|
| 27 |
self.model = None
|
| 28 |
self.model_simplified = None
|
|
@@ -41,8 +41,8 @@ class RSM_BoxBehnken:
|
|
| 41 |
|
| 42 |
def get_levels(self, variable_name):
|
| 43 |
"""
|
| 44 |
-
|
| 45 |
-
|
| 46 |
if variable_name == self.x1_name:
|
| 47 |
return self.x1_levels
|
| 48 |
elif variable_name == self.x2_name:
|
|
@@ -54,8 +54,8 @@ class RSM_BoxBehnken:
|
|
| 54 |
|
| 55 |
def fit_model(self):
|
| 56 |
"""
|
| 57 |
-
|
| 58 |
-
|
| 59 |
formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
|
| 60 |
f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + ' \
|
| 61 |
f'{self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}'
|
|
@@ -66,19 +66,19 @@ class RSM_BoxBehnken:
|
|
| 66 |
|
| 67 |
def fit_simplified_model(self):
|
| 68 |
"""
|
| 69 |
-
|
| 70 |
-
|
| 71 |
formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + ' \
|
| 72 |
f'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 |
print("\nModelo Simplificado:")
|
| 75 |
print(self.model_simplified.summary())
|
| 76 |
return self.model_simplified, self.pareto_chart_f(self.model_simplified, "Pareto - Modelo Simplificado (F)"), self.pareto_chart_t(self.model_simplified, "Pareto - Modelo Simplificado (t)")
|
| 77 |
-
|
| 78 |
def optimize(self, method='Nelder-Mead'):
|
| 79 |
"""
|
| 80 |
-
|
| 81 |
-
|
| 82 |
if self.model_simplified is None:
|
| 83 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 84 |
return
|
|
@@ -113,15 +113,15 @@ class RSM_BoxBehnken:
|
|
| 113 |
|
| 114 |
def plot_rsm_individual(self, fixed_variable, fixed_level):
|
| 115 |
"""
|
| 116 |
-
|
| 117 |
-
|
| 118 |
if self.model_simplified is None:
|
| 119 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 120 |
return None
|
| 121 |
|
| 122 |
# Determinar las variables que varían y sus niveles naturales
|
| 123 |
varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
|
| 124 |
-
|
| 125 |
# Establecer los niveles naturales para las variables que varían
|
| 126 |
x_natural_levels = self.get_levels(varying_variables[0])
|
| 127 |
y_natural_levels = self.get_levels(varying_variables[1])
|
|
@@ -220,9 +220,9 @@ class RSM_BoxBehnken:
|
|
| 220 |
|
| 221 |
def get_units(self, variable_name):
|
| 222 |
"""
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
units = {
|
| 227 |
'Glucosa': 'g/L',
|
| 228 |
'Extracto_de_Levadura': 'g/L',
|
|
@@ -233,9 +233,9 @@ class RSM_BoxBehnken:
|
|
| 233 |
|
| 234 |
def generate_all_plots(self):
|
| 235 |
"""
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
if self.model_simplified is None:
|
| 240 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 241 |
return
|
|
@@ -268,25 +268,25 @@ class RSM_BoxBehnken:
|
|
| 268 |
|
| 269 |
def pareto_chart_f(self, model, title):
|
| 270 |
"""
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
# Calcular los estadísticos F para cada término manualmente
|
| 275 |
# Necesitamos los coeficientes, los errores estándar y los grados de libertad del error
|
| 276 |
coef = model.params[1:] # Excluir la Intercept
|
| 277 |
-
stderr = model.bse[1:]
|
| 278 |
df_resid = model.df_resid
|
| 279 |
-
|
| 280 |
# Calcular F = (coef / stderr)^2
|
| 281 |
fvalues = (coef / stderr) ** 2
|
| 282 |
-
|
| 283 |
abs_fvalues = np.abs(fvalues)
|
| 284 |
sorted_idx = np.argsort(abs_fvalues)[::-1]
|
| 285 |
sorted_fvalues = abs_fvalues[sorted_idx]
|
| 286 |
sorted_names = fvalues.index[sorted_idx]
|
| 287 |
-
|
| 288 |
# Cambiar I() por "" en los nombres de los terminos
|
| 289 |
-
sorted_names = sorted_names.str.replace('I(','').str.replace('
|
| 290 |
|
| 291 |
# Calcular el valor crítico de F para la línea de significancia
|
| 292 |
alpha = 0.05 # Nivel de significancia
|
|
@@ -306,16 +306,16 @@ class RSM_BoxBehnken:
|
|
| 306 |
|
| 307 |
# Agregar la línea de significancia
|
| 308 |
fig.add_vline(x=f_critical, line_dash="dot",
|
| 309 |
-
|
| 310 |
-
|
| 311 |
|
| 312 |
return fig
|
| 313 |
-
|
| 314 |
def pareto_chart_t(self, model, title):
|
| 315 |
"""
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
# Calcular los estadísticos t para cada término
|
| 320 |
tvalues = model.tvalues[1:] # Excluir la Intercept
|
| 321 |
abs_tvalues = np.abs(tvalues)
|
|
@@ -324,7 +324,7 @@ class RSM_BoxBehnken:
|
|
| 324 |
sorted_names = tvalues.index[sorted_idx]
|
| 325 |
|
| 326 |
# Cambiar I() por "" en los nombres de los terminos
|
| 327 |
-
sorted_names = sorted_names.str.replace('I(','').str.replace('
|
| 328 |
|
| 329 |
# Calcular el valor crítico de t para la línea de significancia
|
| 330 |
alpha = 0.05 # Nivel de significancia
|
|
@@ -350,8 +350,8 @@ class RSM_BoxBehnken:
|
|
| 350 |
|
| 351 |
def get_simplified_equation(self):
|
| 352 |
"""
|
| 353 |
-
|
| 354 |
-
|
| 355 |
if self.model_simplified is None:
|
| 356 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 357 |
return None
|
|
@@ -375,11 +375,11 @@ class RSM_BoxBehnken:
|
|
| 375 |
equation += f" + {coef:.3f}*{self.x3_name}^2"
|
| 376 |
|
| 377 |
return equation
|
| 378 |
-
|
| 379 |
def generate_prediction_table(self):
|
| 380 |
"""
|
| 381 |
-
|
| 382 |
-
|
| 383 |
if self.model_simplified is None:
|
| 384 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 385 |
return None
|
|
@@ -391,8 +391,8 @@ class RSM_BoxBehnken:
|
|
| 391 |
|
| 392 |
def calculate_anova_table(self):
|
| 393 |
"""
|
| 394 |
-
|
| 395 |
-
|
| 396 |
if self.model_simplified is None:
|
| 397 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 398 |
return None
|
|
@@ -444,7 +444,7 @@ class RSM_BoxBehnken:
|
|
| 444 |
# 11. Estadísticos F y valores p
|
| 445 |
f_regression = ms_regression / ms_residual
|
| 446 |
p_regression = 1 - f.cdf(f_regression, df_regression, df_residual)
|
| 447 |
-
|
| 448 |
f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) else np.nan
|
| 449 |
p_lack_of_fit = 1 - f.cdf(f_lack_of_fit, df_lack_of_fit, df_pure_error) if not np.isnan(f_lack_of_fit) else np.nan
|
| 450 |
|
|
@@ -457,11 +457,11 @@ class RSM_BoxBehnken:
|
|
| 457 |
'F': [f_regression, np.nan, f_lack_of_fit, np.nan, np.nan],
|
| 458 |
'Valor p': [p_regression, np.nan, p_lack_of_fit, np.nan, np.nan]
|
| 459 |
})
|
| 460 |
-
|
| 461 |
# Calcular la suma de cuadrados y estadísticos F para la curvatura
|
| 462 |
ss_curvature = anova_reduced['sum_sq'][f'I({self.x1_name} ** 2)'] + \
|
| 463 |
-
|
| 464 |
-
|
| 465 |
df_curvature = 3
|
| 466 |
ms_curvature = ss_curvature / df_curvature
|
| 467 |
f_curvature = ms_curvature / ms_residual
|
|
@@ -469,14 +469,14 @@ class RSM_BoxBehnken:
|
|
| 469 |
|
| 470 |
# Añadir la fila de curvatura a la tabla ANOVA
|
| 471 |
detailed_anova_table.loc[len(detailed_anova_table)] = [
|
| 472 |
-
'Curvatura',
|
| 473 |
-
ss_curvature,
|
| 474 |
-
df_curvature,
|
| 475 |
ms_curvature,
|
| 476 |
f_curvature,
|
| 477 |
p_curvature
|
| 478 |
]
|
| 479 |
-
|
| 480 |
# --- Agregar % de Contribución ---
|
| 481 |
detailed_anova_table['% Contribución'] = (detailed_anova_table['Suma de Cuadrados'] / ss_total) * 100
|
| 482 |
|
|
@@ -487,8 +487,8 @@ class RSM_BoxBehnken:
|
|
| 487 |
|
| 488 |
def get_all_tables(self):
|
| 489 |
"""
|
| 490 |
-
|
| 491 |
-
|
| 492 |
prediction_table = self.generate_prediction_table()
|
| 493 |
anova_table = self.calculate_anova_table()
|
| 494 |
|
|
@@ -499,8 +499,8 @@ class RSM_BoxBehnken:
|
|
| 499 |
|
| 500 |
def save_figures_to_zip(self):
|
| 501 |
"""
|
| 502 |
-
|
| 503 |
-
|
| 504 |
if not self.all_figures:
|
| 505 |
return None
|
| 506 |
|
|
@@ -520,14 +520,14 @@ class RSM_BoxBehnken:
|
|
| 520 |
|
| 521 |
def save_fig_to_bytes(self, fig):
|
| 522 |
"""
|
| 523 |
-
|
| 524 |
-
|
| 525 |
return fig.to_image(format="png")
|
| 526 |
|
| 527 |
def save_all_figures_png(self):
|
| 528 |
"""
|
| 529 |
-
|
| 530 |
-
|
| 531 |
png_paths = []
|
| 532 |
for idx, fig in enumerate(self.all_figures, start=1):
|
| 533 |
img_bytes = fig.to_image(format="png")
|
|
@@ -539,8 +539,8 @@ class RSM_BoxBehnken:
|
|
| 539 |
|
| 540 |
def save_tables_to_excel(self):
|
| 541 |
"""
|
| 542 |
-
|
| 543 |
-
|
| 544 |
tables = self.get_all_tables()
|
| 545 |
excel_buffer = io.BytesIO()
|
| 546 |
with pd.ExcelWriter(excel_buffer, engine='xlsxwriter') as writer:
|
|
@@ -558,8 +558,8 @@ class RSM_BoxBehnken:
|
|
| 558 |
|
| 559 |
def export_tables_to_word(self, tables_dict):
|
| 560 |
"""
|
| 561 |
-
|
| 562 |
-
|
| 563 |
if not tables_dict:
|
| 564 |
return None
|
| 565 |
|
|
@@ -615,8 +615,8 @@ class RSM_BoxBehnken:
|
|
| 615 |
|
| 616 |
def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str):
|
| 617 |
"""
|
| 618 |
-
|
| 619 |
-
|
| 620 |
try:
|
| 621 |
# Convertir los niveles a listas de números
|
| 622 |
x1_levels = [float(x.strip()) for x in x1_levels_str.split(',')]
|
|
|
|
| 21 |
class RSM_BoxBehnken:
|
| 22 |
def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels):
|
| 23 |
"""
|
| 24 |
+
Inicializa la clase con los datos del diseño Box-Behnken.
|
| 25 |
+
"""
|
| 26 |
self.data = data.copy()
|
| 27 |
self.model = None
|
| 28 |
self.model_simplified = None
|
|
|
|
| 41 |
|
| 42 |
def get_levels(self, variable_name):
|
| 43 |
"""
|
| 44 |
+
Obtiene los niveles para una variable específica.
|
| 45 |
+
"""
|
| 46 |
if variable_name == self.x1_name:
|
| 47 |
return self.x1_levels
|
| 48 |
elif variable_name == self.x2_name:
|
|
|
|
| 54 |
|
| 55 |
def fit_model(self):
|
| 56 |
"""
|
| 57 |
+
Ajusta el modelo de segundo orden completo a los datos.
|
| 58 |
+
"""
|
| 59 |
formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \
|
| 60 |
f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + ' \
|
| 61 |
f'{self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}'
|
|
|
|
| 66 |
|
| 67 |
def fit_simplified_model(self):
|
| 68 |
"""
|
| 69 |
+
Ajusta el modelo de segundo orden a los datos, eliminando términos no significativos.
|
| 70 |
+
"""
|
| 71 |
formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + ' \
|
| 72 |
f'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 |
print("\nModelo Simplificado:")
|
| 75 |
print(self.model_simplified.summary())
|
| 76 |
return self.model_simplified, self.pareto_chart_f(self.model_simplified, "Pareto - Modelo Simplificado (F)"), self.pareto_chart_t(self.model_simplified, "Pareto - Modelo Simplificado (t)")
|
| 77 |
+
|
| 78 |
def optimize(self, method='Nelder-Mead'):
|
| 79 |
"""
|
| 80 |
+
Encuentra los niveles óptimos de los factores para maximizar la respuesta usando el modelo simplificado.
|
| 81 |
+
"""
|
| 82 |
if self.model_simplified is None:
|
| 83 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 84 |
return
|
|
|
|
| 113 |
|
| 114 |
def plot_rsm_individual(self, fixed_variable, fixed_level):
|
| 115 |
"""
|
| 116 |
+
Genera un gráfico de superficie de respuesta (RSM) individual para una configuración específica.
|
| 117 |
+
"""
|
| 118 |
if self.model_simplified is None:
|
| 119 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 120 |
return None
|
| 121 |
|
| 122 |
# Determinar las variables que varían y sus niveles naturales
|
| 123 |
varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable]
|
| 124 |
+
|
| 125 |
# Establecer los niveles naturales para las variables que varían
|
| 126 |
x_natural_levels = self.get_levels(varying_variables[0])
|
| 127 |
y_natural_levels = self.get_levels(varying_variables[1])
|
|
|
|
| 220 |
|
| 221 |
def get_units(self, variable_name):
|
| 222 |
"""
|
| 223 |
+
Define las unidades de las variables para etiquetas.
|
| 224 |
+
Puedes personalizar este método según tus necesidades.
|
| 225 |
+
"""
|
| 226 |
units = {
|
| 227 |
'Glucosa': 'g/L',
|
| 228 |
'Extracto_de_Levadura': 'g/L',
|
|
|
|
| 233 |
|
| 234 |
def generate_all_plots(self):
|
| 235 |
"""
|
| 236 |
+
Genera todas las gráficas de RSM, variando la variable fija y sus niveles usando el modelo simplificado.
|
| 237 |
+
Almacena las figuras en self.all_figures.
|
| 238 |
+
"""
|
| 239 |
if self.model_simplified is None:
|
| 240 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 241 |
return
|
|
|
|
| 268 |
|
| 269 |
def pareto_chart_f(self, model, title):
|
| 270 |
"""
|
| 271 |
+
Genera un diagrama de Pareto para los efectos usando estadísticos F,
|
| 272 |
+
incluyendo la línea de significancia.
|
| 273 |
+
"""
|
| 274 |
# Calcular los estadísticos F para cada término manualmente
|
| 275 |
# Necesitamos los coeficientes, los errores estándar y los grados de libertad del error
|
| 276 |
coef = model.params[1:] # Excluir la Intercept
|
| 277 |
+
stderr = model.bse[1:] # Excluir la Intercept
|
| 278 |
df_resid = model.df_resid
|
| 279 |
+
|
| 280 |
# Calcular F = (coef / stderr)^2
|
| 281 |
fvalues = (coef / stderr) ** 2
|
| 282 |
+
|
| 283 |
abs_fvalues = np.abs(fvalues)
|
| 284 |
sorted_idx = np.argsort(abs_fvalues)[::-1]
|
| 285 |
sorted_fvalues = abs_fvalues[sorted_idx]
|
| 286 |
sorted_names = fvalues.index[sorted_idx]
|
| 287 |
+
|
| 288 |
# Cambiar I() por "" en los nombres de los terminos
|
| 289 |
+
sorted_names = sorted_names.str.replace(r'I\(', '', regex=True).str.replace(r'\*\*2\)', '^2', regex=True).str.replace(r' \* ', '·', regex=True)
|
| 290 |
|
| 291 |
# Calcular el valor crítico de F para la línea de significancia
|
| 292 |
alpha = 0.05 # Nivel de significancia
|
|
|
|
| 306 |
|
| 307 |
# Agregar la línea de significancia
|
| 308 |
fig.add_vline(x=f_critical, line_dash="dot",
|
| 309 |
+
annotation_text=f"F crítico = {f_critical:.3f}",
|
| 310 |
+
annotation_position="bottom right")
|
| 311 |
|
| 312 |
return fig
|
| 313 |
+
|
| 314 |
def pareto_chart_t(self, model, title):
|
| 315 |
"""
|
| 316 |
+
Genera un diagrama de Pareto para los efectos usando estadísticos t,
|
| 317 |
+
incluyendo la línea de significancia.
|
| 318 |
+
"""
|
| 319 |
# Calcular los estadísticos t para cada término
|
| 320 |
tvalues = model.tvalues[1:] # Excluir la Intercept
|
| 321 |
abs_tvalues = np.abs(tvalues)
|
|
|
|
| 324 |
sorted_names = tvalues.index[sorted_idx]
|
| 325 |
|
| 326 |
# Cambiar I() por "" en los nombres de los terminos
|
| 327 |
+
sorted_names = sorted_names.str.replace(r'I\(', '', regex=True).str.replace(r'\*\*2\)', '^2', regex=True).str.replace(r' \* ', '·', regex=True)
|
| 328 |
|
| 329 |
# Calcular el valor crítico de t para la línea de significancia
|
| 330 |
alpha = 0.05 # Nivel de significancia
|
|
|
|
| 350 |
|
| 351 |
def get_simplified_equation(self):
|
| 352 |
"""
|
| 353 |
+
Retorna la ecuación del modelo simplificado como una cadena de texto.
|
| 354 |
+
"""
|
| 355 |
if self.model_simplified is None:
|
| 356 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 357 |
return None
|
|
|
|
| 375 |
equation += f" + {coef:.3f}*{self.x3_name}^2"
|
| 376 |
|
| 377 |
return equation
|
| 378 |
+
|
| 379 |
def generate_prediction_table(self):
|
| 380 |
"""
|
| 381 |
+
Genera una tabla con los valores actuales, predichos y residuales.
|
| 382 |
+
"""
|
| 383 |
if self.model_simplified is None:
|
| 384 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 385 |
return None
|
|
|
|
| 391 |
|
| 392 |
def calculate_anova_table(self):
|
| 393 |
"""
|
| 394 |
+
Calcula la tabla ANOVA detallada, incluyendo la contribución porcentual.
|
| 395 |
+
"""
|
| 396 |
if self.model_simplified is None:
|
| 397 |
print("Error: Ajusta el modelo simplificado primero.")
|
| 398 |
return None
|
|
|
|
| 444 |
# 11. Estadísticos F y valores p
|
| 445 |
f_regression = ms_regression / ms_residual
|
| 446 |
p_regression = 1 - f.cdf(f_regression, df_regression, df_residual)
|
| 447 |
+
|
| 448 |
f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) else np.nan
|
| 449 |
p_lack_of_fit = 1 - f.cdf(f_lack_of_fit, df_lack_of_fit, df_pure_error) if not np.isnan(f_lack_of_fit) else np.nan
|
| 450 |
|
|
|
|
| 457 |
'F': [f_regression, np.nan, f_lack_of_fit, np.nan, np.nan],
|
| 458 |
'Valor p': [p_regression, np.nan, p_lack_of_fit, np.nan, np.nan]
|
| 459 |
})
|
| 460 |
+
|
| 461 |
# Calcular la suma de cuadrados y estadísticos F para la curvatura
|
| 462 |
ss_curvature = anova_reduced['sum_sq'][f'I({self.x1_name} ** 2)'] + \
|
| 463 |
+
anova_reduced['sum_sq'][f'I({self.x2_name} ** 2)'] + \
|
| 464 |
+
anova_reduced['sum_sq'][f'I({self.x3_name} ** 2)']
|
| 465 |
df_curvature = 3
|
| 466 |
ms_curvature = ss_curvature / df_curvature
|
| 467 |
f_curvature = ms_curvature / ms_residual
|
|
|
|
| 469 |
|
| 470 |
# Añadir la fila de curvatura a la tabla ANOVA
|
| 471 |
detailed_anova_table.loc[len(detailed_anova_table)] = [
|
| 472 |
+
'Curvatura',
|
| 473 |
+
ss_curvature,
|
| 474 |
+
df_curvature,
|
| 475 |
ms_curvature,
|
| 476 |
f_curvature,
|
| 477 |
p_curvature
|
| 478 |
]
|
| 479 |
+
|
| 480 |
# --- Agregar % de Contribución ---
|
| 481 |
detailed_anova_table['% Contribución'] = (detailed_anova_table['Suma de Cuadrados'] / ss_total) * 100
|
| 482 |
|
|
|
|
| 487 |
|
| 488 |
def get_all_tables(self):
|
| 489 |
"""
|
| 490 |
+
Obtiene todas las tablas generadas para ser exportadas a Excel.
|
| 491 |
+
"""
|
| 492 |
prediction_table = self.generate_prediction_table()
|
| 493 |
anova_table = self.calculate_anova_table()
|
| 494 |
|
|
|
|
| 499 |
|
| 500 |
def save_figures_to_zip(self):
|
| 501 |
"""
|
| 502 |
+
Guarda todas las figuras almacenadas en self.all_figures a un archivo ZIP en memoria.
|
| 503 |
+
"""
|
| 504 |
if not self.all_figures:
|
| 505 |
return None
|
| 506 |
|
|
|
|
| 520 |
|
| 521 |
def save_fig_to_bytes(self, fig):
|
| 522 |
"""
|
| 523 |
+
Convierte una figura Plotly a bytes en formato PNG.
|
| 524 |
+
"""
|
| 525 |
return fig.to_image(format="png")
|
| 526 |
|
| 527 |
def save_all_figures_png(self):
|
| 528 |
"""
|
| 529 |
+
Guarda todas las figuras en archivos PNG temporales y retorna las rutas.
|
| 530 |
+
"""
|
| 531 |
png_paths = []
|
| 532 |
for idx, fig in enumerate(self.all_figures, start=1):
|
| 533 |
img_bytes = fig.to_image(format="png")
|
|
|
|
| 539 |
|
| 540 |
def save_tables_to_excel(self):
|
| 541 |
"""
|
| 542 |
+
Guarda todas las tablas en un archivo Excel con múltiples hojas y retorna la ruta del archivo.
|
| 543 |
+
"""
|
| 544 |
tables = self.get_all_tables()
|
| 545 |
excel_buffer = io.BytesIO()
|
| 546 |
with pd.ExcelWriter(excel_buffer, engine='xlsxwriter') as writer:
|
|
|
|
| 558 |
|
| 559 |
def export_tables_to_word(self, tables_dict):
|
| 560 |
"""
|
| 561 |
+
Exporta las tablas proporcionadas a un documento de Word.
|
| 562 |
+
"""
|
| 563 |
if not tables_dict:
|
| 564 |
return None
|
| 565 |
|
|
|
|
| 615 |
|
| 616 |
def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str):
|
| 617 |
"""
|
| 618 |
+
Carga los datos del diseño Box-Behnken desde cajas de texto y crea la instancia de RSM_BoxBehnken.
|
| 619 |
+
"""
|
| 620 |
try:
|
| 621 |
# Convertir los niveles a listas de números
|
| 622 |
x1_levels = [float(x.strip()) for x in x1_levels_str.split(',')]
|