"""
Ejemplo didáctico de Regresión Logística
========================================
Objetivo:
Predecir si un correo es SPAM (1) o NO SPAM (0).
Este ejemplo está pensado para enseñar:
1. Qué es un dataset.
2. Por qué se divide en entrenamiento (train) y prueba (test).
3. Cómo aprende un modelo de regresión logística.
4. Qué significa predecir.
5. Cómo interpretar las métricas.
Dataset:
- 30 correos ficticios.
- Variables:
* num_palabras_promocionales
* num_enlaces
* porcentaje_mayusculas
* spam (objetivo)
La variable spam vale:
1 = spam
0 = no spam
"""
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (
accuracy_score,
confusion_matrix,
classification_report
)
# ------------------------------------------------------------------
# 1. DATASET
# ------------------------------------------------------------------
datos = [
[0,0,5,0],
[1,0,4,0],
[0,1,8,0],
[2,0,6,0],
[1,1,7,0],
[0,0,3,0],
[2,1,10,0],
[1,0,12,0],
[0,1,6,0],
[2,0,8,0],
[5,3,45,1],
[6,4,55,1],
[4,2,40,1],
[7,5,60,1],
[5,4,50,1],
[6,3,48,1],
[8,5,70,1],
[4,3,42,1],
[7,4,65,1],
[5,2,46,1],
[1,1,15,0],
[2,1,18,0],
[3,1,22,0],
[3,2,25,0],
[4,1,28,0],
[4,2,30,1],
[3,3,35,1],
[2,2,20,0],
[5,1,32,1],
[1,2,18,0]
]
df = pd.DataFrame(
datos,
columns=[
"num_palabras_promocionales",
"num_enlaces",
"porcentaje_mayusculas",
"spam"
]
)
print("="*70)
print("DATASET COMPLETO")
print("="*70)
print(df)
# ------------------------------------------------------------------
# 2. SEPARAR VARIABLES DE ENTRADA Y SALIDA
# ------------------------------------------------------------------
X = df[
[
"num_palabras_promocionales",
"num_enlaces",
"porcentaje_mayusculas"
]
]
y = df["spam"]
# ------------------------------------------------------------------
# 3. DIVISIÓN TRAIN / TEST
# ------------------------------------------------------------------
"""
¿Por qué dividimos los datos?
Imagina que un profesor enseña las respuestas exactas de un examen
a un alumno y luego le pone exactamente el mismo examen.
Sacar un 10 no demostraría que ha aprendido.
Con Machine Learning ocurre lo mismo.
TRAIN:
Datos que el modelo utiliza para aprender.
TEST:
Datos que el modelo NO ha visto nunca.
Si funciona bien en TEST, tenemos más confianza en que
generaliza correctamente.
"""
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.30,
random_state=42
)
print("\n")
print("="*70)
print("DIVISIÓN TRAIN / TEST")
print("="*70)
print(f"Filas para entrenamiento: {len(X_train)}")
print(f"Filas para prueba: {len(X_test)}")
print("Estos datos los hemos reservado para test")
print(X_test)
print("Y estas son los datos reales")
print(y_test)
# ------------------------------------------------------------------
# 4. ENTRENAMIENTO
# ------------------------------------------------------------------
modelo = LogisticRegression()
modelo.fit(X_train, y_train)
print("\nModelo entrenado.")
# ------------------------------------------------------------------
# 5. PREDICCIÓN
# ------------------------------------------------------------------
"""
¿Qué significa predecir?
El modelo observa las características de un correo que nunca
ha visto y estima:
P(spam)
Es decir, la probabilidad de que sea spam.
Después convierte esa probabilidad en una clase:
>= 0.5 -> spam
< 0.5 -> no spam
"""
probabilidades = modelo.predict_proba(X_test)
predicciones = modelo.predict(X_test)
print("\n")
print("="*70)
print("PREDICCIONES")
print("="*70)
for i in range(len(X_test)):
prob_no_spam = probabilidades[i][0]
prob_spam = probabilidades[i][1]
print(
f"Correo {i+1:2d} | "
f"P(No Spam)={prob_no_spam:.3f} | "
f"P(Spam)={prob_spam:.3f} | "
f"Predicción={predicciones[i]} | "
f"Real={list(y_test)[i]}"
)
print("Los datos reales vs. predichos:")
print(y_test.tolist())
print(predicciones)
# ------------------------------------------------------------------
# 6. MÉTRICAS
# ------------------------------------------------------------------
accuracy = accuracy_score(y_test, predicciones)
print("\n")
print("="*70)
print("ACCURACY")
print("="*70)
print(f"Accuracy = {accuracy:.3f}")
"""
Accuracy:
(Número de aciertos) / (Número total de casos)
Ejemplo:
Si el modelo acierta 8 de 10 correos:
Accuracy = 8/10 = 0.80 = 80%
"""
print("\n")
print("="*70)
print("MATRIZ DE CONFUSIÓN")
print("="*70)
cm = confusion_matrix(y_test, predicciones)
print(cm)
"""
Matriz de confusión:
Predijo No Spam Predijo Spam
Real No Spam VN FP
Real Spam FN VP
VN = Verdadero Negativo
FP = Falso Positivo
FN = Falso Negativo
VP = Verdadero Positivo
FP:
Correo normal marcado como spam.
FN:
Correo spam que el modelo dejó pasar.
"""
print("\n")
print("="*70)
print("CLASSIFICATION REPORT")
print("="*70)
print(classification_report(y_test, predicciones))
"""
Precision:
De todos los correos marcados como spam,
¿cuántos eran realmente spam?
Recall:
De todos los spam reales,
¿cuántos encontró el modelo?
F1:
Media armónica entre Precision y Recall.
Support:
Número de ejemplos de cada clase.
"""
# ------------------------------------------------------------------
# 7. EJEMPLO NUEVO
# ------------------------------------------------------------------
nuevo_correo = pd.DataFrame(
[[6, 4, 58]],
columns=[
"num_palabras_promocionales",
"num_enlaces",
"porcentaje_mayusculas"
]
)
prob = modelo.predict_proba(nuevo_correo)[0][1]
print("\n")
print("="*70)
print("EJEMPLO DE CORREO NUEVO")
print("="*70)
print(nuevo_correo)
print(f"Probabilidad de spam: {prob:.3f}")
print(f"Clasificación final: {modelo.predict(nuevo_correo)[0]}")
"""
Conclusión:
Entrenamiento:
El modelo aprende patrones.
Prueba:
Verificamos si esos patrones funcionan en datos nuevos.
Predicción:
Aplicamos lo aprendido a correos nunca vistos.
Métricas:
Cuantifican si el modelo está funcionando bien.
"""