Mobile Payment Adoption
Task type: BinaryClassificationTask
Industry: Fintech / Banking
Driving adoption of mobile payment features is a key growth lever for banking apps. New users who do not try the feature within the first two weeks are far less likely to adopt it later. By predicting which new users will adopt mobile payments, marketing teams can target the remaining users with tailored onboarding flows, tutorials, or promotional incentives during the critical first-impression window.
What makes this advanced? New-user lifecycle detection — the target function checks history to confirm the user has never used mobile payments before, ensuring only genuinely new-to-feature users enter the training set, then monitors a tight 14-day future window for adoption events.
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):
mobile_payments
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 casenp.array([0], dtype=np.float32)— negative caseNone— exclude this entity from training
Full Example
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
# === Configuration ===
TARGET_WINDOW_DAYS = 14
MOBILE_PAYMENTS_DATA_SOURCE = "mobile_payments"
def mobile_payment_adoption_target_fn(
history: Events,
future: Events,
attributes: Attributes,
ctx: Dict,
) -> np.ndarray | None:
"""Predict if a new user adopts mobile payments within 14 days."""
split_ts = ctx[SPLIT_TIMESTAMP]
if has_incomplete_training_window(ctx, timedelta(days=TARGET_WINDOW_DAYS)):
return None
# 1. Only target users who haven't used mobile payments before
if history[MOBILE_PAYMENTS_DATA_SOURCE].count() > 0:
return None
# 2. Trim future to the 14-day adoption window
future = future.interval_from(split_ts, timedelta(days=TARGET_WINDOW_DAYS))
future_mobile = future[MOBILE_PAYMENTS_DATA_SOURCE]
# 3. Label: adopted (1) or not (0)
adopted = 1 if future_mobile.count() > 0 else 0
return np.array([adopted], dtype=np.float32)
Step-by-Step Breakdown
① Filter to new users only
Users who already have mobile payment events in their history are excluded by returning None. This ensures the model only trains on users who are genuinely new to the feature, avoiding contamination from already-adopted users who would trivially be labeled positive.
② Trim future window
future = future.interval_from(split_ts, timedelta(days=TARGET_WINDOW_DAYS))
future_mobile = future[MOBILE_PAYMENTS_DATA_SOURCE]
The future events are restricted to exactly 14 days from the split timestamp. This creates a tight adoption window that aligns with the business goal — catching users in their critical first two weeks.
③ Check for adoption events
If any mobile payment event exists in the 14-day window, the user is labeled as adopted (positive). Otherwise, they are labeled as not adopted (negative). The simplicity here is intentional — any usage counts as adoption.
Training
Once the target function is defined, fine-tune a downstream model:
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=mobile_payment_adoption_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
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
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)
Recommended Metrics
| 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
- Define "new user" precisely. The current function uses zero historical mobile payment events as the criterion. Depending on your product, you may also want to filter by account age (e.g., only users who registered in the last 90 days) to avoid including dormant accounts.
- Adjust the adoption window to match your onboarding cadence. If your app sends onboarding nudges over 7 days, a 14-day window gives enough buffer. If onboarding is longer, extend the window accordingly.
- Segment predictions by user cohort. New users acquired through different channels (organic, paid, referral) may have very different adoption rates. Consider building channel-specific models or adding acquisition channel as a feature.
- Re-score users frequently. Run predictions daily or weekly so that users who are approaching the end of their adoption window without converting receive escalated interventions.
- Track downstream impact. Measure not just model accuracy but the actual lift in mobile payment adoption rate among users who received targeted campaigns based on model predictions.