dima806's picture
Upload 51 files
3677ab8 verified
Raw
History Blame Contribute Delete
3.02 kB
"""Trend features: slope of order frequency, rolling averages, spend change."""
import numpy as np
import pandas as pd
from src.utils.logger import get_logger
logger = get_logger(__name__)
_N_TREND_PERIODS = 6 # months of history used for slope computation
def _ols_slope(values: np.ndarray) -> float:
"""Return the OLS slope of a 1-D time series (no intercept shift needed)."""
n = len(values)
if n < 2:
return 0.0
x = np.arange(n, dtype=float)
x -= x.mean()
y = values.astype(float) - values.mean()
denom = float((x * x).sum())
if denom == 0.0:
return 0.0
return float((x * y).sum() / denom)
def compute_trend_features(
orders: pd.DataFrame,
reference_date: pd.Timestamp,
n_periods: int = _N_TREND_PERIODS,
) -> pd.DataFrame:
"""Compute trend-based features for each customer.
Features produced:
- order_frequency_slope – OLS slope of monthly order counts (last n_periods months)
- spend_slope – OLS slope of monthly spend
- rolling_avg_orders_3m – mean monthly order count over last 3 months
- spend_change_ratio – rolling_avg_orders_3m / (avg over previous 3 months + 1)
Args:
orders: Full orders fact table.
reference_date: Upper bound for feature computation (exclusive).
n_periods: Number of monthly buckets used for slope computation.
Returns:
DataFrame with one row per customer.
"""
hist = orders[orders["order_date"] < reference_date].copy()
hist["year_month"] = hist["order_date"].dt.to_period("M")
monthly = (
hist.groupby(["customer_id", "year_month"])
.agg(monthly_orders=("order_id", "count"), monthly_spend=("total_value", "sum"))
.reset_index()
)
# Build the index of the last n_periods complete months
all_months = pd.period_range(
end=reference_date - pd.Timedelta(days=1), periods=n_periods, freq="M"
)
records: list[dict] = []
for cust_id, grp in monthly.groupby("customer_id"):
ts = grp.set_index("year_month").reindex(all_months, fill_value=0)
order_vals = ts["monthly_orders"].to_numpy()
spend_vals = ts["monthly_spend"].to_numpy()
order_slope = _ols_slope(order_vals)
spend_slope = _ols_slope(spend_vals)
last_3 = float(order_vals[-3:].mean()) if len(order_vals) >= 3 else float(order_vals.mean())
prev_3 = float(order_vals[-6:-3].mean()) if len(order_vals) >= 6 else 0.0
spend_change_ratio = round(last_3 / (prev_3 + 1.0), 4)
records.append(
{
"customer_id": cust_id,
"order_frequency_slope": round(order_slope, 6),
"spend_slope": round(spend_slope, 6),
"rolling_avg_orders_3m": round(last_3, 4),
"spend_change_ratio": spend_change_ratio,
}
)
result = pd.DataFrame(records)
logger.info(f"compute_trend_features: {len(result):,} customers")
return result