File size: 2,964 Bytes
3677ab8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
"""Time-based feature computation: rolling windows, inter-order stats."""

import pandas as pd

from src.config import FEATURE_WINDOWS
from src.utils.logger import get_logger

logger = get_logger(__name__)


def compute_time_features(
    orders: pd.DataFrame,
    reference_date: pd.Timestamp,
    windows: list[int] = FEATURE_WINDOWS,
) -> pd.DataFrame:
    """Compute time-based features per customer.

    Features produced:
    - avg_inter_order_days  – mean gap between consecutive orders
    - std_inter_order_days  – std dev of that gap (0 for single-order customers)
    - orders_last_Nd        – order count in the last N days (per ``windows``)
    - spend_last_Nd         – total spend in the last N days
    - ratio_recent_historical – orders_last_30d / (orders_last_90d + 1)

    Args:
        orders: Full orders fact table.
        reference_date: Features are computed on orders strictly before this date.
        windows: List of rolling-window sizes in days.

    Returns:
        DataFrame with one row per customer.
    """
    hist = orders[orders["order_date"] < reference_date].sort_values(["customer_id", "order_date"])

    # Inter-order gap stats
    hist = hist.copy()
    hist["prev_date"] = hist.groupby("customer_id")["order_date"].shift(1)
    hist["inter_days"] = (hist["order_date"] - hist["prev_date"]).dt.days

    inter_stats = (
        hist.groupby("customer_id")["inter_days"]
        .agg(avg_inter_order_days="mean", std_inter_order_days="std")
        .reset_index()
    )
    inter_stats["avg_inter_order_days"] = inter_stats["avg_inter_order_days"].fillna(0).round(2)
    inter_stats["std_inter_order_days"] = inter_stats["std_inter_order_days"].fillna(0).round(2)

    result = inter_stats.copy()

    # Rolling-window counts and spend
    for w in sorted(windows):
        w_start = reference_date - pd.Timedelta(days=w)
        window_orders = hist[hist["order_date"] >= w_start]

        cnt = window_orders.groupby("customer_id").size().reset_index(name=f"orders_last_{w}d")
        spend = (
            window_orders.groupby("customer_id")["total_value"]
            .sum()
            .reset_index()
            .rename(columns={"total_value": f"spend_last_{w}d"})
        )

        result = result.merge(cnt, on="customer_id", how="left")
        result[f"orders_last_{w}d"] = result[f"orders_last_{w}d"].fillna(0).astype(int)

        result = result.merge(spend, on="customer_id", how="left")
        result[f"spend_last_{w}d"] = result[f"spend_last_{w}d"].fillna(0.0).round(2)

    # Ratio recent vs historical
    if "orders_last_30d" in result.columns and "orders_last_90d" in result.columns:
        result["ratio_recent_historical"] = (
            result["orders_last_30d"] / (result["orders_last_90d"] + 1)
        ).round(4)
    else:
        result["ratio_recent_historical"] = 0.0

    logger.info(f"compute_time_features: {len(result):,} customers, windows={windows}")
    return result