Interpretación correcta
Si construimos 100 intervalos IC al 95%, ~95 contendrán el valor verdadero. No es "95% de probabilidad de que el parámetro esté en el intervalo".
Capítulo 8
Cómo cuantificar la incertidumbre de las estimaciones y tomar decisiones basadas en datos.
El rango plausible del parámetro
Un intervalo de confianza al es un rango que cubre el valor verdadero con probabilidad :
Si construimos 100 intervalos IC al 95%, ~95 contendrán el valor verdadero. No es "95% de probabilidad de que el parámetro esté en el intervalo".
Cuando σ es desconocido y n es pequeño, usamos la distribución t de Student con n-1 grados de libertad (colas más anchas).
import numpy as np
from scipy import stats
datos = np.array([1.2, 0.8, 1.5, 0.9, 1.1])
n = len(datos)
media = datos.mean()
se = stats.sem(datos) # error estándar = std / sqrt(n)
# IC 95% usando t-student
alpha = 0.05
t_crit = stats.t.ppf(1 - alpha/2, df=n-1)
ic_inf = media - t_crit * se
ic_sup = media + t_crit * se
print(f"Media: {media:.3f}")
print(f"IC 95%: [{ic_inf:.3f}, {ic_sup:.3f}]")
print(f"Interpretación: con 95% de confianza, la media poblacional")
print(f"está entre {ic_inf:.3f} y {ic_sup:.3f}")Cuando la teoría no alcanza
El bootstrap es un método no paramétrico para estimar la distribución de un estadístico mediante remuestreo con reemplazo de los datos originales.
import numpy as np
datos = np.array([1.2, 0.8, 1.5, 0.9, 1.1, 3.2, 0.7])
n = len(datos)
B = 10000
# Bootstrap para la mediana
boot_medians = []
for _ in range(B):
muestra = np.random.choice(datos, size=n, replace=True)
boot_medians.append(np.median(muestra))
boot_medians = np.array(boot_medians)
ic_inf = np.percentile(boot_medians, 2.5)
ic_sup = np.percentile(boot_medians, 97.5)
print(f"Mediana muestral: {np.median(datos):.3f}")
print(f"IC 95% (bootstrap): [{ic_inf:.3f}, {ic_sup:.3f}]")¿El efecto es real o es ruido?
Marco general:
Rechazar cuando es verdadera (falso positivo). Probabilidad = α (nivel de significancia, típicamente 0.05).
No rechazar cuando es falsa (falso negativo). Probabilidad = β. Potencia = 1 - β.
p < α → rechazamos (resultado "significativo"). p ≥ α → no podemos rechazar .
from scipy import stats
import numpy as np
# T-test: ¿la media es diferente de 0?
datos = np.array([0.5, 0.8, -0.2, 0.6, 1.1, 0.3])
t_stat, p_valor = stats.ttest_1samp(datos, popmean=0)
print(f"Estadístico t: {t_stat:.3f}")
print(f"p-valor: {p_valor:.4f}")
alpha = 0.05
if p_valor < alpha:
print("Rechazamos H₀: la media es significativamente distinta de 0")
else:
print("No podemos rechazar H₀")
# T-test de dos muestras independientes
grupo_a = np.array([2.1, 2.5, 1.9, 2.3, 2.7])
grupo_b = np.array([1.8, 1.5, 2.0, 1.6, 1.7])
t_stat2, p_valor2 = stats.ttest_ind(grupo_a, grupo_b)
print(f"
Test de dos muestras: p = {p_valor2:.4f}")Evaluando clasificadores
La curva ROC (Receiver Operating Characteristic) grafica la tasa de verdaderos positivos vs tasa de falsos positivos al variar el umbral de clasificación.
ROC: clases balanceadas. PR (Precision-Recall): clases desbalanceadas (la curva PR es más sensible a falsos positivos cuando la clase positiva es rara).
from sklearn.metrics import roc_curve, auc, precision_recall_curve
import numpy as np
# Scores de un clasificador y etiquetas reales
y_true = np.array([0, 0, 1, 1, 0, 1, 0, 1, 1, 0])
y_scores = np.array([0.1, 0.2, 0.8, 0.6, 0.3, 0.9, 0.4, 0.7, 0.5, 0.2])
# ROC
fpr, tpr, thresholds = roc_curve(y_true, y_scores)
roc_auc = auc(fpr, tpr)
print(f"AUC: {roc_auc:.3f}")
# Precision-Recall
precision, recall, _ = precision_recall_curve(y_true, y_scores)
# Interpretación del AUC
if roc_auc >= 0.9:
print("Clasificador excelente")
elif roc_auc >= 0.8:
print("Clasificador bueno")
elif roc_auc >= 0.7:
print("Clasificador aceptable")
else:
print("Clasificador deficiente")