Last active
November 23, 2025 21:07
-
-
Save gbaeza2002/96d25f621ddd055d72a47eed79a9db54 to your computer and use it in GitHub Desktop.
Analisis transporte santiago (EDA)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Análisis de Reseñas de Transporte en Santiago | |
| Evaluación 2 - Machine Learning | |
| """ | |
| import pandas as pd | |
| import numpy as np | |
| from sklearn.model_selection import train_test_split, KFold | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| # ============================================================================ | |
| # A. EXPLORACIÓN INICIAL | |
| # ============================================================================ | |
| def exploracion_inicial(df): | |
| """ | |
| Realiza exploración inicial del dataset | |
| """ | |
| print("=" * 80) | |
| print("A. EXPLORACIÓN INICIAL") | |
| print("=" * 80) | |
| # Cantidad de registros | |
| print(f"\n1. Cantidad de registros: {len(df)}") | |
| print(f" Cantidad de columnas: {len(df.columns)}") | |
| # Tipos de datos | |
| print("\n2. Tipos de datos por columna:") | |
| print(df.dtypes) | |
| # Porcentaje de nulos por columna | |
| print("\n3. Porcentaje de valores nulos por columna:") | |
| nulos = df.isnull().sum() | |
| porcentaje_nulos = (nulos / len(df)) * 100 | |
| df_nulos = pd.DataFrame({ | |
| 'Cantidad': nulos, | |
| 'Porcentaje (%)': porcentaje_nulos.round(2) | |
| }) | |
| print(df_nulos[df_nulos['Cantidad'] > 0]) | |
| # Duplicados | |
| duplicados = df.duplicated().sum() | |
| porcentaje_duplicados = (duplicados / len(df)) * 100 | |
| print(f"\n4. Duplicados:") | |
| print(f" Cantidad: {duplicados}") | |
| print(f" Porcentaje: {porcentaje_duplicados:.2f}%") | |
| # Inconsistencias en categorías (mayúsculas/minúsculas) | |
| print("\n5. Inconsistencias en categorías (mayúsculas/minúsculas):") | |
| # Verificar medio_transporte | |
| if 'medio_transporte' in df.columns: | |
| valores_medio = df['medio_transporte'].value_counts() | |
| print(f"\n Valores únicos en 'medio_transporte':") | |
| print(f" {valores_medio.index.tolist()}") | |
| # Verificar empresa_bus | |
| if 'empresa_bus' in df.columns: | |
| valores_empresa = df['empresa_bus'].dropna().value_counts() | |
| print(f"\n Valores únicos en 'empresa_bus' (primeros 10):") | |
| print(f" {valores_empresa.head(10).index.tolist()}") | |
| # Verificar barrio | |
| if 'barrio' in df.columns: | |
| valores_barrio = df['barrio'].dropna().value_counts() | |
| print(f"\n Valores únicos en 'barrio' (primeros 10):") | |
| print(f" {valores_barrio.head(10).index.tolist()}") | |
| print("\n" + "=" * 80) | |
| return df_nulos, duplicados | |
| # ============================================================================ | |
| # B. LIMPIEZA DE DATOS | |
| # ============================================================================ | |
| def limpieza_datos(df): | |
| """ | |
| Realiza limpieza completa del dataset | |
| """ | |
| print("\n" + "=" * 80) | |
| print("B. LIMPIEZA DE DATOS") | |
| print("=" * 80) | |
| df_limpio = df.copy() | |
| registros_iniciales = len(df_limpio) | |
| # 1. Eliminar duplicados (~3% del dataset) | |
| print(f"\n1. Eliminando duplicados...") | |
| duplicados_antes = df_limpio.duplicated().sum() | |
| df_limpio = df_limpio.drop_duplicates() | |
| duplicados_eliminados = registros_iniciales - len(df_limpio) | |
| print(f" Duplicados eliminados: {duplicados_eliminados} ({duplicados_eliminados/registros_iniciales*100:.2f}%)") | |
| # 2. Normalizar categorías (mayúsculas/minúsculas) | |
| print(f"\n2. Normalizando categorías...") | |
| # Normalizar medio_transporte | |
| if 'medio_transporte' in df_limpio.columns: | |
| df_limpio['medio_transporte'] = df_limpio['medio_transporte'].str.strip().str.title() | |
| print(f" 'medio_transporte' normalizado") | |
| # Normalizar empresa_bus | |
| if 'empresa_bus' in df_limpio.columns: | |
| df_limpio['empresa_bus'] = df_limpio['empresa_bus'].str.strip().str.title() | |
| print(f" 'empresa_bus' normalizado") | |
| # Normalizar barrio | |
| if 'barrio' in df_limpio.columns: | |
| df_limpio['barrio'] = df_limpio['barrio'].str.strip().str.title() | |
| print(f" 'barrio' normalizado") | |
| # Normalizar linea_metro | |
| if 'linea_metro' in df_limpio.columns: | |
| df_limpio['linea_metro'] = df_limpio['linea_metro'].str.strip() | |
| print(f" 'linea_metro' normalizado") | |
| # 3. Convertir fecha a formato datetime | |
| print(f"\n3. Convirtiendo fecha a formato datetime...") | |
| if 'fecha' in df_limpio.columns: | |
| df_limpio['fecha'] = pd.to_datetime(df_limpio['fecha'], errors='coerce') | |
| fechas_invalidas = df_limpio['fecha'].isnull().sum() | |
| if fechas_invalidas > 0: | |
| print(f" Advertencia: {fechas_invalidas} fechas no pudieron ser convertidas") | |
| else: | |
| print(f" Fechas convertidas exitosamente") | |
| # 4. Corregir tipos numéricos | |
| print(f"\n4. Corrigiendo tipos numéricos...") | |
| # Rating | |
| if 'rating' in df_limpio.columns: | |
| df_limpio['rating'] = pd.to_numeric(df_limpio['rating'], errors='coerce') | |
| print(f" 'rating' convertido a numérico") | |
| # tiempo_espera_min | |
| if 'tiempo_espera_min' in df_limpio.columns: | |
| df_limpio['tiempo_espera_min'] = pd.to_numeric(df_limpio['tiempo_espera_min'], errors='coerce') | |
| print(f" 'tiempo_espera_min' convertido a numérico") | |
| # duracion_viaje_min | |
| if 'duracion_viaje_min' in df_limpio.columns: | |
| df_limpio['duracion_viaje_min'] = pd.to_numeric(df_limpio['duracion_viaje_min'], errors='coerce') | |
| print(f" 'duracion_viaje_min' convertido a numérico") | |
| # likes | |
| if 'likes' in df_limpio.columns: | |
| df_limpio['likes'] = pd.to_numeric(df_limpio['likes'], errors='coerce').fillna(0).astype(int) | |
| print(f" 'likes' convertido a entero") | |
| # respuestas | |
| if 'respuestas' in df_limpio.columns: | |
| df_limpio['respuestas'] = pd.to_numeric(df_limpio['respuestas'], errors='coerce').fillna(0).astype(int) | |
| print(f" 'respuestas' convertida a entero") | |
| # 5. Eliminar o imputar valores faltantes | |
| print(f"\n5. Tratando valores faltantes...") | |
| # Para rating: si es crítico, podemos imputar con la mediana o eliminar | |
| # Decidimos eliminar registros sin rating ya que es necesario para el etiquetado | |
| rating_nulos = df_limpio['rating'].isnull().sum() | |
| if rating_nulos > 0: | |
| print(f" Registros sin rating: {rating_nulos}") | |
| print(f" Justificación: Se eliminan registros sin rating porque es necesario para el etiquetado de sentimiento") | |
| df_limpio = df_limpio.dropna(subset=['rating']) | |
| print(f" Registros eliminados: {rating_nulos}") | |
| # Para otras columnas numéricas, imputar con mediana | |
| columnas_numericas = ['tiempo_espera_min', 'duracion_viaje_min'] | |
| for col in columnas_numericas: | |
| if col in df_limpio.columns: | |
| nulos = df_limpio[col].isnull().sum() | |
| if nulos > 0: | |
| mediana = df_limpio[col].median() | |
| df_limpio[col] = df_limpio[col].fillna(mediana) | |
| print(f" '{col}': {nulos} valores imputados con mediana ({mediana:.2f})") | |
| # Para columnas categóricas, imputar con 'No especificado' o eliminar según el caso | |
| columnas_categoricas = ['barrio', 'empresa_bus', 'linea_metro'] | |
| for col in columnas_categoricas: | |
| if col in df_limpio.columns: | |
| nulos = df_limpio[col].isnull().sum() | |
| if nulos > 0: | |
| df_limpio[col] = df_limpio[col].fillna('No especificado') | |
| print(f" '{col}': {nulos} valores imputados con 'No especificado'") | |
| registros_finales = len(df_limpio) | |
| print(f"\n Resumen:") | |
| print(f" Registros iniciales: {registros_iniciales}") | |
| print(f" Registros finales: {registros_finales}") | |
| print(f" Registros eliminados: {registros_iniciales - registros_finales}") | |
| print("\n" + "=" * 80) | |
| return df_limpio | |
| # ============================================================================ | |
| # C. ETIQUETADO DE SENTIMIENTO | |
| # ============================================================================ | |
| def etiquetar_sentimiento(df): | |
| """ | |
| Crea columna de satisfacción según el rating | |
| """ | |
| print("\n" + "=" * 80) | |
| print("C. ETIQUETADO DE SENTIMIENTO") | |
| print("=" * 80) | |
| df_etiquetado = df.copy() | |
| # Crear columna satisfacción | |
| def clasificar_sentimiento(rating): | |
| if pd.isna(rating): | |
| return 'Sin clasificar' | |
| elif rating >= 4: | |
| return 'Positivo' | |
| elif rating == 3: | |
| return 'Neutro' | |
| elif rating >= 1: | |
| return 'Negativo' | |
| else: | |
| return 'Sin clasificar' | |
| df_etiquetado['satisfaccion'] = df_etiquetado['rating'].apply(clasificar_sentimiento) | |
| # Mostrar distribución | |
| print("\nDistribución de satisfacción:") | |
| distribucion = df_etiquetado['satisfaccion'].value_counts() | |
| porcentaje = df_etiquetado['satisfaccion'].value_counts(normalize=True) * 100 | |
| df_distribucion = pd.DataFrame({ | |
| 'Cantidad': distribucion, | |
| 'Porcentaje (%)': porcentaje.round(2) | |
| }) | |
| print(df_distribucion) | |
| # Justificación del tratamiento de registros sin rating | |
| registros_sin_rating = df_etiquetado[df_etiquetado['satisfaccion'] == 'Sin clasificar'] | |
| print(f"\nJustificación del tratamiento de registros sin rating:") | |
| print(f" Registros sin rating encontrados: {len(registros_sin_rating)}") | |
| print(f" Estos registros fueron eliminados en la fase de limpieza porque:") | |
| print(f" - El rating es esencial para el etiquetado de sentimiento") | |
| print(f" - No podemos clasificar satisfacción sin conocer el rating") | |
| print(f" - Mantenerlos introduciría ruido en el modelo de ML") | |
| print("\n" + "=" * 80) | |
| return df_etiquetado | |
| # ============================================================================ | |
| # D. VALIDACIÓN CRUZADA | |
| # ============================================================================ | |
| def validacion_cruzada(df): | |
| """ | |
| Divide en train/test y aplica K-Fold Cross Validation | |
| """ | |
| print("\n" + "=" * 80) | |
| print("D. VALIDACIÓN CRUZADA") | |
| print("=" * 80) | |
| # Dividir en train/test (80/20) | |
| print("\n1. División Train/Test (80/20):") | |
| # Separar características y variable objetivo | |
| X = df.drop(columns=['satisfaccion', 'id'], errors='ignore') | |
| y = df['satisfaccion'] | |
| X_train, X_test, y_train, y_test = train_test_split( | |
| X, y, | |
| test_size=0.2, | |
| random_state=42, | |
| stratify=y # Mantener proporción de clases | |
| ) | |
| print(f" Tamaño conjunto de entrenamiento: {len(X_train)} ({len(X_train)/len(df)*100:.1f}%)") | |
| print(f" Tamaño conjunto de prueba: {len(X_test)} ({len(X_test)/len(df)*100:.1f}%)") | |
| # Distribución de clases en train | |
| print(f"\n Distribución de clases en entrenamiento:") | |
| print(y_train.value_counts()) | |
| # Distribución de clases en test | |
| print(f"\n Distribución de clases en prueba:") | |
| print(y_test.value_counts()) | |
| # Aplicar K-Fold Cross Validation | |
| print("\n2. K-Fold Cross Validation:") | |
| # Usar k=5 como estándar | |
| k_fold = KFold(n_splits=5, shuffle=True, random_state=42) | |
| print(f" Número de folds: 5") | |
| print(f" Razones del uso de K-Fold:") | |
| print(f" - Evalúa el modelo de manera más robusta usando todo el dataset") | |
| print(f" - Reduce la varianza en la estimación del rendimiento") | |
| print(f" - Permite detectar overfitting o underfitting") | |
| print(f" - Proporciona múltiples evaluaciones para promediar resultados") | |
| print(f" - Es especialmente útil con datasets pequeños o medianos") | |
| # Ejemplo de cómo se usaría K-Fold (solo para demostración) | |
| print(f"\n Ejemplo de división con K-Fold (5 folds):") | |
| fold_sizes = [] | |
| for fold, (train_idx, val_idx) in enumerate(k_fold.split(X_train), 1): | |
| fold_sizes.append((len(train_idx), len(val_idx))) | |
| if fold <= 3: # Mostrar solo los primeros 3 folds | |
| print(f" Fold {fold}: Train={len(train_idx)}, Validation={len(val_idx)}") | |
| if len(fold_sizes) > 3: | |
| print(f" ... (mostrando solo primeros 3 folds)") | |
| print("\n" + "=" * 80) | |
| return X_train, X_test, y_train, y_test, k_fold | |
| # ============================================================================ | |
| # E. GUARDAR Y EXPORTAR | |
| # ============================================================================ | |
| def guardar_archivo(df, nombre_archivo='transporte_santiago_clean.csv'): | |
| """ | |
| Guarda el dataset limpio en un archivo CSV | |
| """ | |
| print("\n" + "=" * 80) | |
| print("E. GUARDAR Y EXPORTAR") | |
| print("=" * 80) | |
| df.to_csv(nombre_archivo, index=False, encoding='utf-8') | |
| print(f"\nArchivo guardado exitosamente: {nombre_archivo}") | |
| print(f"Registros guardados: {len(df)}") | |
| print(f"Columnas guardadas: {len(df.columns)}") | |
| print(f"\nColumnas en el archivo final:") | |
| for i, col in enumerate(df.columns, 1): | |
| print(f" {i}. {col}") | |
| print("\n" + "=" * 80) | |
| # ============================================================================ | |
| # FUNCIÓN PRINCIPAL | |
| # ============================================================================ | |
| def main(): | |
| """ | |
| Función principal que ejecuta todo el pipeline | |
| """ | |
| print("\n" + "=" * 80) | |
| print("ANÁLISIS DE RESEÑAS DE TRANSPORTE EN SANTIAGO") | |
| print("=" * 80) | |
| # Cargar datos | |
| print("\nCargando datos desde 'resenas_transporte_santiago_1000.csv'...") | |
| # Intentar diferentes codificaciones comunes | |
| try: | |
| df = pd.read_csv('resenas_transporte_santiago_1000.csv', encoding='utf-8-sig') | |
| except UnicodeDecodeError: | |
| try: | |
| df = pd.read_csv('resenas_transporte_santiago_1000.csv', encoding='latin-1') | |
| except UnicodeDecodeError: | |
| df = pd.read_csv('resenas_transporte_santiago_1000.csv', encoding='cp1252') | |
| print(f"Datos cargados: {len(df)} registros") | |
| # A. Exploración Inicial | |
| df_nulos, duplicados = exploracion_inicial(df) | |
| # B. Limpieza de Datos | |
| df_limpio = limpieza_datos(df) | |
| # C. Etiquetado de Sentimiento | |
| df_etiquetado = etiquetar_sentimiento(df_limpio) | |
| # D. Validación Cruzada | |
| X_train, X_test, y_train, y_test, k_fold = validacion_cruzada(df_etiquetado) | |
| # E. Guardar y Exportar | |
| guardar_archivo(df_etiquetado, 'transporte_santiago_clean.csv') | |
| print("\n" + "=" * 80) | |
| print("PROCESO COMPLETADO EXITOSAMENTE") | |
| print("=" * 80) | |
| return df_etiquetado, X_train, X_test, y_train, y_test, k_fold | |
| # ============================================================================ | |
| # EJECUTAR | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| df_final, X_train, X_test, y_train, y_test, k_fold = main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment