Estadística para IA — 9. Inferencia estadística

estadistica
inferencia
intervalos
Estimación puntual, error estándar, intervalos de confianza, pruebas de hipótesis, errores tipo I y II, p-value y un caso A/B de latencia.
Author

Wilder Ramírez Delgado

Published

September 25, 2026

Sesión 9 — Inferencia Estadística

Open in Colab

👋 Sobre el autor

Wilder Ramírez Delgado es Científico de Datos, Arquitecto de IA, Ingeniero Electrónico y Magíster en Analítica de Datos. CEO y fundador de Business Innovation Technology (BIT), consultor y docente universitario, trabaja en la intersección entre Data Science, Inteligencia Artificial, Big Data e IoT, transformando problemas reales en soluciones aplicadas.

De la teoría a la práctica, un problema a la vez.

De la muestra a la población, con margen

Una media de 47 filas no es la media de todos los usuarios. Es un estadístico: un número calculado en la muestra. El parámetro —el número de la población— no se ve. La inferencia es el aparato que permite hablar del parámetro y declarar la incertidumbre.

Este notebook construye ese aparato: estimación puntual, error estándar, intervalo de confianza, prueba de hipótesis, nivel de significancia, p-value y los errores tipo I y tipo II. La práctica es simular: se fabrica una población, se extraen muestras y se ve qué cubre el intervalo y qué rechaza la prueba. El contraste que importa es operativo y frecuente en producto: un efecto minúsculo con \(n\) enorme sale “significativo”; un efecto útil con \(n\) chico, no.

Contexto: latencia de un servicio de inferencia (milisegundos) y un A/B entre dos versiones del modelo. Los datos son sintéticos y diseñados.

Electiva I — Estadística Aplicada a la Inteligencia Artificial · Módulo 3 · Sesión 9

Objetivos

  • Distinguir parámetro, estadístico y distribución muestral.
  • Calcular una estimación puntual y su error estándar.
  • Construir e interpretar un intervalo de confianza para la media (y ver qué significa “95%”).
  • Plantear \(H_0\) y \(H_1\), fijar \(\alpha\) y calcular el p-value con SciPy.
  • Estimar por simulación las tasas de error tipo I y tipo II, y la potencia.
  • No confundir significancia estadística con relevancia práctica.
  • Redactar un reporte de inferencia (estimación, intervalo, prueba, decisión, limitación).

Nota: se recomienda ejecutar las celdas en orden. Las fórmulas usan LaTeX; si no se ven renderizadas, abre el notebook en Jupyter o Colab.

1. El lenguaje de la inferencia

1.1 Parámetro y estadístico

El parámetro \(\theta\) describe la población (media \(\mu\), proporción \(p\), diferencia de medias). No se observa: se estima.

El estadístico \(\hat\theta\) se calcula en la muestra: \(\bar x\), \(\hat p\), \(\bar x_B-\bar x_A\). Cambia si cambia la muestra. Por eso \(\hat\theta\) es una variable aleatoria.

1.2 Distribución muestral

La distribución muestral de \(\hat\theta\) es la distribución de ese estadístico si el muestreo se repitiera. Su centro suele estar cerca de \(\theta\) (si el estimador es insesgado). Su dispersión baja cuando \(n\) crece.

Esa dispersión se resume con el error estándar (SE): la desviación estándar de \(\hat\theta\), no la de una observación. Para la media, si las observaciones son independientes y \(s\) estima \(\sigma\),

\[ \mathrm{SE}(\bar x)=\frac{s}{\sqrt n}. \]

\(s\) mide cómo varían las filas. El SE mide cómo varía la media de una muestra a otra.

1.3 Estimación puntual

Un número: \(\hat\theta=\bar x\). Es el mejor resumen puntual, y es incompleto. Sin SE no se sabe si \(\bar x=201\) con \(n=8\) o con \(n=8000\) son el mismo tipo de afirmación.

1.4 Intervalo de confianza

Un intervalo de confianza de nivel \(1-\alpha\) es un rango aleatorio que, en repeticiones del muestreo, cubre el parámetro en una fracción \(1-\alpha\) de las veces. Para la media, con \(\sigma\) desconocida y muestra aproximadamente normal (o \(n\) no demasiado chico),

\[ \bar x \;\pm\; t_{n-1,\,1-\alpha/2}\,\frac{s}{\sqrt n}. \]

\(t_{n-1,1-\alpha/2}\) es el cuantil de la t de Student con \(n-1\) grados de libertad. Si \(\sigma\) se conociera, se usaría \(z_{1-\alpha/2}\) de la normal estándar (1.96 cuando \(\alpha=0.05\)). En la práctica \(\sigma\) no se conoce: se usa \(t\).

Lectura correcta (frecuentista): el procedimiento cubre \(\mu\) el 95% de las veces. Este intervalo ya calculado o cubre a \(\mu\) o no; \(\mu\) no es aleatorio. Decir “hay 95% de probabilidad de que \(\mu\) esté aquí” mezcla el procedimiento con el número ya observado.

1.5 Prueba de hipótesis

Se contrastan dos afirmaciones sobre el parámetro:

  • \(H_0\): afirmación de ningún efecto o valor de referencia (por ejemplo \(\mu=200\), o \(\mu_B=\mu_A\)).
  • \(H_1\): la alternativa (bilateral \(\mu\neq 200\), o unilateral \(\mu<200\)).

El estadístico de prueba mide cuánto se aleja la muestra de \(H_0\) en unidades de error estándar. Para la media,

\[ t=\frac{\bar x-\mu_0}{s/\sqrt n}. \]

1.6 Nivel de significancia y p-value

El nivel de significancia \(\alpha\) es la probabilidad, si \(H_0\) es cierta, de rechazarla por azar. Es una tasa de error que se elige (0.05 es costumbre, no teorema).

El p-value es la probabilidad, bajo \(H_0\), de obtener un estadístico tan extremo o más que el observado. No es \(P(H_0\mid \text{datos})\). No es “qué tan probable es el efecto”. Es un área en la cola de la distribución suponiendo que no hay efecto.

Regla operativa: si \(\text{p-value}\le\alpha\), se rechaza \(H_0\) al nivel \(\alpha\). Si no, no se rechaza. No rechazar no es “demostrar \(H_0\)”.

1.7 Errores tipo I, tipo II y potencia

Error Qué ocurre Control
Tipo I Se rechaza \(H_0\) siendo cierta Su tasa, si \(H_0\) es cierta, es \(\alpha\)
Tipo II No se rechaza \(H_0\) siendo falsa Su tasa se llama \(\beta\)

La potencia es \(1-\beta\): probabilidad de detectar el efecto cuando existe. Sube con \(n\), con el tamaño del efecto y con \(\sigma\) más chica; baja si \(\alpha\) se hace más estricta.

1.8 Significativo no es importante

“Significativo” en esta jerga significa el azar, bajo \(H_0\), rara vez produce algo tan extremo. No significa grande, útil ni desplegable a producción. Con \(n\) enorme, un efecto de 0.2 ms puede tener p-value \(10^{-8}\). Con \(n\) chico, un efecto de 15 ms puede no rechazar.

Pregunta Herramienta
¿Qué número de la muestra uso para \(\theta\)? Estimación puntual
¿Cuánto tiembla ese número entre muestras? Error estándar
¿Qué rangos produce el procedimiento? Intervalo de confianza
¿Los datos son raros si \(H_0\) es cierta? p-value / prueba
¿Cuánto error de falsa alarma acepto? \(\alpha\)
¿Detectaría este efecto si existiera? Potencia

2. Por qué importa en IA y Machine Learning

El modelo en validación entrega números de muestra: accuracy 0.81, latencia 118 ms, lift de 1.4%. Sin inferencia esos números se leen como propiedades del sistema. Son propiedades de estas filas.

  • Un A/B de producto es una prueba de hipótesis sobre \(\mu_B-\mu_A\) o \(p_B-p_A\). El p-value no decide el despliegue: informa si el azar basta para explicar la diferencia.
  • El intervalo dice el rango plausible del efecto. Un p pequeño con intervalo \((0.001,\,0.004)\) en conversión no es el mismo hallazgo que \((0.04,\,0.11)\).
  • En selección de modelos, comparar dos accuracies con \(n=80\) en test y declarar un ganador es, en silencio, una inferencia mal hecha.
  • \(\alpha\) y la potencia son costos: un falso positivo despliega un cambio inútil; un falso negativo mata un cambio bueno.

Idea central: el p-value responde a \(H_0\), no a “¿vale la pena?”. El intervalo y el tamaño del efecto contestan la segunda pregunta.

3. Flujo recomendado de trabajo

  1. Escribir el parámetro y la población (¿media de latencia? ¿diferencia de conversión?).
  2. Estimar \(\hat\theta\) y el SE.
  3. Intervalo de nivel declarado (casi siempre 95%) e interpretarlo como cobertura del procedimiento.
  4. Prueba: \(H_0\), \(H_1\), \(\alpha\), estadístico, p-value. No invertir el condicional.
  5. Mirar \(n\) y el efecto: ¿la significancia viene del tamaño de la muestra o de un cambio que importa?
  6. Decidir con costos, no solo con \(p<0.05\).

Criterio general: un p-value sin intervalo, sin \(n\) y sin unidades del efecto es un sello, no un argumento.

4. Preparar el entorno

%matplotlib inline
import sys
print("Python:", sys.version.split()[0])
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats

rng = np.random.default_rng(42)

plt.rcParams["figure.figsize"] = (8, 5)
plt.rcParams["axes.grid"] = True
plt.rcParams["grid.alpha"] = 0.25

print("Entorno listo: numpy, pandas, matplotlib, scipy.")
print("scipy:", __import__("scipy").__version__, "| numpy:", np.__version__)
Python: 3.13.9
Entorno listo: numpy, pandas, matplotlib, scipy.
scipy: 1.16.3 | numpy: 2.3.5

Funciones de visualización (definidas una sola vez)

Cada etapa solo llama a la función. El detalle del gráfico queda aquí.

def graficar_dist_muestral(medias_nchico, medias_ngrande, mu, n_chico, n_grande, titulo):
    fig, ax = plt.subplots(figsize=(8.4, 4.3))
    ax.hist(medias_nchico, bins=28, density=True, alpha=0.55, label=f"n = {n_chico}")
    ax.hist(medias_ngrande, bins=28, density=True, alpha=0.55, label=f"n = {n_grande}")
    ax.axvline(mu, color="C3", linestyle="--", label=fr"$\mu$ = {mu:g}")
    ax.set_xlabel("media muestral")
    ax.set_ylabel("densidad")
    ax.set_title(titulo)
    ax.legend(fontsize=8)
    plt.tight_layout()
    plt.show()


def graficar_una_muestra(muestra, mu, low, high, titulo):
    fig, ax = plt.subplots(figsize=(8.2, 3.6))
    ax.scatter(muestra, np.zeros_like(muestra), alpha=0.45, s=28, label="observaciones")
    ax.axvline(mu, color="C3", linestyle="--", label=fr"$\mu$ = {mu:g}")
    ax.axvline(muestra.mean(), color="C0", label=fr"$\bar x$ = {muestra.mean():.2f}")
    ax.hlines(0.35, low, high, color="C0", linewidth=4, label="IC 95%")
    ax.set_yticks([])
    ax.set_xlabel("latencia (ms)")
    ax.set_title(titulo)
    ax.legend(fontsize=8, loc="upper right")
    plt.tight_layout()
    plt.show()


def graficar_cobertura(intervalos, mu, titulo):
    fig, ax = plt.subplots(figsize=(8.4, 5.2))
    cubre = 0
    for i, (lo, hi) in enumerate(intervalos):
        ok = lo <= mu <= hi
        cubre += int(ok)
        ax.hlines(i, lo, hi, color="C0" if ok else "C3", linewidth=1.1)
    ax.axvline(mu, color="0.2", linestyle="--", label=fr"$\mu$ = {mu:g}")
    ax.set_xlabel("intervalo de confianza 95% para la media")
    ax.set_ylabel("réplica")
    ax.set_title(titulo)
    ax.legend(fontsize=8)
    plt.tight_layout()
    plt.show()
    return cubre


def graficar_pvalores(p_h0, p_h1, titulo):
    fig, ax = plt.subplots(figsize=(8.2, 4.2))
    ax.hist(p_h0, bins=20, range=(0, 1), density=True, alpha=0.55, label="bajo H0 (debe verse plano)")
    ax.hist(p_h1, bins=20, range=(0, 1), density=True, alpha=0.55, label="bajo H1 (se acumula a la izquierda)")
    ax.axvline(0.05, color="C3", linestyle="--", label=r"$\alpha$ = 0.05")
    ax.set_xlabel("p-value")
    ax.set_ylabel("densidad")
    ax.set_title(titulo)
    ax.legend(fontsize=8)
    plt.tight_layout()
    plt.show()


def graficar_tipo_i_ii(mu0, mu1, se, alpha, titulo):
    z = stats.norm.ppf(1 - alpha / 2)
    crit_lo, crit_hi = mu0 - z * se, mu0 + z * se
    xs = np.linspace(mu0 - 4.5 * se, mu1 + 4.5 * se, 400)
    f0 = stats.norm.pdf(xs, mu0, se)
    f1 = stats.norm.pdf(xs, mu1, se)
    fig, ax = plt.subplots(figsize=(8.6, 4.4))
    ax.plot(xs, f0, label=fr"muestral si $H_0$ ($\mu$={mu0:g})")
    ax.plot(xs, f1, label=fr"muestral si $H_1$ ($\mu$={mu1:g})")
    ax.axvline(crit_lo, color="0.35", linestyle=":")
    ax.axvline(crit_hi, color="0.35", linestyle=":", label="umbrales de rechazo")
    ax.fill_between(xs, f0, where=(xs <= crit_lo) | (xs >= crit_hi), alpha=0.35, color="C3", label="tipo I (rechazar H0 cierta)")
    ax.fill_between(xs, f1, where=(xs > crit_lo) & (xs < crit_hi), alpha=0.35, color="C1", label="tipo II (no rechazar H0 falsa)")
    ax.set_xlabel(r"media muestral $\bar x$")
    ax.set_ylabel("densidad")
    ax.set_title(titulo)
    ax.legend(fontsize=7, loc="upper right")
    plt.tight_layout()
    plt.show()
    return crit_lo, crit_hi


def graficar_ab(a, b, titulo):
    fig, ax = plt.subplots(figsize=(8.0, 4.2))
    ax.boxplot([a, b], tick_labels=["A (actual)", "B (cuantizado)"], showfliers=False)
    ax.scatter(np.ones(len(a)) + rng.normal(0, 0.04, len(a)), a, s=10, alpha=0.25)
    ax.scatter(np.full(len(b), 2) + rng.normal(0, 0.04, len(b)), b, s=10, alpha=0.25)
    ax.set_ylabel("latencia (ms)")
    ax.set_title(titulo)
    plt.tight_layout()
    plt.show()

5. Desarrollo por etapas

Se fabrica una población de latencias con \(\mu\) conocida. Así se puede ver si el intervalo cubre y si la prueba se equivoca. Después se pasa a un A/B donde \(\mu\) ya no se “conoce” como en el laboratorio: se estima y se reporta.

Etapa 1: Población, muestra y estadístico

Población de \(N=20\,000\) latencias (ms), \(\mu=200\), \(\sigma=40\). Se extrae una muestra de \(n=25\). El parámetro no cambia; \(\bar x\) sí.

mu, sigma, N = 200.0, 40.0, 20_000
poblacion = rng.normal(mu, sigma, size=N)
print("Parámetro μ (población) =", round(poblacion.mean(), 3), "  (diseño: 200)")
print("σ poblacional =", round(poblacion.std(ddof=0), 3))

n = 25
muestra = rng.choice(poblacion, size=n, replace=False)
print()
print("n =", n)
print("x̄  =", round(muestra.mean(), 3), "  ← estadístico, no es μ")
print("s  =", round(muestra.std(ddof=1), 3), "  ← estima σ")
print("La muestra no 'es' la población: x̄ − μ =", round(muestra.mean() - poblacion.mean(), 3))
Parámetro μ (población) = 200.202   (diseño: 200)
σ poblacional = 40.188

n = 25
x̄  = 194.439   ← estadístico, no es μ
s  = 35.769   ← estima σ
La muestra no 'es' la población: x̄ − μ = -5.763

Lectura. \(\bar x\) falló por unos milisegundos. Eso no es un error de código: es el muestreo. Inferir es cuantificar ese fallo, no negarlo.

Etapa 2: Distribución muestral — el estadístico también tiene forma

Se repite el muestreo \(4\,000\) veces con \(n=10\) y con \(n=80\). El histograma de las \(\bar x\) no es el histograma de las latencias: es la distribución de la media.

def medias_muestrales(n, n_rep=4000):
    idx = rng.integers(0, N, size=(n_rep, n))
    return poblacion[idx].mean(axis=1)

medias_10 = medias_muestrales(10)
medias_80 = medias_muestrales(80)
print("n=10:  media de x̄ =", round(medias_10.mean(), 3), "  DE de x̄ =", round(medias_10.std(ddof=1), 3), "  (σ/√n =", round(sigma / np.sqrt(10), 3), ")")
print("n=80:  media de x̄ =", round(medias_80.mean(), 3), "  DE de x̄ =", round(medias_80.std(ddof=1), 3), "  (σ/√n =", round(sigma / np.sqrt(80), 3), ")")

graficar_dist_muestral(
    medias_10, medias_80, mu, 10, 80,
    "La media se concentra alrededor de μ; n grande estrecha la campana",
)
n=10:  media de x̄ = 199.842   DE de x̄ = 12.701   (σ/√n = 12.649 )
n=80:  media de x̄ = 200.062   DE de x̄ = 4.492   (σ/√n = 4.472 )

Después. Las dos campanas están centradas en \(200\). La de \(n=80\) es más angosta: \(\sigma/\sqrt n\) cayó. Esa angostura es el error estándar cuando \(\sigma\) se conoce. El histograma de una sola muestra de latencias no muestra esto; hace falta el experimento repetido (aquí, simulado).

Etapa 3: Estimación puntual y error estándar

Para la muestra de \(n=25\),

\[ \hat\mu=\bar x,\qquad \mathrm{SE}=\frac{s}{\sqrt n}. \]

El SE no es “el error de cada medición”. Es el error típico de la media.

xbar = muestra.mean()
s = muestra.std(ddof=1)
se = s / np.sqrt(n)
print("Estimación puntual μ̂ = x̄ =", round(xbar, 3))
print("s =", round(s, 3))
print("SE = s/√n =", round(se, 3), "   (si se usara σ: ", round(sigma / np.sqrt(n), 3), ")")
print()
print(f"Un intervalo grosero x̄ ± 2·SE = [{xbar - 2 * se:.2f}, {xbar + 2 * se:.2f}]")
print("Eso todavía no es el IC con t; solo ubica la escala del temblor.")
Estimación puntual μ̂ = x̄ = 194.439
s = 35.769
SE = s/√n = 7.154    (si se usara σ:  8.0 )

Un intervalo grosero x̄ ± 2·SE = [180.13, 208.75]
Eso todavía no es el IC con t; solo ubica la escala del temblor.

Lectura. Reportar \(\bar x=194.4\) sin SE invita a leer 194.4 como si fuera \(\mu\). Con \(\mathrm{SE}\approx 7.2\), 194.4 y 200 son compatibles: están a menos de un error estándar. El número suelto sobrestima la precisión.

Etapa 4: Intervalo de confianza para la media

Con \(\sigma\) desconocida se usa la \(t\) de Student, \(n-1\) grados de libertad, \(\alpha=0.05\):

\[ \bar x \pm t_{24,\,0.975}\,\mathrm{SE}. \]

SciPy lo calcula con stats.t.interval o a mano. Las dos vías deben coincidir.

alpha = 0.05
gl = n - 1
t_crit = stats.t.ppf(1 - alpha / 2, df=gl)
low = xbar - t_crit * se
high = xbar + t_crit * se
low_sp, high_sp = stats.t.interval(1 - alpha, df=gl, loc=xbar, scale=se)

print("t crítico (gl=24, 97.5%) =", round(t_crit, 4), "  (z sería 1.960)")
print("IC 95% a mano:   [", round(low, 2), ",", round(high, 2), "]")
print("IC 95% SciPy:    [", round(low_sp, 2), ",", round(high_sp, 2), "]")
print("¿Cubre μ = 200?", low <= mu <= high)
print("Anchura =", round(high - low, 2), "ms")

graficar_una_muestra(
    muestra, mu, low, high,
    "Una muestra, su media y el IC 95% (t). μ puede quedar dentro o fuera",
)
t crítico (gl=24, 97.5%) = 2.0639   (z sería 1.960)
IC 95% a mano:   [ 179.67 , 209.2 ]
IC 95% SciPy:    [ 179.67 , 209.2 ]
¿Cubre μ = 200? True
Anchura = 29.53 ms

Lectura. El intervalo es un rango para \(\mu\), no para “el 95% de las latencias”. El 95% de las observaciones vive cerca de \(\mu\pm 2\sigma\) (unos \(\pm 80\) ms), un intervalo mucho más ancho. Confundir IC de la media con rango de los datos es un error de denominador: se está hablando del centro, no de cada fila.

Etapa 5: Qué significa el 95% — cobertura

Se construyen \(100\) intervalos, cada uno con su propia muestra de \(n=25\). En teoría, alrededor de 95 cubren \(\mu\). Los que fallan (en rojo) no son “intervalos mal calculados”: son el 5% que el procedimiento acepta perder.

n_rep = 100
intervalos = []
for _ in range(n_rep):
    m = rng.choice(poblacion, size=n, replace=False)
    xb, ss = m.mean(), m.std(ddof=1)
    lo, hi = stats.t.interval(0.95, df=n - 1, loc=xb, scale=ss / np.sqrt(n))
    intervalos.append((lo, hi))

cubre = graficar_cobertura(
    intervalos, mu,
    "100 intervalos al 95%: los rojos no cubren μ (eso debe pasar ~5 veces)",
)
print("Intervalos que cubren μ:", cubre, "de", n_rep)
print("Cobertura empírica:", cubre / n_rep)
print("No es 'este intervalo tiene P=0.95'. Es 'el método acierta ~95 de cada 100'.")

Intervalos que cubren μ: 98 de 100
Cobertura empírica: 0.98
No es 'este intervalo tiene P=0.95'. Es 'el método acierta ~95 de cada 100'.

El contraste de lectura. Tras ver la figura, “IC 95% = \((a,b)\)” se redacta así: un procedimiento que, usado una y otra vez, cubre \(\mu\) en el 95% de las muestras produjo este rango. No: \(\mu\) es aleatorio y vive aquí con probabilidad 0.95. Esa segunda frase es otra escuela (se trata en el enfoque bayesiano). Aquí \(\mu\) es fijo; el intervalo es lo que se mueve.

Etapa 6: Prueba de hipótesis — \(H_0\): \(\mu=200\)

Misma muestra de la etapa 1. ¿Es compatible con un SLA de 200 ms?

\[ H_0:\mu=200,\qquad H_1:\mu\neq 200,\qquad \alpha=0.05. \]

\[ t=\frac{\bar x-200}{s/\sqrt n},\qquad \text{p-value}=P\big(|T_{24}|\ge |t|\big). \]

mu0 = 200.0
t_obs = (xbar - mu0) / se
p_manual = 2 * stats.t.sf(abs(t_obs), df=gl)
tt = stats.ttest_1samp(muestra, popmean=mu0)

print("t observado =", round(t_obs, 4))
print("p-value a mano =", round(p_manual, 4))
print("SciPy ttest_1samp: t =", round(tt.statistic, 4), "  p =", round(tt.pvalue, 4))
print("α = 0.05. ¿Rechazar H0?", tt.pvalue < 0.05)
print()
print("IC 95% y prueba bilateral a α=0.05 son la misma frontera:")
print("  200 está en el IC  ↔  p > 0.05  →", (low <= mu0 <= high), "y p =", round(tt.pvalue, 4))
t observado = -0.7774
p-value a mano = 0.4445
SciPy ttest_1samp: t = -0.7774   p = 0.4445
α = 0.05. ¿Rechazar H0? False

IC 95% y prueba bilateral a α=0.05 son la misma frontera:
  200 está en el IC  ↔  p > 0.05  → True y p = 0.4445

Lectura. Aquí no se rechaza \(H_0\) (\(p=0.44\)): una media muestral de 194.4 es perfectamente compatible con \(\mu=200\). Eso tampoco demuestra que \(\mu\) sea 200: el IC incluye todo el rango de 179.7 a 209.2 ms. Y si se hubiera rechazado, tampoco probaría que \(\mu\) sea \(\bar x\): solo diría que 200 queda incómodo como explicación. En la prueba bilateral a \(\alpha=0.05\), \(H_0\) se rechaza si y solo si \(\mu_0\) queda fuera del IC 95%.

Etapa 7: El p-value no es \(P(H_0\mid \text{datos})\)

Bajo \(H_0\) (datos con \(\mu=200\)), el p-value de una prueba bilateral bien calibrada se reparte casi uniforme en \((0,1)\). Por eso el 5% de las réplicas tiene \(p\le 0.05\) aunque \(H_0\) sea cierta: esa es la definición de \(\alpha\).

Bajo \(H_1\) (aquí \(\mu=220\)), los p-values se acumulan cerca de 0.

def pvalores_unamuestra(mu_true, n=25, n_rep=3000, mu0=200.0):
    datos = rng.normal(mu_true, sigma, size=(n_rep, n))
    return stats.ttest_1samp(datos, popmean=mu0, axis=1).pvalue

p_h0 = pvalores_unamuestra(200.0)
p_h1 = pvalores_unamuestra(220.0)
print("Bajo H0:  P(p ≤ 0.05) =", round((p_h0 <= 0.05).mean(), 4), "  ← debe ≈ α = 0.05")
print("Bajo H0:  media de p  =", round(p_h0.mean(), 3), "  ← uniforme ⇒ cerca de 0.50")
print("Bajo H1 (μ=220): P(p ≤ 0.05) =", round((p_h1 <= 0.05).mean(), 4), "  ← potencia")

graficar_pvalores(
    p_h0, p_h1,
    "Si H0 es cierta el p-value no se pega a cero: se reparte en [0, 1]",
)
Bajo H0:  P(p ≤ 0.05) = 0.0417   ← debe ≈ α = 0.05
Bajo H0:  media de p  = 0.502   ← uniforme ⇒ cerca de 0.50
Bajo H1 (μ=220): P(p ≤ 0.05) = 0.669   ← potencia

Qué no es el p-value. No es la probabilidad de que el SLA se cumpla. No es \(1-\text{confianza}\). No es el tamaño del efecto. Es un área de cola condicional a \(H_0\). Invertirlo —leer \(p=0.03\) como “hay 3% de probabilidad de que no haya efecto”— es el mismo error que \(P(E\mid +)\) versus \(P(+\mid E)\).

Etapa 8: Tipo I, tipo II y potencia

Se usa \(\sigma\) conocida solo para dibujar el argumento (normal, umbrales \(\pm z_{1-\alpha/2}\,\mathrm{SE}\)). \(H_0:\mu=200\). La alternativa dibujada es \(\mu=215\). \(n=30\), \(\alpha=0.05\).

  • Área roja bajo la campana de \(H_0\) y fuera de los umbrales: tipo I.
  • Área naranja bajo la campana de \(H_1\) y entre los umbrales: tipo II.
n_pot, mu1, alpha = 30, 215.0, 0.05
se_con = sigma / np.sqrt(n_pot)
crit_lo, crit_hi = graficar_tipo_i_ii(
    200.0, mu1, se_con, alpha,
    "Tipo I (falsa alarma) y tipo II (efecto que se escapa), n=30",
)
z = stats.norm.ppf(1 - alpha / 2)
# Tipo I teórico (bilateral, H0 cierta) = alpha
# Tipo II: P(crit_lo < Xbar < crit_hi | μ=215)
beta = stats.norm.cdf(crit_hi, loc=mu1, scale=se_con) - stats.norm.cdf(crit_lo, loc=mu1, scale=se_con)
print("SE (σ conocida) =", round(se_con, 3))
print("Umbrales de x̄: [", round(crit_lo, 2), ",", round(crit_hi, 2), "]")
print("α (tipo I, teoría) =", alpha)
print("β (tipo II, μ=215) =", round(beta, 3), "  potencia =", round(1 - beta, 3))

# Simulación t (σ desconocida), más realista
def tasa_rechazo(mu_true, n=30, n_rep=4000, mu0=200.0, alpha=0.05):
    x = rng.normal(mu_true, sigma, size=(n_rep, n))
    p = stats.ttest_1samp(x, popmean=mu0, axis=1).pvalue
    return (p <= alpha).mean()

print()
print("Simulación t, n=30:  P(rechazar | H0) =", round(tasa_rechazo(200.0), 4), "  ← tipo I ≈ 0.05")
print("Simulación t, n=30:  P(rechazar | μ=215) =", round(tasa_rechazo(215.0), 4), "  ← potencia")
print("Simulación t, n=80:  P(rechazar | μ=215) =", round(tasa_rechazo(215.0, n=80), 4), "  ← más n, más potencia")

SE (σ conocida) = 7.303
Umbrales de x̄: [ 185.69 , 214.31 ]
α (tipo I, teoría) = 0.05
β (tipo II, μ=215) = 0.463   potencia = 0.537

Simulación t, n=30:  P(rechazar | H0) = 0.046   ← tipo I ≈ 0.05
Simulación t, n=30:  P(rechazar | μ=215) = 0.4985   ← potencia
Simulación t, n=80:  P(rechazar | μ=215) = 0.9132   ← más n, más potencia

Lectura. Bajar \(\alpha\) (pedir \(p<0.01\)) encoge la zona roja y agranda la naranja: menos falsas alarmas, más efectos perdidos. Subir \(n\) estrecha las dos campanas y aumenta la potencia sin tocar \(\alpha\). En un A/B, “no salió significativo” puede ser \(\beta\) alto, no ausencia de efecto.

Etapa 9: El contraste que importa — \(n\) grande no hace importante el efecto

Dos experimentos con el mismo efecto verdadero \(\delta=0.4\) ms (irrelevante para un servicio de ~200 ms) y \(\sigma=8\).

  • Experimento chico: \(n=20\).
  • Experimento enorme: \(n=4\,000\).

La pregunta no es cuál tiene razón. Es qué afirma cada p-value.

delta, sigma_c = 0.4, 8.0

def experimento(n, delta=delta, sigma=sigma_c):
    a = rng.normal(200.0, sigma, size=n)
    b = rng.normal(200.0 + delta, sigma, size=n)
    tt = stats.ttest_ind(a, b, equal_var=True)
    dif = b.mean() - a.mean()
    se_d = np.sqrt(a.var(ddof=1) / n + b.var(ddof=1) / n)
    lo, hi = stats.t.interval(0.95, df=2 * n - 2, loc=dif, scale=se_d)
    return {
        "n": n,
        "dif": dif,
        "p": tt.pvalue,
        "lo": lo,
        "hi": hi,
        "rechaza": tt.pvalue < 0.05,
    }

e_chico = experimento(20)
e_grande = experimento(4000)
tab = pd.DataFrame([e_chico, e_grande]).round(4)
print(tab.to_string(index=False))
print()
print("Efecto de diseño δ = 0.4 ms (el usuario no lo percibe).")
print("n=20:   rechaza H0 (igualdad de medias) =", e_chico["rechaza"])
print("n=4000: rechaza H0 =", e_grande["rechaza"], "  pero el IC sigue siendo de décimas de ms.")
   n    dif      p      lo     hi  rechaza
  20 2.2581 0.3852 -2.9451 7.4612    False
4000 0.6133 0.0006  0.2636 0.9629     True

Efecto de diseño δ = 0.4 ms (el usuario no lo percibe).
n=20:   rechaza H0 (igualdad de medias) = False
n=4000: rechaza H0 = True   pero el IC sigue siendo de décimas de ms.

El contraste. Con \(n=4\,000\) el azar ya no explica \(0.4\) ms: el p-value cae y se “gana” el A/B. El intervalo, si está bien leído, delata que el ganador es minúsculo. Desplegar B por \(p<0.05\) sería adorar el sello. El número que decide es el efecto en milisegundos (y su costo), no la estrella al lado del p.

Etapa 10: A/B de un servicio de inferencia

Versión A: modelo actual. Versión B: modelo cuantizado. \(n=90\) latencias por rama, diseñadas con

  • \(\mu_A=120\) ms, \(\mu_B=108\) ms, \(\sigma=18\) ms.

Aquí el efecto (12 ms) sí importa para experiencia y costo. SciPy: ttest_ind.

n_ab = 90
lat_a = rng.normal(120, 18, size=n_ab)
lat_b = rng.normal(108, 18, size=n_ab)

tt_ab = stats.ttest_ind(lat_b, lat_a, equal_var=True)   # B − A: mismo signo que la diferencia
dif_ab = lat_b.mean() - lat_a.mean()
se_ab = np.sqrt(lat_a.var(ddof=1) / n_ab + lat_b.var(ddof=1) / n_ab)
lo_ab, hi_ab = stats.t.interval(0.95, df=2 * n_ab - 2, loc=dif_ab, scale=se_ab)

print("A: x̄ =", round(lat_a.mean(), 2), "  s =", round(lat_a.std(ddof=1), 2))
print("B: x̄ =", round(lat_b.mean(), 2), "  s =", round(lat_b.std(ddof=1), 2))
print("B − A =", round(dif_ab, 2), "ms   SE =", round(se_ab, 2))
print("IC 95% para μB − μA: [", round(lo_ab, 2), ",", round(hi_ab, 2), "]")
print("t =", round(tt_ab.statistic, 3), "  p =", float(f"{tt_ab.pvalue:.2e}"))
print("¿Rechazar igualdad de medias a α=0.05?", tt_ab.pvalue < 0.05)

graficar_ab(lat_a, lat_b, "A/B de latencia: B está corrido hacia abajo (n=90 por rama)")
A: x̄ = 118.61   s = 16.78
B: x̄ = 107.85   s = 18.47
B − A = -10.76 ms   SE = 2.63
IC 95% para μB − μA: [ -15.95 , -5.57 ]
t = -4.091   p = 6.5e-05
¿Rechazar igualdad de medias a α=0.05? True

Traducción. \(H_0\): las dos versiones tienen la misma media. Se rechaza. El IC de \(\mu_B-\mu_A\) queda por debajo de 0 y su orden de magnitud es de varios milisegundos, no de décimas. A diferencia de la etapa 9, aquí coinciden significancia y relevancia. Eso no siempre pasa: por eso se reportan las dos.

Caso aplicado: reportar una inferencia

Plantilla mínima:

  1. Población y parámetro. Qué se mide, en quién, qué \(\theta\) se discute.
  2. Diseño. \(n\), grupos, \(\alpha\), si la prueba es uni o bilateral.
  3. Estimación. \(\hat\theta\), SE.
  4. Intervalo. Nivel y rango, en las unidades del problema.
  5. Prueba. \(H_0\), \(H_1\), estadístico, p-value, decisión a ese \(\alpha\).
  6. Relevancia y límite. ¿El efecto importa? ¿Qué amenaza la lectura (n chico, \(\sigma\) alta, muchas pruebas)?

Se aplica al A/B de la etapa 10.

Los seis pasos, aplicados

  1. Población. Requests de un servicio de inferencia. Parámetro: \(\mu_B-\mu_A\), diferencia de latencia media (ms).
  2. Diseño. \(n=90\) por rama, asignación como dos muestras independientes, \(\alpha=0.05\), alternativa bilateral.
  3. Estimación. \(\widehat{\mu_B-\mu_A}=\bar x_B-\bar x_A=-10.76\) ms, con SE \(=2.63\) ms.
  4. Intervalo. IC 95% \(t\) para la diferencia: \([-15.95,\,-5.57]\) ms. Queda entero bajo 0.
  5. Prueba. \(H_0:\mu_B=\mu_A\) vs \(H_1:\mu_B\neq\mu_A\). ttest_ind: \(t=-4.09\), \(p=6.5\times10^{-5}\). Se rechaza \(H_0\).
  6. Relevancia. El corrimiento es del orden de 10 ms sobre una base de ~120 ms: relevante para cola p95 y costo de cómputo. Limitación: datos sintéticos, varianzas tratadas como iguales, una sola métrica, sin corrección por múltiples tests.

En un A/B de latencia con 90 observaciones por versión, la diferencia muestral \(\bar x_B-\bar x_A\) es \(-10.8\) ms (IC 95%: \(-16.0\) a \(-5.6\) ms), el intervalo no cruza 0 y se rechaza igualdad de medias a \(\alpha=0.05\). Se recomienda B por el tamaño del efecto en ms, no solo por el p-value. Limitación: una métrica, sin p95 ni costo, y \(n\) fijo de antemano (no se detuvo el test al primer \(p<0.05\)).

Errores que conviene nombrar

  1. Leer el p-value como \(P(H_0\mid \text{datos})\). Es \(P(\text{dato tan extremo}\mid H_0)\).
  2. Leer el IC 95% como probabilidad sobre \(\mu\). El 95% es cobertura del procedimiento.
  3. Confundir IC de la media con el rango del 95% de las observaciones.
  4. “No significativo = no hay efecto.” Puede ser \(n\) chico (potencia baja).
  5. “Significativo = importante.” Puede ser \(n\) enorme y \(\delta\) irrelevante.
  6. \(\alpha=0.05\) como ley. Es un costo de falsa alarma, no un teorema.
  7. Mirar \(p\) a cada minuto del A/B y parar en el primer 0.049. Infla el tipo I.

Idea clave

La inferencia declara incertidumbre; no convierte la muestra en población. El SE y el intervalo miden el temblor de \(\hat\theta\). El p-value mide la rareza bajo \(H_0\). Ninguno de los dos mide, por sí solo, si el cambio vale el despliegue.

Síntesis

Se partió de una población con \(\mu\) conocida para ver la distribución muestral, el SE, la cobertura del IC y la calibración del p-value. Se separaron tipo I, tipo II y potencia. El contraste \(n=20\) versus \(n=4\,000\) con el mismo \(\delta\) irrelevante mostró que la significancia no es magnitud. El A/B de latencia aplicó la misma plantilla a una diferencia que sí importa. El objeto que queda es \(\hat\theta\pm\text{incertidumbre}\), no un sello \(p<0.05\).

6. Guía rápida: ¿qué fórmula usar?

Situación Qué usar Por qué
Un número para \(\mu\) o \(p\) \(\bar x\), \(\hat p\) Estimación puntual
Precisión de esa media \(s/\sqrt n\) Error estándar, no \(s\)
Rango para \(\mu\) (\(\sigma\) desconocida) \(\bar x\pm t_{n-1}\,s/\sqrt n\) IC de la media
\(\sigma\) conocida, \(n\) grande \(\bar x\pm z_{1-\alpha/2}\,\sigma/\sqrt n\) Límite normal
¿Estos datos son raros si \(\mu=\mu_0\)? \(t\) / ttest_1samp Prueba de una media
¿Dos grupos difieren? ttest_ind (o z de proporciones) A/B de medias
¿Cuántas alarmas falsas acepto? \(\alpha\) Tipo I
¿Detectaría este \(\delta\) con este \(n\)? Potencia \(1-\beta\) Tipo II

Regla de comunicación: parámetro, \(n\), estimación, intervalo, p-value y unidades del efecto, en ese orden.

7. Guía práctica para reportar una inferencia

Paso 1: población y parámetro

Qué se observa y qué \(\theta\) se discute (\(\mu\), \(p\), \(\mu_B-\mu_A\)).

Paso 2: diseño y \(\alpha\)

\(n\), grupos, unilateral o bilateral, nivel de significancia.

Paso 3: estimación y SE

\(\hat\theta\) y cómo tiembla.

Paso 4: intervalo y prueba

IC de nivel declarado; \(H_0\), estadístico, p-value, decisión.

Paso 5: redactar

“En [población], con \(n=\)[...], la estimación de [parámetro] es [θ̂] (SE [...]). El IC [nivel] es [rango]. Se prueba [H0] frente a [H1]: t/z=[...], p=[...]. A \(\alpha=\)[...] se [rechaza / no rechaza] \(H_0\). El efecto en unidades de [ms / %] [sí / no] es relevante porque [...]. Limitación: [n / potencia / una métrica / múltiples tests].”

El A/B de latencia (etapa 10) ya llenó esta plantilla.

8. Puente hacia las siguientes sesiones

La inferencia de esta sesión es frecuentista: el parámetro es fijo, los datos varían, el 95% es cobertura. Otra lectura trata \(\theta\) como incierta y actualiza \(P(\theta\mid \text{datos})\). Esa es la comparación entre máxima verosimilitud, MAP y posterior de la Sesión 10. Los modelos (regresión, clasificación) volverán a estimar parámetros y a necesitar intervalos, pruebas y —sobre todo— no confundir \(p\) con importancia.

9. Cierre

Este notebook deja el aparato mínimo para hablar de un parámetro con margen:

  • estadístico frente a parámetro, y la distribución muestral,
  • estimación puntual y error estándar,
  • intervalo de confianza y su cobertura,
  • \(H_0\), \(\alpha\), p-value (sin invertirlo),
  • tipo I, tipo II y potencia,
  • el contraste \(n\) enorme / efecto minúsculo,
  • un A/B de latencia reportado con intervalo y decisión.

Resultado esperado: el estudiante puede construir un IC y una prueba con SciPy, explicar el 95% sin convertir \(\mu\) en aleatorio, y negarse a desplegar un modelo solo porque \(p<0.05\).

💬 ¿Te sirvió?

Deja en los comentarios una duda o un caso donde aplicarías esto — respondo todos. Sígueme para no perderte el próximo artículo de la serie y comparte con alguien que esté aprendiendo análisis de datos.

👉 El código completo está disponible para ejecutar directamente.