chore: auto-commit (26 archivos)
- python/functions/bigquery/bq_auth.md - python/functions/bigquery/bq_load_from_file.md - python/functions/bigquery/bq_load_from_gcs.md - python/functions/bigquery/client.py - python/functions/bigquery/queries.py - python/functions/datascience/__init__.py - python/functions/datascience/decode_qr_image.py - python/functions/datascience/load_bq_table_to_duckdb.md - python/functions/datascience/load_bq_table_to_duckdb.py - python/functions/pipelines/profile_bq_table.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""forecast_seasonal_median — forecast diario por mediana estacional + tendencia.
|
||||
|
||||
Funcion PURA (sin I/O, sin datetime.now(), determinista). Predice el valor futuro
|
||||
de una o varias series temporales diarias combinando dos senales:
|
||||
|
||||
1. Base estacional: la mediana del valor en las ultimas `dow_weeks` fechas con el
|
||||
MISMO dia de semana que la fecha objetivo (dias ausentes = 0, para series
|
||||
intermitentes donde "sin fila" significa "sin venta").
|
||||
2. Factor de tendencia por serie: cuanto ha crecido/caido la actividad reciente
|
||||
respecto al periodo inmediatamente anterior (razon de sumas), acotado a un
|
||||
rango para no amplificar ruido.
|
||||
|
||||
Disenada para el forecast de ventas diarias de Aurgi (dia x centro x subcategoria
|
||||
CGQ): cada serie es un par centro|subcategoria y el patron semanal domina la
|
||||
demanda (los sabados venden distinto que los martes). Solo usa stdlib
|
||||
(datetime, statistics).
|
||||
"""
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
from statistics import median
|
||||
|
||||
|
||||
def _to_date(value: str) -> date:
|
||||
"""Convierte una fecha ISO 'YYYY-MM-DD' (o datetime.date) a datetime.date."""
|
||||
if isinstance(value, date) and not isinstance(value, datetime):
|
||||
return value
|
||||
if isinstance(value, datetime):
|
||||
return value.date()
|
||||
return datetime.strptime(value[:10], "%Y-%m-%d").date()
|
||||
|
||||
|
||||
def forecast_seasonal_median(
|
||||
history: list[dict],
|
||||
horizon_dates: list[str],
|
||||
as_of: str,
|
||||
dow_weeks: int = 8,
|
||||
trend_recent_weeks: int = 4,
|
||||
trend_clip: tuple = (0.5, 2.0),
|
||||
) -> list[dict]:
|
||||
"""Predice el valor de cada serie para cada fecha del horizonte.
|
||||
|
||||
Para cada serie presente en `history` y cada fecha objetivo del horizonte:
|
||||
|
||||
1. Base estacional = mediana del valor en las ultimas `dow_weeks` fechas con el
|
||||
MISMO dia de semana que la fecha objetivo, todas <= `as_of`. Se toman las
|
||||
fechas EXACTAS del calendario (la mas reciente <= as_of con ese dia de
|
||||
semana, y de ahi 7 dias hacia atras por punto); una fecha ausente en la
|
||||
historia cuenta como 0 (series intermitentes).
|
||||
2. Factor de tendencia por serie = suma de los valores de las ultimas
|
||||
`trend_recent_weeks` semanas (desde `as_of` hacia atras) dividida entre la
|
||||
suma de las `trend_recent_weeks` semanas anteriores a esas. Si el
|
||||
denominador es 0 el factor es 1.0. Se acota a `trend_clip`.
|
||||
3. y_pred = max(0.0, base * factor).
|
||||
|
||||
Args:
|
||||
history: observaciones {"series_id": str, "date": "YYYY-MM-DD",
|
||||
"value": float}. Filas duplicadas (misma serie y fecha) se suman. Los
|
||||
dias sin fila dentro de las ventanas se tratan como valor 0.
|
||||
horizon_dates: fechas futuras a predecir (strings ISO 'YYYY-MM-DD').
|
||||
as_of: fecha de corte (ultimo dia de historia utilizable, inclusive).
|
||||
dow_weeks: numero de fechas del mismo dia de semana a promediar para la
|
||||
base estacional. Default 8.
|
||||
trend_recent_weeks: tamano (en semanas) de cada una de las dos ventanas de
|
||||
tendencia (reciente y anterior). Default 4.
|
||||
trend_clip: (min, max) al que se acota el factor de tendencia. Default
|
||||
(0.5, 2.0): la prediccion no puede menos que caer a la mitad ni mas
|
||||
que duplicarse por tendencia.
|
||||
|
||||
Returns:
|
||||
Lista de {"series_id": str, "date": str, "y_pred": float}, una fila por
|
||||
cada serie presente en `history` y cada fecha del horizonte. Ordenada por
|
||||
series_id (asc) y luego por el orden de `horizon_dates`.
|
||||
"""
|
||||
as_of_d = _to_date(as_of)
|
||||
lo_clip, hi_clip = trend_clip
|
||||
|
||||
# Mapa (series_id, date) -> valor acumulado + conjunto de series presentes.
|
||||
values: dict[tuple[str, date], float] = {}
|
||||
series_ids: set[str] = set()
|
||||
for obs in history:
|
||||
sid = obs["series_id"]
|
||||
d = _to_date(obs["date"])
|
||||
v = float(obs.get("value", 0.0) or 0.0)
|
||||
series_ids.add(sid)
|
||||
values[(sid, d)] = values.get((sid, d), 0.0) + v
|
||||
|
||||
# Ventanas de tendencia (en dias) relativas a as_of.
|
||||
span = 7 * trend_recent_weeks
|
||||
recent_lo = as_of_d - timedelta(days=span) # reciente: recent_lo < d <= as_of
|
||||
prior_lo = as_of_d - timedelta(days=2 * span) # anterior: prior_lo < d <= recent_lo
|
||||
|
||||
# Factor de tendencia por serie (una sola vez por serie, no depende del horizonte).
|
||||
trend_factor: dict[str, float] = {}
|
||||
for sid in series_ids:
|
||||
recent_sum = 0.0
|
||||
prior_sum = 0.0
|
||||
for (s, d), v in values.items():
|
||||
if s != sid:
|
||||
continue
|
||||
if recent_lo < d <= as_of_d:
|
||||
recent_sum += v
|
||||
elif prior_lo < d <= recent_lo:
|
||||
prior_sum += v
|
||||
if prior_sum == 0.0:
|
||||
factor = 1.0
|
||||
else:
|
||||
factor = recent_sum / prior_sum
|
||||
trend_factor[sid] = min(hi_clip, max(lo_clip, factor))
|
||||
|
||||
horizon = [_to_date(h) for h in horizon_dates]
|
||||
out: list[dict] = []
|
||||
for sid in sorted(series_ids):
|
||||
factor = trend_factor[sid]
|
||||
for h_str, h_d in zip(horizon_dates, horizon):
|
||||
# Fecha mas reciente <= as_of con el mismo dia de semana que la objetivo.
|
||||
back = (as_of_d.weekday() - h_d.weekday()) % 7
|
||||
anchor = as_of_d - timedelta(days=back)
|
||||
dow_values = [
|
||||
values.get((sid, anchor - timedelta(days=7 * i)), 0.0)
|
||||
for i in range(dow_weeks)
|
||||
]
|
||||
base = median(dow_values)
|
||||
y_pred = max(0.0, base * factor)
|
||||
out.append({"series_id": sid, "date": h_str, "y_pred": y_pred})
|
||||
|
||||
return out
|
||||
Reference in New Issue
Block a user