Skip to content

Predict Online Purchase on a Specific Weekday

Task type: BinaryClassificationTask Industry: Retail / E-commerce

Targeted promotions are most effective when timed to match customer behavior patterns. By predicting which customers are likely to make online purchases starting from a specific weekday, marketing teams can schedule flash sales, email campaigns, and push notifications to coincide with peak purchase propensity windows.

What makes this advanced? Calendar weekday arithmetic — computes the next occurrence of a specific weekday from the split timestamp, then defines the purchase window from that date using modular arithmetic.


Prerequisites

Before writing a target function you need:

  • A trained foundation model built on event data that includes the relevant data sources.
  • The monad library installed in your environment.
  • Data source(s): transactions

Target Function

The target function tells monad how to label each entity for training. It receives four arguments:

Argument Type Description
history Events All events before the temporal split.
future Events All events after the temporal split.
attributes Attributes Static entity attributes.
ctx Dict Context dictionary containing SPLIT_TIMESTAMP, data mode, etc.

The function must return one of:

  • np.array([1], dtype=np.float32)positive case
  • np.array([0], dtype=np.float32)negative case
  • Noneexclude this entity from training

Full Example

Python
import numpy as np
from datetime import timedelta
from typing import Dict

from monad.ui.target_function import Events, Attributes
from monad.ui.target_function import SPLIT_TIMESTAMP
from monad.ui.target_function import has_incomplete_training_window

from datetime import datetime

# === Configuration ===
TARGET_WEEKDAY = 2  # Wednesday (0=Monday)
PURCHASE_WINDOW_DAYS = 10
TRANSACTION_DATA_SOURCE = "transactions"
ONLINE_CHANNEL = "ONLINE"

def weekday_purchase_target_fn(
    history: Events,
    future: Events,
    attributes: Attributes,
    ctx: Dict,
) -> np.ndarray | None:
    """Predict if customer makes an online purchase starting next Wednesday."""

    if has_incomplete_training_window(ctx, timedelta(days=17)):  # worst case: 7 days to Wed + 10 days
        return None

    # 1. Compute the next Wednesday from the split date
    current_date = datetime.fromtimestamp(ctx[SPLIT_TIMESTAMP])
    days_until_target = (TARGET_WEEKDAY - current_date.weekday() + 7) % 7
    days_until_target = 7 if days_until_target == 0 else days_until_target
    target_date = current_date + timedelta(days=days_until_target)

    # 2. Slice the purchase window (10 days from Wednesday)
    purchase_window = future.interval_from(
        start=target_date.timestamp(),
        interval_length=timedelta(days=PURCHASE_WINDOW_DAYS),
    )

    # 3. Count online transactions in the window
    online_count = purchase_window[TRANSACTION_DATA_SOURCE].filter(
        "channel", lambda x: x == ONLINE_CHANNEL
    ).count()

    return np.array([int(online_count > 0)], dtype=np.float32)

Step-by-Step Breakdown

① Calculate next weekday occurrence

Python
current_date = datetime.fromtimestamp(ctx[SPLIT_TIMESTAMP])
days_until_target = (TARGET_WEEKDAY - current_date.weekday() + 7) % 7
days_until_target = 7 if days_until_target == 0 else days_until_target
target_date = current_date + timedelta(days=days_until_target)

Modular arithmetic computes the number of days until the next Wednesday (weekday index 2). If the split timestamp already falls on a Wednesday, the function targets the following Wednesday (7 days ahead) to ensure the prediction window is always in the future.

② Define purchase window from that date

Python
purchase_window = future.interval_from(
    start=target_date.timestamp(),
    interval_length=timedelta(days=PURCHASE_WINDOW_DAYS),
)

A 10-day window starting from the next Wednesday is sliced from the future events. This creates a consistent, calendar-aligned prediction window regardless of when the split timestamp falls within the week.

③ Filter for online transactions

Python
online_count = purchase_window[TRANSACTION_DATA_SOURCE].filter(
    "channel", lambda x: x == ONLINE_CHANNEL
).count()

return np.array([int(online_count > 0)], dtype=np.float32)

Only online transactions within the purchase window are counted. The label is positive if at least one online purchase occurred, negative otherwise.


Training

Once the target function is defined, fine-tune a downstream model:

Python
from pathlib import Path
from monad.ui.config import TrainingParams, MetricParams, MetricMonitoringMode
from monad.config.early_stopping import EarlyStopping

from monad.ui.module import load_from_foundation_model, BinaryClassificationTask

module = load_from_foundation_model(
    checkpoint_path=Path("./foundation_model"),
    downstream_task=BinaryClassificationTask(),
    target_fn=weekday_purchase_target_fn,
)

training_params = TrainingParams(
    checkpoint_dir=Path("./<this_model>"),
    learning_rate=1e-4,
    epochs=20,
    devices=[0],
    metrics=[
        MetricParams(alias="auroc", metric_name="AUROC", kwargs={"task": "binary"}),
        MetricParams(alias="auprc", metric_name="AveragePrecision", kwargs={"task": "binary"}),
        MetricParams(alias="recall", metric_name="Recall", kwargs={"task": "binary"}),
        MetricParams(alias="precision", metric_name="Precision", kwargs={"task": "binary"}),
    ],
    metric_to_monitor="val_auroc_0",
    metric_monitoring_mode=MetricMonitoringMode.MAX,
    early_stopping=EarlyStopping(min_delta=1e-4, patience=5),
)

module.fit(training_params, seed=42)

Evaluation

Python
from pathlib import Path
from datetime import datetime, timezone
from monad.ui.module import load_from_checkpoint
from monad.ui.config import TestingParams, MetricParams, OutputType

module = load_from_checkpoint(Path("./<this_model>"))

testing_params = TestingParams(
    prediction_date=datetime(2024, 5, 1, tzinfo=timezone.utc),
    output_type=OutputType.DECODED,
    devices=[0],
    metrics=[
        MetricParams(alias="auroc", metric_name="AUROC"),
        MetricParams(alias="auprc", metric_name="AveragePrecision"),
        MetricParams(alias="recall", metric_name="Recall"),
    ],
)

results = module.test(testing_params)

Prediction

Python
from pathlib import Path
from datetime import datetime, timezone
from monad.ui.module import load_from_checkpoint
from monad.ui.config import TestingParams, OutputType

module = load_from_checkpoint(Path("./<this_model>"))

testing_params = TestingParams(
    local_save_location=Path("./predictions.tsv"),
    output_type=OutputType.DECODED,
    prediction_date=datetime(2024, 6, 1, tzinfo=timezone.utc),
    devices=[0],
)

predictions = module.predict(testing_params)

Metric Why it matters
AUROC Measures overall ranking quality.
AUPRC More informative when the positive class is rare.
Recall Proportion of actual positives caught.
Precision Proportion of predicted positives that are correct.
F1 Score Harmonic mean of precision and recall.

Production Tips

  1. Parameterise the target weekday. Wednesday is used here as an example, but the same pattern works for any day of the week. Run the model for different weekdays to find which aligns best with your customer base.
  2. Adjust the purchase window length. 10 days captures roughly a week-and-a-half of behavior. Shorter windows (3-5 days) may be better for flash sales, while longer windows suit broader campaign planning.
  3. Account for public holidays. A Wednesday that falls on a holiday will have atypical purchase patterns. Consider filtering out holiday weeks or adding a holiday indicator as a feature.
  4. Segment by purchase frequency. Heavy online shoppers may always purchase on any given Wednesday, while occasional buyers are the more interesting prediction targets. Consider excluding very high-frequency purchasers.
  5. Validate across time zones. If your customer base spans multiple time zones, "Wednesday" may mean different things. Ensure timestamps are normalised to a consistent reference.