DEV Community

Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

MLZC25-10. Análisis Exploratorio de Datos y Marco de Validación: El Detective de Datos

🎯 Objetivo del Post: Aprenderás a convertir datos en insights valiosos mediante análisis exploratorio y a dividir correctamente tus datos para evitar el sobreajuste.

🕵️ ¿Qué es el Análisis Exploratorio de Datos (EDA)?

El Análisis Exploratorio de Datos es como ser un detective que examina la escena del crimen. Tienes pistas (datos), pero necesitas entender qué te están diciendo antes de sacar conclusiones.

EDA es el proceso de investigar y visualizar datos para:

  • 🔍 Descubrir patrones ocultos en los datos
  • ⚠️ Detectar anomalías y valores atípicos
  • 💡 Generar hipótesis sobre relaciones entre variables
  • 📊 Comunicar insights de manera visual y clara

🎯 ¿Por qué es Importante el EDA?

  1. 🧠 Entender tus datos: Conocer qué información tienes disponible y su calidad
  2. ⚠️ Detectar problemas: Encontrar errores antes de entrenar modelos (ahorro de tiempo)
  3. 💡 Generar hipótesis: Formar ideas sobre qué variables son más importantes
  4. 📢 Comunicar hallazgos: Explicar tus datos a otros de manera visual y convincente
  5. 🎯 Guíar decisiones: Decidir qué variables usar y cómo preprocesarlas

El Marco de Validación: ¿Por qué Dividir los Datos?

Imagina que estás estudiando para un examen. Si solo memorizas las respuestas exactas de los ejercicios de práctica, ¿realmente aprendiste el material? Probablemente no. Lo mismo pasa con Machine Learning.

El Problema del Sobreajuste (Overfitting)

Sobreajuste ocurre cuando un modelo memoriza los datos de entrenamiento en lugar de aprender patrones generales. Es como memorizar las respuestas en lugar de entender el concepto.

Ejemplo: Un modelo que predice precios de autos basándose solo en los datos que vio, sin poder generalizar a autos nuevos.

La Solución: División de Datos

Dividimos nuestros datos en tres conjuntos:

  1. Conjunto de Entrenamiento (60-70%): Para enseñar al modelo
  2. Conjunto de Validación (15-20%): Para ajustar parámetros
  3. Conjunto de Prueba (15-20%): Para evaluar el rendimiento final

Análisis Exploratorio de Datos: Paso a Paso

Paso 1: Estadísticas Descriptivas Básicas

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# Cargar datos del dataset de clase
url = "https://raw.githubusercontent.com/alexeygrigorev/mlbookcamp-code/refs/heads/master/chapter-02-car-price/data.csv"
df = pd.read_csv(url)

# Información general
print("Información del dataset:")
print(df.info())
print(f"\nForma del dataset: {df.shape}")
print(f"Memoria utilizada: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
Enter fullscreen mode Exit fullscreen mode

Resultado esperado:

Información del dataset:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8500 entries, 0 to 8499
Data columns (total 8 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   marca       8500 non-null   object 
 1   modelo      8500 non-null   object 
 2   año         8500 non-null   int64  
 3   precio      8500 non-null   float64
 4   kilometraje 8500 non-null   float64
 5   transmision 8500 non-null   object 
 6   edad_auto   8500 non-null   int64  
 7   tipo_auto   8500 non-null   object 
Enter fullscreen mode Exit fullscreen mode

Paso 2: Análisis de la Variable Objetivo (MSRP)

# Estadísticas del MSRP
print("Estadísticas del MSRP:")
print(df['MSRP'].describe())

# Visualización de la distribución del precio
fig, axes = plt.subplots(2, 2, figsize=(15, 10))

# Histograma
axes[0,0].hist(df['MSRP'], bins=50, alpha=0.7, color='skyblue')
axes[0,0].set_title('Distribución del MSRP')
axes[0,0].set_xlabel('MSRP ($)')
axes[0,0].set_ylabel('Frecuencia')

# Box plot
axes[0,1].boxplot(df['MSRP'])
axes[0,1].set_title('Box Plot del MSRP')
axes[0,1].set_ylabel('MSRP ($)')

# Histograma con escala logarítmica
axes[1,0].hist(np.log(df['MSRP']), bins=50, alpha=0.7, color='lightgreen')
axes[1,0].set_title('Distribución del Log(MSRP)')
axes[1,0].set_xlabel('Log(MSRP)')
axes[1,0].set_ylabel('Frecuencia')

# Q-Q plot para normalidad
from scipy import stats
stats.probplot(df['MSRP'], dist="norm", plot=axes[1,1])
axes[1,1].set_title('Q-Q Plot del MSRP')

plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

¿Qué nos dicen estos gráficos?

  • Histograma: ¿Está sesgado el MSRP? ¿Hay valores extremos?
  • Box plot: ¿Dónde están los outliers? ¿Cuál es el rango intercuartílico?
  • Log-MSRP: ¿La distribución es más normal con transformación logarítmica?
  • Q-Q plot: ¿Sigue el MSRP una distribución normal?

Paso 3: Análisis de Variables Categóricas

# Análisis por Make
marca_stats = df.groupby('Make')['MSRP'].agg(['count', 'mean', 'std']).round(2)
print("Estadísticas por Make:")
print(marca_stats)

# Visualización por Make
plt.figure(figsize=(12, 6))
df.boxplot(column='MSRP', by='Make', ax=plt.gca())
plt.title('Distribución de MSRP por Make')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

# Top 10 marcas por frecuencia
top_marcas = df['Make'].value_counts().head(10)
plt.figure(figsize=(10, 6))
top_marcas.plot(kind='bar', color='coral')
plt.title('Top 10 Marcas por Frecuencia')
plt.xlabel('Marca')
plt.ylabel('Cantidad de Autos')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

Paso 4: Análisis de Variables Numéricas

# Correlación entre variables numéricas
numeric_cols = ['año', 'kilometraje', 'edad_auto', 'precio']
correlation_matrix = df[numeric_cols].corr()

# Heatmap de correlaciones
plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0,
            square=True, fmt='.2f')
plt.title('Matriz de Correlación')
plt.tight_layout()
plt.show()

# Scatter plots para relaciones importantes
fig, axes = plt.subplots(2, 2, figsize=(15, 10))

# Precio vs Año
axes[0,0].scatter(df['año'], df['precio'], alpha=0.5, color='blue')
axes[0,0].set_xlabel('Año')
axes[0,0].set_ylabel('Precio ($)')
axes[0,0].set_title('Precio vs Año')

# Precio vs Kilometraje
axes[0,1].scatter(df['kilometraje'], df['precio'], alpha=0.5, color='red')
axes[0,1].set_xlabel('Kilometraje')
axes[0,1].set_ylabel('Precio ($)')
axes[0,1].set_title('Precio vs Kilometraje')

# Precio vs Edad del Auto
axes[1,0].scatter(df['edad_auto'], df['precio'], alpha=0.5, color='green')
axes[1,0].set_xlabel('Edad del Auto (años)')
axes[1,0].set_ylabel('Precio ($)')
axes[1,0].set_title('Precio vs Edad del Auto')

# Kilometraje vs Edad del Auto
axes[1,1].scatter(df['edad_auto'], df['kilometraje'], alpha=0.5, color='orange')
axes[1,1].set_xlabel('Edad del Auto (años)')
axes[1,1].set_ylabel('Kilometraje')
axes[1,1].set_title('Kilometraje vs Edad del Auto')

plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

Paso 5: Análisis de Variables Categóricas vs Precio

# Precio promedio por tipo de auto
tipo_precio = df.groupby('tipo_auto')['precio'].mean().sort_values(ascending=False)
print("Precio promedio por tipo de auto:")
print(tipo_precio)

# Visualización
plt.figure(figsize=(10, 6))
tipo_precio.plot(kind='bar', color='lightcoral')
plt.title('Precio Promedio por Tipo de Auto')
plt.xlabel('Tipo de Auto')
plt.ylabel('Precio Promedio ($)')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

# Análisis por transmisión
transmision_stats = df.groupby('transmision')['precio'].agg(['count', 'mean', 'std'])
print("\nEstadísticas por tipo de transmisión:")
print(transmision_stats)
Enter fullscreen mode Exit fullscreen mode

Marco de Validación: Implementación Práctica

Paso 1: División Temporal vs Aleatoria

Para datos de precios de autos, tenemos dos opciones:

A) División Aleatoria (Recomendada para este caso)

from sklearn.model_selection import train_test_split

# Dividir en entrenamiento (60%) y temporal (40%)
df_train, df_temp = train_test_split(df, test_size=0.4, random_state=42)

# Dividir temporal en validación (15%) y prueba (25%)
df_val, df_test = train_test_split(df_temp, test_size=0.625, random_state=42)

print(f"Entrenamiento: {len(df_train)} muestras ({len(df_train)/len(df)*100:.1f}%)")
print(f"Validación: {len(df_val)} muestras ({len(df_val)/len(df)*100:.1f}%)")
print(f"Prueba: {len(df_test)} muestras ({len(df_test)/len(df)*100:.1f}%)")
Enter fullscreen mode Exit fullscreen mode

B) División Temporal (Para datos con componente temporal)

# Si los datos tienen componente temporal
df_sorted = df.sort_values('fecha_venta')
cutoff_train = int(len(df_sorted) * 0.6)
cutoff_val = int(len(df_sorted) * 0.8)

df_train = df_sorted[:cutoff_train]
df_val = df_sorted[cutoff_train:cutoff_val]
df_test = df_sorted[cutoff_val:]
Enter fullscreen mode Exit fullscreen mode

Paso 2: Validación de la División

# Verificar que las distribuciones son similares
def comparar_distribuciones(df1, df2, columna, nombre1, nombre2):
    """Comparar distribuciones de dos conjuntos de datos"""
    print(f"\nComparación de {columna}:")
    print(f"{nombre1}: Media={df1[columna].mean():.2f}, Std={df1[columna].std():.2f}")
    print(f"{nombre2}: Media={df2[columna].mean():.2f}, Std={df2[columna].std():.2f}")

    # Visualización
    plt.figure(figsize=(10, 4))

    plt.subplot(1, 2, 1)
    plt.hist(df1[columna], bins=30, alpha=0.7, label=nombre1)
    plt.title(f'Distribución {nombre1}')
    plt.xlabel(columna)
    plt.ylabel('Frecuencia')

    plt.subplot(1, 2, 2)
    plt.hist(df2[columna], bins=30, alpha=0.7, label=nombre2, color='orange')
    plt.title(f'Distribución {nombre2}')
    plt.xlabel(columna)
    plt.ylabel('Frecuencia')

    plt.tight_layout()
    plt.show()

# Comparar distribuciones de precio
comparar_distribuciones(df_train, df_val, 'precio', 'Entrenamiento', 'Validación')
comparar_distribuciones(df_train, df_test, 'precio', 'Entrenamiento', 'Prueba')
Enter fullscreen mode Exit fullscreen mode

Paso 3: Validación Cruzada (Cross-Validation)

from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LinearRegression

# Preparar datos para validación cruzada
X = df_train[['año', 'kilometraje', 'edad_auto']]
y = df_train['precio']

# Validación cruzada con 5 folds
model = LinearRegression()
scores = cross_val_score(model, X, y, cv=5, scoring='neg_mean_squared_error')

print("Scores de validación cruzada (MSE):")
print(-scores)
print(f"Media: {-scores.mean():.2f}")
print(f"Desviación estándar: {scores.std():.2f}")
Enter fullscreen mode Exit fullscreen mode

Hallazgos Típicos del EDA

1. Distribución del Precio

  • Sesgo positivo: Más autos baratos que caros
  • Outliers: Autos de lujo y autos muy dañados
  • Transformación logarítmica: Puede mejorar la normalidad

2. Relaciones Importantes

  • Año vs Precio: Correlación positiva fuerte (autos más nuevos = más caros)
  • Kilometraje vs Precio: Correlación negativa (más km = menos precio)
  • Edad vs Precio: Correlación negativa fuerte

3. Diferencias por Categorías

  • Marcas de lujo: Precios significativamente más altos
  • Transmisión automática: Generalmente más cara que manual
  • Tipo de auto: Impacto importante en el precio

Mejores Prácticas para EDA

1. Siempre Visualiza

Los números pueden mentir, pero los gráficos revelan la verdad.

2. Documenta tus Hallazgos

# Crear un resumen de hallazgos
hallazgos = {
    'distribucion_precio': 'Sesgo positivo, outliers en ambos extremos',
    'correlacion_fuerte': ['año', 'edad_auto'],
    'correlacion_moderada': ['kilometraje'],
    'categorias_importantes': ['marca', 'tipo_auto'],
    'transformaciones_necesarias': ['log_precio']
}
Enter fullscreen mode Exit fullscreen mode

3. Itera y Refina

El EDA es un proceso iterativo. Cada visualización puede generar nuevas preguntas.

4. Mantén la Consistencia

Usa los mismos colores, estilos y tamaños en todas tus visualizaciones.

Errores Comunes en EDA

1. Ignorar el Contexto de Negocio

No solo analices estadísticas, piensa en qué significa cada hallazgo para el negocio.

2. Sobre-analizar

No necesitas visualizar cada combinación posible de variables.

3. Ignorar la División de Datos

Nunca uses el conjunto de prueba durante el EDA.

4. No Validar la División

Asegúrate de que tus conjuntos de datos son representativos.

Conclusión

El Análisis Exploratorio de Datos y el Marco de Validación son fundamentales para cualquier proyecto de Machine Learning exitoso. El EDA nos ayuda a entender nuestros datos y generar hipótesis, mientras que el marco de validación nos asegura que nuestro modelo puede generalizar a datos nuevos.

Puntos clave:

  • EDA es exploración, no confirmación
  • Siempre divide tus datos antes de entrenar
  • Valida que tus divisiones son representativas
  • Documenta todos tus hallazgos
  • Usa visualizaciones para comunicar insights

En el siguiente post, implementaremos nuestro primer modelo de regresión lineal y veremos cómo convertir nuestros insights del EDA en predicciones útiles.


¿Qué patrones has encontrado en tus propios datos? ¿Cómo decides qué variables son más importantes para tu modelo?

Top comments (0)