Skip to content

Predict Conditional Median Paid Price in the Next 30 Days

Task type: RegressionTask

This regression objective predicts the median paid price in the next 30 days for each customer with at least four historical purchases. It evaluates only customers who later buy in that window. That is a retrospective conditional population: the model predicts purchase price among later buyers, not whether a customer will buy.

Target contract

The example treats one event row as one purchase. It uses calendar days 1 through 30 after the cutoff, so it starts 86,400 seconds after SPLIT_TIMESTAMP and requires data through the exclusive end of day 30. Replace transactions and price only when your configured event source uses different names.

Python
from datetime import timedelta
from typing import Dict

import numpy as np
from monad.batch import SPLIT_TIMESTAMP
from monad.targets import Attributes, Events
from monad.ui.target_function import has_incomplete_training_window

TRANSACTIONS = "transactions"
PAID_PRICE = "price"
MIN_HISTORY_PURCHASES = 4
TARGET_WINDOW_DAYS = 30
SECONDS_PER_DAY = 86_400


def conditional_median_paid_price_target_fn(
    history: Events,
    future: Events,
    attributes: Attributes,
    ctx: Dict,
) -> np.ndarray | None:
    if history[TRANSACTIONS].count() < MIN_HISTORY_PURCHASES:
        return None

    complete_horizon = timedelta(days=TARGET_WINDOW_DAYS + 1)
    if has_incomplete_training_window(ctx, complete_horizon):
        return None

    target_events = future.interval_from(
        ctx[SPLIT_TIMESTAMP] + SECONDS_PER_DAY,
        timedelta(days=TARGET_WINDOW_DAYS),
    )
    prices = target_events[TRANSACTIONS][PAID_PRICE].events
    if len(prices) == 0:
        return None

    median_price = float(np.nanmedian(prices))
    if not np.isfinite(median_price):
        return None
    return np.array([median_price], dtype=np.float32)

The historical-count guard is known at the cutoff. The no-future-purchase guard implements the stated conditional evaluation cohort. If the business question must score every eligible customer, return a defined no-purchase outcome instead and evaluate that different population.

Configure and verify

Use RegressionTask(num_targets=1, max_value=...). Select max_value from training-period labels only, before reading fixed-test errors. Then run verify_target() and confirm:

  • accepted labels have shape (n, 1) and dtype float32;
  • the accepted count is large enough for both training and validation loaders;
  • the None rate matches the declared history and future-purchase conditions;
  • the evaluation joins model and baseline predictions to the same accepted entities.

For price-band diagnostics, pre-register fixed thresholds or a quantile rule with tie handling. Apply the resulting bands to the same held-out rows for the model and baseline, report each band's count and MAE, and name the band with the largest model MAE. Do not change the rule after inspecting test errors.