Spaces:
Sleeping
Sleeping
| 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) |