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}
{fixed_variable} fijo en {fixed_level:.3f} ({self.get_units(fixed_variable)}) (Modelo Simplificado)", 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(" + ", "
+ ").replace(" ** ", "^").replace("*", " × ") equation_formatted = f"### Ecuación del Modelo Simplificado:
{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()