File size: 21,333 Bytes
1fb0787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1ed0844
1fb0787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
abb123d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
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)