A risk analyst can calculate a transition matrix in R quickly. The recurring work is deciding which cells changed, whether cure is weakening, where deterioration persists, which segments and exposures explain it, and which cases deserve collections attention.
The matrix is automated. The investigation often is not. A transition matrix describes how risk moves; a useful collections system determines which movements matter and where intervention has the greatest potential value.
How does risk migrate?
Roll Rate Analysis and Migration Matrices develops the methodology and interpretation.
How do we operationalise it?
This build calculates, monitors and investigates transitions repeatedly at portfolio scale without duplicating the methodology article.
All definitions, code, data and results below are original, entirely hypothetical Entimema examples. They do not reproduce a bank's policy, data, thresholds, rules or procedures.
Comparable snapshots create observable migration
A hypothetical input uses account_id, customer_id, observation_date, dpd, state, ead, product, vintage, risk_grade, payment_amount and collection_status. Exact schemas vary; stable identity, observation dates, state, exposure and meaningful segmentation do not.
Frequency must be consistent: a monthly transition is not comparable with a weekly one. The illustrative ordered states are S₀ Current, S₁ 1–30 DPD, S₂ 31–60 DPD, S₃ 61–90 DPD and Sᴰ Default. They are examples, not universal policy.
library(dplyr)
# Hypothetical buckets: replace only through controlled methodology.
portfolio <- portfolio %>%
mutate(state = case_when(
dpd == 0 ~ "Current", dpd <= 30 ~ "1-30",
dpd <= 60 ~ "31-60", dpd <= 90 ~ "61-90",
TRUE ~ "Default"
))
transitions <- current_snapshot %>%
select(account_id, state_t = state, ead_t = ead) %>%
inner_join(next_snapshot %>%
select(account_id, state_t1 = state), by = "account_id")The inner join is the analytical pair, not the reconciliation. New accounts, closed accounts and missing observations belong in explicit exception populations with counts and EAD; they must not silently disappear.
The transition matrix is the deterministic foundation
pᵢⱼ = Nᵢⱼ / ΣⱼNᵢⱼ ; Σⱼpᵢⱼ = 1
transition_counts <- transitions %>% count(state_t, state_t1, name = "accounts") transition_matrix <- transition_counts %>% group_by(state_t) %>% mutate(transition_probability = accounts / sum(accounts)) %>% ungroup()
Counts expose workload but scale with portfolio size. Row probabilities describe the destination distribution conditional on each starting state. Production checks should reconcile every row to approximately 100%, subject only to rounding.
| From / To | Current | 1–30 | 31–60 | 61–90 | Default |
|---|---|---|---|---|---|
| Current | ● 91% | → 6% | → 2% | → 0.5% | ◆ 0.5% |
| 1–30 | ← 36% | ● 42% | → 15% | → 5% | ◆ 2% |
| 31–60 | ← 12% | ← 18% | ● 41% | → 24% | ◆ 5% |
| 61–90 | ← 5% | ← 8% | ← 14% | ● 43% | ◆ 30% |
| Default | ← 2% | ← 1% | ← 1% | ← 3% | ◆ 93% |
Roll forward, roll back and cure are distinct
Under this ordered example, Sₜ₊₁ > Sₜ is roll forward: 1–30 → 31–60 is 15%, while 31–60 → 61–90 is 24%. Their incidence and economics differ. Sₜ₊₁ < Sₜ is roll back: 31–60 → 1–30 is 18%, while 1–30 → Current is 36%.
A roll back is not automatically a formal cure. One controlled hypothetical cure measure could require exit from delinquency and remaining Current for two subsequent observations. Sustained improvement, eligibility and any additional methodology must enter the engine as versioned configuration—not be inferred from one cell.
Baseline, persistence and velocity turn a matrix into monitoring
transition_diagnostics <- current_rates %>%
left_join(historical_baseline,
by = c("state_t", "state_t1")) %>%
mutate(deviation = transition_probability - baseline_probability)The baseline might be a selected historical median or another approved reference. It is controlled context, not permanent truth. Rising Current → 1–30, 1–30 → 31–60 and 31–60 → 61–90 alongside falling 1–30 → Current is more informative than one isolated cell: entry, progression and weakening improvement reinforce one another.
Velocityᵢⱼ,ₜ = pᵢⱼ,ₜ − pᵢⱼ,ₜ₋₁
The threshold c and horizon are not universal. One abnormal month may be noise; repeated deviation increases confidence that a change may be structural. A transition can be worse than baseline, continuing to worsen, or both.
- Direction
- Magnitude
- Persistence
- Materiality
- Investigation
Borrower incidence and exposure economics tell different stories
pᴱᴬᴰᵢⱼ = Σₖ∈ᵢ→ⱼ EADₖ / Σₖ∈ᵢ EADₖ
Account weighting gives each account equal influence and supports behavioural incidence, borrower counts and workload. EAD weighting asks what share of starting-state exposure migrated and can reveal concentration in a few large accounts.
ead_transitions <- transitions %>% group_by(state_t, state_t1) %>% summarise(transition_ead = sum(ead_t, na.rm = TRUE), .groups = "drop") %>% group_by(state_t) %>% mutate(ead_transition_rate = transition_ead / sum(transition_ead)) %>% ungroup()
The denominator is total observed ead_t in the starting-state row. In a hypothetical 31–60 → 61–90 transition, an 18% account rate and 31% EAD rate mean relatively fewer accounts deteriorated but carried disproportionately large exposure—materially raising investigation priority.
Controlled drill-down isolates where migration changed
The engine can compare product, vintage, risk grade, acquisition channel and relevant customer segment. Dimensions should be approved and analytically meaningful; unconstrained slicing creates multiple-comparison noise and fragile stories.
If the overall 31–60 → 61–90 rate worsens, a vintage breakdown may show concentration in the hypothetical 2026-03 cohort. Product analysis may show Pᵢⱼᴾʳᵒᵈᵘᶜᵗ A ≠ Pᵢⱼᴾʳᵒᵈᵘᶜᵗ B. The Agent can then distinguish a portfolio-wide change from a Product B cohort concentration.
Which cohorts deteriorate?
Through which states?
Cohort deterioration + state migration = richer portfolio diagnosis.
Move from an R script to a reusable migration engine
assign_state() pair_snapshots() calculate_transition_matrix() calculate_roll_rates() calculate_cure_rates() compare_to_baseline() calculate_persistence() compare_account_vs_ead() analyse_segment() rank_deteriorating_transitions()
These are conceptual interfaces, not claims of existing software. A structured output might be:
transition: 31-60 -> 61-90 current_rate: 0.24 baseline_rate: 0.16 deviation: +0.08 persistence_periods: 3 ead_affected: 18.4m largest_segment: Product B
Place an AI Collections Analyst above deterministic evidence
The Agent does not calculate probabilities from prose. It asks: what changed, where, for how long, how much exposure is affected, which segments or borrowers explain it, and which cases deserve attention first?
| Migration Engine | AI Collections Analyst |
|---|---|
| Assigns states | Interprets state movement |
| Calculates transitions and roll rates | Explains deterioration |
| Calculates controlled cure | Investigates weakening cure |
| Measures persistence and EAD impact | Synthesises material evidence |
| Returns structured evidence | Produces analyst-ready context and priorities |
get_transition_matrix() compare_transition_to_baseline() get_roll_forward_changes() get_cure_rate_changes() compare_account_vs_ead() breakdown_transition_by_segment() get_accounts_in_transition() rank_collection_priorities()
Never: Prompt → LLM-invented transition matrix.
Why has the 31–60 DPD portfolio deteriorated this month?
The Agent queries the matrix, baseline, persistence, account/EAD movement, product and vintage breakdowns, then the largest exposures. It returns evidence, not unsupported causality:
- Current / baseline
- 24% / 16%
- Deviation / persistence
- +8 pp / 3 periods
- EAD-weighted transition
- 31%
- EAD affected
- 18.4m
- Primary concentration
- Product B / vintage 2026-03
- Interpretation
- Concentrated, not portfolio-wide
- Next investigation
- Underwriting and collections behaviour
- Causality
- Not established
Current state and expected trajectory are different risk dimensions
Two borrowers can both be 30 DPD. Borrower A may have a high estimated probability of cure; Borrower B a high estimated probability of further deterioration. Therefore:
This is the bridge from portfolio migration monitoring to collections prioritisation. The same delinquency queue can contain materially different forward risk and potential value from intervention.
Current state
Observed delinquency position and operational status.
Forward risk
Controlled estimate of further adverse migration, not an LLM guess.
EAD
Exposure potentially affected and its concentration.
PD
Approved risk estimate used with its definition and horizon.
Persistence
Whether adverse behaviour or signals repeat across observations.
Priority
A governed ranking for investigation—not an automatic customer treatment decision.
No universal priority formula is imposed. Policy must define eligibility, weights, constraints, capacity and human authority. Forward migration risk may be estimated by an approved model or segment transition evidence; its version, horizon and validation status must remain visible.
Controls make assistance operationally credible
- Data reconciliation: account and EAD totals, duplicates, missing snapshots, new and closed populations.
- Methodology control: version state order, snapshot frequency, eligibility, cure, baseline, thresholds and weighting.
- Calculation tests: non-negative counts, complete destinations and probability rows reconciling to one.
- Lineage: retain snapshot, code, configuration, engine and tool-call versions behind every result.
- Access: account-level tools expose only authorised fields and log every query.
- Human authority: the Agent proposes priorities and investigation paths; analysts approve interpretation and collections action.
Backtest alerts: did persistent deviations precede later adverse outcomes, how much lead time existed, and how many false investigations were created? Monitor baseline drift and challenger configurations without optimising blindly to historical noise.
The resolve is recurring portfolio and collections intelligence
The finished path is Account snapshots → state assignment → transition pairing → matrix → baseline → deviation → persistence → exposure materiality → segment drill-down → collections priority → AI investigation → analyst action.
Methodology → R implementation → deterministic analytical engine → AI-assisted workflow. R preserves the arithmetic. The engine standardises evidence. The Agent investigates controlled outputs. The analyst owns decisions and intervention.