"""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