Skip to content

Instantly share code, notes, and snippets.

@gbaeza2002
Last active November 23, 2025 21:07
Show Gist options
  • Select an option

  • Save gbaeza2002/96d25f621ddd055d72a47eed79a9db54 to your computer and use it in GitHub Desktop.

Select an option

Save gbaeza2002/96d25f621ddd055d72a47eed79a9db54 to your computer and use it in GitHub Desktop.
Analisis transporte santiago (EDA)
"""
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