Spaces:
Paused
Paused
| # prepare.py - Módulo para datos | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.express as px | |
| from sklearn.preprocessing import StandardScaler, LabelEncoder | |
| from datetime import datetime | |
| from ydata_profiling import ProfileReport | |
| import os | |
| def show_prepare(): | |
| # Crear un contenedor para mensajes de estado | |
| status_container = st.empty() | |
| # Verificar si hay datos cargados | |
| if 'er_data' not in st.session_state: | |
| status_container.warning("⚠️ No hay datos cargados. Por favor, carga un dataset en la página Upload primero.") | |
| return | |
| try: | |
| # Usar los datos preparados si existen, si no, usar los datos originales | |
| if 'temp_prepared_data' in st.session_state: | |
| prepare = st.session_state.temp_prepared_data.copy() | |
| else: | |
| # Si no hay datos temporales, intentar usar datos preparados permanentes | |
| if 'prepared_data' in st.session_state: | |
| prepare = st.session_state.prepared_data.copy() | |
| else: | |
| prepare = st.session_state.er_data.copy() | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| except AttributeError: | |
| status_container.warning("⚠️ No hay datos cargados o los datos son inválidos.") | |
| return | |
| # Análisis de valores únicos por columna | |
| st.markdown("### Análisis de Valores Únicos por Columna") | |
| # Selección de columnas para analizar - sin selección por defecto | |
| all_columns = prepare.columns.tolist() | |
| selected_columns = st.multiselect( | |
| "Seleccionar columnas para analizar", | |
| all_columns, | |
| default=[], # Sin selección por defecto | |
| key="unique_values_columns", | |
| help="Selecciona las columnas que deseas analizar" | |
| ) | |
| if not selected_columns: | |
| st.info("👆 Selecciona una o más columnas para ver su análisis detallado.") | |
| else: | |
| # Controles para número de valores a mostrar | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| n_first = st.number_input( | |
| "Número de primeros valores a mostrar", | |
| min_value=1, | |
| max_value=20, | |
| value=5, | |
| key="n_first_values" | |
| ) | |
| with col2: | |
| n_last = st.number_input( | |
| "Número de últimos valores a mostrar", | |
| min_value=1, | |
| max_value=20, | |
| value=5, | |
| key="n_last_values" | |
| ) | |
| # Crear tabs para cada columna seleccionada | |
| tabs = st.tabs([f"📊 {col}" for col in selected_columns]) | |
| # Análisis por cada columna seleccionada | |
| for tab, col in zip(tabs, selected_columns): | |
| with tab: | |
| try: | |
| st.markdown(f"### Análisis de {col} ({prepare[col].dtype})") | |
| # Safely convert column to handle mixed types | |
| column_data = prepare[col].fillna('Sin valor').astype(str) | |
| valores_unicos = column_data.unique() | |
| n_valores = len(valores_unicos) | |
| # Información general en columnas | |
| col1, col2, col3 = st.columns([2, 1, 1]) | |
| with col1: | |
| st.write(f"Total de valores únicos: {n_valores}") | |
| with col2: | |
| st.write(f"Valores nulos: {prepare[col].isnull().sum()}") | |
| with col3: | |
| st.write(f"% nulos: {(prepare[col].isnull().sum() / len(prepare) * 100).round(2)}%") | |
| st.markdown("---") | |
| # Visualización de valores únicos | |
| if n_valores > (n_first + n_last): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.write("🔼 Primeros valores:") | |
| for valor in valores_unicos[:n_first]: | |
| st.write(f"• {str(valor)}") | |
| with col2: | |
| st.write("🔽 Últimos valores:") | |
| for valor in valores_unicos[-n_last:]: | |
| st.write(f"• {str(valor)}") | |
| else: | |
| st.write("📝 Todos los valores únicos:") | |
| for valor in valores_unicos: | |
| st.write(f"• '{str(valor)}'") | |
| st.markdown("---") | |
| # Distribución de frecuencias | |
| value_counts = column_data.value_counts() | |
| # Gráfico de barras con conversión segura | |
| fig_bar = { | |
| 'data': [{ | |
| 'type': 'bar', | |
| 'x': value_counts.index, | |
| 'y': value_counts.values, | |
| 'name': 'Frecuencia' | |
| }], | |
| 'layout': { | |
| 'title': f'Distribución de valores en {col}', | |
| 'xaxis': {'title': 'Valor'}, | |
| 'yaxis': {'title': 'Frecuencia'}, | |
| 'height': 400, | |
| 'showlegend': False | |
| } | |
| } | |
| st.plotly_chart(fig_bar, use_container_width=True) | |
| # Tabla de frecuencias | |
| freq_df = pd.DataFrame({ | |
| 'Valor': value_counts.index, | |
| 'Frecuencia': value_counts.values, | |
| 'Porcentaje': (value_counts.values / len(prepare) * 100).round(2) | |
| }) | |
| st.dataframe(freq_df, use_container_width=True) | |
| except Exception as e: | |
| st.error(f"Error al procesar la columna {col}") | |
| st.error(str(e)) | |
| st.write(f"Detalles técnicos del error en la columna {col}:", e) | |
| st.subheader("Preparación de Datos") | |
| st.subheader("Eliminación de Columnas") | |
| # Verificar el estado actual de valores nulos | |
| current_null_count = prepare.isnull().sum().sum() | |
| all_columns = prepare.columns.tolist() | |
| columns_to_drop = st.multiselect( | |
| "Seleccionar columnas a eliminar", | |
| all_columns, | |
| key="columns_to_drop" | |
| ) | |
| if columns_to_drop: | |
| if st.button("Eliminar columnas seleccionadas", key="drop_columns_button"): | |
| try: | |
| # Crear una copia temporal antes de eliminar columnas | |
| temp_prepare = prepare.copy() | |
| # Verificar que las columnas existen antes de eliminarlas | |
| missing_cols = [col for col in columns_to_drop if col not in temp_prepare.columns] | |
| if missing_cols: | |
| st.error(f"❌ Las siguientes columnas no existen: {', '.join(missing_cols)}") | |
| return | |
| # Eliminar las columnas | |
| temp_prepare = temp_prepare.drop(columns=columns_to_drop) | |
| # Verificar valores nulos después de la eliminación | |
| new_null_count = temp_prepare.isnull().sum().sum() | |
| if new_null_count <= current_null_count: # Permitir igual o menor cantidad de nulos | |
| # Actualizar el DataFrame y el estado | |
| prepare = temp_prepare | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| # Mostrar mensaje de éxito | |
| st.success(f"✅ Columnas eliminadas exitosamente: {', '.join(columns_to_drop)}") | |
| # Actualizar información sobre valores nulos | |
| if new_null_count == 0: | |
| st.success("✅ No hay valores nulos en los datos.") | |
| else: | |
| st.warning(f"⚠️ Hay {new_null_count} valores nulos en los datos.") | |
| # Mostrar resumen de las columnas restantes | |
| st.write(f"Columnas restantes: {len(prepare.columns)}") | |
| if st.checkbox("Ver lista de columnas restantes", key="remaining_columns_checkbox"): | |
| st.write(prepare.columns.tolist()) | |
| else: | |
| st.error(f"❌ La operación incrementaría los valores nulos de {current_null_count} a {new_null_count}. Operación cancelada.") | |
| except Exception as e: | |
| st.error(f"Error durante la eliminación de columnas: {str(e)}") | |
| st.exception(e) | |
| # Manejo de valores faltantes | |
| st.subheader("Manejo de Valores Faltantes") | |
| missing_values = prepare.isnull().sum() | |
| missing_percentages = (missing_values / len(prepare) * 100).round(2) | |
| # Crear DataFrame y ordenar por número de valores faltantes de mayor a menor | |
| missing_df = pd.DataFrame({ | |
| 'Columna': missing_values.index, | |
| 'Valores Faltantes': missing_values.values, | |
| 'Porcentaje': missing_percentages.values, | |
| 'Tipo': prepare[missing_values.index].dtypes | |
| }) | |
| missing_df = missing_df[missing_df['Valores Faltantes'] > 0].sort_values('Valores Faltantes', ascending=False) | |
| if not missing_df.empty: | |
| # Mostrar advertencia si hay columnas de tipo object con valores faltantes | |
| object_cols = missing_df[missing_df['Tipo'] == 'object'] | |
| if not object_cols.empty: | |
| st.warning("⚠️ Se detectaron columnas de tipo texto/categórico (object) con valores faltantes. " | |
| "Se recomienda revisar estos casos con especial atención ya que el método de imputación " | |
| "podría afectar significativamente el análisis.") | |
| st.write("Columnas de tipo texto/categórico con valores faltantes:") | |
| st.dataframe(object_cols) | |
| st.write("Valores faltantes por columna (ordenados de mayor a menor):") | |
| st.dataframe(missing_df) | |
| # Checkbox para manejo especial de columnas object | |
| handle_objects = st.checkbox("Especificar valor de reemplazo para columnas de texto", | |
| help="Marca esta opción para especificar un valor personalizado para rellenar " | |
| "los valores faltantes en columnas de texto/categóricas") | |
| object_replacement = None | |
| if handle_objects: | |
| object_replacement = st.text_input("Valor de reemplazo para columnas de texto:", | |
| value="MISSING", | |
| help="Este valor se usará para rellenar los valores faltantes " | |
| "en todas las columnas de texto/categóricas") | |
| missing_strategy = st.radio( | |
| "Selecciona estrategia para valores faltantes:", | |
| ["Eliminar filas", "Rellenar con media", "Rellenar con mediana", "Rellenar con moda"] | |
| ) | |
| if st.button("Aplicar estrategia de valores faltantes", key="apply_missing_strategy_button"): | |
| try: | |
| # Guardar el estado anterior de prepare para verificación | |
| nulls_before = prepare.isnull().sum().sum() | |
| if missing_strategy == "Eliminar filas": | |
| # Guardar el número de filas antes | |
| rows_before = len(prepare) | |
| # Crear una copia para no modificar el original | |
| prepare_cleaned = prepare.copy() | |
| # Eliminar filas con valores nulos | |
| prepare_cleaned = prepare_cleaned.dropna(how='any') | |
| # Verificar que no queden valores nulos | |
| if prepare_cleaned.isnull().sum().sum() == 0: | |
| prepare = prepare_cleaned # Actualizar prepare solo si la limpieza fue exitosa | |
| st.session_state.temp_prepared_data = prepare.copy() # Actualizar el estado temporal | |
| rows_removed = rows_before - len(prepare) | |
| st.success(f"Se eliminaron {rows_removed} filas con valores faltantes. No quedan valores nulos.") | |
| else: | |
| st.error(f"Error: Aún quedan {prepare_cleaned.isnull().sum().sum()} valores faltantes después de la eliminación.") | |
| return | |
| else: | |
| # Separar columnas numéricas y no numéricas | |
| numeric_cols = prepare.select_dtypes(include=['int64', 'float64']).columns | |
| non_numeric_cols = prepare.select_dtypes(exclude=['int64', 'float64']).columns | |
| # Manejar columnas object primero si se especificó un valor de reemplazo | |
| if handle_objects and object_replacement is not None: | |
| object_cols = prepare.select_dtypes(include=['object']).columns | |
| for col in object_cols: | |
| prepare[col] = prepare[col].fillna(object_replacement) | |
| if missing_strategy == "Rellenar con media": | |
| # Para columnas numéricas usar media | |
| if len(numeric_cols) > 0: | |
| prepare[numeric_cols] = prepare[numeric_cols].fillna(prepare[numeric_cols].mean()) | |
| # Para columnas no numéricas sin valor especificado usar moda | |
| if len(non_numeric_cols) > 0 and not handle_objects: | |
| for col in non_numeric_cols: | |
| prepare[col] = prepare[col].fillna(prepare[col].mode()[0] if not prepare[col].mode().empty else 'NA') | |
| elif missing_strategy == "Rellenar con mediana": | |
| # Para columnas numéricas usar mediana | |
| if len(numeric_cols) > 0: | |
| prepare[numeric_cols] = prepare[numeric_cols].fillna(prepare[numeric_cols].median()) | |
| # Para columnas no numéricas sin valor especificado usar moda | |
| if len(non_numeric_cols) > 0 and not handle_objects: | |
| for col in non_numeric_cols: | |
| prepare[col] = prepare[col].fillna(prepare[col].mode()[0] if not prepare[col].mode().empty else 'NA') | |
| else: # Rellenar con moda | |
| # Usar moda para todas las columnas que no son object o no tienen valor especificado | |
| for col in prepare.columns: | |
| if prepare[col].dtype in ['object']: | |
| if handle_objects: | |
| continue # Ya se manejaron las columnas object | |
| mode_value = prepare[col].mode() | |
| prepare[col] = prepare[col].fillna(mode_value[0] if not mode_value.empty else ('NA' if col in non_numeric_cols else 0)) | |
| # Actualizar el estado temporal después de la imputación | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| # Verificar los cambios | |
| nulls_after = prepare.isnull().sum().sum() | |
| values_filled = nulls_before - nulls_after | |
| if missing_strategy == "Eliminar filas": | |
| st.success(f"Se eliminaron {values_filled} filas con valores faltantes") | |
| else: | |
| st.success(f"Se rellenaron {values_filled} valores faltantes") | |
| # Verificar si quedan valores nulos | |
| remaining_nulls = prepare.isnull().sum() | |
| remaining_nulls = remaining_nulls[remaining_nulls > 0] | |
| if not remaining_nulls.empty: | |
| st.error("⚠️ Error: Aún quedan valores faltantes en las siguientes columnas:") | |
| for col in remaining_nulls.index: | |
| st.write(f"- {col}: {remaining_nulls[col]} valores faltantes") | |
| st.write("Por favor, contacta al equipo de desarrollo para revisar este error.") | |
| # Mostrar un resumen de los datos actualizados | |
| st.write("\n### Resumen después del procesamiento:") | |
| st.write(f"- Total de filas: {len(prepare)}") | |
| st.write(f"- Total de columnas: {len(prepare.columns)}") | |
| st.write(f"- Valores faltantes totales: {prepare.isnull().sum().sum()}") | |
| # Verificación final de valores nulos | |
| final_null_check = prepare.isnull().sum().sum() | |
| if final_null_check == 0: | |
| st.success("✅ ¡No quedan valores faltantes en el dataset!") | |
| else: | |
| st.error(f"⚠️ Aún quedan {final_null_check} valores nulos en el dataset.") | |
| return | |
| # Actualizar la sesión solo si no hay valores nulos | |
| if final_null_check == 0: | |
| st.session_state.prepared_data = prepare.copy() | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| # No sobrescribir 'er_data' | |
| except Exception as e: | |
| st.error(f"Error al procesar valores faltantes: {str(e)}") | |
| st.error("Detalles técnicos del error:") | |
| st.code(str(e)) | |
| # Manejo de fechas | |
| st.subheader("Manejo de Fechas") | |
| with st.expander("Procesamiento de Fechas"): | |
| date_columns = st.multiselect( | |
| "Seleccionar columnas de fecha", | |
| prepare.columns, | |
| key="date_columns" | |
| ) | |
| if date_columns: | |
| date_format = st.selectbox( | |
| "Formato de fecha", | |
| [ | |
| "yyyy-mm-dd", | |
| "dd-mm-yyyy", | |
| "mm-dd-yyyy", | |
| "yyyy-mm-dd hh:mm", | |
| "dd-mm-yyyy hh:mm", | |
| "mm-dd-yyyy hh:mm", | |
| "yyyy-mm-dd hh:mm:ss", | |
| "dd-mm-yyyy hh:mm:ss", | |
| "mm-dd-yyyy hh:mm:ss", | |
| "hh:mm", | |
| "hh:mm:ss" | |
| ], | |
| help="Selecciona el formato que coincida con tus datos de fecha/hora." | |
| ) | |
| time_format = st.radio( | |
| "Formato de hora", | |
| ["24 horas", "12 horas (AM/PM)"], | |
| help="Selecciona si el formato de hora está en 12 o 24 horas" | |
| ) | |
| # Ajustar las características disponibles según el formato | |
| if date_format in ["hh:mm", "hh:mm:ss"]: | |
| available_features = ["Hora del día", "Periodo del día", "Minutos", "Segundos"] | |
| else: | |
| if "hh:mm:ss" in date_format: | |
| available_features = [ | |
| "Año", "Mes", "Día", "Día de la semana", "Trimestre", "Estación", | |
| "Es fin de semana", "Hora del día", "Periodo del día", "Minutos", "Segundos" | |
| ] | |
| else: | |
| available_features = [ | |
| "Año", "Mes", "Día", "Día de la semana", "Trimestre", "Estación", | |
| "Es fin de semana", "Hora del día", "Periodo del día", "Minutos" | |
| ] | |
| date_features = st.multiselect( | |
| "Seleccionar características a extraer", | |
| available_features | |
| ) | |
| if st.button("Procesar fechas", key="process_dates_button"): | |
| for col in date_columns: | |
| try: | |
| if date_format in ["hh:mm", "hh:mm:ss"]: | |
| # Procesar solo tiempo | |
| if date_format == "hh:mm:ss": | |
| time_parse_format = '%I:%M:%S %p' if time_format == "12 horas (AM/PM)" else '%H:%M:%S' | |
| time_with_seconds = True | |
| else: | |
| time_parse_format = '%I:%M %p' if time_format == "12 horas (AM/PM)" else '%H:%M' | |
| time_with_seconds = False | |
| def convert_time(time_str): | |
| try: | |
| time_obj = datetime.strptime(time_str.strip(), time_parse_format) | |
| if time_with_seconds: | |
| return time_obj.hour, time_obj.minute, time_obj.second | |
| else: | |
| return time_obj.hour, time_obj.minute, None | |
| except ValueError: | |
| st.warning(f"⚠️ Formato de hora inesperado en {col}: '{time_str}'") | |
| return None, None, None if time_with_seconds else None | |
| # Aplicar la conversión y crear nuevas columnas | |
| hours_minutes_seconds = prepare[col].apply(convert_time) | |
| # Depuración: Mostrar una vista previa de la conversión | |
| st.write(f"Vista previa de la conversión de tiempo para la columna {col}:") | |
| st.write(hours_minutes_seconds.head()) | |
| if "Hora del día" in date_features: | |
| prepare[f'{col}_hora'] = hours_minutes_seconds.apply(lambda x: x[0] if x and x[0] is not None else None) | |
| if "Minutos" in date_features: | |
| prepare[f'{col}_minutos'] = hours_minutes_seconds.apply(lambda x: x[1] if x and x[1] is not None else None) | |
| if "Segundos" in date_features and time_with_seconds: | |
| prepare[f'{col}_segundos'] = hours_minutes_seconds.apply(lambda x: x[2] if x and x[2] is not None else None) | |
| # Agregar periodo del día si se seleccionó | |
| if "Periodo del día" in date_features: | |
| def get_period(hour): | |
| if hour is None: | |
| return None | |
| if 5 <= hour < 12: | |
| return 'Mañana' | |
| elif 12 <= hour < 17: | |
| return 'Tarde' | |
| elif 17 <= hour < 21: | |
| return 'Noche' | |
| else: | |
| return 'Madrugada' | |
| prepare[f'{col}_periodo'] = prepare[f'{col}_hora'].apply(get_period) | |
| else: | |
| # Definir el formato de parsing según la selección | |
| if date_format == "yyyy-mm-dd": | |
| date_parse_format = '%Y-%m-%d' | |
| elif date_format == "dd-mm-yyyy": | |
| date_parse_format = '%d-%m-%Y' | |
| elif date_format == "mm-dd-yyyy": | |
| date_parse_format = '%m-%d-%Y' | |
| elif date_format == "yyyy-mm-dd hh:mm": | |
| date_parse_format = '%Y-%m-%d %H:%M' if time_format == "24 horas" else '%Y-%m-%d %I:%M %p' | |
| elif date_format == "dd-mm-yyyy hh:mm": | |
| date_parse_format = '%d-%m-%Y %H:%M' if time_format == "24 horas" else '%d-%m-%Y %I:%M %p' | |
| elif date_format == "mm-dd-yyyy hh:mm": | |
| date_parse_format = '%m-%d-%Y %H:%M' if time_format == "24 horas" else '%m-%d-%Y %I:%M %p' | |
| elif date_format == "yyyy-mm-dd hh:mm:ss": | |
| date_parse_format = '%Y-%m-%d %H:%M:%S' if time_format == "24 horas" else '%Y-%m-%d %I:%M:%S %p' | |
| elif date_format == "dd-mm-yyyy hh:mm:ss": | |
| date_parse_format = '%d-%m-%Y %H:%M:%S' if time_format == "24 horas" else '%d-%m-%Y %I:%M:%S %p' | |
| elif date_format == "mm-dd-yyyy hh:mm:ss": | |
| date_parse_format = '%m-%d-%Y %H:%M:%S' if time_format == "24 horas" else '%m-%d-%Y %I:%M:%S %p' | |
| else: | |
| st.error(f"Formato de fecha no reconocido: {date_format}") | |
| continue | |
| # Convertir a datetime con manejo de errores | |
| temp_dates = pd.to_datetime(prepare[col], format=date_parse_format, errors='coerce') | |
| # Depuración: Mostrar una vista previa de las fechas parseadas | |
| st.write(f"Vista previa de las fechas parseadas para la columna {col}:") | |
| st.write(temp_dates.head()) | |
| # Manejo de valores que no se pudieron parsear | |
| if temp_dates.isnull().any(): | |
| st.warning(f"⚠️ Algunas fechas en la columna {col} no pudieron ser parseadas y se asignaron como NaT.") | |
| # Extraer características según selección | |
| if "Año" in date_features: | |
| prepare[f'{col}_año'] = temp_dates.dt.year | |
| if "Mes" in date_features: | |
| prepare[f'{col}_mes'] = temp_dates.dt.month | |
| if "Día" in date_features: | |
| prepare[f'{col}_dia'] = temp_dates.dt.day | |
| if "Día de la semana" in date_features: | |
| prepare[f'{col}_dia_semana'] = temp_dates.dt.dayofweek + 1 | |
| if "Trimestre" in date_features: | |
| prepare[f'{col}_trimestre'] = temp_dates.dt.quarter | |
| if "Es fin de semana" in date_features: | |
| prepare[f'{col}_fin_semana'] = temp_dates.dt.dayofweek.isin([5, 6]).astype(int) | |
| if "Estación" in date_features: | |
| def get_season(month): | |
| if month in [12, 1, 2]: | |
| return 'Invierno' | |
| elif month in [3, 4, 5]: | |
| return 'Primavera' | |
| elif month in [6, 7, 8]: | |
| return 'Verano' | |
| else: | |
| return 'Otoño' | |
| prepare[f'{col}_estacion'] = temp_dates.dt.month.apply(get_season) | |
| if "Hora del día" in date_features and any(sub in date_format for sub in ["hh:mm", "hh:mm:ss"]): | |
| prepare[f'{col}_hora'] = temp_dates.dt.hour | |
| if "Minutos" in date_features and any(sub in date_format for sub in ["hh:mm", "hh:mm:ss"]): | |
| prepare[f'{col}_minutos'] = temp_dates.dt.minute | |
| if "Segundos" in date_features and "hh:mm:ss" in date_format: | |
| prepare[f'{col}_segundos'] = temp_dates.dt.second | |
| if "Periodo del día" in date_features and any(sub in date_format for sub in ["hh:mm", "hh:mm:ss"]): | |
| def get_period(hour): | |
| if hour is None: | |
| return None | |
| if 5 <= hour < 12: | |
| return 'Mañana' | |
| elif 12 <= hour < 17: | |
| return 'Tarde' | |
| elif 17 <= hour < 21: | |
| return 'Noche' | |
| else: | |
| return 'Madrugada' | |
| prepare[f'{col}_periodo'] = temp_dates.dt.hour.apply(get_period) | |
| # Eliminar la columna original de fecha | |
| prepare = prepare.drop(columns=[col]) | |
| st.success(f"Columna {col} procesada exitosamente") | |
| except Exception as e: | |
| st.error(f"Error procesando {col}: {str(e)}") | |
| st.exception(e) | |
| # Actualizar el estado temporal después de procesar fechas | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| # Codificación de variables categóricas | |
| st.subheader("Codificación de Variables Categóricas") | |
| categorical_columns = prepare.select_dtypes(include=['object']).columns | |
| if len(categorical_columns) > 0: | |
| encoding_method = st.radio( | |
| "Método de codificación:", | |
| ["Label Encoding", "One-Hot Encoding"] | |
| ) | |
| cols_to_encode = st.multiselect( | |
| "Seleccionar columnas para codificar", | |
| categorical_columns, | |
| key="cols_to_encode" | |
| ) | |
| if st.button("Aplicar codificación", key="apply_encoding_button"): | |
| if encoding_method == "Label Encoding": | |
| le = LabelEncoder() | |
| for col in cols_to_encode: | |
| try: | |
| prepare[col] = le.fit_transform(prepare[col].astype(str)) | |
| st.success(f"✅ Label Encoding aplicado a la columna '{col}'") | |
| except Exception as e: | |
| st.error(f"Error al codificar la columna {col} con Label Encoding: {str(e)}") | |
| # Actualizar el estado temporal después de la codificación | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| else: # One-Hot Encoding | |
| try: | |
| prepare = pd.get_dummies(prepare, columns=cols_to_encode) | |
| st.success("✅ One-Hot Encoding aplicado") | |
| # Actualizar el estado temporal después de la codificación | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| except Exception as e: | |
| st.error(f"Error al aplicar One-Hot Encoding: {str(e)}") | |
| # Normalización de variables numéricas | |
| st.subheader("Normalización de Variables Numéricas") | |
| numeric_columns = prepare.select_dtypes(include=['int64', 'float64']).columns | |
| if len(numeric_columns) > 0: | |
| cols_to_normalize = st.multiselect( | |
| "Seleccionar columnas para normalizar", | |
| numeric_columns, | |
| key="cols_to_normalize" | |
| ) | |
| if cols_to_normalize and st.button("Aplicar normalización", key="apply_normalization_button"): | |
| try: | |
| scaler = StandardScaler() | |
| prepare[cols_to_normalize] = scaler.fit_transform(prepare[cols_to_normalize]) | |
| st.success("✅ Normalización aplicada") | |
| # Actualizar el estado temporal después de la normalización | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| except Exception as e: | |
| st.error(f"Error al aplicar normalización: {str(e)}") | |
| # Guardar datos preparados y mostrar matriz de correlación | |
| st.write("### Vista previa de los datos:") | |
| st.dataframe(prepare.head()) | |
| # Información sobre valores nulos | |
| null_count = prepare.isnull().sum().sum() | |
| if null_count > 0: | |
| st.warning(f"⚠️ Hay {null_count} valores nulos en los datos.") | |
| else: | |
| st.success("✅ No hay valores nulos en los datos.") | |
| # Matriz de correlación | |
| st.subheader("Matriz de Correlación") | |
| numerical_columns = prepare.select_dtypes(include=['int64', 'float64']).columns.tolist() | |
| if len(numerical_columns) > 1: | |
| corr_variables = st.multiselect( | |
| "Selecciona las variables para incluir en la matriz de correlación", | |
| options=numerical_columns, | |
| default=numerical_columns[:min(5, len(numerical_columns))] # Seleccionar hasta 5 columnas por defecto | |
| ) | |
| if corr_variables: | |
| try: | |
| # ------------------------------------------- | |
| # NUEVO: Detección de Outliers y Visualización | |
| # ------------------------------------------- | |
| for var in corr_variables: | |
| # Cálculo de Q1, Q3 e IQR | |
| Q1 = prepare[var].quantile(0.25) | |
| Q3 = prepare[var].quantile(0.75) | |
| IQR = Q3 - Q1 | |
| lower_bound = Q1 - 1.5 * IQR | |
| upper_bound = Q3 + 1.5 * IQR | |
| # Identificación de outliers | |
| outliers = prepare[(prepare[var] < lower_bound) | (prepare[var] > upper_bound)][var] | |
| num_outliers = outliers.shape[0] | |
| # Mostrar advertencia si hay outliers | |
| if num_outliers > 0: | |
| st.warning(f"⚠️ La variable **{var}** tiene {num_outliers} datos atípicos (outliers) detectados.") | |
| # Mostrar boxplot usando Plotly | |
| fig_box = px.box(prepare, y=var, title=f'Boxplot de {var}') | |
| st.plotly_chart(fig_box, use_container_width=True) | |
| # Calcular y mostrar la matriz de correlación | |
| corr_matrix = prepare[corr_variables].corr(method='pearson') | |
| # Mapa de calor de correlación | |
| fig_corr = px.imshow( | |
| corr_matrix, | |
| text_auto=True, | |
| aspect="auto", | |
| color_continuous_scale='RdBu_r', | |
| title='Matriz de Correlación de Pearson' | |
| ) | |
| st.plotly_chart(fig_corr, use_container_width=True) | |
| # Botón de descarga | |
| csv_corr = corr_matrix.to_csv(index=True).encode('utf-8') | |
| st.download_button( | |
| label="Descargar Matriz de Correlación como CSV", | |
| data=csv_corr, | |
| file_name='matriz_correlacion.csv', | |
| mime='text/csv', | |
| ) | |
| # Análisis de correlaciones significativas | |
| st.write("### Análisis de Correlaciones Significativas") | |
| threshold = st.slider( | |
| "Selecciona el umbral mínimo de correlación para considerar significativa", | |
| min_value=0.0, | |
| max_value=1.0, | |
| value=0.5, | |
| step=0.05 | |
| ) | |
| # Obtener y mostrar correlaciones significativas | |
| corr_pairs = corr_matrix.unstack() | |
| significant_corr = corr_pairs[ | |
| (abs(corr_pairs) >= threshold) & | |
| (abs(corr_pairs) < 1) | |
| ].drop_duplicates().sort_values(ascending=False) | |
| if not significant_corr.empty: | |
| st.write(f"Correlaciones significativas (|correlación| ≥ {threshold}):") | |
| for (var1, var2), corr_value in significant_corr.items(): | |
| st.write(f"- **{var1}** y **{var2}**: correlación de **{corr_value:.2f}**") | |
| else: | |
| st.write("No se encontraron correlaciones significativas con el umbral seleccionado.") | |
| except Exception as e: | |
| st.error(f"Error al calcular la matriz de correlación: {str(e)}") | |
| else: | |
| st.warning("Por favor, selecciona al menos una variable para mostrar la matriz de correlación.") | |
| else: | |
| st.warning("No hay suficientes variables numéricas para calcular correlaciones.") | |
| # Button para guardar datos preparados | |
| if st.button("Guardar datos preparados", key="save_prepared_data_button"): | |
| try: | |
| null_count = prepare.isnull().sum().sum() | |
| if null_count == 0: | |
| st.session_state.prepared_data = prepare.copy() | |
| st.session_state.temp_prepared_data = prepare.copy() | |
| st.session_state.data_saved = True | |
| st.success("✅ Datos preparados guardados exitosamente") | |
| # Generar reporte | |
| progress_container = st.empty() | |
| with progress_container: | |
| with st.spinner('Generando reporte del dataset...'): | |
| profile = ProfileReport(prepare, title="Dataset Report", explorative=True) | |
| st.session_state.report_html = profile.to_html() | |
| st.success("¡Reporte generado exitosamente!") | |
| else: | |
| st.error(f"❌ No se pueden guardar los datos. Aún hay {null_count} valores nulos.") | |
| st.warning("Por favor, aplica una estrategia de manejo de valores faltantes antes de guardar.") | |
| except Exception as e: | |
| st.error(f"Error al guardar los datos preparados: {str(e)}") | |
| # Botones de descarga fuera del bloque principal | |
| if 'data_saved' in st.session_state and st.session_state.data_saved: | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| csv = st.session_state.prepared_data.to_csv(index=False).encode('utf-8') | |
| st.download_button( | |
| label="Descargar Dataset Preparado", | |
| data=csv, | |
| file_name="prepared_dataset.csv", | |
| mime="text/csv" | |
| ) | |
| with col2: | |
| st.download_button( | |
| label="Descargar Reporte del Dataset", | |
| data=st.session_state.report_html, | |
| file_name="dataset_report.html", | |
| mime="text/html" | |
| ) | |
| st.info("👆 No te olvides de guardar los datos preparados antes de continuar con el análisis en la página Training o Test.") | |