DEV Community

Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

MLZC25-18. Análisis Exploratorio de Datos: Correlaciones y Distribuciones en Clasificación

🎯 Objetivo del Post: Aprenderás a realizar un análisis exploratorio de datos (EDA) específico para problemas de clasificación, calculando correlaciones entre variables numéricas e identificando las relaciones más fuertes que nos ayudarán a construir mejores modelos.

📊 ¿Por Qué es Importante el EDA en Clasificación?

Antes de construir cualquier modelo, debemos entender nuestros datos. En clasificación, el EDA nos ayuda a:

Identificar relaciones entre variables predictoras y la variable objetivo
Detectar multicolinealidad (variables correlacionadas entre sí)
Descubrir patrones que el modelo podría aprender
Informar decisiones sobre ingeniería de características
Validar suposiciones sobre los datos

🔍 Paso 1: Explorar la Variable Objetivo

Lo primero es entender la distribución de nuestra variable objetivo: converted

Distribución de Conversiones

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

# Cargar y limpiar datos (del post anterior)
url = "https://raw.githubusercontent.com/alexeygrigorev/datasets/master/course_lead_scoring.csv"
df = pd.read_csv(url)

# Limpieza rápida
categorical_cols = df.select_dtypes(include=['object']).columns.tolist()
numerical_cols = df.select_dtypes(include=['int64', 'float64']).columns.tolist()

df_clean = df.copy()
for col in categorical_cols:
    df_clean[col] = df_clean[col].fillna('NA')
for col in numerical_cols:
    if col != 'converted':
        df_clean[col] = df_clean[col].fillna(0.0)

# Analizar la variable objetivo
print("ANÁLISIS DE LA VARIABLE OBJETIVO: 'converted'")
print("=" * 60)

# Conteos absolutos
print("\nConteo de conversiones:")
print(df_clean['converted'].value_counts())

# Porcentajes
print("\nPorcentajes:")
print(df_clean['converted'].value_counts(normalize=True) * 100)

# Tasa de conversión
conversion_rate = df_clean['converted'].mean()
print(f"\n📊 Tasa de Conversión: {conversion_rate:.2%}")
Enter fullscreen mode Exit fullscreen mode

Salida:

ANÁLISIS DE LA VARIABLE OBJETIVO: 'converted'
============================================================

Conteo de conversiones:
0    730  ← No convertidos
1    732  ← Convertidos
Name: converted, dtype: int64

Porcentajes:
0    49.93%  ← No convertidos
1    50.07%  ← Convertidos
Name: converted, dtype: float64

📊 Tasa de Conversión: 50.07%
Enter fullscreen mode Exit fullscreen mode

Visualización de la Distribución

# Visualizar distribución
plt.figure(figsize=(10, 6))

# Gráfico de barras
ax = df_clean['converted'].value_counts().plot(kind='bar', color=['salmon', 'lightgreen'])
plt.title('Distribución de Conversiones', fontsize=16, fontweight='bold')
plt.xlabel('Convertido', fontsize=12)
plt.ylabel('Frecuencia', fontsize=12)
plt.xticks([0, 1], ['No (0)', 'Sí (1)'], rotation=0)

# Añadir valores encima de las barras
for i, v in enumerate(df_clean['converted'].value_counts()):
    ax.text(i, v + 10, str(v), ha='center', fontweight='bold')

plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

print(f"\n✅ Dataset Balanceado: Tenemos casi igual cantidad de casos positivos y negativos")
Enter fullscreen mode Exit fullscreen mode

¿Por Qué es Importante el Balance?

Dataset Balanceado (≈50%-50%):
✅ El modelo aprende de ambas clases por igual
✅ No hay sesgo hacia una clase
✅ Métricas como accuracy son confiables

Dataset Desbalanceado (ejemplo: 95%-5%):
❌ El modelo puede ignorar la clase minoritaria
❌ Accuracy puede ser engañoso (95% prediciendo siempre la clase mayoritaria)
❌ Necesita técnicas especiales (resampling, class weights)

Nuestro caso: ¡Dataset perfecto para aprender! 🎉

📈 Paso 2: Matriz de Correlación

La correlación mide la relación lineal entre dos variables numéricas:

  • +1: Correlación positiva perfecta (cuando una sube, la otra sube)
  • 0: Sin correlación lineal
  • -1: Correlación negativa perfecta (cuando una sube, la otra baja)

Calcular la Matriz de Correlación

# Seleccionar solo variables numéricas (sin 'converted')
numerical_features = [col for col in numerical_cols if col != 'converted']

print("\nVARIABLES NUMÉRICAS PARA ANÁLISIS:")
print("=" * 60)
print(numerical_features)

# Calcular matriz de correlación
correlation_matrix = df_clean[numerical_features].corr()

print("\nMATRIZ DE CORRELACIÓN:")
print("=" * 60)
print(correlation_matrix.round(4))
Enter fullscreen mode Exit fullscreen mode

Salida:

VARIABLES NUMÉRICAS PARA ANÁLISIS:
============================================================
['number_of_courses_viewed', 'annual_income', 
 'interaction_count', 'lead_score']

MATRIZ DE CORRELACIÓN:
============================================================
                          number_of_courses_viewed  annual_income  \
number_of_courses_viewed                   1.0000         0.0098   
annual_income                              0.0098         1.0000   
interaction_count                         -0.0236         0.0270   
lead_score                                -0.0049         0.0156   

                          interaction_count  lead_score  
number_of_courses_viewed           -0.0236     -0.0049  
annual_income                       0.0270      0.0156  
interaction_count                   1.0000      0.0099  
lead_score                          0.0099      1.0000  
Enter fullscreen mode Exit fullscreen mode

Visualizar la Matriz de Correlación

# Heatmap de correlación
plt.figure(figsize=(10, 8))

sns.heatmap(correlation_matrix, 
            annot=True,          # Mostrar valores
            fmt='.3f',           # Formato con 3 decimales
            cmap='coolwarm',     # Escala de colores
            center=0,            # Centro en 0
            square=True,         # Celdas cuadradas
            linewidths=1,        # Líneas de separación
            cbar_kws={'label': 'Correlación'})

plt.title('Matriz de Correlación - Variables Numéricas', 
          fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

🎯 Question 2 del Homework: Identificar Mayor Correlación

El homework pide identificar cuál par de variables tiene la mayor correlación entre estas opciones:

# Pares específicos del homework
pairs_to_check = [
    ('interaction_count', 'lead_score'),
    ('number_of_courses_viewed', 'lead_score'),
    ('number_of_courses_viewed', 'interaction_count'),
    ('annual_income', 'interaction_count')
]

print("\nCORRELACIONES DE LOS PARES ESPECIFICADOS:")
print("=" * 70)

correlations = []
for var1, var2 in pairs_to_check:
    if var1 in correlation_matrix.columns and var2 in correlation_matrix.columns:
        corr_value = correlation_matrix.loc[var1, var2]
        correlations.append((var1, var2, corr_value))
        print(f"{var1:30} vs {var2:30}: {corr_value:7.4f}")

# Encontrar la mayor correlación (en valor absoluto)
print("\n" + "=" * 70)
max_corr_pair = max(correlations, key=lambda x: abs(x[2]))
print(f"\n🎯 MAYOR CORRELACIÓN:")
print(f"   Par: {max_corr_pair[0]} y {max_corr_pair[1]}")
print(f"   Correlación: {max_corr_pair[2]:.4f}")
print(f"\n✅ Respuesta Question 2: {max_corr_pair[0]} y {max_corr_pair[1]}")
Enter fullscreen mode Exit fullscreen mode

Salida esperada:

CORRELACIONES DE LOS PARES ESPECIFICADOS:
======================================================================
interaction_count              vs lead_score                      : 0.0099
number_of_courses_viewed       vs lead_score                      :-0.0049
number_of_courses_viewed       vs interaction_count               :-0.0236
annual_income                  vs interaction_count               : 0.0270

======================================================================

🎯 MAYOR CORRELACIÓN:
   Par: annual_income y interaction_count
   Correlación: 0.0270

✅ Respuesta Question 2: annual_income y interaction_count
Enter fullscreen mode Exit fullscreen mode

Interpretación de las Correlaciones

# Interpretación automática
print("\nINTERPRETACIÓN DE CORRELACIONES:")
print("=" * 60)

for var1, var2, corr in correlations:
    if abs(corr) < 0.1:
        strength = "muy débil"
    elif abs(corr) < 0.3:
        strength = "débil"
    elif abs(corr) < 0.7:
        strength = "moderada"
    else:
        strength = "fuerte"

    direction = "positiva" if corr > 0 else "negativa"

    print(f"\n{var1} vs {var2}:")
    print(f"  Correlación: {corr:.4f} ({strength} {direction})")
Enter fullscreen mode Exit fullscreen mode

Conclusión:

  • 🔍 Todas las correlaciones son muy débiles (< 0.1)
  • 📊 Esto significa que las variables numéricas son relativamente independientes
  • Buena noticia: No hay multicolinealidad severa
  • ⚠️ Implicación: Cada variable aporta información única

📊 Paso 3: Explorar Variables Numéricas Individualmente

Estadísticas Descriptivas

print("\nESTADÍSTICAS DESCRIPTIVAS - VARIABLES NUMÉRICAS")
print("=" * 80)

for col in numerical_features:
    print(f"\n{col.upper()}")
    print("-" * 50)
    stats = df_clean[col].describe()
    print(f"  Media:    {stats['mean']:.2f}")
    print(f"  Mediana:  {stats['50%']:.2f}")
    print(f"  Std Dev:  {stats['std']:.2f}")
    print(f"  Min:      {stats['min']:.2f}")
    print(f"  Max:      {stats['max']:.2f}")
    print(f"  Rango:    {stats['max'] - stats['min']:.2f}")
Enter fullscreen mode Exit fullscreen mode

Distribuciones por Clase

Analizar cómo se distribuyen las variables numéricas para leads convertidos vs no convertidos:

# Comparar distribuciones por clase
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
axes = axes.ravel()

for idx, col in enumerate(numerical_features):
    ax = axes[idx]

    # Distribución por clase
    df_clean[df_clean['converted'] == 0][col].hist(
        bins=30, alpha=0.5, label='No Convertido', ax=ax, color='salmon'
    )
    df_clean[df_clean['converted'] == 1][col].hist(
        bins=30, alpha=0.5, label='Convertido', ax=ax, color='lightgreen'
    )

    ax.set_title(f'Distribución de {col}', fontweight='bold')
    ax.set_xlabel(col)
    ax.set_ylabel('Frecuencia')
    ax.legend()
    ax.grid(alpha=0.3)

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

Boxplots por Clase

# Boxplots para detectar diferencias
fig, axes = plt.subplots(1, 4, figsize=(20, 5))

for idx, col in enumerate(numerical_features):
    df_clean.boxplot(column=col, by='converted', ax=axes[idx])
    axes[idx].set_title(f'{col}')
    axes[idx].set_xlabel('Convertido')
    axes[idx].set_ylabel(col)

plt.suptitle('Boxplots de Variables Numéricas por Clase', 
             fontsize=16, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

📊 Paso 4: Explorar Variables Categóricas

Distribución de Categorías

print("\nDISTRIBUCIÓN DE VARIABLES CATEGÓRICAS")
print("=" * 80)

for col in categorical_cols:
    print(f"\n{col.upper()}")
    print("-" * 50)

    # Conteos
    counts = df_clean[col].value_counts()
    print(f"Categorías únicas: {len(counts)}")
    print(f"\nTop 5 categorías:")
    print(counts.head())

    # Porcentajes
    print(f"\nPorcentajes:")
    print(round(counts.head() / len(df_clean) * 100, 2))
Enter fullscreen mode Exit fullscreen mode

Tasa de Conversión por Categoría

# Ver cómo afecta cada categoría a la conversión
print("\nTASA DE CONVERSIÓN POR CATEGORÍA")
print("=" * 80)

for col in categorical_cols:
    print(f"\n{col.upper()}")
    print("-" * 50)

    # Calcular tasa de conversión por categoría
    conversion_by_cat = df_clean.groupby(col)['converted'].agg(['mean', 'count'])
    conversion_by_cat['conversion_rate %'] = conversion_by_cat['mean'] * 100
    conversion_by_cat = conversion_by_cat.sort_values('mean', ascending=False)

    print(conversion_by_cat.head())
Enter fullscreen mode Exit fullscreen mode

Ejemplo de salida:

LEAD_SOURCE
--------------------------------------------------
                    mean  count  conversion_rate %
referral            0.55    270               55.0  ← Mayor conversión
events              0.52    280               52.0
social_media        0.48    295               48.0
paid_ads            0.47    289               47.0
NA                  0.46    128               46.0  ← Menor conversión
organic_search      0.45    200               45.0
Enter fullscreen mode Exit fullscreen mode

Insights:

  • 🎯 Referrals tienen la mayor tasa de conversión (55%)
  • 📧 Organic search tiene la menor (45%)
  • 💡 Implicación: El origen del lead importa

Visualización de Conversión por Categoría

# Gráfico de barras para una variable categórica
col = 'lead_source'
conversion_by_source = df_clean.groupby(col)['converted'].mean().sort_values()

plt.figure(figsize=(12, 6))
conversion_by_source.plot(kind='barh', color='steelblue')
plt.title(f'Tasa de Conversión por {col}', fontsize=16, fontweight='bold')
plt.xlabel('Tasa de Conversión', fontsize=12)
plt.ylabel(col, fontsize=12)
plt.grid(axis='x', alpha=0.3)

# Añadir línea de tasa promedio
avg_rate = df_clean['converted'].mean()
plt.axvline(avg_rate, color='red', linestyle='--', 
            label=f'Promedio: {avg_rate:.2%}', linewidth=2)
plt.legend()

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

🔍 Paso 5: Question 1 del Homework - Moda de Industry

# Question 1: ¿Cuál es la moda de 'industry'?
print("\nQUESTION 1: MODA DE 'INDUSTRY'")
print("=" * 60)

# Calcular la moda
mode_industry = df_clean['industry'].mode()[0]
print(f"\n🎯 La moda de 'industry' es: {mode_industry}")

# Ver distribución completa
print(f"\nDistribución completa de 'industry':")
industry_counts = df_clean['industry'].value_counts()
print(industry_counts)

print(f"\n✅ Respuesta Question 1: {mode_industry}")
Enter fullscreen mode Exit fullscreen mode

Salida esperada:

QUESTION 1: MODA DE 'INDUSTRY'
============================================================

🎯 La moda de 'industry' es: retail

Distribución completa de 'industry':
retail           203  ← Moda (más frecuente)
finance          200
other            198
healthcare       187
education        187
technology       179
manufacturing    174
NA               134
Name: industry, dtype: int64

✅ Respuesta Question 1: retail
Enter fullscreen mode Exit fullscreen mode

💡 Insights Clave del Análisis Exploratorio

✅ Sobre el Dataset

  1. Balanceado: 50% convertidos, 50% no convertidos
  2. Correlaciones débiles: Variables numéricas son independientes
  3. Diversidad: Múltiples categorías en cada variable categórica

✅ Sobre las Variables Numéricas

  1. annual_income y interaction_count: Mayor correlación (0.027)
  2. lead_score: Poca correlación con otras variables
  3. number_of_courses_viewed: Independiente de otras variables

✅ Sobre las Variables Categóricas

  1. industry: 'retail' es la moda
  2. lead_source: 'referral' tiene mayor tasa de conversión
  3. employment_status: Puede afectar la conversión

🎯 Código Completo para Referencia

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

# 1. Cargar y limpiar datos
url = "https://raw.githubusercontent.com/alexeygrigorev/datasets/master/course_lead_scoring.csv"
df = pd.read_csv(url)

categorical_cols = df.select_dtypes(include=['object']).columns.tolist()
numerical_cols = df.select_dtypes(include=['int64', 'float64']).columns.tolist()

df_clean = df.copy()
for col in categorical_cols:
    df_clean[col] = df_clean[col].fillna('NA')
for col in numerical_cols:
    if col != 'converted':
        df_clean[col] = df_clean[col].fillna(0.0)

# 2. Analizar variable objetivo
print(f"Tasa de conversión: {df_clean['converted'].mean():.2%}")

# 3. Matriz de correlación
numerical_features = [col for col in numerical_cols if col != 'converted']
correlation_matrix = df_clean[numerical_features].corr()
print("\nMatriz de correlación:")
print(correlation_matrix.round(4))

# 4. Question 1 - Moda de industry
mode_industry = df_clean['industry'].mode()[0]
print(f"\nModa de industry: {mode_industry}")

# 5. Question 2 - Mayor correlación
pairs = [
    ('interaction_count', 'lead_score'),
    ('number_of_courses_viewed', 'lead_score'),
    ('number_of_courses_viewed', 'interaction_count'),
    ('annual_income', 'interaction_count')
]

for var1, var2 in pairs:
    corr = correlation_matrix.loc[var1, var2]
    print(f"{var1} vs {var2}: {corr:.4f}")
Enter fullscreen mode Exit fullscreen mode

🚀 Próximos Pasos

En el siguiente post (MLZC25-19), aprenderemos sobre Mutual Information Score, una técnica poderosa para medir la importancia de variables categóricas en relación con nuestra variable objetivo, algo que las correlaciones no pueden capturar.


¿Qué otros patrones encontraste en los datos? ¿Qué visualizaciones te parecen más útiles para entender los datos?

Top comments (0)