DEV Community

Jesus Oviedo Riquelme
Jesus Oviedo Riquelme

Posted on

MLZC25-20. Regresión Logística y One-Hot Encoding: Entrenando Nuestro Primer Modelo de Clasificación

🎯 Objetivo del Post: Aprenderás a implementar regresión logística con scikit-learn, aplicar one-hot encoding a variables categóricas, entrenar el modelo y evaluar su rendimiento con accuracy.

🎯 El Desafío: Variables Categóricas

Los algoritmos de ML, incluida la regresión logística, trabajan con números, no con texto. Tenemos un problema:

# Nuestros datos tienen texto
lead_source: ['paid_ads', 'social_media', 'referral', ...]
industry: ['technology', 'finance', 'healthcare', ...]

# El modelo necesita números
model.fit(X, y)  # ❌ Error si X contiene texto
Enter fullscreen mode Exit fullscreen mode

Solución: One-Hot Encoding 🔥

🔄 ¿Qué es One-Hot Encoding?

One-Hot Encoding convierte cada categoría en una columna binaria (0 o 1).

Ejemplo Visual

Antes:

lead_source
-----------
paid_ads
social_media
referral
paid_ads
Enter fullscreen mode Exit fullscreen mode

Después:

lead_source=paid_ads  lead_source=social_media  lead_source=referral
-------------------  ------------------------  -------------------
        1                      0                       0
        0                      1                       0
        0                      0                       1
        1                      0                       0
Enter fullscreen mode Exit fullscreen mode

¿Por Qué Funciona?

Preserva la información categórica sin imponer orden
Compatible con algoritmos de ML que requieren números
No introduce relaciones falsas (como haría Label Encoding)

One-Hot vs Label Encoding

# ❌ Label Encoding (MAL para variables nominales)
industry:  technology  0
          finance  1
          healthcare  2
# Problema: Implica que healthcare > finance > technology (orden falso)

# ✅ One-Hot Encoding (BIEN)
industry=technology:   [1, 0, 0]
industry=finance:      [0, 1, 0]
industry=healthcare:   [0, 0, 1]
# No hay orden implícito
Enter fullscreen mode Exit fullscreen mode

💻 Implementación Paso a Paso

Paso 1: Preparar los Datos

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction import DictVectorizer
from sklearn.metrics import accuracy_score

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

# Limpieza de valores faltantes
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)

print("✅ Datos cargados y limpiados")
print(f"   Forma: {df_clean.shape}")
Enter fullscreen mode Exit fullscreen mode

Paso 2: División de Datos (60%/20%/20%)

print("\nDIVISIÓN DE DATOS (60%/20%/20%)")
print("=" * 60)

# Primera división: train (60%) vs temp (40%)
df_train_full, df_temp = train_test_split(
    df_clean, 
    test_size=0.4, 
    random_state=42,
    stratify=df_clean['converted']
)

# Segunda división: val (20%) y test (20%)
df_val, df_test = train_test_split(
    df_temp, 
    test_size=0.5, 
    random_state=42,
    stratify=df_temp['converted']
)

print(f"Train: {len(df_train_full)} registros ({len(df_train_full)/len(df_clean)*100:.1f}%)")
print(f"Val:   {len(df_val)} registros ({len(df_val)/len(df_clean)*100:.1f}%)")
print(f"Test:  {len(df_test)} registros ({len(df_test)/len(df_clean)*100:.1f}%)")

# Separar features (X) y target (y)
y_train = df_train_full['converted'].values
y_val = df_val['converted'].values
y_test = df_test['converted'].values

X_train_df = df_train_full.drop('converted', axis=1).reset_index(drop=True)
X_val_df = df_val.drop('converted', axis=1).reset_index(drop=True)
X_test_df = df_test.drop('converted', axis=1).reset_index(drop=True)

print(f"\n✅ División completada")
print(f"   X_train: {X_train_df.shape}")
print(f"   y_train: {y_train.shape}")
Enter fullscreen mode Exit fullscreen mode

Paso 3: One-Hot Encoding con DictVectorizer

DictVectorizer es la herramienta perfecta porque:

  • ✅ Maneja automáticamente variables categóricas y numéricas
  • ✅ Aplica one-hot encoding a las categóricas
  • ✅ Mantiene las numéricas sin cambios
  • ✅ Garantiza consistencia entre train/val/test
print("\nONE-HOT ENCODING CON DICTVECTORIZER")
print("=" * 60)

# Convertir DataFrames a diccionarios
train_dicts = X_train_df.to_dict(orient='records')
val_dicts = X_val_df.to_dict(orient='records')
test_dicts = X_test_df.to_dict(orient='records')

# Crear DictVectorizer
dv = DictVectorizer(sparse=False)

# Fit en train y transform en todos
X_train = dv.fit_transform(train_dicts)
X_val = dv.transform(val_dicts)
X_test = dv.transform(test_dicts)

print(f"✅ One-hot encoding aplicado")
print(f"   Forma original: {X_train_df.shape}")
print(f"   Forma después de encoding: {X_train.shape}")
print(f"   Nuevas features creadas: {X_train.shape[1] - len(X_train_df.columns)}")

# Ver algunas de las nuevas features
print(f"\nPrimeras 10 features después del encoding:")
for i, feature_name in enumerate(dv.get_feature_names_out()[:10]):
    print(f"  {i+1}. {feature_name}")
Enter fullscreen mode Exit fullscreen mode

Salida esperada:

ONE-HOT ENCODING CON DICTVECTORIZER
============================================================
✅ One-hot encoding aplicado
   Forma original: (877, 8)
   Forma después de encoding: (877, 36)
   Nuevas features creadas: 28

Primeras 10 features después del encoding:
  1. annual_income
  2. employment_status=NA
  3. employment_status=employed
  4. employment_status=self_employed
  5. employment_status=student
  6. employment_status=unemployed
  7. industry=NA
  8. industry=education
  9. industry=finance
  10. industry=healthcare
Enter fullscreen mode Exit fullscreen mode

Entender el Resultado

# Analizar las dimensiones
print(f"\nANÁLISIS DE DIMENSIONES")
print("=" * 60)

# Contar features por tipo
original_features = X_train_df.columns.tolist()
n_categorical = len([col for col in original_features 
                    if X_train_df[col].dtype == 'object'])
n_numerical = len(original_features) - n_categorical

print(f"Features originales:")
print(f"  - Categóricas: {n_categorical}")
print(f"  - Numéricas: {n_numerical}")
print(f"  - Total: {len(original_features)}")

print(f"\nFeatures después de one-hot encoding:")
print(f"  - Total: {X_train.shape[1]}")

# Desglose por variable categórica
print(f"\nDesglose de one-hot encoding:")
for col in X_train_df.select_dtypes(include=['object']).columns:
    n_categories = X_train_df[col].nunique()
    print(f"  {col}: {n_categories} categorías → {n_categories} columnas binarias")
Enter fullscreen mode Exit fullscreen mode

🧠 Paso 4: Entrenar Regresión Logística

Parámetros del Modelo

Según el homework, usamos estos parámetros específicos:

model = LogisticRegression(
    solver='liblinear',  # Algoritmo de optimización
    C=1.0,               # Parámetro de regularización inverso
    max_iter=1000,       # Iteraciones máximas
    random_state=42      # Semilla para reproducibilidad
)
Enter fullscreen mode Exit fullscreen mode

Explicación de parámetros:

Parámetro Valor Explicación
solver 'liblinear' Algoritmo rápido y efectivo para datasets pequeños
C 1.0 Regularización moderada (C más alto = menos regularización)
max_iter 1000 Suficientes iteraciones para converger
random_state 42 Resultados reproducibles

Entrenar el Modelo

print("\nENTRENAMIENTO DE REGRESIÓN LOGÍSTICA")
print("=" * 60)

# Crear y entrenar el modelo
model = LogisticRegression(
    solver='liblinear', 
    C=1.0, 
    max_iter=1000, 
    random_state=42
)

print("Entrenando modelo...")
model.fit(X_train, y_train)
print("✅ Modelo entrenado exitosamente")

# Información del modelo
print(f"\nInformación del modelo:")
print(f"  - Coeficientes: {model.coef_.shape}")
print(f"  - Intercepto: {model.intercept_}")
print(f"  - Clases: {model.classes_}")
Enter fullscreen mode Exit fullscreen mode

📊 Paso 5: Evaluar el Modelo con Accuracy

¿Qué es Accuracy?

Accuracy (exactitud) es el porcentaje de predicciones correctas:

Accuracy = (Predicciones Correctas) / (Total de Predicciones)
Enter fullscreen mode Exit fullscreen mode

Calcular Accuracy en Validación

print("\nEVALUACIÓN DEL MODELO")
print("=" * 60)

# Hacer predicciones en el conjunto de validación
y_pred_val = model.predict(X_val)

# Calcular accuracy
accuracy = accuracy_score(y_val, y_pred_val)

print(f"Accuracy en validación: {accuracy:.6f}")
print(f"Accuracy en validación (redondeado): {accuracy:.4f}")

# Redondear a 2 decimales según el homework
accuracy_rounded = round(accuracy, 2)
print(f"\n🎯 RESPUESTA QUESTION 4: {accuracy_rounded}")
Enter fullscreen mode Exit fullscreen mode

Salida esperada:

EVALUACIÓN DEL MODELO
============================================================
Accuracy en validación: 0.849315
Accuracy en validación (redondeado): 0.8493

🎯 RESPUESTA QUESTION 4: 0.85
Enter fullscreen mode Exit fullscreen mode

Interpretar Accuracy

# Análisis detallado del accuracy
print(f"\nINTERPRETACIÓN DEL ACCURACY")
print("=" * 60)

n_val = len(y_val)
n_correct = int(accuracy * n_val)
n_incorrect = n_val - n_correct

print(f"Total de predicciones: {n_val}")
print(f"Predicciones correctas: {n_correct} ({accuracy*100:.2f}%)")
print(f"Predicciones incorrectas: {n_incorrect} ({(1-accuracy)*100:.2f}%)")

# Desglose por clase
from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_val, y_pred_val)
print(f"\nMatriz de Confusión:")
print(f"                Predicho No  Predicho Sí")
print(f"Real No              {cm[0,0]:4d}        {cm[0,1]:4d}")
print(f"Real Sí              {cm[1,0]:4d}        {cm[1,1]:4d}")

# Calcular métricas por clase
tn, fp, fn, tp = cm.ravel()
print(f"\nVerdaderos Negativos (TN): {tn}")
print(f"Falsos Positivos (FP): {fp}")
print(f"Falsos Negativos (FN): {fn}")
print(f"Verdaderos Positivos (TP): {tp}")
Enter fullscreen mode Exit fullscreen mode

Accuracy en Train (Verificación)

# También verificar accuracy en train
y_pred_train = model.predict(X_train)
accuracy_train = accuracy_score(y_train, y_pred_train)

print(f"\n📊 COMPARACIÓN TRAIN vs VALIDATION")
print("=" * 60)
print(f"Accuracy en Train: {accuracy_train:.4f} ({accuracy_train*100:.2f}%)")
print(f"Accuracy en Val:   {accuracy:.4f} ({accuracy*100:.2f}%)")

diff = accuracy_train - accuracy
if diff < 0.05:
    print(f"\n✅ Diferencia mínima ({diff:.4f}) - Modelo generaliza bien")
elif diff < 0.10:
    print(f"\n⚠️ Diferencia moderada ({diff:.4f}) - Ligero overfitting")
else:
    print(f"\n❌ Diferencia alta ({diff:.4f}) - Overfitting significativo")
Enter fullscreen mode Exit fullscreen mode

🔍 Análisis de los Coeficientes

Los coeficientes nos dicen qué features son más importantes:

print("\nANÁLISIS DE COEFICIENTES DEL MODELO")
print("=" * 70)

# Obtener nombres de features y coeficientes
feature_names = dv.get_feature_names_out()
coefficients = model.coef_[0]

# Crear DataFrame para análisis
coef_df = pd.DataFrame({
    'Feature': feature_names,
    'Coefficient': coefficients,
    'Abs_Coefficient': np.abs(coefficients)
})

# Ordenar por importancia (valor absoluto)
coef_df = coef_df.sort_values('Abs_Coefficient', ascending=False)

# Top 10 features más importantes
print(f"\nTop 10 Features Más Importantes:")
print("-" * 70)
print(f"{'Feature':<40} {'Coeficiente':>15} {'Impacto':>10}")
print("-" * 70)

for idx, row in coef_df.head(10).iterrows():
    impact = "Positivo ↑" if row['Coefficient'] > 0 else "Negativo ↓"
    print(f"{row['Feature']:<40} {row['Coefficient']:>15.4f} {impact:>10}")
Enter fullscreen mode Exit fullscreen mode

Interpretación de coeficientes:

  • Positivo (+): Aumenta la probabilidad de conversión
  • Negativo (-): Disminuye la probabilidad de conversión
  • Magnitud: Qué tan fuerte es el efecto

Visualizar Coeficientes

import matplotlib.pyplot as plt

# Visualizar top 15 coeficientes
plt.figure(figsize=(12, 8))

top_15 = coef_df.head(15)
colors = ['green' if x > 0 else 'red' for x in top_15['Coefficient']]

plt.barh(range(len(top_15)), top_15['Coefficient'], color=colors, alpha=0.7)
plt.yticks(range(len(top_15)), top_15['Feature'])
plt.xlabel('Coeficiente', fontweight='bold')
plt.title('Top 15 Features por Importancia (Coeficientes)', 
          fontsize=14, fontweight='bold')
plt.axvline(x=0, color='black', linestyle='--', linewidth=1)
plt.grid(axis='x', alpha=0.3)
plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

🎯 Predicciones en Nuevos Datos

Predecir Probabilidades

# Obtener probabilidades de conversión
y_pred_proba = model.predict_proba(X_val)

print("\nEJEMPLO DE PREDICCIONES")
print("=" * 70)
print(f"{'Real':>6} {'Predicho':>10} {'Prob No Conv':>15} {'Prob Conv':>12}")
print("-" * 70)

# Mostrar primeros 10 ejemplos
for i in range(10):
    real = y_val[i]
    pred = y_pred_val[i]
    prob_no = y_pred_proba[i, 0]
    prob_si = y_pred_proba[i, 1]

    marker = "" if real == pred else ""
    print(f"{real:>6} {pred:>10} {prob_no:>15.4f} {prob_si:>12.4f}  {marker}")
Enter fullscreen mode Exit fullscreen mode

Hacer Predicción para Nuevo Lead

# Ejemplo: Predecir para un nuevo lead
nuevo_lead = {
    'lead_source': 'referral',
    'industry': 'technology',
    'number_of_courses_viewed': 5,
    'annual_income': 75000.0,
    'employment_status': 'employed',
    'location': 'north_america',
    'interaction_count': 4,
    'lead_score': 0.85
}

# Transformar con el mismo DictVectorizer
nuevo_lead_encoded = dv.transform([nuevo_lead])

# Predecir
pred_class = model.predict(nuevo_lead_encoded)[0]
pred_proba = model.predict_proba(nuevo_lead_encoded)[0]

print(f"\nPREDICCIÓN PARA NUEVO LEAD")
print("=" * 60)
print(f"Datos del lead:")
for key, value in nuevo_lead.items():
    print(f"  {key}: {value}")

print(f"\nPredicción:")
print(f"  Clase predicha: {'Convertirá' if pred_class == 1 else 'No convertirá'}")
print(f"  Probabilidad de conversión: {pred_proba[1]:.2%}")
print(f"  Confianza: {'Alta' if max(pred_proba) > 0.8 else 'Media' if max(pred_proba) > 0.6 else 'Baja'}")
Enter fullscreen mode Exit fullscreen mode

📝 Código Completo para Referencia

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction import DictVectorizer
from sklearn.metrics import accuracy_score

# 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. División de datos
df_train_full, df_temp = train_test_split(df_clean, test_size=0.4, random_state=42)
df_val, df_test = train_test_split(df_temp, test_size=0.5, random_state=42)

y_train = df_train_full['converted'].values
y_val = df_val['converted'].values

X_train_df = df_train_full.drop('converted', axis=1)
X_val_df = df_val.drop('converted', axis=1)

# 3. One-hot encoding
dv = DictVectorizer(sparse=False)
X_train = dv.fit_transform(X_train_df.to_dict(orient='records'))
X_val = dv.transform(X_val_df.to_dict(orient='records'))

# 4. Entrenar modelo
model = LogisticRegression(solver='liblinear', C=1.0, max_iter=1000, random_state=42)
model.fit(X_train, y_train)

# 5. Evaluar
y_pred_val = model.predict(X_val)
accuracy = accuracy_score(y_val, y_pred_val)
accuracy_rounded = round(accuracy, 2)

print(f"Accuracy: {accuracy_rounded}")
Enter fullscreen mode Exit fullscreen mode

🎯 Conclusión

En este post aprendimos a:

  1. Aplicar one-hot encoding con DictVectorizer
  2. Entrenar regresión logística con parámetros específicos
  3. Evaluar el modelo con accuracy
  4. Interpretar coeficientes para entender importancia de features
  5. Hacer predicciones en nuevos datos

Resultado: Un modelo con ~85% de accuracy en validación, capaz de predecir conversiones de leads con buena precisión.

En el próximo post (MLZC25-21), exploraremos técnicas de feature elimination para identificar qué variables podemos remover sin afectar el rendimiento, simplificando nuestro modelo.


¿Qué accuracy obtuviste en tu modelo? ¿Qué features encontraste más importantes?

Top comments (0)