import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, precision_score, recall_score
# Datos didácticos (reproducibles): ingreso, deuda e incumplimiento
datos = pd.DataFrame({
"ingreso": [
3144, 2173, 3390, 2922, 1173, 3729, 3107, 3180,
1272, 2206, 1975, 3588, 2767, 3286, 2186, 1559, 2508, 1085,
],
"deuda": [
1172, 914, 1081, 548, 1361, 1259, 1107, 337,
696, 138, 284, 982, 1063, 1357, 510, 569, 700, 330,
],
"incumplio": [1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1],
})
X = datos[["ingreso", "deuda"]]
y = datos["incumplio"]
modelo = LogisticRegression(random_state=42, max_iter=2000)
modelo.fit(X, y)
# Probabilidades de la clase positiva: salen DEL MODELO
prob = modelo.predict_proba(X)[:, 1]
detalle = datos.copy()
detalle["real"] = y.to_numpy()
detalle["probabilidad"] = np.round(prob, 3)
display(detalle[["ingreso", "deuda", "real", "probabilidad"]])
# El modelo y las p_i se fijan; solo cambia la regla tau
y_pred_30 = (prob >= 0.30).astype(int)
y_pred_50 = (prob >= 0.50).astype(int)
y_pred_80 = (prob >= 0.80).astype(int)
comparacion = pd.DataFrame({
"real": y.to_numpy(),
"probabilidad": np.round(prob, 3),
"pred_umbral_0.30": y_pred_30,
"pred_umbral_0.50": y_pred_50,
"pred_umbral_0.80": y_pred_80,
})
print("Misma p_i; distintas decisiones según tau:")
display(comparacion)
print(
"\nEl modelo no cambió. La probabilidad no cambió. "
"Solo cambió la regla de decisión."
)
print("El modelo estima. El umbral decide. La matriz de confusión evalúa.")
def tpr_fpr(y_true, y_pred):
tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel()
tpr = tp / (tp + fn) if (tp + fn) else 0.0
fpr = fp / (fp + tn) if (fp + tn) else 0.0
return {
"TN": int(tn),
"FP": int(fp),
"FN": int(fn),
"TP": int(tp),
"precision": precision_score(y_true, y_pred, zero_division=0),
"recall": recall_score(y_true, y_pred, zero_division=0),
"TPR": tpr,
"FPR": fpr,
}
umbrales = {"0.30": y_pred_30, "0.50": y_pred_50, "0.80": y_pred_80}
fig, axes = plt.subplots(1, 3, figsize=(12, 3.6))
filas = []
for ax, (nombre, pred) in zip(axes, umbrales.items()):
cm = confusion_matrix(y, pred, labels=[0, 1])
sns.heatmap(
cm,
annot=True,
fmt="d",
cmap="Blues",
ax=ax,
cbar=False,
xticklabels=["Pred. 0", "Pred. 1"],
yticklabels=["Real 0", "Real 1"],
)
ax.set_title(f"Matriz (umbral = {nombre})")
m = tpr_fpr(y, pred)
m["umbral"] = nombre
filas.append(m)
plt.tight_layout()
plt.show()
resumen = pd.DataFrame(filas)[
["umbral", "TN", "FP", "FN", "TP", "precision", "recall", "FPR", "TPR"]
]
print("Cada umbral: una matriz, un par (FPR, TPR) que sería un punto de la ROC.")
display(resumen.round(3))