cswork / libraryOpen practice workspace

Interview guides · AF Group

AF Group Data Scientist Interview Questions & Guide 2026

Case practice for AF Group data scientist candidates: scope a vague prompt into a measurable question before you analyze it.

The 60-second brief

As a Data Scientist at AF Group, you are stepping into a pivotal role within the insurance and workers' compensation sector. Your work directly influences how the organization assesses risk, optimizes premium modeling, and improves outcomes for policyholders. You will not be operating in a silo; you will bridge the gap between complex data sets and actionable business strategies.

Most of the loop measures decision-making under uncertainty rather than recall. You are scored on whether you state your assumptions, commit to an estimate you can defend, and say explicitly what evidence would change it.

Focus areas

  • Report only matured cohorts for loss metrics
  • Decompose expected loss into PD, LGD, EAD
  • Set fraud thresholds by expected cost

Overview

This role requires a blend of technical rigor and business acumen. You will translate abstract business problems—such as detecting patterns in injury claims or predicting policy trends—into robust machine learning solutions. Because AF Group is a major player in the work-comp space, your models have real-world stakes, making your contributions both intellectually stimulating and highly impactful to the company’s bottom line.

Where to focus your preparation

Treat the sections below as preparation areas and confirm the format with your recruiter.

Preparation focus

No round sequence has been reported for this company, so work the categories below and confirm the format with your recruiter.

What to demonstrate

  • Breadth across SQL, experimentation and product reasoning
  • Ability to state assumptions before choosing a method

How to prepare

  • Drill the practice exercises below and time yourself
  • Prepare three quantified stories about decisions you drove

Where candidates lose points

Reading the most recent months of fraud and dispute rates as final

Consumer dispute rights commonly run around 120 days from the transaction or expected delivery date, and several reason codes run considerably longer, so the disputes belonging to a recent transaction month have simply not been filed yet. Any chart attributed by transaction date therefore slopes down at the right edge regardless of what is happening. The fix is to report only matured cohorts, or to apply development factors estimated from completed months and to show the estimate as an estimate.

Averaging delinquency across a book that is growing

A loan three months old cannot be 90 days past due, so a portfolio with many recent originations reports a low blended 90+ rate purely from age mix. The blended rate falls fastest exactly when originations grow fastest, which is precisely when credit quality most needs watching, so the metric moves in the reassuring direction during the riskiest period. Only comparisons at equal months on book are valid, which is what a vintage or roll-rate view enforces.

Reaching for a model before the target metric exists

Before naming an algorithm, write down the label, the prediction time, and the action that changes when the score crosses a threshold. If you cannot say what decision the output drives, any modelling choice is guesswork dressed up as method.

Ignoring interference between units in a marketplace experiment

Ask whether one unit's treatment can change another unit's outcome through shared inventory, a matching pool, a social graph or a common budget. Where it can, randomise at a level that contains the spillover, such as region or time slice, and say explicitly what that costs you in statistical power.

Questions & practice

Explain how you would select precision versus recall for a disease detection model.

Medium · machine learning and modelling

Approach

  1. Pick an evaluation metric that matches the cost of each error type, not a default.
  2. Say how the offline result would be validated online before it is trusted.
  3. Set a baseline first, so any model has something honest to beat.

Follow-up

  • What would you monitor after launch to know the model is still valid?
  • How would you choose the decision threshold, and who owns that choice?

Describe your process for feature selection when working with highly correlated variables.

Medium · machine learning and modelling

Approach

  1. Check what information would not exist at prediction time, and exclude it.
  2. Say how the offline result would be validated online before it is trusted.
  3. Pick an evaluation metric that matches the cost of each error type, not a default.

Follow-up

  • What would you monitor after launch to know the model is still valid?
  • How would you choose the decision threshold, and who owns that choice?

How do you validate a model before moving it into production?

Medium · machine learning and modelling

Approach

  1. Set a baseline first, so any model has something honest to beat.
  2. Pick an evaluation metric that matches the cost of each error type, not a default.
  3. Say how the offline result would be validated online before it is trusted.

Follow-up

  • What would you monitor after launch to know the model is still valid?
  • How would you choose the decision threshold, and who owns that choice?

Measure calibration of a twelve-month default probability from scratch

Hard · calibration · brier score · binning

fct_loan_application gives application_id, model_pd_12m, model_version, decision, funded_at and loan_id. fct_loan_performance_monthly gives loan_id, months_on_book, days_past_due and charge_off_flag. Define the outcome as ever 90 or more days past due, or charged off, by months_on_book = 12. Without sklearn or scipy, build an equal-count binned reliability table, the expected calibration error, the Brier score and its reliability, resolution and uncertainty components, and report the residual the binned identity leaves behind. Restrict to cohorts that have actually reached 12 months on book.

Approach

  1. Build the label first and name the population it covers out loud: only funded loans have outcomes, so this measures calibration on the approved population. The declined region is unmeasured, and no binning scheme repairs that.
  2. Restrict to applications whose loans have reached months_on_book = 12. A cohort observed at 8 months has a mechanically lower default rate and will read as systematic over-prediction that is really just immaturity.
  3. Bin by equal count, deciles of model_pd_12m through a rank-based cut, not equal width. The PD distribution is heavily right-skewed, so equal-width bins put most of the mass in the first bin and leave the risky bins with single-digit counts whose observed rates mean nothing.
  4. Per bin compute n, mean predicted, observed rate, and the binomial standard error sqrt(o(1-o)/n) so a gap can be read against noise. ECE is the count-weighted mean absolute gap between mean predicted and observed.
  5. Compute Brier directly as the mean squared error, then reliability = sum of n_k (pbar_k - obar_k)^2 over N, resolution = sum of n_k (obar_k - obar)^2 over N, uncertainty = obar(1 - obar). Report residual = Brier - (reliability - resolution + uncertainty). That identity is exact only for discrete forecasts, so with binned continuous scores the residual is the within-bin spread of the score; a large one means the bins are too wide to support the decomposition.
  6. Split by model_version. A mixed-version population can look well calibrated in aggregate while each version is biased in opposite directions.

Worked solution (45 min)

  1. Reduce fct_loan_performance_monthly to one row per loan_id with the maximum days_past_due and any charge_off_flag over months_on_book 0 to 12, plus the maximum months_on_book observed, and keep only loans reaching 12.
  2. Inner-join to approved and funded applications, and record how many approved applications were dropped for immaturity and how many decisions were declines that never enter the measurement at all.
  3. Assign deciles with a rank-based cut on model_pd_12m, then aggregate n, mean predicted, observed rate and standard error per bin.
  4. Compute ECE, Brier, reliability, resolution, uncertainty and the residual, and print all six.
  5. Repeat the whole computation split by model_version and compare the per-version reliability against the pooled figure.

Expected result: A ten-row reliability table with n, mean predicted, observed and standard error; an ECE of a few tenths of a percentage point to a couple of points; a Brier score close to the uncertainty term, because a rare-event model has little resolution to subtract; and a residual small relative to the reliability term.

Verify it yourself

  • Bin counts sum to the filtered row count, and the count-weighted mean of bin observed rates equals the overall observed rate.
  • Uncertainty equals obar(1 - obar) computed directly, and reliability is zero on a synthetic input where observed is set equal to predicted in every bin.
  • The residual shrinks as the bin count rises from 5 to 20; if it does not, the label or the binning is wrong.
  • Multiplying every PD by 1.5 must raise reliability and ECE while leaving resolution, uncertainty and AUC unchanged, since equal-count bins are invariant to a monotone transform.

Follow-up

  • AUC is unchanged after a population shift but the reliability curve has moved. What happened, and what do you do about it?
  • How would you recalibrate without retraining, and what would you check afterwards?
  • The top decile shows observed default well above predicted. Is that a calibration problem or a policy problem?

Vintage ninety-plus rate at twelve months on book

Medium · vintage analysis · window functions · right censoring

From fct_loan_performance_monthly, build a vintage table keyed on origination_month: the share of each cohort that ever reached days_past_due of 90 or more, or charge_off_flag = true, at or before months_on_book = 12. Restructuring resets days_past_due, so for any loan with restructured_flag true, evaluate only the month ends strictly before its first restructured month. Return origination_month, loans_funded, bad_loans and bad_rate. Exclude any cohort that does not yet have a months_on_book = 12 observation for every loan still on book.

Approach

  1. Establish the cohort denominator from the first month end each loan appears at, months_on_book = 0, so a loan is counted once in its origination_month rather than once per monthly row.
  2. Find each loan's first restructured month with MIN(as_of_month_end) FILTER (WHERE restructured_flag) OVER (PARTITION BY loan_id), or the equivalent grouped subquery, and keep it null for loans never restructured.
  3. Flag a loan bad if any row with months_on_book <= 12 and as_of_month_end earlier than that first restructured month has days_past_due >= 90 or charge_off_flag = true, which is what 'pre-restructure worst state' means in practice.
  4. Gate maturity by requiring the cohort's newest month end to be at least 12 months after origination_month, and report immature cohorts as incomplete rather than letting them appear at a flattering low rate.
  5. Aggregate to one row per origination_month and read the column downward, not across calendar time, because the whole point is comparing cohorts at equal age.

Follow-up

  • Should a restructure inside 12 months count as bad in its own right? Argue both sides and say what you would actually ship.
  • A loan that prepaid in full at month 4 never had a chance to go 90 days past due. In or out of the denominator, and why?
  • The 2025-11 cohort is two points worse at month 12 than its neighbours. What three queries do you run before you call it a credit-quality change?

Count-weighted and dollar-weighted approval rates on one currency

Easy · aggregation · denominators · minor units

Using fct_payment_authorization, report the trailing 7-day authorization approval rate two ways for transaction_currency = 'EUR': count-weighted, and dollar-weighted on amount_minor. Exclude is_reversal = true, exclude incremental authorizations (parent_auth_id not null), and exclude zero-amount account verifications. auth_result = 'approved' is the numerator; the four declined_* values make up the rest of the denominator. Return channel, attempts, approved_attempts, approval_rate_count and approval_rate_value. State every exclusion and its reason before you write the SELECT.

Approach

  1. Say the denominator out loud first: attempts on a single transaction currency, excluding reversals, incremental authorizations and zero-amount verifications, because none of those is a purchase attempt a merchant is trying to get approved.
  2. Filter requested_at against a half-open interval (>= start AND < end) so the boundary day is neither dropped nor double counted.
  3. Compute both rates in one pass with FILTER clauses: COUNT() FILTER (WHERE auth_result = 'approved') over COUNT(), and SUM(amount_minor) FILTER (WHERE auth_result = 'approved') over SUM(amount_minor).
  4. Cast one side of each ratio to numeric before dividing, since amount_minor and the counts are integers and integer division silently truncates to zero.
  5. Group by channel and sort by the value-weighted rate, then read the gap between the two rates as a statement about where the declines sit rather than as noise.

Worked solution (20 min)

  1. Write the exclusion list as comments above the query: is_reversal = false, parent_auth_id is null, amount_minor > 0, transaction_currency = 'EUR'.
  2. Build a single aggregate query over fct_payment_authorization with a half-open requested_at predicate and those four filters.
  3. Emit attempts, approved_attempts, approval_rate_count and approval_rate_value with FILTER clauses and a numeric cast on the numerator.
  4. Group by channel, order by approval_rate_value ascending so the worst channel is on top.

Expected result: One row per channel present in the window, with attempts and approved_attempts as integers and both rates as numerics strictly between 0 and 1. approval_rate_value is typically lower than approval_rate_count on ecommerce and moto, because declines concentrate in higher-value card-not-present traffic.

Verify it yourself

  • SUM(attempts) across channels equals a separate COUNT(*) run with the same four filters and no GROUP BY.
  • Dropping the amount_minor > 0 filter raises attempts and moves approval_rate_count while leaving approval_rate_value unchanged, which confirms the zero-amount rows were only ever affecting the count-weighted figure.
  • Both rates are strictly less than 1 unless a channel had no declines at all in the window.

Follow-up

  • The two rates diverge by four points on the ecommerce channel but agree on card_present. What does that tell you, and what would you cut next?
  • How would you extend this to all currencies without summing amount_minor across them?
  • Which of the four decline reasons belong in the denominator of a rate you would put in front of a risk team, and which are really the network's problem?

What are the trade-offs between interpretability and predictive power in insurance models?

Medium · product sense and metrics

Approach

  1. Restate the decision this analysis has to support, and who acts on the answer.
  2. Fix the population and the time window before naming any metric.
  3. State what result would change your recommendation, so the answer is falsifiable.

Follow-up

  • What would you do if the primary metric and the guardrail moved in opposite directions?
  • How would you detect that the metric is being gamed rather than genuinely improving?

Test a referral rule when reviewers are a shared queue

Medium · interference · switchback · cluster randomisation

A proposed risk rule raises the share of ecommerce authorizations routed to manual review. Reviewers work a shared pool of queues serving both arms, so extra referrals from treatment lengthen the wait for control cases too. The current plan randomises by customer_id and reads decision latency plus matured fraud basis points. Explain why that design is biased and in which direction, propose a design that is not, and say what governs its power.

Approach

  1. Name the violated assumption precisely: a unit's outcome depends on other units' assignments through the shared reviewer capacity, so the stable unit treatment value assumption fails. Customer-level randomisation then estimates a contrast between a degraded treatment and a degraded control, not between treatment and the status quo.
  2. State the direction. Treatment pushes work into the shared queue, control absorbs part of that wait, so the measured latency difference understates the true effect of full rollout. The test can look acceptable while the rolled-out state is materially worse, which is the expensive failure mode here.
  3. Move randomisation up to a unit that contains the interference. Either cluster-randomise whole queues or sites, or run a switchback that flips the rule for an entire queue over time blocks. Switchback is usually the better choice here because queue count is small and each queue serves as its own control, removing between-queue heterogeneity.
  4. Specify the switchback concretely. Block length should be several times the queue sojourn time; discard a burn-in after each switch equal to the 95th percentile sojourn so carryover cases from the previous regime do not contaminate the new block; balance assignment within each day so the daily arrival pattern cannot correlate with arm.
  5. Analyse at the randomisation unit. Aggregate to queue-block means, include queue and time-block fixed effects, and use randomisation inference or a wild cluster bootstrap rather than cluster-robust standard errors, which are anti-conservative with few clusters. Check residual autocorrelation between adjacent blocks; if it is material, widen the blocks or model it.
  6. Separate the two readouts by maturity. Latency and referral precision at case close are available inside the test window; matured fraud basis points require at least 120 days of dispute maturity from the transaction month, so it is a deferred confirmatory read and must not be presented as a low number on immature cohorts.

Worked solution (30 min)

  1. Write the interference channel explicitly: reviewer capacity is fixed per queue per hour, arrivals are the sum of both arms, so control's wait is a function of treatment's assignment.
  2. Choose a queue-by-four-hour-block switchback across 14 queues for 30 days, giving 14 * 6 * 30 = 2,520 blocks, with within-day balanced assignment and a burn-in equal to the 95th percentile sojourn discarded at each switch.
  3. Compute power at the block level. With a residual block-level standard deviation of decision latency of 6 minutes after removing queue and day-of-week fixed effects, and half the blocks per arm, MDE = 2.8016 * sqrt(2 * 36 / 1260).
  4. State the caveat that adjacent blocks are autocorrelated, so the effective block count is below 2,520 and the analytic MDE is a floor; estimate the inflation from historical block-to-block autocorrelation before committing.
  5. Split the readout: latency and referral precision as the in-window primary, matured fraud basis points as a deferred read at 120 days with immature months marked incomplete rather than plotted.

Expected result: A queue-by-time-block switchback with burn-in exclusion and within-day balanced assignment, analysed on block means with queue and time fixed effects and randomisation inference. With 2,520 blocks and a 6-minute residual block standard deviation, the analytic MDE on decision latency is 2.8016 * sqrt(72 / 1260) = 2.8016 * 0.2390 = 0.67 minutes, stated as a floor pending the autocorrelation correction. Power is governed by block count and between-block residual variance, not by transaction volume.

Verify it yourself

  • An A/A switchback on historical data should produce block-level differences centred on zero with a standard deviation matching the residual block standard deviation used in the power calculation.
  • Plot the effect against time since the last switch. If it is not flat after the burn-in window, the burn-in is too short and carryover is still present.
  • Confirm that arm assignment is orthogonal to hour of day and day of week in the realised schedule, since queue load is strongly periodic.
  • Cluster count, not row count, should appear in the degrees of freedom of the final inference; if the standard error implies thousands of independent observations, the analysis is at the wrong grain.

Follow-up

  • How do you set the block length when the queue sojourn time itself changes under treatment?
  • You have 14 queues and 30 days. Compare a queue-level cluster design against a queue-day switchback on power and on what each can estimate.
  • The shared resource is capacity. What happens to your estimate if reviewers work faster when the queue is long?

Manual review rate doubles on the day two changes ship

Hard · definition change · model version · population stability

Manual review rate on fct_loan_application, defined as decided_by = 'manual' over decisions in ('approve','decline'), stepped from 9 to 18 percent on a single day. That day a new model_version went live, and the same release changed which referral paths are recorded as manual. Columns: submitted_at, channel, bureau_score, model_pd_12m, model_version, policy_rule_hits, decision, decided_by, decision_at. Attribute the step across definition, model and population, with a number against each.

Approach

  1. Establish the shape before the cause. A definition or code change produces a step at the deploy timestamp; a population change produces a ramp. Plot at hourly granularity either side of the release and read which one this is.
  2. Dual-run the definition. Recompute the metric under both the old and the new recording rules across several months of history. If the historical series under the new rule sits near 18 percent throughout, that part of the step is definitional and should be restated, not investigated.
  3. Hold the model constant and decompose the residual by policy_rule_hits identifier. A referral rule keyed to a score threshold shows its own step, and naming the rule is what makes the finding actionable to the policy owner.
  4. Test the score, not the applicants. Compare model_pd_12m distributions by model_version on the same applications where shadow scores exist; otherwise compare adjacent weeks with a population stability index, treating the customary 0.1 and 0.25 marks as heuristics rather than tests, and assess calibration separately from ranking.
  5. Test the applicants, not the score. Compare channel mix, bureau_score distribution and null-bureau share across the boundary. If those are stable, population is not a driver and should be reported as approximately zero rather than left unquantified.
  6. Deliver three components that sum to the observed step, each with the team that owns it.

Follow-up

  • If the new model is better calibrated, is a higher referral rate the correct outcome or a threshold nobody retuned?
  • How would you have caught the recording change before it reached the dashboard?
  • What is the swap set here, and which swap group would you inspect first?

Suggested practice checklist

Roughly 90 minutes a night on weekdays with one longer weekend block. The plan deliberately cuts scope rather than compressing everything, on the assumption that finishing one thing a night beats half-starting four.

Day 1 — Fix the scope and set a baseline

  • Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list for everything else and keep it visible all week.
  • Take one 20-minute SQL prompt and one 10-minute metric question cold, and write the single sentence that says what blocked each attempt, since that sentence is what decides which two topics get the most evenings.
  • Set the week's one rule: one problem finished to completion every night, including the night you only have 40 minutes.

Deliverable: A one-page scope with an explicit not-doing list and two cold attempts, each carrying one sentence on what blocked it.

Day 2 — One query pattern, written three times

  • Choose the single pattern most likely to appear (a cohort retention grid, or a funnel counted by user) and write it three times from a blank file rather than editing the previous attempt.
  • On the third attempt, write the grain of every CTE as a comment before writing its body.
  • Stop at 90 minutes even if the third version is imperfect, and write the one thing you would fix with another hour.

Deliverable: Three independent versions of the same query plus a note on what changed between them.

Day 3 — Only the statistics you will be asked to defend

  • Write, in under 200 words, how you would decide whether a difference between two groups is real: the test, its assumptions, and what you would switch to when an assumption fails.
  • Compute a 95 percent confidence interval for a difference in proportions by hand on realistic numbers, then write in one sentence what changes if the two samples are paired rather than independent.
  • Write your answer to "what does a p-value mean", check it against a definition, and delete the version that describes it as the probability the hypothesis is true.

Deliverable: A 200-word written answer and one hand-computed interval you can reproduce under pressure.

Day 4 — One case, and the assumptions holding it up

  • Answer one product case aloud in 20 minutes with a recording running, then listen back with a pen and mark every claim you asserted without saying what it rested on: an assumed user behaviour, an assumed data source, an assumed baseline rate, an assumed grain.
  • Pick the three assumptions the recommendation actually depends on, write how you would check each one against data, and say which one being wrong would flip the recommendation rather than merely weaken it.
  • Write the four-step structure you used onto a card small enough to hold in working memory when you are nervous.

Deliverable: One recording, three load-bearing assumptions each with a written check, and a four-step structure card.

Day 5 — Your own work, timed

  • Write a 90-second version and a four-minute version of your main project, and time both out loud rather than reading them.
  • Prepare answers to the two follow-ups that always come: what you would do differently, and how you knew it worked.
  • Put one number in the first sentence and be able to say exactly where that number came from and what it excludes.

Deliverable: Two timed narratives with one defensible number in the opening line.

Day 6 — The one full rehearsal, in a longer weekend block

  • Run a 60-minute mock covering query work, a case and a behavioural question in a single sitting with no breaks, because sustained attention is the thing evenings have not trained.
  • Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
  • Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.

Deliverable: Mock notes naming three failure moments with a specific fix written under each.

Day 7 — Taper

  • Write the 20-minute warm-up you will actually do on the morning of the interview: one query you can already write from a blank file, one metric you can define out loud, and nothing you have never seen before.
  • Re-read only your own notes from this week, and open no new material.
  • Write down the logistics: the tool you will be asked to work in, whether lookups are allowed, and the sentence you will use when you do not know something.

Deliverable: A one-page card holding the case structure, the project numbers, and the logistics.

What the behavioural round is actually scoring

Interviewers here are not checking whether you can describe a project. They want the decision you made, why you made it under the information you had, and what changed afterwards that someone else could measure. A story that ends at 'I built a model' has no ending. Say what the model caused, or what you stopped doing because of it.

How do you handle imbalanced datasets in the context of premium modeling?

Medium · behavioural and stakeholder questions

Approach

  1. Name the disagreement or constraint, and how you resolved it with evidence.
  2. Close with what you would do differently, concretely.
  3. Pick a story where you drove the decision, not one where you observed it.

Follow-up

  • What would you do differently if you ran that project again?
  • What did you decide not to do, and why?

Recommend a decision whose true outcome matures a year later

Hard · decision under censoring · leading indicators · staged rollout

An underwriting rule change must be decided in six weeks. Its real outcome, the vintage 90-plus rate at months_on_book 12 in fct_loan_performance_monthly, matures in a year. The executive wants a yes or no, not a range. Randomising the credit decision across the whole population is not available. Name the leading indicator you would accept, state its bias and the direction of that bias, define the decision rule and stopping condition before any rollout starts, and say what reading would make you recommend reversing the change.

Approach

  1. Fix the readout before the rollout, because a readout chosen after the data arrives is a story rather than a decision rule: indicator, window, threshold and reversal condition all go in writing first.
  2. Choose the leading indicator on its measured relationship to the matured outcome in historical vintages rather than on availability. Early delinquency, typically the share reaching dpd_1_29 or missing a first scheduled payment by months_on_book 3, is the usual candidate, and you quantify how well it predicted the 12-month rate across past cohorts.
  3. State the bias and its direction plainly: early delinquency under-represents default that emerges later and is contaminated by servicing and payment-date effects, so treat it as a floor on risk rather than an estimate of it.
  4. Buy identification where full randomisation is unavailable: a narrow randomised approval band around the cutoff, or a staged rollout by channel or region read as a difference-in-differences, with the parallel-trends assumption stated and checked in the pre-period rather than assumed.
  5. Give the executive the binary they asked for with the trigger attached in the same sentence: yes, conditional on the month-3 indicator staying inside a stated band, with an automatic hold if it breaches.

Follow-up

  • How would you validate that the month-3 indicator predicts the 12-month outcome, and what evidence would invalidate it mid-rollout?
  • Compliance refuses a randomised band. What is your next-best identification strategy, and what precision do you lose by taking it?

Defend a vintage finding that contradicts the portfolio dashboard

Medium · vintage analysis · mix shift · stakeholder pushback

The lending dashboard shows blended 90-plus days-past-due falling for four consecutive quarters while originations grew 60 percent. Using fct_loan_performance_monthly, you build a vintage view keyed on origination_month by months_on_book and find the three most recent vintages are worse than their predecessors at the same age. The business lead presents that dashboard weekly and pushes back hard, suggesting you picked favourable cohorts. You get one meeting and the vintage table. Present the finding so it survives the cherry-picking objection and ends in a decision.

Approach

  1. Reconcile before you contradict: show that aggregating your vintage table along the calendar diagonal reproduces the published blended series, so the disagreement is about age mix rather than about data quality.
  2. Make the mechanism arithmetic rather than rhetorical: a loan cannot reach 90 days past due before it is 90 days old, so rapid origination growth shifts weight onto young months-on-book where the rate is structurally near zero.
  3. Show every vintage rather than a selected pair, all indexed at months_on_book equal to 12, with cohort sizes printed beside each curve so nobody can claim the divergence rests on a thin cohort.
  4. Handle restructuring explicitly, because restructured_flag resets days_past_due: count each loan on its worst pre-restructure state, or recent vintages will look better than they are.
  5. Close on the decision rather than the chart: state what the divergence implies for the cutoff or the channel mix, and state in advance what evidence would make you withdraw the claim.

Follow-up

  • Two cohorts differ at month 12. How do you separate a seasoning effect from a genuine credit-quality effect?
  • Someone argues the recent vintages are simply a broker-channel mix shift. How do you test that, and what would confirm it?