Skip to content

Learning-to-Rank

LTRRanker trains a single global ranking booster over decision-occasion groups and projects it onto a calibrated-shaped (n, K) matrix over the shared ranking surface — the same ranking.json contract as the recommender, but from one model instead of many.

Install extras

pip install "parleml-ml4[ltr]". The lambdarank / rank_xendcg objectives use LightGBM; yetirank / yetirank_pairwise use CatBoost. Only the library backing your chosen objective is imported.

1. Event-grain decision occasions

In event grain every observed row is a positive purchase; negatives are sampled from the catalogue per occasion. A "group" is one decision occasion.

import numpy as np
import pandas as pd

rng = np.random.default_rng(0)
products = ["a", "b", "c", "d", "e", "f"]
rows = []
for _ in range(3_000):
    cust = rng.integers(0, 500)
    ts = pd.Timestamp("2026-01-01") + pd.Timedelta(days=int(rng.integers(0, 60)))
    # Preference depends on a session feature, so there is rank signal.
    session_len = rng.integers(1, 30)
    weights = np.array([session_len, 5, 4, 3, 2, 1], dtype=float)
    taken = rng.choice(products, p=weights / weights.sum())
    rows.append({"customer_id": cust, "product": taken, "ts": ts,
                 "session_len": session_len})
df = pd.DataFrame(rows)

2. Fit

fit returns a deployable pipeline directly.

from parleml_ml4.ltr import LTRRanker
from parleml_ml4.ltr.config import ltr_thorough

cfg = ltr_thorough(grain="event", entity_col="customer_id",
                   target_key_col="product", timestamp_col="ts",
                   ranker_objective="lambdarank", k=5)
pipeline = LTRRanker(cfg, outputs_dir="runs/ltr").fit(df)
result = pipeline.result

Groups resolve by (entity, floor(timestamp, bundle_time_granularity)) when a timestamp_col is set. Feature columns are derived automatically from every non-reserved column (here, session_len).

3. Read the ranking report

print(result.ranking)              # shared ranking.json payload (Hit@K, MRR@K, NDCG@K, ...)
print(result.best_valid_ndcg)      # best full-catalogue NDCG@k from Optuna tuning
print(result.feature_importance)   # per-feature gains from the booster
print(result.backend, result.objective, result.model_label)

Optuna maximises full-catalogue NDCG@k from ml4's own ranking metrics — not the library's sampled-group NDCG, which can report a spurious 1.0 for a degenerate constant-score booster.

ranker_objective vs ranking_objective

ranker_objective is the training loss (lambdarank here); ranking_objective is the evaluation/sort objective (accuracy / balanced / coverage). They are independent — see Ranking.

4. Serve

With outputs_dir set, fit persisted the FittedLTRPipeline under runs/ltr/deploy/ltr/ alongside events.ndjson / manifest.json.

See the Learning-to-Rank guide, the Ranking guide, and the LTRRanker reference.

Next: Ranking guide · Recommendation example