Predict churn
Goal: for every currently active customer, the probability of no activity in the next 30 days.
1. Wire your tables
import pandas as pd
import relativedb
# Declare `schema`, translate DataFrame records to `relativedb.Row`, and wire
# entity/link/scanner callbacks. The complete example connector is linked below.
wiring = relativedb.RetrieverWiring.new_wiring()...build()
engine = relativedb.Engine(schema, wiring)
2. Write the query
Define churn in the target; restrict the population to active users in
WHERE (past-facing window):
PREDICT NOT EXISTS(events.*) OVER (30 DAYS FOLLOWING)
FOR EACH users.user_id
WHERE EXISTS(events.*) OVER (90 DAYS PRECEDING)
The target aggregates the next 30 days (30 DAYS FOLLOWING); the WHERE
looks back 90 days (90 DAYS PRECEDING). EXISTS(events.*) is the boolean
existence check — shorthand for the older COUNT(events.*) > 0 idiom.
3. Score as of today
result = engine.execute(relativedb.ExecutionInput(
query=query, anchor_time=pd.Timestamp.utcnow().to_pydatetime()))
df = pd.DataFrame({"entity_id": [p.id for p in result.predictions],
"probability": [p.probability for p in result.predictions]})
df.sort_values("probability", ascending=False).head(20)
Each row is entity_id, probability. Users inactive for 90+ days are excluded
by the WHERE clause — they already churned.
Variations
- Different definition:
NOT EXISTS(orders.*) OVER (60 DAYS FOLLOWING)(no purchase) orSUM(usage.minutes) OVER (30 DAYS FOLLOWING) < 10(low usage). - Backtest: rerun with a past
anchor_timeand compare against what actually happened — the engine guarantees the context is point-in-time correct. - Real model: construct the engine with
model_backend=relativedb.RtNativeBackend(schema=schema)(guide).
A complete self-checking version lives at examples/industry/growth_churn.py;
its pandas_connector.py shows the application-owned connector.