import streamlit as st import pandas as pd import numpy as np import plotly.graph_objects as go from scipy.optimize import minimize, differential_evolution from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score import matplotlib.pyplot as plt import seaborn as sns import io # Configuración inicial # Configuración de la aplicación st.set_page_config(page_title="Optimización Avanzada", page_icon="📊",layout="wide") # Display the image above the title st.image('cannabis.jpg', use_container_width=True) st.title("Optimización Avanzada con Diseño Experimental Box-Behnken") st.write("Aplicación con regresión cuadrática y estrategias de optimización mejoradas.") # Crear las pestañas tabs = st.selectbox("Selecciona una opción", ["Fundamento Teórico", "Aplicación Interactiva"]) if tabs == "Fundamento Teórico": st.header("Fundamento Teórico") # Imagen del proceso st.subheader("Proceso de Extracción de CBD") st.image("CBD extraction process.png", caption="Proceso de Extracción de CBD con CO2 Supercrítico", use_container_width=True) st.write(""" El proceso de extracción de CBD utiliza tecnología de CO2 supercrítico debido a su eficiencia y capacidad para producir extractos puros. Este método incluye etapas clave como molienda, extracción, separación y refinamiento, garantizando un producto de alta calidad para aplicaciones medicinales y comerciales. """) # Diseño Experimental Box-Behnken st.subheader("Diseño Experimental Box-Behnken") st.write(""" El diseño experimental **Box-Behnken** se utiliza para modelar y optimizar procesos complejos. En este caso, se aplica para maximizar el rendimiento de CBD considerando variables clave como **Temperatura**, **Presión**, **Flujo de CO2** y **Tiempo**. Este enfoque reduce significativamente la cantidad de experimentos necesarios, permitiendo explorar interacciones no lineales de manera eficiente. """) # Proceso de Producción de CBD st.subheader("Proceso de Producción de CBD") st.write(""" Según el documento *Optimization of Supercritical Carbon Dioxide Fluid Extraction of Medicinal Cannabis from Quebec*, el proceso de extracción con CO2 supercrítico es preferido por su alta selectividad y pureza. Las principales etapas incluyen: 1. **Preparación de la materia prima**: Molienda y acondicionamiento. 2. **Extracción supercrítica**: - El CO2 actúa como solvente bajo condiciones controladas de presión y temperatura. - Variables clave: presión (150-320 bar), temperatura (40-70°C), flujo de CO2 (5-15 g/min), tiempo (2-4 horas). 3. **Separación y recolección**: El CO2 se despresuriza para liberar los cannabinoides extraídos. 4. **Refinamiento posterior**: Remoción de ceras y otros compuestos no deseados. """) # Caso de Negocio st.subheader("Caso de Negocio: Optimización del Rendimiento") st.write(""" Optimizar el rendimiento del proceso de extracción permite: - Maximizar la cantidad de CBD extraído por lote. - Reducir costos operativos (energía, solventes, tiempo). - Mejorar la calidad del producto final. Este enfoque es crucial en la industria del cannabis medicinal, donde la eficiencia del proceso impacta directamente en la rentabilidad y sostenibilidad del negocio. """) # Método Basado en CRISP-DM st.subheader("Metodología Basada en CRISP-DM") st.write(""" La metodología **CRISP-DM** estructura el desarrollo del modelo en seis etapas: 1. **Comprensión del Negocio**: Definir objetivos y restricciones del proceso. 2. **Comprensión de los Datos**: Analizar datos experimentales y evaluar su calidad. 3. **Preparación de los Datos**: Limpiar y transformar datos para el modelado. 4. **Modelado**: Ajustar un modelo de regresión cuadrática para capturar relaciones no lineales. 5. **Evaluación**: Validar el modelo y analizar su desempeño. 6. **Despliegue**: Implementar el modelo en una aplicación interactiva para optimización en tiempo real. """) # Optimización st.subheader("Optimización") st.write(""" Se emplean técnicas avanzadas para maximizar el rendimiento: - **L-BFGS-B**: Método de optimización local. - **Evolución Diferencial**: Optimización global para evitar óptimos locales. - **Múltiples inicios aleatorios**: Combina estrategias locales y globales para robustez. """) # Referencias st.subheader("Referencias") st.write(""" - [Optimization of Supercritical Carbon Dioxide Fluid Extraction of Medicinal Cannabis from Quebec](https://www.mdpi.com/2227-9717/11/7/1953). - Herrero, M., Cifuentes, A., & Ibañez, E. (2006). Supercritical fluid extraction: Recent advances and applications. *Journal of Chromatography A*, 1131(1), 1–24. - Turner, C., Mathiasson, L., & Lewis, G. (2001). Supercritical fluid extraction and chromatography. *Journal of Biochemical Analysis*, 121(3), 35–58. """) elif tabs == "Aplicación Interactiva": st.header("Aplicación Interactiva") st.write("A continuación, puedes cargar tus datos, realizar predicciones y optimizar el rendimiento del proceso de extracción de cannabinoides.") # Opciones de selección de datos de ejemplo st.subheader("Datos de Ejemplo") usar_datos_exp = st.checkbox("Usar datos de ejemplo: datos_exp.csv") usar_datos_process = st.checkbox("Usar datos de ejemplo: datos_process.csv") # Inicializar variable de datos data = None # Verificar qué checkbox está seleccionado y cargar el archivo correspondiente if usar_datos_exp and not usar_datos_process: data = pd.read_csv("data_exp.csv") st.success("Datos de ejemplo (datos_exp.csv) cargados exitosamente.") st.dataframe(data, use_container_width=True) # Ajustar al ancho de la app elif usar_datos_process and not usar_datos_exp: data = pd.read_csv("data_process.csv") st.success("Datos de ejemplo (datos_process.csv) cargados exitosamente.") st.dataframe(data, use_container_width=True) # Ajustar al ancho de la app elif usar_datos_exp and usar_datos_process: st.error("Por favor, selecciona solo un conjunto de datos de ejemplo a la vez.") # Opción para cargar datos personalizados st.subheader("Carga tus Datos") uploaded_file = st.file_uploader("Carga un archivo CSV con los datos experimentales:", type="csv") if uploaded_file is not None: # Leer datos cargados data = pd.read_csv(uploaded_file) st.success("Datos cargados exitosamente desde el archivo proporcionado.") st.dataframe(data, use_container_width=True) # Ajustar al ancho de la app # Validación para asegurarse de que se cargaron datos if data is None: st.warning("No se han cargado datos. Por favor, selecciona un archivo o usa datos de ejemplo.") else: st.write("### Datos listos para su análisis.") # Definir el orden fijo de variables variable_columns = ['Temperatura', 'Presión', 'Flujo_CO2', 'Tiempo'] # Extraer variables independientes y dependiente en el orden correcto X = data[variable_columns] y = data['Rendimiento'] # Generar términos cuadráticos (regresión polinómica de segundo grado) poly = PolynomialFeatures(degree=2, include_bias=False) X_poly = poly.fit_transform(X) columnas_poly = poly.get_feature_names_out(X.columns) # Ajustar modelo cuadrático modelo = LinearRegression() modelo.fit(X_poly, y) # Predicciones del modelo y_pred = modelo.predict(X_poly) # Evaluación del modelo st.subheader("Evaluación del Modelo Cuadrático") st.write(f"**Error Cuadrático Medio (MSE):** {mean_squared_error(y, y_pred):.4f}") st.write(f"**R² (Coeficiente de Determinación):** {r2_score(y, y_pred):.4f}") # Resumen del modelo: coeficientes # Visualización paralela usando columnas col1, col2 = st.columns(2) # En la columna 1, mostrar el DataFrame con términos y coeficientes, adaptado al ancho de la columna with col1: st.markdown("#### Términos y Coeficientes del Modelo") # Crear un DataFrame con los coeficientes coeficientes = pd.DataFrame({ 'Término': columnas_poly, 'Coeficiente': modelo.coef_ }) coeficientes = coeficientes.sort_values(by='Coeficiente', ascending=False) # Función para aplicar colores a los coeficientes def color_coef(val): color = 'red' if val > 0 else 'blue' # Los coeficientes positivos serán rojos, negativos azules return f'background-color: {color}; color: white;' # Aplicar estilo a la columna 'Coeficiente' para colorear los valores styled_coef = coeficientes.style.applymap(color_coef, subset=['Coeficiente']) # Mostrar el DataFrame estilizado y ajustado al ancho de la columna st.dataframe(styled_coef, use_container_width=True) # En la columna 2, mostrar el gráfico de importancia de las variables, adaptado al ancho de la columna with col2: st.markdown("#### Importancia de las Variables (Feature Importance)") # Calcular la importancia de las variables (valor absoluto de los coeficientes) coef_abs = np.abs(modelo.coef_) # Valor absoluto de los coeficientes feature_importance = pd.DataFrame({ 'Variable': columnas_poly, 'Importancia': coef_abs }).sort_values(by='Importancia', ascending=False) # De mayor a menor # Gráfico de barras horizontal con el eje Y invertido fig_importance = go.Figure(go.Bar( y=feature_importance['Variable'], x=feature_importance['Importancia'], orientation='h', marker=dict(color='teal') )) fig_importance.update_layout( title="Importancia de las Variables en el Modelo Cuadrático", xaxis_title="Importancia", yaxis_title="Variables", yaxis=dict(autorange="reversed"), # Invertir el eje Y margin=dict(l=0, r=0, t=30, b=30) # Ajustar márgenes ) st.plotly_chart(fig_importance, use_container_width=True) # Superficies de respuesta dinámicas st.subheader("Superficies de Respuesta") # Selector de variables eje_x = st.selectbox("Selecciona la variable para el eje X:", variable_columns, index=0) eje_z = st.selectbox("Selecciona la variable para el eje Z:", variable_columns, index=1) # Rango para generar puntos x_range = np.linspace(X[eje_x].min(), X[eje_x].max(), 50) z_range = np.linspace(X[eje_z].min(), X[eje_z].max(), 50) X_grid, Z_grid = np.meshgrid(x_range, z_range) # Preparar valores para las otras dos variables otras_variables = [col for col in variable_columns if col not in [eje_x, eje_z]] # Crear grilla de predicción con orden de columnas fijo grid_data = [] for x_val, z_val in zip(X_grid.ravel(), Z_grid.ravel()): # Crear un diccionario con todas las variables en el orden correcto row_data = dict(zip(variable_columns, [ x_val if eje_x == 'Temperatura' else X['Temperatura'].mean(), x_val if eje_x == 'Presión' else (z_val if eje_z == 'Presión' else X['Presión'].mean()), x_val if eje_x == 'Flujo_CO2' else (z_val if eje_z == 'Flujo_CO2' else X['Flujo_CO2'].mean()), x_val if eje_x == 'Tiempo' else (z_val if eje_z == 'Tiempo' else X['Tiempo'].mean()) ])) grid_data.append(row_data) # Convertir a DataFrame con orden de columnas fijo grid_df = pd.DataFrame(grid_data)[variable_columns] # Transformar datos para predicciones grid_poly = poly.transform(grid_df) # Predecir valores Y_grid = modelo.predict(grid_poly).reshape(X_grid.shape) # Gráfico dinámico con Plotly fig = go.Figure(data=[go.Surface(z=Y_grid, x=X_grid, y=Z_grid, colorscale='Viridis')]) fig.update_layout( title=f"Superficie de Respuesta: {eje_x} vs {eje_z} vs Rendimiento", scene=dict( xaxis_title=eje_x, yaxis_title=eje_z, zaxis_title="Rendimiento (%)" ) ) st.plotly_chart(fig, use_container_width=True) # Análisis de sensibilidad st.subheader("Análisis de Sensibilidad") temp = st.slider("Temperatura (°C)", int(X['Temperatura'].min()), int(X['Temperatura'].max()), int(X['Temperatura'].mean())) pres = st.slider("Presión (Bar)", int(X['Presión'].min()), int(X['Presión'].max()), int(X['Presión'].mean())) flujo = st.slider("Flujo CO2 (g/min)", int(X['Flujo_CO2'].min()), int(X['Flujo_CO2'].max()), int(X['Flujo_CO2'].mean())) tiempo = st.slider("Tiempo (h)", int(X['Tiempo'].min()), int(X['Tiempo'].max()), int(X['Tiempo'].mean())) # Predicción para los valores seleccionados entrada_sensibilidad = pd.DataFrame({'Temperatura': [temp], 'Presión': [pres], 'Flujo_CO2': [flujo], 'Tiempo': [tiempo]}) entrada_poly = poly.transform(entrada_sensibilidad) prediccion = modelo.predict(entrada_poly) st.write(f"**Rendimiento Predicho:** {prediccion[0]:.2f}%") # Optimización de puntos mejorada st.subheader("Determinación de Puntos Óptimos") def objetivo(params): """Función objetivo para optimización.""" # Transformar parámetros a DataFrame entrada = pd.DataFrame([params], columns=variable_columns) entrada_poly = poly.transform(entrada) return -modelo.predict(entrada_poly)[0] # Negativo para maximizar # Métodos de Optimización st.write("#### Comparación de Métodos de Optimización") # Límites de las variables limites = [(X[col].min(), X[col].max()) for col in variable_columns] # 1. Optimización por L-BFGS-B (Método Local) #st.write("##### Método L-BFGS-B (Optimización Local)") x0 = [X[col].mean() for col in variable_columns] resultado_lbfgs = minimize( objetivo, x0=x0, bounds=limites, method='L-BFGS-B' ) # 2. Evolución Diferencial (Método Global) #st.write("##### Evolución Diferencial (Optimización Global)") resultado_de = differential_evolution( objetivo, bounds=limites, strategy='best1bin', popsize=15, maxiter=100 ) # 3. Múltiples Inicios Aleatorios #st.write("##### Múltiples Inicios Aleatorios") def multi_start_optimize(num_starts=10): resultados = [] for _ in range(num_starts): # Punto inicial aleatorio x0 = [np.random.uniform(low, high) for low, high in limites] resultado = minimize( objetivo, x0=x0, bounds=limites, method='L-BFGS-B' ) resultados.append((resultado, -resultado.fun)) # Encontrar el mejor resultado return max(resultados, key=lambda x: x[1]) resultado_multi = multi_start_optimize() # Mostrar resultados de optimización metodos = [ ("L-BFGS-B", resultado_lbfgs, -resultado_lbfgs.fun), ("Evolución Diferencial", resultado_de, -resultado_de.fun), ("Múltiples Inicios", resultado_multi[0], resultado_multi[1]) ] # Tabla comparativa de resultados resultados_df = pd.DataFrame(columns=variable_columns + ['Rendimiento Predicho']) for nombre, resultado, rendimiento in metodos: if resultado.success: fila = pd.DataFrame([list(resultado.x) + [rendimiento]], columns=variable_columns + ['Rendimiento Predicho']) fila.insert(0, 'Método', nombre) resultados_df = pd.concat([resultados_df, fila], ignore_index=True) # Mostrar tabla de resultados #st.write("### Comparación de Resultados de Optimización") st.dataframe(resultados_df) # Seleccionar el mejor resultado mejor_resultado = resultados_df.loc[resultados_df['Rendimiento Predicho'].idxmax()] st.write("### Punto Óptimo Recomendado") st.write(f"**Método:** {mejor_resultado['Método']}") # Mostrar detalles del mejor punto detalles_optimos = mejor_resultado[variable_columns].to_dict() detalles_str = ", ".join([f"{col}: {val:.2f}" for col, val in detalles_optimos.items()]) st.write(f"**Punto Óptimo:** {detalles_str}") st.write(f"**Rendimiento Máximo Predicho:** {mejor_resultado['Rendimiento Predicho']:.2f}%") # Análisis de Incertidumbre st.subheader("Análisis de Incertidumbre") # Input para número de bootstraps num_bootstraps = st.number_input( "Número de Bootstraps", min_value=10, max_value=1000, value=100, step=10, help="Número de remuestreos para el análisis de incertidumbre" ) # Botón para realizar análisis de incertidumbre if st.button("Realizar Análisis de Incertidumbre"): with st.spinner('Realizando análisis de bootstrapping...'): # Bootstrap para estimar intervalos de confianza def bootstrap_optimize(num_bootstraps=num_bootstraps): resultados_bootstrap = [] for _ in range(num_bootstraps): # Muestreo con reemplazo indices = np.random.randint(0, len(X), len(X)) X_boot = X.iloc[indices] y_boot = y.iloc[indices] # Ajustar modelo poly_boot = PolynomialFeatures(degree=2, include_bias=False) X_poly_boot = poly_boot.fit_transform(X_boot) modelo_boot = LinearRegression() modelo_boot.fit(X_poly_boot, y_boot) # Definir nueva función objetivo def objetivo_boot(params): entrada = pd.DataFrame([params], columns=variable_columns) entrada_poly = poly_boot.transform(entrada) return -modelo_boot.predict(entrada_poly)[0] # Optimizar resultado = differential_evolution( objetivo_boot, bounds=limites, strategy='best1bin', popsize=15, maxiter=100 ) resultados_bootstrap.append({ 'Punto': resultado.x, 'Rendimiento': -resultado.fun }) return resultados_bootstrap # Realizar bootstrap resultados_bootstrap = bootstrap_optimize() # Convertir a DataFrame bootstrap_df = pd.DataFrame(resultados_bootstrap) # Calcular intervalos de confianza intervalos_confianza = {} for i, col in enumerate(variable_columns): intervalos_confianza[col] = ( np.percentile(bootstrap_df['Punto'].apply(lambda x: x[i]), 2.5), np.percentile(bootstrap_df['Punto'].apply(lambda x: x[i]), 97.5) ) # Mostrar intervalos de confianza st.write("### Intervalos de Confianza (95%)") for col, (min_val, max_val) in intervalos_confianza.items(): st.write(f"**{col}:** [{min_val:.2f}, {max_val:.2f}]") # Distribución de rendimientos st.write("### Distribución de Rendimientos en Bootstrap") fig_bootstrap = plt.figure(figsize=(10, 6)) plt.hist(bootstrap_df['Rendimiento'], bins=30, edgecolor='black') plt.title(f'Distribución de Rendimientos Predichos (Bootstrap: {num_bootstraps})') plt.xlabel('Rendimiento (%)') plt.ylabel('Frecuencia') st.pyplot(fig_bootstrap)