Spaces:
Sleeping
Sleeping
import numpy as np | |
import pandas as pd | |
import statsmodels.formula.api as smf | |
import statsmodels.api as sm | |
import plotly.graph_objects as go | |
from scipy.optimize import minimize | |
import plotly.express as px | |
from scipy.stats import f | |
import gradio as gr | |
import io | |
import zipfile | |
import tempfile | |
from datetime import datetime | |
import docx | |
from docx.shared import Inches, Pt | |
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT | |
import os | |
from pyDOE import bbdesign, ccdesign | |
# --- Clase RSM_ExperimentalDesign --- | |
class RSM_ExperimentalDesign: | |
def __init__(self, data, design_type, factor_names, y_name, factor_levels): | |
""" | |
Inicializa la clase con los datos del diseño experimental. | |
Args: | |
data (pd.DataFrame): Datos del experimento. | |
design_type (str): Tipo de diseño ('Box-Behnken' o 'Central Compuesto'). | |
factor_names (list): Lista de nombres de los factores. | |
y_name (str): Nombre de la variable dependiente. | |
factor_levels (dict): Diccionario con los niveles de cada factor. | |
""" | |
self.data = data.copy() | |
self.design_type = design_type | |
self.factor_names = factor_names | |
self.y_name = y_name | |
self.factor_levels = factor_levels | |
self.model = None | |
self.model_simplified = None | |
self.optimized_results = None | |
self.optimal_levels = None | |
self.all_figures = [] | |
self.selected_factors = [] | |
def generate_design(self): | |
""" | |
Genera el diseño experimental basado en el tipo especificado y asigna los niveles naturales. | |
""" | |
num_factors = len(self.factor_names) | |
if self.design_type == 'Box-Behnken': | |
design = bbdesign(num_factors) | |
elif self.design_type == 'Central Compuesto': | |
design = ccdesign(num_factors, center=(3, 3)) # Puedes ajustar los puntos centrales si lo deseas | |
else: | |
raise ValueError("Tipo de diseño no soportado. Elige 'Box-Behnken' o 'Central Compuesto'.") | |
# Asignar niveles naturales a las variables | |
for i, factor in enumerate(self.factor_names): | |
self.data[factor] = self.coded_to_natural(design[:, i], factor) | |
return self.data | |
def coded_to_natural(self, coded_value, variable_name): | |
"""Convierte un valor codificado a su valor natural.""" | |
levels = self.factor_levels[variable_name] | |
if len(levels) != 3: | |
raise ValueError(f"Se requieren exactamente 3 niveles para el factor '{variable_name}'.") | |
return levels[0] + (coded_value + 1) * (levels[-1] - levels[0]) / 2 | |
def natural_to_coded(self, natural_value, variable_name): | |
"""Convierte un valor natural a su valor codificado.""" | |
levels = self.factor_levels[variable_name] | |
return -1 + 2 * (natural_value - levels[0]) / (levels[-1] - levels[0]) | |
def fit_model(self): | |
""" | |
Ajusta el modelo de segundo orden completo a los datos. | |
""" | |
terms = self.factor_names.copy() | |
# Términos cuadráticos | |
terms += [f'I({var}**2)' for var in self.factor_names] | |
# Términos de interacción | |
for i in range(len(self.factor_names)): | |
for j in range(i+1, len(self.factor_names)): | |
terms.append(f'{self.factor_names[i]}:{self.factor_names[j]}') | |
formula = f'{self.y_name} ~ ' + ' + '.join(terms) | |
self.model = smf.ols(formula, data=self.data).fit() | |
print("Modelo Completo:") | |
print(self.model.summary()) | |
return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo") | |
def fit_simplified_model(self, selected_factors): | |
""" | |
Ajusta el modelo simplificado basado en los factores seleccionados. | |
Args: | |
selected_factors (list): Lista de factores a incluir en el modelo. | |
""" | |
self.selected_factors = selected_factors | |
terms = selected_factors.copy() | |
# Términos cuadráticos | |
terms += [f'I({var}**2)' for var in selected_factors] | |
formula = f'{self.y_name} ~ ' + ' + '.join(terms) | |
self.model_simplified = smf.ols(formula, data=self.data).fit() | |
print("\nModelo Simplificado:") | |
print(self.model_simplified.summary()) | |
return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado") | |
def optimize(self, method='Nelder-Mead'): | |
""" | |
Encuentra los niveles óptimos de los factores para maximizar la respuesta usando el modelo simplificado. | |
""" | |
if self.model_simplified is None: | |
print("Error: Ajusta el modelo simplificado primero.") | |
return | |
def objective_function(x): | |
input_data = {var: [x[i]] for i, var in enumerate(self.selected_factors)} | |
return -self.model_simplified.predict(pd.DataFrame(input_data)).values[0] | |
# Definir límites codificados para cada factor | |
bounds = [(-1, 1) for _ in self.selected_factors] | |
x0 = [0] * len(self.selected_factors) | |
self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds) | |
self.optimal_levels = self.optimized_results.x | |
# Convertir niveles óptimos de codificados a naturales | |
optimal_levels_natural = [ | |
self.coded_to_natural(self.optimal_levels[i], self.selected_factors[i]) | |
for i in range(len(self.selected_factors)) | |
] | |
# Crear la tabla de optimización | |
optimization_table = pd.DataFrame({ | |
'Variable': self.selected_factors, | |
'Nivel Óptimo (Natural)': optimal_levels_natural, | |
'Nivel Óptimo (Codificado)': self.optimal_levels | |
}) | |
return optimization_table.round(3) | |
def plot_rsm_individual(self, fixed_variable, fixed_level): | |
""" | |
Genera un gráfico de superficie de respuesta (RSM) individual para una configuración específica. | |
""" | |
if self.model_simplified is None: | |
print("Error: Ajusta el modelo simplificado primero.") | |
return None | |
varying_variables = [var for var in self.selected_factors if var != fixed_variable] | |
if len(varying_variables) < 2: | |
print("Se requieren al menos dos variables para generar un gráfico de superficie.") | |
return None | |
var1, var2 = varying_variables[:2] # Solo tomar las dos primeras variables para el gráfico | |
# Determinar los niveles naturales para las variables que varían | |
x_natural_levels = self.factor_levels[var1] | |
y_natural_levels = self.factor_levels[var2] | |
# Crear una malla de puntos para las variables que varían (en unidades naturales) | |
x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100) | |
y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100) | |
x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural) | |
# Convertir la malla de variables naturales a codificadas | |
x_grid_coded = self.natural_to_coded(x_grid_natural, var1) | |
y_grid_coded = self.natural_to_coded(y_grid_natural, var2) | |
# Crear un DataFrame para la predicción con variables codificadas | |
prediction_data = pd.DataFrame({ | |
var1: x_grid_coded.flatten(), | |
var2: y_grid_coded.flatten(), | |
}) | |
# Asignar valores codificados a las otras variables fijas | |
for var in self.selected_factors: | |
if var not in [var1, var2]: | |
prediction_data[var] = self.natural_to_coded(fixed_level, var) | |
# Calcular los valores predichos | |
z_pred = self.model_simplified.predict(prediction_data).values.reshape(x_grid_coded.shape) | |
# Crear el gráfico de superficie | |
fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)]) | |
# Añadir líneas de cuadrícula | |
for i in range(x_grid_natural.shape[0]): | |
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' | |
)) | |
for j in range(x_grid_natural.shape[1]): | |
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' | |
)) | |
# Añadir los puntos de los experimentos | |
experiments_data = self.data.copy() | |
experiments_data['Predicho'] = self.model_simplified.predict(self.data[self.selected_factors]) | |
colors = px.colors.qualitative.Safe | |
point_labels = [f"{row[self.y_name]:.3f}" for _, row in experiments_data.iterrows()] | |
fig.add_trace(go.Scatter3d( | |
x=experiments_data[var1], | |
y=experiments_data[var2], | |
z=experiments_data[self.y_name], | |
mode='markers+text', | |
marker=dict(size=4, color=colors[:len(experiments_data)]), | |
text=point_labels, | |
textposition='top center', | |
name='Experimentos' | |
)) | |
# Añadir etiquetas y título con variables naturales | |
fig.update_layout( | |
scene=dict( | |
xaxis_title=f"{var1} ({self.get_units(var1)})", | |
yaxis_title=f"{var2} ({self.get_units(var2)})", | |
zaxis_title=self.y_name, | |
), | |
title=f"{self.y_name} vs {var1} y {var2}<br><sup>{fixed_variable} fijo en {fixed_level:.3f} ({self.get_units(fixed_variable)}) (Modelo Simplificado)</sup>", | |
height=800, | |
width=1000, | |
showlegend=True | |
) | |
return fig | |
def get_units(self, variable_name): | |
""" | |
Define las unidades de las variables para etiquetas. | |
Puedes personalizar este método según tus necesidades. | |
""" | |
units = { | |
'Glucosa': 'g/L', | |
'Extracto_de_Levadura': 'g/L', | |
'Triptófano': 'g/L', | |
'AIA_ppm': 'ppm', | |
# Agrega más unidades según tus variables | |
} | |
return units.get(variable_name, '') | |
def generate_all_plots(self): | |
""" | |
Genera todas las gráficas de RSM, variando la variable fija y sus niveles usando el modelo simplificado. | |
Almacena las figuras en self.all_figures. | |
""" | |
if self.model_simplified is None: | |
print("Error: Ajusta el modelo simplificado primero.") | |
return | |
self.all_figures = [] # Resetear la lista de figuras | |
# Niveles naturales para graficar | |
for fixed_variable in self.selected_factors: | |
for level in self.factor_levels[fixed_variable]: | |
fig = self.plot_rsm_individual(fixed_variable, level) | |
if fig is not None: | |
self.all_figures.append(fig) | |
def pareto_chart(self, model, title): | |
""" | |
Genera un diagrama de Pareto para los efectos usando estadísticos F, | |
incluyendo la línea de significancia. | |
""" | |
# Calcular los estadísticos F para cada término | |
fvalues = model.tvalues[1:]**2 # Excluir la Intercept y convertir t a F | |
abs_fvalues = np.abs(fvalues) | |
sorted_idx = np.argsort(abs_fvalues)[::-1] | |
sorted_fvalues = abs_fvalues[sorted_idx] | |
sorted_names = fvalues.index[sorted_idx] | |
# Calcular el valor crítico de F para la línea de significancia | |
alpha = 0.05 # Nivel de significancia | |
dof_num = 1 # Grados de libertad del numerador (cada término) | |
dof_den = model.df_resid # Grados de libertad residuales | |
f_critical = f.ppf(1 - alpha, dof_num, dof_den) | |
# Crear el diagrama de Pareto | |
fig = px.bar( | |
x=sorted_fvalues.round(3), | |
y=sorted_names, | |
orientation='h', | |
labels={'x': 'Estadístico F', 'y': 'Término'}, | |
title=title | |
) | |
fig.update_yaxes(autorange="reversed") | |
# Agregar la línea de significancia | |
fig.add_vline(x=f_critical, line_dash="dot", | |
annotation_text=f"F crítico = {f_critical:.3f}", | |
annotation_position="bottom right") | |
return fig | |
def get_simplified_equation(self): | |
""" | |
Retorna la ecuación del modelo simplificado como una cadena de texto. | |
""" | |
if self.model_simplified is None: | |
print("Error: Ajusta el modelo simplificado primero.") | |
return None | |
coefficients = self.model_simplified.params | |
equation = f"{self.y_name} = {coefficients['Intercept']:.3f}" | |
for term, coef in coefficients.items(): | |
if term != 'Intercept': | |
if term.startswith('I('): | |
equation += f" + {coef:.3f}*{term[2:-1]}" | |
elif ':' in term: | |
equation += f" + {coef:.3f}*{term}" | |
else: | |
equation += f" + {coef:.3f}*{term}" | |
return equation | |
def generate_prediction_table(self): | |
""" | |
Genera una tabla con los valores actuales, predichos y residuales. | |
""" | |
if self.model_simplified is None: | |
print("Error: Ajusta el modelo simplificado primero.") | |
return None | |
self.data['Predicho'] = self.model_simplified.predict(self.data[self.selected_factors]) | |
self.data['Residual'] = self.data[self.y_name] - self.data['Predicho'] | |
return self.data[[self.y_name, 'Predicho', 'Residual']].round(3) | |
def calculate_contribution_percentage(self): | |
""" | |
Calcula el porcentaje de contribución de cada factor usando estadísticos F. | |
""" | |
if self.model_simplified is None: | |
print("Error: Ajusta el modelo simplificado primero.") | |
return None | |
# ANOVA del modelo simplificado | |
anova_table = sm.stats.anova_lm(self.model_simplified, typ=2) | |
# Suma de cuadrados total | |
ss_total = anova_table['sum_sq'].sum() | |
# Crear tabla de contribución | |
contribution_table = pd.DataFrame({ | |
'Fuente de Variación': [], | |
'Suma de Cuadrados': [], | |
'Grados de Libertad': [], | |
'Cuadrado Medio': [], | |
'F': [], | |
'Valor p': [], | |
'% Contribución': [] | |
}) | |
# Calcular estadísticos F y porcentaje de contribución para cada factor | |
ms_error = anova_table.loc['Residual', 'sum_sq'] / anova_table.loc['Residual', 'df'] | |
# Agregar filas para cada término del modelo | |
for index, row in anova_table.iterrows(): | |
if index != 'Residual': | |
factor_name = index | |
if factor_name.startswith('I('): | |
factor_name = factor_name[2:-1] # Quitar 'I(' y ')' | |
ss_factor = row['sum_sq'] | |
df_factor = row['df'] | |
ms_factor = ss_factor / df_factor | |
f_stat = ms_factor / ms_error | |
p_value = f.sf(f_stat, df_factor, anova_table.loc['Residual', 'df']) | |
contribution_percentage = (ss_factor / ss_total) * 100 | |
contribution_table = pd.concat([contribution_table, pd.DataFrame({ | |
'Fuente de Variación': [factor_name], | |
'Suma de Cuadrados': [ss_factor], | |
'Grados de Libertad': [df_factor], | |
'Cuadrado Medio': [ms_factor], | |
'F': [f_stat], | |
'Valor p': [p_value], | |
'% Contribución': [contribution_percentage] | |
})], ignore_index=True) | |
# Agregar fila para Cor Total | |
contribution_table = pd.concat([contribution_table, pd.DataFrame({ | |
'Fuente de Variación': ['Cor Total'], | |
'Suma de Cuadrados': [ss_total], | |
'Grados de Libertad': [len(self.data) - 1], | |
'Cuadrado Medio': [np.nan], | |
'F': [np.nan], | |
'Valor p': [np.nan], | |
'% Contribución': [100] | |
})], ignore_index=True) | |
return contribution_table.round(3) | |
def calculate_detailed_anova(self): | |
""" | |
Calcula la tabla ANOVA detallada con la descomposición del error residual. | |
""" | |
if self.model_simplified is None: | |
print("Error: Ajusta el modelo simplificado primero.") | |
return None | |
# ANOVA del modelo simplificado | |
anova_reduced = sm.stats.anova_lm(self.model_simplified, typ=2) | |
# Suma de cuadrados total | |
ss_total = np.sum((self.data[self.y_name] - self.data[self.y_name].mean())**2) | |
# Suma de cuadrados de la regresión | |
ss_regression = anova_reduced['sum_sq'].sum() | |
# Grados de libertad de la regresión | |
df_regression = anova_reduced['df'].sum() | |
# Suma de cuadrados del error residual | |
ss_residual = anova_reduced.loc['Residual', 'sum_sq'] | |
df_residual = anova_reduced.loc['Residual', 'df'] | |
# Suma de cuadrados del error puro (si hay réplicas) | |
duplicates = self.data.duplicated(subset=self.selected_factors, keep=False) | |
if duplicates.any(): | |
ss_pure_error = self.data[duplicates].groupby(self.selected_factors)[self.y_name].var().sum() * self.data[duplicates].groupby(self.selected_factors).ngroups() | |
df_pure_error = self.data[duplicates].shape[0] - self.data[duplicates].groupby(self.selected_factors).ngroups() | |
else: | |
ss_pure_error = np.nan | |
df_pure_error = np.nan | |
# Suma de cuadrados de la falta de ajuste | |
ss_lack_of_fit = ss_residual - ss_pure_error if not np.isnan(ss_pure_error) else np.nan | |
df_lack_of_fit = df_residual - df_pure_error if not np.isnan(df_pure_error) else np.nan | |
# Cuadrados medios | |
ms_regression = ss_regression / df_regression | |
ms_residual = ss_residual / df_residual | |
ms_lack_of_fit = ss_lack_of_fit / df_lack_of_fit if not np.isnan(ss_lack_of_fit) else np.nan | |
ms_pure_error = ss_pure_error / df_pure_error if not np.isnan(ss_pure_error) else np.nan | |
# Estadísticos F y valores p | |
f_regression = ms_regression / ms_residual | |
p_regression = 1 - f.cdf(f_regression, df_regression, df_residual) | |
f_lack_of_fit = ms_lack_of_fit / ms_pure_error if not np.isnan(ms_lack_of_fit) else np.nan | |
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 | |
# Crear la tabla ANOVA detallada | |
detailed_anova_table = pd.DataFrame({ | |
'Fuente de Variación': ['Regresión', 'Residual', 'Falta de Ajuste', 'Error Puro', 'Total'], | |
'Suma de Cuadrados': [ss_regression, ss_residual, ss_lack_of_fit, ss_pure_error, ss_total], | |
'Grados de Libertad': [df_regression, df_residual, df_lack_of_fit, df_pure_error, len(self.data) - 1], | |
'Cuadrado Medio': [ms_regression, ms_residual, ms_lack_of_fit, ms_pure_error, np.nan], | |
'F': [f_regression, np.nan, f_lack_of_fit, np.nan, np.nan], | |
'Valor p': [p_regression, np.nan, p_lack_of_fit, np.nan, np.nan] | |
}) | |
# Reorganizar las filas y resetear el índice | |
detailed_anova_table = detailed_anova_table.reindex([0, 1, 2, 3, 4]).reset_index(drop=True) | |
return detailed_anova_table.round(3) | |
def get_all_tables(self): | |
""" | |
Obtiene todas las tablas generadas para ser exportadas a Excel. | |
""" | |
prediction_table = self.generate_prediction_table() | |
contribution_table = self.calculate_contribution_percentage() | |
anova_table = self.calculate_detailed_anova() | |
return { | |
'Predicciones': prediction_table, | |
'% Contribución': contribution_table, | |
'ANOVA Detallada': anova_table | |
} | |
def save_figures_to_zip(self): | |
""" | |
Guarda todas las figuras almacenadas en self.all_figures a un archivo ZIP en memoria. | |
""" | |
if not self.all_figures: | |
return None | |
zip_buffer = io.BytesIO() | |
with zipfile.ZipFile(zip_buffer, 'w') as zip_file: | |
for idx, fig in enumerate(self.all_figures, start=1): | |
img_bytes = fig.to_image(format="png") | |
zip_file.writestr(f'Grafico_{idx}.png', img_bytes) | |
zip_buffer.seek(0) | |
# Guardar en un archivo temporal | |
with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as temp_file: | |
temp_file.write(zip_buffer.read()) | |
temp_path = temp_file.name | |
return temp_path | |
def save_fig_to_bytes(self, fig): | |
""" | |
Convierte una figura Plotly a bytes en formato PNG. | |
""" | |
return fig.to_image(format="png") | |
def save_tables_to_excel(self): | |
""" | |
Guarda todas las tablas en un archivo Excel con múltiples hojas y retorna la ruta del archivo. | |
""" | |
tables = self.get_all_tables() | |
excel_buffer = io.BytesIO() | |
with pd.ExcelWriter(excel_buffer, engine='xlsxwriter') as writer: | |
for sheet_name, table in tables.items(): | |
table.to_excel(writer, sheet_name=sheet_name, index=False) | |
excel_buffer.seek(0) | |
excel_bytes = excel_buffer.read() | |
# Guardar en un archivo temporal | |
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as temp_file: | |
temp_file.write(excel_bytes) | |
temp_path = temp_file.name | |
return temp_path | |
def export_tables_to_word(self, tables_dict): | |
""" | |
Exporta las tablas proporcionadas a un documento de Word. | |
""" | |
if not tables_dict: | |
return None | |
doc = docx.Document() | |
# Configurar estilo de fuente | |
style = doc.styles['Normal'] | |
font = style.font | |
font.name = 'Times New Roman' | |
font.size = Pt(12) | |
# Título del informe | |
titulo = doc.add_heading('Informe de Optimización de Producción de AIA', 0) | |
titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER | |
doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}").alignment = WD_PARAGRAPH_ALIGNMENT.CENTER | |
doc.add_paragraph('\n') # Espacio | |
for sheet_name, table in tables_dict.items(): | |
# Añadir título de la tabla | |
doc.add_heading(sheet_name, level=1) | |
if table.empty: | |
doc.add_paragraph("No hay datos disponibles para esta tabla.") | |
continue | |
# Añadir tabla al documento | |
table_doc = doc.add_table(rows=1, cols=len(table.columns)) | |
table_doc.style = 'Light List Accent 1' | |
# Añadir encabezados | |
hdr_cells = table_doc.rows[0].cells | |
for idx, col_name in enumerate(table.columns): | |
hdr_cells[idx].text = col_name | |
# Añadir filas de datos | |
for _, row in table.iterrows(): | |
row_cells = table_doc.add_row().cells | |
for idx, item in enumerate(row): | |
row_cells[idx].text = str(item) | |
doc.add_paragraph('\n') # Espacio entre tablas | |
# Guardar el documento en un archivo temporal | |
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp: | |
doc.save(tmp.name) | |
tmp_path = tmp.name | |
return tmp_path | |
# --- Funciones para la Interfaz de Gradio --- | |
def load_data(design_type, factor_names, factor_levels, y_name, data_str): | |
""" | |
Carga los datos del diseño experimental desde las entradas y crea la instancia de RSM_ExperimentalDesign. | |
""" | |
try: | |
# Parsear nombres de factores | |
factor_names = [fn.strip() for fn in factor_names.split(',')] | |
num_factors = len(factor_names) | |
# Parsear niveles de factores | |
factor_levels_dict = {} | |
levels = factor_levels.split(';') | |
if len(levels) != num_factors: | |
raise ValueError(f"Se esperaban {num_factors} conjuntos de niveles separados por ';'.") | |
for i, level_str in enumerate(levels): | |
level_values = [float(x.strip()) for x in level_str.split(',')] | |
if len(level_values) != 3: | |
raise ValueError(f"El factor '{factor_names[i]}' requiere exactamente 3 niveles separados por comas.") | |
factor_levels_dict[factor_names[i]] = level_values | |
# Crear DataFrame a partir de la cadena de datos | |
data_list = [row.split(',') for row in data_str.strip().split('\n')] | |
column_names = ['Exp.'] + factor_names + [y_name] | |
data = pd.DataFrame(data_list, columns=column_names) | |
data = data.apply(pd.to_numeric, errors='coerce') # Convertir a numérico | |
# Crear la instancia de RSM_ExperimentalDesign | |
global rsm | |
rsm = RSM_ExperimentalDesign( | |
data=data, | |
design_type=design_type, | |
factor_names=factor_names, | |
y_name=y_name, | |
factor_levels=factor_levels_dict | |
) | |
# Generar el diseño | |
rsm.generate_design() | |
return data.round(3), gr.update(visible=True), factor_names | |
except Exception as e: | |
# Mostrar mensaje de error | |
error_message = f"Error al cargar los datos: {str(e)}" | |
print(error_message) | |
return None, gr.update(visible=False), [] | |
def fit_and_optimize_model(selected_factors): | |
if 'rsm' not in globals(): | |
return [None]*11 # Ajustar el número de outputs | |
if not selected_factors: | |
return [None]*11 # No se han seleccionado factores | |
# Ajustar modelos y optimizar | |
model_completo, pareto_completo = rsm.fit_model() | |
rsm.selected_factors = selected_factors | |
model_simplificado, pareto_simplificado = rsm.fit_simplified_model(selected_factors) | |
optimization_table = rsm.optimize() | |
equation = rsm.get_simplified_equation() | |
prediction_table = rsm.generate_prediction_table() | |
contribution_table = rsm.calculate_contribution_percentage() | |
anova_table = rsm.calculate_detailed_anova() | |
# Generar todas las figuras y almacenarlas | |
rsm.generate_all_plots() | |
# Formatear la ecuación para que se vea mejor en Markdown | |
if equation: | |
equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** ", "^").replace("*", " × ") | |
equation_formatted = f"### Ecuación del Modelo Simplificado:<br>{equation_formatted}" | |
else: | |
equation_formatted = "No se pudo generar la ecuación del modelo simplificado." | |
# Guardar las tablas en Excel temporal | |
excel_path = rsm.save_tables_to_excel() | |
# Guardar todas las figuras en un ZIP temporal | |
zip_path = rsm.save_figures_to_zip() | |
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, # Ruta del ZIP de gráficos | |
excel_path # Ruta del Excel de tablas | |
) | |
def download_current_plot(all_figures, current_index): | |
""" | |
Descarga la figura actual como PNG. | |
""" | |
if not all_figures: | |
return None | |
fig = all_figures[current_index] | |
img_bytes = rsm.save_fig_to_bytes(fig) | |
filename = f"Grafico_RSM_{current_index + 1}.png" | |
return (filename, img_bytes) | |
def download_all_plots_zip(): | |
""" | |
Descarga todas las figuras en un archivo ZIP. | |
""" | |
if 'rsm' not in globals(): | |
return None | |
zip_path = rsm.save_figures_to_zip() | |
if zip_path: | |
with open(zip_path, 'rb') as f: | |
zip_bytes = f.read() | |
filename = f"Graficos_RSM_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" | |
return (filename, zip_bytes) | |
return None | |
def download_all_tables_excel(): | |
""" | |
Descarga todas las tablas en un archivo Excel con múltiples hojas. | |
""" | |
if 'rsm' not in globals(): | |
return None | |
excel_path = rsm.save_tables_to_excel() | |
if excel_path: | |
with open(excel_path, 'rb') as f: | |
excel_bytes = f.read() | |
filename = f"Tablas_RSM_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" | |
return (filename, excel_bytes) | |
return None | |
def exportar_word(tables_dict): | |
""" | |
Función para exportar las tablas a un documento de Word. | |
""" | |
if not tables_dict: | |
return None | |
word_path = rsm.export_tables_to_word(tables_dict) | |
if word_path and os.path.exists(word_path): | |
with open(word_path, 'rb') as f: | |
word_bytes = f.read() | |
filename = f"Tablas_RSM_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx" | |
return (filename, word_bytes) | |
return None | |
# --- Crear la interfaz de Gradio --- | |
def create_gradio_interface(): | |
with gr.Blocks() as demo: | |
gr.Markdown("# Optimización de la Producción de AIA usando RSM") | |
with gr.Row(): | |
with gr.Column(): | |
gr.Markdown("## Configuración del Diseño") | |
design_type_input = gr.Dropdown( | |
label="Tipo de Diseño", | |
choices=["Box-Behnken", "Central Compuesto"], | |
value="Box-Behnken" | |
) | |
factor_names_input = gr.Textbox( | |
label="Nombres de los Factores (separados por comas)", | |
value="Glucosa, Extracto_de_Levadura, Triptófano" | |
) | |
factor_levels_input = gr.Textbox( | |
label="Niveles de los Factores (cada conjunto separado por ';' y niveles separados por comas)", | |
value="1, 3.5, 5.5; 0.03, 0.2, 0.3; 0.4, 0.65, 0.9" | |
) | |
y_name_input = gr.Textbox( | |
label="Nombre de la Variable Dependiente (ej. AIA_ppm)", | |
value="AIA_ppm" | |
) | |
data_input = gr.Textbox( | |
label="Datos del Experimento (formato CSV)", | |
lines=10, | |
value="""1,-1,-1,0,166.594 | |
2,1,-1,0,177.557 | |
3,-1,1,0,127.261 | |
4,1,1,0,147.573 | |
5,-1,0,-1,188.883 | |
6,1,0,-1,224.527 | |
7,-1,0,1,190.238 | |
8,1,0,1,226.483 | |
9,0,-1,-1,195.550 | |
10,0,1,-1,149.493 | |
11,0,-1,1,187.683 | |
12,0,1,1,148.621 | |
13,0,0,0,278.951 | |
14,0,0,0,297.238 | |
15,0,0,0,280.896""" | |
) | |
load_button = gr.Button("Cargar Datos") | |
with gr.Column(): | |
gr.Markdown("## Datos Cargados") | |
data_output = gr.Dataframe(label="Tabla de Datos", interactive=False) | |
# Sección de análisis visible solo después de cargar los datos | |
with gr.Row(visible=False) as analysis_row: | |
with gr.Column(): | |
fit_button = gr.Button("Ajustar Modelo y Optimizar") | |
gr.Markdown("**Modelo Completo**") | |
model_completo_output = gr.HTML() | |
pareto_completo_output = gr.Plot() | |
gr.Markdown("**Modelo Simplificado**") | |
model_simplificado_output = gr.HTML() | |
pareto_simplificado_output = gr.Plot() | |
gr.Markdown("**Ecuación del Modelo Simplificado**") | |
equation_output = gr.HTML() | |
gr.Markdown("**Tabla de Optimización**") | |
optimization_table_output = gr.Dataframe(label="Tabla de Optimización", interactive=False) | |
gr.Markdown("**Tabla de Predicciones**") | |
prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False) | |
gr.Markdown("**Tabla de % de Contribución**") | |
contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución", interactive=False) | |
gr.Markdown("**Tabla ANOVA Detallada**") | |
anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False) | |
gr.Markdown("## Descargar Todas las Tablas") | |
download_excel_button = gr.DownloadButton(label="Descargar Tablas en Excel") | |
download_word_button = gr.DownloadButton(label="Descargar Tablas en Word") | |
with gr.Column(): | |
gr.Markdown("## Selección de Factores para el Modelo Simplificado") | |
selected_factors_input = gr.CheckboxGroup( | |
label="Selecciona los Factores a Incluir", | |
choices=[], # Actualizar dinámicamente | |
value=[] | |
) | |
fit_button_2 = gr.Button("Aplicar Selección de Factores") | |
gr.Markdown("## Generar Gráficos de Superficie de Respuesta") | |
plot_button = gr.Button("Generar Gráficos") | |
with gr.Row(): | |
left_button = gr.Button("<") | |
right_button = gr.Button(">") | |
rsm_plot_output = gr.Plot() | |
plot_info = gr.Textbox(label="Información del Gráfico", value="Gráfico 1 de 0", interactive=False) | |
with gr.Row(): | |
download_plot_button = gr.DownloadButton(label="Descargar Gráfico Actual (PNG)") | |
download_all_plots_button = gr.DownloadButton(label="Descargar Todos los Gráficos (ZIP)") | |
current_index_state = gr.State(0) # Estado para el índice actual | |
all_figures_state = gr.State([]) # Estado para todas las figuras | |
# Funciones de carga y ajuste | |
def load_data_interface(design_type, factor_names, factor_levels, y_name, data_str): | |
data, analysis_visibility, factor_names_list = load_data(design_type, factor_names, factor_levels, y_name, data_str) | |
if data is not None: | |
# Actualizar las opciones de selección de factores | |
selected_factors = factor_names_list | |
return data, analysis_visibility, selected_factors | |
else: | |
return data, analysis_visibility, [] | |
load_button.click( | |
load_data_interface, | |
inputs=[design_type_input, factor_names_input, factor_levels_input, y_name_input, data_input], | |
outputs=[data_output, analysis_row, selected_factors_input] | |
) | |
# Ajustar modelo y optimizar | |
fit_button.click( | |
fit_and_optimize_model, | |
inputs=[selected_factors_input], | |
outputs=[ | |
model_completo_output, | |
pareto_completo_output, | |
model_simplificado_output, | |
pareto_simplificado_output, | |
equation_output, | |
optimization_table_output, | |
prediction_table_output, | |
contribution_table_output, | |
anova_table_output, | |
download_all_plots_button, | |
download_excel_button | |
] | |
) | |
# Conectar los botones de descarga de tablas | |
download_excel_button.click( | |
fn=download_all_tables_excel, | |
inputs=[], | |
outputs=download_excel_button | |
) | |
download_word_button.click( | |
fn=exportar_word, | |
inputs=[gr.State()], | |
outputs=download_word_button | |
) | |
# Generar y mostrar los gráficos | |
def generate_plots(): | |
if 'rsm' not in globals() or not rsm.all_figures: | |
return None, "No hay gráficos disponibles.", 0, [] | |
# Mostrar el primer gráfico | |
return rsm.all_figures[0], f"Gráfico 1 de {len(rsm.all_figures)}", 0, rsm.all_figures | |
plot_button.click( | |
generate_plots, | |
inputs=[], | |
outputs=[rsm_plot_output, plot_info, current_index_state, all_figures_state] | |
) | |
# Navegación de gráficos | |
def navigate(direction, current_index, all_figures): | |
if not all_figures: | |
return None, "No hay gráficos disponibles.", current_index | |
if direction == 'left': | |
new_index = (current_index - 1) % len(all_figures) | |
elif direction == 'right': | |
new_index = (current_index + 1) % len(all_figures) | |
else: | |
new_index = current_index | |
selected_fig = all_figures[new_index] | |
plot_info_text = f"Gráfico {new_index + 1} de {len(all_figures)}" | |
return selected_fig, plot_info_text, new_index | |
left_button.click( | |
navigate, | |
inputs=[gr.Button("left"), current_index_state, all_figures_state], | |
outputs=[rsm_plot_output, plot_info, current_index_state] | |
) | |
right_button.click( | |
navigate, | |
inputs=[gr.Button("right"), current_index_state, all_figures_state], | |
outputs=[rsm_plot_output, plot_info, current_index_state] | |
) | |
# Descargar gráfico actual | |
download_plot_button.click( | |
download_current_plot, | |
inputs=[all_figures_state, current_index_state], | |
outputs=download_plot_button | |
) | |
# Descargar todos los gráficos en ZIP | |
download_all_plots_button.click( | |
download_all_plots_zip, | |
inputs=[], | |
outputs=download_all_plots_button | |
) | |
# Ejemplo de uso | |
gr.Markdown("## Ejemplo de uso") | |
gr.Markdown(""" | |
1. **Configura el Diseño:** | |
- Selecciona el tipo de diseño (Box-Behnken o Central Compuesto). | |
- Ingresa los nombres de los factores separados por comas. | |
- Ingresa los niveles para cada factor separados por comas y cada conjunto de niveles por ';'. | |
- Especifica el nombre de la variable dependiente. | |
- Proporciona los datos del experimento en formato CSV. | |
2. **Cargar Datos:** | |
- Haz clic en 'Cargar Datos' para cargar y visualizar los datos. | |
3. **Seleccionar Factores para el Modelo Simplificado:** | |
- Marca los factores que deseas incluir en el modelo simplificado. | |
4. **Ajustar Modelo y Optimizar:** | |
- Haz clic en 'Ajustar Modelo y Optimizar' para ajustar los modelos y obtener los resultados. | |
5. **Generar y Navegar Gráficos:** | |
- Haz clic en 'Generar Gráficos' para crear las superficies de respuesta. | |
- Navega entre los gráficos usando los botones '<' y '>'. | |
- Descarga el gráfico actual en PNG o todos los gráficos en un archivo ZIP. | |
6. **Descargar Tablas:** | |
- Descarga todas las tablas generadas en un archivo Excel o Word. | |
""") | |
return demo | |
# --- Función Principal --- | |
def main(): | |
interface = create_gradio_interface() | |
interface.launch(share=True) | |
if __name__ == "__main__": | |
main() | |