DEV Community

Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

MLZC25-05. Análisis Exploratorio de Datos (EDA): El Arte de Descubrir Historias Ocultas

🔍 EDA: Tu Brújula en el Mundo de los Datos

Imagina que eres un detective llegando a la escena de un crimen. Tienes pistas dispersas, testimonios contradictorios, y un montón de evidencia. ¿Por dónde empiezas? Por explorar todo meticulosamente antes de sacar conclusiones.

El Análisis Exploratorio de Datos (EDA) es exactamente eso: ser un detective de datos. Es el proceso de investigar, examinar y visualizar datos para descubrir patrones, detectar anomalías, y formular hipótesis antes de aplicar técnicas de machine learning.

🎯 ¿Por qué EDA es tan importante?

1. Conocer a tu "enemigo"

Los datos nunca son perfectos. Tienen:

  • Valores faltantes
  • Outliers (valores extremos)
  • Errores de entrada
  • Distribuciones inesperadas
  • Correlaciones ocultas

2. Formular las preguntas correctas

EDA te ayuda a preguntarte:

  • ¿Qué patrones veo?
  • ¿Qué no tiene sentido?
  • ¿Qué correlaciones existen?
  • ¿Qué variables son importantes?

3. Evitar errores costosos

Sin EDA, puedes:

  • Entrenar modelos con datos corruptos
  • Perder variables importantes
  • Interpretar mal los resultados
  • Tomar decisiones basadas en datos incorrectos

🛠️ El Kit de Herramientas del Detective de Datos

Paso 1: La Primera Impresión

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

# Cargar datos
df = pd.read_csv('datos.csv')

# Primer vistazo
print("=== INFORMACIÓN BÁSICA ===")
print(f"Forma del dataset: {df.shape}")
print(f"Columnas: {df.columns.tolist()}")
print(f"Tipos de datos:\n{df.dtypes}")

# Primeras filas
print("\n=== PRIMERAS FILAS ===")
print(df.head())

# Últimas filas
print("\n=== ÚLTIMAS FILAS ===")
print(df.tail())
Enter fullscreen mode Exit fullscreen mode

¿Qué buscas aquí?

  • Forma: ¿Cuántas filas y columnas?
  • Columnas: ¿Los nombres tienen sentido?
  • Tipos: ¿Los tipos de datos son correctos?
  • Valores: ¿Los datos se ven razonables?

Paso 2: La Investigación de Valores Faltantes

def analizar_valores_faltantes(df):
    """Análisis detallado de valores faltantes"""

    # Información básica
    faltantes = df.isnull().sum()
    porcentaje = (faltantes / len(df)) * 100

    # Crear DataFrame resumen
    resumen = pd.DataFrame({
        'Columna': faltantes.index,
        'Valores_Faltantes': faltantes.values,
        'Porcentaje': porcentaje.values
    })

    # Filtrar solo columnas con valores faltantes
    resumen = resumen[resumen['Valores_Faltantes'] > 0]
    resumen = resumen.sort_values('Valores_Faltantes', ascending=False)

    print("=== ANÁLISIS DE VALORES FALTANTES ===")
    print(resumen)

    # Visualización
    if len(resumen) > 0:
        plt.figure(figsize=(12, 6))
        sns.barplot(data=resumen, x='Porcentaje', y='Columna')
        plt.title('Porcentaje de Valores Faltantes por Columna')
        plt.show()

    return resumen

# Usar la función
valores_faltantes = analizar_valores_faltantes(df)
Enter fullscreen mode Exit fullscreen mode

¿Qué hacer con valores faltantes?

  • < 5%: Eliminar o imputar
  • 5-20%: Imputar con cuidado
  • > 20%: Considerar eliminar la columna

Paso 3: El Perfil Estadístico

def perfil_estadistico(df):
    """Análisis estadístico completo"""

    print("=== ESTADÍSTICAS DESCRIPTIVAS ===")
    print(df.describe())

    print("\n=== INFORMACIÓN DETALLADA ===")
    print(df.info())

    # Análisis por tipo de columna
    columnas_numericas = df.select_dtypes(include=[np.number]).columns
    columnas_categoricas = df.select_dtypes(include=['object']).columns

    print(f"\n=== COLUMNAS NUMÉRICAS ({len(columnas_numericas)}) ===")
    print(columnas_numericas.tolist())

    print(f"\n=== COLUMNAS CATEGÓRICAS ({len(columnas_categoricas)}) ===")
    print(columnas_categoricas.tolist())

    # Distribuciones de columnas numéricas
    if len(columnas_numericas) > 0:
        fig, axes = plt.subplots(2, 2, figsize=(15, 10))
        axes = axes.ravel()

        for i, col in enumerate(columnas_numericas[:4]):
            if i < len(axes):
                df[col].hist(bins=30, ax=axes[i])
                axes[i].set_title(f'Distribución de {col}')

        plt.tight_layout()
        plt.show()

    return columnas_numericas, columnas_categoricas

# Usar la función
num_cols, cat_cols = perfil_estadistico(df)
Enter fullscreen mode Exit fullscreen mode

Paso 4: La Caza de Outliers

def detectar_outliers(df, columnas_numericas):
    """Detección de outliers usando IQR"""

    outliers_info = {}

    for col in columnas_numericas:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1

        # Límites para outliers
        limite_inferior = Q1 - 1.5 * IQR
        limite_superior = Q3 + 1.5 * IQR

        # Contar outliers
        outliers = df[(df[col] < limite_inferior) | (df[col] > limite_superior)]

        outliers_info[col] = {
            'cantidad': len(outliers),
            'porcentaje': (len(outliers) / len(df)) * 100,
            'limites': (limite_inferior, limite_superior)
        }

    # Crear DataFrame resumen
    resumen_outliers = pd.DataFrame(outliers_info).T
    resumen_outliers = resumen_outliers[resumen_outliers['cantidad'] > 0]

    print("=== ANÁLISIS DE OUTLIERS ===")
    print(resumen_outliers)

    # Visualización de outliers
    if len(resumen_outliers) > 0:
        fig, axes = plt.subplots(2, 2, figsize=(15, 10))
        axes = axes.ravel()

        for i, col in enumerate(resumen_outliers.index[:4]):
            if i < len(axes):
                sns.boxplot(data=df, y=col, ax=axes[i])
                axes[i].set_title(f'Boxplot de {col}')

        plt.tight_layout()
        plt.show()

    return outliers_info

# Usar la función
outliers = detectar_outliers(df, num_cols)
Enter fullscreen mode Exit fullscreen mode

¿Qué hacer con outliers?

  • Investigar: ¿Son errores o valores reales?
  • Contexto: ¿Tienen sentido en el dominio?
  • Impacto: ¿Afectan significativamente el análisis?

Paso 5: El Mapa de Correlaciones

def analizar_correlaciones(df, columnas_numericas):
    """Análisis de correlaciones entre variables"""

    if len(columnas_numericas) < 2:
        print("Se necesitan al menos 2 columnas numéricas para analizar correlaciones")
        return

    # Matriz de correlación
    corr_matrix = df[columnas_numericas].corr()

    # Visualización
    plt.figure(figsize=(12, 10))
    mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
    sns.heatmap(corr_matrix, 
                mask=mask,
                annot=True, 
                cmap='coolwarm', 
                center=0,
                square=True,
                fmt='.2f')
    plt.title('Matriz de Correlación')
    plt.tight_layout()
    plt.show()

    # Correlaciones fuertes (|r| > 0.7)
    correlaciones_fuertes = []
    for i in range(len(corr_matrix.columns)):
        for j in range(i+1, len(corr_matrix.columns)):
            corr_val = corr_matrix.iloc[i, j]
            if abs(corr_val) > 0.7:
                correlaciones_fuertes.append({
                    'Variable_1': corr_matrix.columns[i],
                    'Variable_2': corr_matrix.columns[j],
                    'Correlacion': corr_val
                })

    if correlaciones_fuertes:
        print("\n=== CORRELACIONES FUERTES (|r| > 0.7) ===")
        for corr in correlaciones_fuertes:
            print(f"{corr['Variable_1']} - {corr['Variable_2']}: {corr['Correlacion']:.3f}")

    return corr_matrix, correlaciones_fuertes

# Usar la función
corr_matrix, corr_fuertes = analizar_correlaciones(df, num_cols)
Enter fullscreen mode Exit fullscreen mode

Paso 6: El Análisis Categórico

def analizar_categoricas(df, columnas_categoricas):
    """Análisis de variables categóricas"""

    for col in columnas_categoricas:
        print(f"\n=== ANÁLISIS DE {col.upper()} ===")

        # Valores únicos
        valores_unicos = df[col].nunique()
        print(f"Valores únicos: {valores_unicos}")

        # Distribución
        distribucion = df[col].value_counts()
        print(f"Distribución:\n{distribucion}")

        # Porcentajes
        porcentajes = df[col].value_counts(normalize=True) * 100
        print(f"Porcentajes:\n{porcentajes}")

        # Visualización
        plt.figure(figsize=(10, 6))
        sns.countplot(data=df, y=col, order=distribucion.index)
        plt.title(f'Distribución de {col}')
        plt.tight_layout()
        plt.show()

# Usar la función
analizar_categoricas(df, cat_cols)
Enter fullscreen mode Exit fullscreen mode

🎨 Visualizaciones que Cuentan Historias

1. Distribuciones

# Histogramas múltiples
def plot_distribuciones(df, columnas_numericas):
    n_cols = min(3, len(columnas_numericas))
    n_rows = (len(columnas_numericas) + n_cols - 1) // n_cols

    fig, axes = plt.subplots(n_rows, n_cols, figsize=(15, 5*n_rows))
    axes = axes.ravel() if n_rows > 1 else [axes] if n_cols == 1 else axes

    for i, col in enumerate(columnas_numericas):
        if i < len(axes):
            df[col].hist(bins=30, ax=axes[i], alpha=0.7)
            axes[i].set_title(f'Distribución de {col}')
            axes[i].set_xlabel(col)
            axes[i].set_ylabel('Frecuencia')

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

2. Relaciones entre Variables

# Scatter plots para relaciones
def plot_relaciones(df, columnas_numericas, target_col=None):
    if len(columnas_numericas) < 2:
        print("Se necesitan al menos 2 columnas numéricas")
        return

    # Si hay variable target, usarla para colorear
    hue = target_col if target_col and target_col in df.columns else None

    # Pairplot
    if len(columnas_numericas) <= 5:  # Para no sobrecargar
        sns.pairplot(df[columnas_numericas + ([target_col] if target_col else [])], 
                    hue=hue, diag_kind='hist')
        plt.show()

    # Scatter plot de las dos variables más correlacionadas
    corr_pairs = []
    for i in range(len(columnas_numericas)):
        for j in range(i+1, len(columnas_numericas)):
            corr = df[columnas_numericas[i]].corr(df[columnas_numericas[j]])
            corr_pairs.append((columnas_numericas[i], columnas_numericas[j], abs(corr)))

    if corr_pairs:
        corr_pairs.sort(key=lambda x: x[2], reverse=True)
        var1, var2, corr = corr_pairs[0]

        plt.figure(figsize=(10, 6))
        sns.scatterplot(data=df, x=var1, y=var2, hue=hue)
        plt.title(f'Relación entre {var1} y {var2} (r = {corr:.3f})')
        plt.show()
Enter fullscreen mode Exit fullscreen mode

🧠 Patrones que Debes Buscar

1. Patrones Temporales

# Si tienes datos temporales
if 'fecha' in df.columns:
    df['fecha'] = pd.to_datetime(df['fecha'])
    df['mes'] = df['fecha'].dt.month
    df['año'] = df['fecha'].dt.year

    # Tendencia temporal
    plt.figure(figsize=(12, 6))
    df.groupby('mes')['ventas'].sum().plot(kind='line')
    plt.title('Tendencia de Ventas por Mes')
    plt.show()
Enter fullscreen mode Exit fullscreen mode

2. Patrones por Categorías

# Análisis por grupos
def analizar_por_grupos(df, grupo_col, target_col):
    print(f"\n=== ANÁLISIS POR {grupo_col.upper()} ===")

    # Estadísticas por grupo
    stats_por_grupo = df.groupby(grupo_col)[target_col].agg(['mean', 'median', 'std', 'count'])
    print(stats_por_grupo)

    # Visualización
    plt.figure(figsize=(12, 6))
    sns.boxplot(data=df, x=grupo_col, y=target_col)
    plt.title(f'{target_col} por {grupo_col}')
    plt.xticks(rotation=45)
    plt.show()
Enter fullscreen mode Exit fullscreen mode

📊 EDA Automatizado con Pandas Profiling

# Instalación: pip install pandas-profiling
from pandas_profiling import ProfileReport

# Generar reporte automático
profile = ProfileReport(df, title="EDA Report", explorative=True)
profile.to_file("eda_report.html")
Enter fullscreen mode Exit fullscreen mode

🎯 Checklist de EDA

✅ Información Básica

  • [ ] Forma del dataset
  • [ ] Tipos de datos correctos
  • [ ] Nombres de columnas claros
  • [ ] Valores faltantes identificados

✅ Calidad de Datos

  • [ ] Outliers detectados y analizados
  • [ ] Duplicados identificados
  • [ ] Inconsistencias en datos categóricos
  • [ ] Rangos de valores razonables

✅ Distribuciones

  • [ ] Distribuciones de variables numéricas
  • [ ] Distribuciones de variables categóricas
  • [ ] Asimetría y curtosis identificadas
  • [ ] Transformaciones necesarias identificadas

✅ Relaciones

  • [ ] Correlaciones calculadas
  • [ ] Relaciones no lineales exploradas
  • [ ] Interacciones entre variables
  • [ ] Variable objetivo analizada

✅ Insights Generados

  • [ ] Hipótesis formuladas
  • [ ] Patrones interesantes identificados
  • [ ] Próximos pasos definidos
  • [ ] Decisiones de preprocesamiento tomadas

💡 Consejos Avanzados

1. EDA Iterativo

# No hagas todo de una vez, itera
def eda_iterativo(df, max_iteraciones=3):
    for i in range(max_iteraciones):
        print(f"\n=== ITERACIÓN {i+1} ===")

        # Hacer análisis
        insights = analizar_datos(df)

        # Preguntar al usuario
        continuar = input("¿Continuar con más análisis? (y/n): ")
        if continuar.lower() != 'y':
            break
Enter fullscreen mode Exit fullscreen mode

2. EDA por Dominio

# Adapta tu EDA al dominio específico
def eda_financiero(df):
    # Métricas específicas para finanzas
    df['rendimiento'] = df['precio_final'] / df['precio_inicial'] - 1
    df['volatilidad'] = df.groupby('activo')['rendimiento'].rolling(30).std()

def eda_marketing(df):
    # Métricas específicas para marketing
    df['conversion_rate'] = df['conversiones'] / df['visitas']
    df['lifetime_value'] = df['compras_promedio'] * df['frecuencia_compra']
Enter fullscreen mode Exit fullscreen mode

🎯 Reflexión Práctica

Ejercicio: Toma cualquier dataset y aplica estos pasos de EDA. Al final, pregúntate:

  1. ¿Qué aprendí sobre los datos que no sabía antes?
  2. ¿Qué patrones me sorprendieron?
  3. ¿Qué decisiones de preprocesamiento debo tomar?
  4. ¿Qué hipótesis puedo formular para el modelado?

🔗 Lo que viene después

En el próximo post exploraremos el Preprocesamiento de Datos, donde aplicaremos los insights del EDA para preparar nuestros datos para el modelado.

💭 Pregunta para reflexionar

¿Qué tipo de patrón te resulta más fascinante descubrir en los datos? ¿Son las correlaciones inesperadas, los outliers que cuentan historias, o las distribuciones que revelan comportamientos ocultos?


El EDA no es solo un paso obligatorio, es donde la magia de la ciencia de datos realmente comienza. Es donde los datos dejan de ser números y se convierten en historias que podemos entender y actuar.

Top comments (0)