🎯 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
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
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
¿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
💻 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}")
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}")
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}")
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
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")
🧠 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
)
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_}")
📊 Paso 5: Evaluar el Modelo con Accuracy
¿Qué es Accuracy?
Accuracy (exactitud) es el porcentaje de predicciones correctas:
Accuracy = (Predicciones Correctas) / (Total de Predicciones)
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}")
Salida esperada:
EVALUACIÓN DEL MODELO
============================================================
Accuracy en validación: 0.849315
Accuracy en validación (redondeado): 0.8493
🎯 RESPUESTA QUESTION 4: 0.85
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}")
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")
🔍 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}")
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()
🎯 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}")
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'}")
📝 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}")
🎯 Conclusión
En este post aprendimos a:
- ✅ Aplicar one-hot encoding con DictVectorizer
- ✅ Entrenar regresión logística con parámetros específicos
- ✅ Evaluar el modelo con accuracy
- ✅ Interpretar coeficientes para entender importancia de features
- ✅ 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)