4f1530797e
Subsistema de papers reproducibles (grupo de capacidad `papers`). Añade las funciones estadísticas que un paper honesto necesita y la función que congela la hipótesis antes de mirar los datos (anti-HARKing). Nuevas funciones (puras salvo la última): - effect_size_cohens_d: Cohen's d + Hedges' g (corrección de sesgo para N pequeño) + interpretación cualitativa (negligible/small/medium/large por los umbrales de Cohen). Dict-no-throw ante varianza cero / N insuficiente. - confidence_interval_mean: intervalo de confianza de una media (t de Student) o de la diferencia de medias con Welch (df de Welch–Satterthwaite, sin asumir varianzas iguales). Dict-no-throw; el IC colapsa al punto cuando la varianza es cero. - preregister_hypothesis (impura): congela hipótesis + plan de análisis en papers/<slug>/preregistration.md con frozen_at (UTC) y content_hash (sha256 del cuerpo normalizado, no del frontmatter). Inmutabilidad: una vez frozen, un contenido distinto se RECHAZA sin sobrescribir (mata el HARKing); idempotente si el contenido es idéntico. Siempre dict-no-throw. Extensión: - fdr_correction 1.0.0 -> 1.1.0: añade method="holm" (Holm-Bonferroni step-down, controla FWER, más potente que Bonferroni simple). Reúsa la maquinaria de alineación 1:1 con None/inválidos; no rompe los métodos bh/bonferroni. Reutiliza del registry: fdr_correction (BH + Bonferroni ya existían) como base para Holm. pearson y spearman_corr ya cubrían correlación. Tests: 36 pytest verdes (cohen/hedges 8, confidence/welch 8, fdr/holm/bonferroni 12, preregister 4 + extras), golden contra valores conocidos y validados con scipy. Golden manual del preregistro: congela, idempotente, rechaza edición (bytes en disco idénticos al congelado). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
177 lines
5.9 KiB
Python
177 lines
5.9 KiB
Python
"""Intervalo de confianza de la media (una muestra) o de la diferencia de medias (Welch).
|
|
|
|
Funcion pura del grupo papers. Calcula el intervalo de confianza (IC) de la media
|
|
de una muestra usando la t de Student, o el IC de la diferencia de medias de dos
|
|
muestras independientes con el metodo de Welch (sin asumir varianzas iguales).
|
|
|
|
- Una muestra: ``df = n - 1``, ``se = sd / sqrt(n)`` (sd con ddof=1),
|
|
``tcrit = t.ppf((1 + confidence) / 2, df)``, ``ci = mean +/- tcrit * se``.
|
|
- Dos muestras (Welch): IC de ``mean(data) - mean(other)``, con
|
|
``se = sqrt(se1^2 + se2^2)`` y grados de libertad de Welch-Satterthwaite.
|
|
|
|
No lanza excepciones: ante casos degenerados (muestras vacias, ``n < 2``,
|
|
varianza cero) devuelve un dict coherente con ``ci_low``/``ci_high``/``se`` en
|
|
``nan`` (salvo el sub-caso de varianza cero, donde el IC colapsa al punto) y una
|
|
clave ``note`` explicando el caso. Usa ``scipy.stats`` y ``numpy``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import numpy as np
|
|
from scipy import stats
|
|
|
|
|
|
def confidence_interval_mean(
|
|
data: list, other: list = None, confidence: float = 0.95
|
|
) -> dict:
|
|
"""Intervalo de confianza de la media o de la diferencia de medias (Welch).
|
|
|
|
Si ``other`` es ``None``, calcula el IC de la media de ``data`` con la t de
|
|
Student. Si se proporciona ``other``, calcula el IC de la diferencia
|
|
``mean(data) - mean(other)`` con el metodo de Welch (no asume varianzas
|
|
iguales) y grados de libertad de Welch-Satterthwaite.
|
|
|
|
Es una funcion pura y determinista: no hace I/O ni muta las entradas. No
|
|
lanza excepcion ante datos degenerados; en su lugar devuelve un dict con la
|
|
clave ``note`` y los campos numericos indefinidos a ``nan``.
|
|
|
|
Args:
|
|
data: muestra de observaciones numericas (lista de numeros).
|
|
other: segunda muestra independiente. Si se da, el IC es el de la
|
|
diferencia de medias ``mean(data) - mean(other)`` con Welch. Si es
|
|
``None`` (default), el IC es el de la media de ``data``.
|
|
confidence: nivel de confianza en (0, 1), p.ej. 0.95 para el 95%.
|
|
|
|
Returns:
|
|
dict con las claves:
|
|
mean: media de ``data`` (una muestra) o la diferencia
|
|
``mean(data) - mean(other)`` (dos muestras).
|
|
ci_low: extremo inferior del intervalo de confianza.
|
|
ci_high: extremo superior del intervalo de confianza.
|
|
se: error estandar de la media (o de la diferencia).
|
|
df: grados de libertad de la t (Welch-Satterthwaite si dos muestras).
|
|
confidence: nivel de confianza aplicado (float).
|
|
n: tamano de la muestra (una muestra) o tamano total ``n1 + n2``
|
|
(dos muestras; ademas se incluyen ``n1`` y ``n2``).
|
|
|
|
En el caso de dos muestras se incluyen ademas ``n1`` y ``n2``. Casos
|
|
degenerados (muestra vacia, ``n < 2``, etc.) anaden la clave ``note`` y
|
|
dejan ``ci_low``/``ci_high``/``se`` (y a veces ``df``) en ``nan``.
|
|
"""
|
|
conf = float(confidence)
|
|
|
|
if other is None:
|
|
return _ci_one_sample(data, conf)
|
|
return _ci_welch(data, other, conf)
|
|
|
|
|
|
def _ci_one_sample(data: list, conf: float) -> dict:
|
|
"""IC de la media de una sola muestra con la t de Student."""
|
|
arr = np.asarray(list(data), dtype=float)
|
|
n = int(arr.size)
|
|
|
|
base = {
|
|
"mean": float("nan"),
|
|
"ci_low": float("nan"),
|
|
"ci_high": float("nan"),
|
|
"se": float("nan"),
|
|
"df": float("nan"),
|
|
"confidence": conf,
|
|
"n": n,
|
|
}
|
|
|
|
if n == 0:
|
|
base["note"] = "muestra vacia: media e intervalo indefinidos"
|
|
return base
|
|
|
|
mean = float(arr.mean())
|
|
base["mean"] = mean
|
|
|
|
if n < 2:
|
|
base["note"] = "n < 2: error estandar y grados de libertad indefinidos"
|
|
return base
|
|
|
|
df = n - 1
|
|
base["df"] = float(df)
|
|
|
|
sd = float(arr.std(ddof=1))
|
|
se = sd / math.sqrt(n)
|
|
base["se"] = se
|
|
|
|
# Varianza cero: el IC colapsa al punto (no es un error).
|
|
if se == 0.0:
|
|
base["ci_low"] = mean
|
|
base["ci_high"] = mean
|
|
base["note"] = "varianza cero: el intervalo colapsa a la media"
|
|
return base
|
|
|
|
tcrit = float(stats.t.ppf((1.0 + conf) / 2.0, df))
|
|
margin = tcrit * se
|
|
base["ci_low"] = mean - margin
|
|
base["ci_high"] = mean + margin
|
|
return base
|
|
|
|
|
|
def _ci_welch(data: list, other: list, conf: float) -> dict:
|
|
"""IC de la diferencia de medias de dos muestras con el metodo de Welch."""
|
|
a = np.asarray(list(data), dtype=float)
|
|
b = np.asarray(list(other), dtype=float)
|
|
n1 = int(a.size)
|
|
n2 = int(b.size)
|
|
|
|
base = {
|
|
"mean": float("nan"),
|
|
"ci_low": float("nan"),
|
|
"ci_high": float("nan"),
|
|
"se": float("nan"),
|
|
"df": float("nan"),
|
|
"confidence": conf,
|
|
"n": n1 + n2,
|
|
"n1": n1,
|
|
"n2": n2,
|
|
}
|
|
|
|
if n1 == 0 or n2 == 0:
|
|
base["note"] = "alguna muestra esta vacia: diferencia e intervalo indefinidos"
|
|
return base
|
|
|
|
mean1 = float(a.mean())
|
|
mean2 = float(b.mean())
|
|
diff = mean1 - mean2
|
|
base["mean"] = diff
|
|
|
|
if n1 < 2 or n2 < 2:
|
|
base["note"] = (
|
|
"n < 2 en alguna muestra: error estandar y grados de libertad indefinidos"
|
|
)
|
|
return base
|
|
|
|
sd1 = float(a.std(ddof=1))
|
|
sd2 = float(b.std(ddof=1))
|
|
se1 = sd1 / math.sqrt(n1)
|
|
se2 = sd2 / math.sqrt(n2)
|
|
se = math.sqrt(se1 * se1 + se2 * se2)
|
|
base["se"] = se
|
|
|
|
# Ambas varianzas cero: el IC de la diferencia colapsa al punto.
|
|
if se == 0.0:
|
|
base["ci_low"] = diff
|
|
base["ci_high"] = diff
|
|
base["df"] = float("nan")
|
|
base["note"] = "varianza cero en ambas muestras: el intervalo colapsa a la diferencia"
|
|
return base
|
|
|
|
# Grados de libertad de Welch-Satterthwaite.
|
|
df = (se1 * se1 + se2 * se2) ** 2 / (
|
|
(se1**4) / (n1 - 1) + (se2**4) / (n2 - 1)
|
|
)
|
|
base["df"] = float(df)
|
|
|
|
tcrit = float(stats.t.ppf((1.0 + conf) / 2.0, df))
|
|
margin = tcrit * se
|
|
base["ci_low"] = diff - margin
|
|
base["ci_high"] = diff + margin
|
|
return base
|