Files
fn_registry/python/functions/datascience/trend_slope.py
T
egutierrez 763e06c127 feat(browser): auto-commit con 178 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-20 18:22:23 +02:00

92 lines
3.1 KiB
Python

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