feat(datascience): rigor experimental para papers — effect size, IC, Holm + preregistro inmutable
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>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: confidence_interval_mean
|
||||
kind: function
|
||||
lang: py
|
||||
domain: datascience
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "def confidence_interval_mean(data: list, other: list = None, confidence: float = 0.95) -> dict"
|
||||
description: "Intervalo de confianza (IC) de la media de una muestra con la t de Student, o 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_muestral/sqrt(n) (sd con ddof=1), tcrit=t.ppf((1+confidence)/2, df), ci=mean+/-tcrit*se. Dos muestras: IC de mean(data)-mean(other) con se=sqrt(se1^2+se2^2) y grados de libertad de Welch-Satterthwaite. Pura y robusta: nunca lanza; ante casos degenerados (muestra vacia, n<2) devuelve nan + clave note, y con varianza cero el IC colapsa al punto (no es error). Usa scipy.stats y numpy."
|
||||
tags: [papers, statistics, confidence-interval, welch, t-test, python]
|
||||
params:
|
||||
- name: data
|
||||
desc: "muestra de observaciones numericas (lista de numeros). Si other es None, el IC es el de la media de data."
|
||||
- name: other
|
||||
desc: "segunda muestra independiente (lista de numeros) o None (default). Si se da, el IC es el de la diferencia de medias mean(data)-mean(other) calculada con Welch (no asume varianzas iguales)."
|
||||
- name: confidence
|
||||
desc: "nivel de confianza en (0, 1); 0.95 = IC del 95% (default). El cuantil critico es t.ppf((1+confidence)/2, df)."
|
||||
output: "dict {mean, ci_low, ci_high, se, df, confidence, n}. mean = media de data (una muestra) o la diferencia mean(data)-mean(other) (dos muestras). En el caso de dos muestras se anaden ademas n1 y n2 (y n = n1+n2). df son los grados de libertad de la t (Welch-Satterthwaite si dos muestras). Casos degenerados (muestra vacia, n<2) anaden la clave note y dejan ci_low/ci_high/se (y a veces df) en nan; con varianza cero y n>=2 el IC colapsa a [mean, mean] con se=0 (con note, sin nan). Nunca None ni excepcion."
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: [scipy, numpy]
|
||||
tested: true
|
||||
tests: ["test_one_sample_golden_contra_scipy", "test_one_sample_distinto_nivel_confianza", "test_welch_diferencia_golden_contra_scipy", "test_edge_un_solo_elemento_no_lanza_nan_note", "test_edge_lista_vacia_no_lanza_note", "test_edge_varianza_cero_colapsa_al_punto", "test_edge_welch_muestra_vacia_no_lanza_note", "test_edge_welch_n1_uno_no_lanza_note"]
|
||||
test_file_path: "python/functions/datascience/confidence_interval_mean_test.py"
|
||||
file_path: "python/functions/datascience/confidence_interval_mean.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
from datascience import confidence_interval_mean
|
||||
|
||||
# IC del 95% de la media de una muestra (t de Student).
|
||||
data = [2, 4, 4, 4, 5, 5, 7, 9]
|
||||
ci = confidence_interval_mean(data, confidence=0.95)
|
||||
print(ci["mean"]) # -> 5.0
|
||||
print(ci["df"]) # -> 7.0 (n - 1)
|
||||
print(round(ci["ci_low"], 5), round(ci["ci_high"], 5))
|
||||
# -> 3.21251 6.78749 (se con sd muestral ddof=1 ~ 2.13809)
|
||||
|
||||
# IC del 95% de la DIFERENCIA de medias (Welch, no asume varianzas iguales).
|
||||
control = [23.0, 21.0, 25.0, 22.0, 24.0, 26.0]
|
||||
tratado = [18.0, 20.0, 17.0, 19.0, 21.0]
|
||||
diff = confidence_interval_mean(control, tratado, confidence=0.95)
|
||||
print(diff["mean"]) # -> 4.5 (mean(control) - mean(tratado))
|
||||
print(round(diff["ci_low"], 4), round(diff["ci_high"], 4))
|
||||
# Si el intervalo no incluye 0, la diferencia es significativa al 5%.
|
||||
|
||||
# Degenerados: nunca lanza.
|
||||
print(confidence_interval_mean([5])["note"]) # n < 2: ... indefinidos
|
||||
print(confidence_interval_mean([3, 3, 3])["se"]) # -> 0.0 (IC colapsa a [3, 3])
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando quieras cuantificar la **incertidumbre de una media estimada** a partir de
|
||||
una muestra: reporta `[ci_low, ci_high]` en vez de un punto suelto para mostrar
|
||||
el rango plausible del valor real al nivel de confianza pedido. Usala tambien
|
||||
para **comparar dos grupos** (A/B test, control vs tratamiento, antes vs
|
||||
despues con grupos independientes): pasa las dos muestras y, si el IC de la
|
||||
diferencia **no incluye el 0**, la diferencia es significativa al nivel
|
||||
`1 - confidence`. Es el complemento del p-valor: ademas de "hay efecto", te dice
|
||||
"de que tamano y con que margen". Para dos muestras usa Welch por defecto, asi
|
||||
que no necesitas comprobar antes si las varianzas son iguales.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Pura y determinista (no hace I/O, no muta las entradas), pero **no** es
|
||||
stdlib-only: depende de `scipy.stats` y `numpy` (ambos en el venv del proyecto).
|
||||
- Con `other` usa **Welch** (df de Welch-Satterthwaite): NO asume varianzas
|
||||
iguales ni tamanos de muestra iguales. Si necesitas el t-test clasico de
|
||||
varianzas agrupadas (pooled), esta funcion no lo hace.
|
||||
- `sd` se calcula con **ddof=1** (sd muestral), que es lo correcto para el IC de
|
||||
una media con la t. Atajos como `sd_poblacional/sqrt(n)` (ddof=0) dan un
|
||||
intervalo demasiado estrecho.
|
||||
- En el caso de dos muestras, `mean` es la **diferencia** `mean(data) - mean(other)`
|
||||
(no la media de data). El orden importa: el signo del IC depende de cual va
|
||||
primero.
|
||||
- Nunca lanza. Casos degenerados devuelven `nan` en `ci_low`/`ci_high`/`se`
|
||||
(y a veces `df`) mas una clave `note`: muestra vacia o `n < 2` en cualquiera de
|
||||
las muestras. **Excepcion**: con varianza cero y `n >= 2` el IC colapsa al
|
||||
punto `[mean, mean]` con `se = 0` (no es un error, no hay `nan`).
|
||||
- Comprueba `"note" in out` antes de usar `ci_low`/`ci_high` si la muestra puede
|
||||
ser degenerada.
|
||||
Reference in New Issue
Block a user