feat(browser): auto-commit con 178 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""Deteccion de tendencia en una serie via regresion lineal simple (grupo eda)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from scipy.stats import linregress
|
||||
|
||||
|
||||
def trend_slope(values: list, x: list = None) -> dict:
|
||||
"""Detecta la tendencia (sube/baja/plana) de una serie y su significancia.
|
||||
|
||||
Ajusta una regresion lineal simple (minimos cuadrados) de ``values`` sobre
|
||||
``x`` y resume el resultado en una direccion legible mas estadisticos. Si
|
||||
``x`` es ``None`` se usa el indice posicional ``0..n-1``. Los pares cuyo
|
||||
valor (en ``values`` o ``x``) sea ``None`` o ``NaN`` se descartan antes del
|
||||
ajuste, de modo que series con huecos se manejan sin fallar.
|
||||
|
||||
Funcion pura y determinista: no hace I/O, no muta los inputs.
|
||||
|
||||
Args:
|
||||
values: serie de valores numericos (la variable dependiente, eje Y).
|
||||
x: posiciones de cada valor (la variable independiente, eje X). Si es
|
||||
``None`` se usa ``range(len(values))``. Debe tener la misma longitud
|
||||
que ``values`` cuando se proporciona.
|
||||
|
||||
Returns:
|
||||
dict con la pendiente y el resumen de la tendencia:
|
||||
|
||||
- ``slope``: pendiente de la recta ajustada (float) o ``None`` si no
|
||||
hay suficientes pares validos.
|
||||
- ``intercept``: ordenada en el origen (float).
|
||||
- ``r``: coeficiente de correlacion de Pearson (float).
|
||||
- ``r_squared``: ``r**2``, fraccion de varianza explicada (float).
|
||||
- ``p_value``: p-valor del test de pendiente nula (float).
|
||||
- ``std_err``: error estandar de la pendiente (float).
|
||||
- ``direction``: ``"up"`` (slope > 0 y significativa), ``"down"``
|
||||
(slope < 0 y significativa), ``"flat"`` (no significativa) o
|
||||
``"unknown"`` (menos de 3 pares validos).
|
||||
- ``significant``: ``True`` si ``p_value < 0.05``.
|
||||
- ``n``: numero de pares validos usados en el ajuste.
|
||||
|
||||
Con menos de 3 pares validos devuelve
|
||||
``{"slope": None, "direction": "unknown", "significant": False,
|
||||
"n": <n>}``.
|
||||
"""
|
||||
xs_raw = list(range(len(values))) if x is None else list(x)
|
||||
ys_raw = list(values)
|
||||
|
||||
xs: list[float] = []
|
||||
ys: list[float] = []
|
||||
for xi, yi in zip(xs_raw, ys_raw):
|
||||
if xi is None or yi is None:
|
||||
continue
|
||||
if isinstance(xi, float) and math.isnan(xi):
|
||||
continue
|
||||
if isinstance(yi, float) and math.isnan(yi):
|
||||
continue
|
||||
xs.append(float(xi))
|
||||
ys.append(float(yi))
|
||||
|
||||
n = len(xs)
|
||||
if n < 3:
|
||||
return {"slope": None, "direction": "unknown", "significant": False, "n": n}
|
||||
|
||||
result = linregress(xs, ys)
|
||||
slope = float(result.slope)
|
||||
p_value = float(result.pvalue)
|
||||
r = float(result.rvalue)
|
||||
|
||||
significant = p_value < 0.05
|
||||
if not significant:
|
||||
direction = "flat"
|
||||
elif slope > 0:
|
||||
direction = "up"
|
||||
elif slope < 0:
|
||||
direction = "down"
|
||||
else:
|
||||
direction = "flat"
|
||||
|
||||
return {
|
||||
"slope": slope,
|
||||
"intercept": float(result.intercept),
|
||||
"r": r,
|
||||
"r_squared": r * r,
|
||||
"p_value": p_value,
|
||||
"std_err": float(result.stderr),
|
||||
"direction": direction,
|
||||
"significant": significant,
|
||||
"n": n,
|
||||
}
|
||||
Reference in New Issue
Block a user