scr_bands() cuts a score into percentile or tail bands
frozen on a reference sample and reports, per band and sample, the event
rate with a Jeffreys interval, lift, capture, KS, WOE, IV, PSI and a
one-sided Fisher exact test of rank order, plus the AUC, Gini and KS
with a bootstrap interval. Accepts a scorecard, a data.frame (with
weights, a value column and sample labels) or pre-aggregated
counts.scr_tiers() fits 2 to 9 tiers (labeled from “low” to
“high”, with words for 2 to 7 tiers) by an exact dynamic program under
share, event and distinctness constraints, by event-rate anchors or by
equal shares; round_to gives policy-friendly cuts, and
n_boot measures the stability of the cuts. An infeasible
tier count falls back and is recorded in a ledger.scr_rag() lights discrimination, calibration, stability
and, for a scorecard, the variables red, amber or green, per period or
segment with by; the thresholds are an editable table from
scr_rag_plan().scr_apply(), scr_sql() and
scr_export() assign the bands or tiers in R and in SQL and
write the study to a workbook. Tier labels carry their order in
production ("01.very high" for the tier with the highest
event rate, down to "05.very low"), so they sort from the
event-richest tier; the tiers table has the same value in
tier_label, and numbered = FALSE gives the
plain labels.scr_claims() tests statements such as “rate >= 60%
for scores of 625 or more” on a band, a tier or a score range, with an
exact one-sided binomial test, a one-sided Jeffreys bound and a Holm
adjustment, and writes each verdict (“supported”, “refuted” or “not
proven”) as one sentence. type = "floor" tests the weakest
end of the group under a monotone fit of the rate instead of its
average.scr_operating() finds the cut that maximizes the value
of targeting, alerting or approval under volume, share, budget,
event-rate and daily capacity constraints, and reports the binding
constraint and its shadow price.scr_score_cross() crosses two scores on the same rows:
the table of their bands with one or two outcomes, Spearman’s rho and
Kendall’s tau-b, and the overlap of the rows each selects, with the
swap-in and swap-out rates.scr_export(); the
vignette “Score studies” walks through them on credit, fraud and churn
scores.scr_mix_shift() splits the change of the event rate
between two samples, or between each period and a base, into a mix
effect and a rate effect per band; the effects add up to the change
exactly.scr_segments() reads one score on many segments: AUC
with the DeLong standard error and a test of equal AUC, observed against
expected events, the log-odds offset and the slope ratio, with a
suggested action per segment.scr_maturity() estimates the cumulative incidence of
the event by band and horizon under censoring (Kaplan-Meier with
Greenwood’s variance), and the discrimination at each horizon.scr_uplift() reads a score on a treated and a control
group: uplift per band with the Newcombe interval, the Qini coefficient
and the AUUC with bootstrap intervals, and a randomization check.scr_overlap() compares rule flags with the alerts of a
score: what each catches, the incremental recall of one over the other
and the rules the score makes redundant.scr_detection() measures, per alert threshold, the
fraud episodes detected, the fraudulent transactions and the time before
the first alert, and the loss prevented.scr_export().study_bands, study_level,
tier_min_pct, tier_min_events and
tier_max_bins.scr_pd_validate() gives the light "grey"
to a row without a testable result (a missing p-value) instead of
NA, and the overall light is "grey", not
"green", when no row has a testable result.
scr_ead_validate() does the same and adds the overall
light. In scr_lgd_validate(), a summary row
over pools or drivers that are all grey is now grey, not green.scr_monitor() and the CSI timelines of
scr_export() compute the CSI as scr_psi()
does: a bin empty in both samples is left out of the smoothing and of
the degrees of freedom of the adjusted threshold, and the CSI and its
critical value are missing when the period has no rows in the bins or
fewer than two bins are populated. Other results are unchanged.scr_strategy() freezes crossing$cut on the
training scores, like the bands: midway between the training scores on
either side of the band edge of the crossing.
score >= cut, the convention of
scr_cutoff(), then reproduces the split of the bands on
train and on any score seen in training, so rows at a band edge seen in
training no longer change side between the two functions.
breaks given as a number of intervals now also gets a cut,
taken on the evaluated sample.scr_scorecard() test each
band against the previous one with a one-sided Fisher exact test. The
binomial test used before took the previous band’s rate as known and
flagged too many breaks when that band was small.scr_scorecard() says when ties in the training score
give fewer score bands than score_groups.scr_metrics() draws its bootstrap on the counts per
score value when the score has many ties (at most one distinct value for
every ten rows: scorecard points, a grade scale, a WOE score on a large
sample), so the cost of a resample follows the number of distinct
scores, not of rows. The intervals of such scores keep their
distribution but change for a given seed, wherever they are reported;
scores with fewer ties keep the row resampling and their intervals. The
band tables of the scorecard, scr_strategy(),
scr_reject() and scr_psi() assign the bands as
integer indices, without a factor per row. The DeLong standard error of
scr_pd_validate() is computed from the counts and can
differ in the last digits (about 1e-16). No other result changes.scr_strategy() reports the event and non-event
distributions of each band (pct_event,
pct_nonevent, odds_event and
log_odds, the band WOE) and the score where they cross
(crossing); rule = "crossing" sets the
decisions at that boundary.scr_strategy() follows the objective of the scorecard:
under propensity the table starts at the most likely band,
revenue_good is the revenue of an event and the decisions
are "target", "review" and
"skip". Credit and fraud results are unchanged.scr_score_gains() adds pct_event,
pct_nonevent and woe. Under
higher_is_riskier, odds and
log_odds are now events per non-event, the orientation of
the scale, so log_odds rises with the score in both
directions.Variable_Gains_IV sheet of
scr_export() add pct_event and
pct_nonevent per bin.utilisation, realised and the
"grey" traffic light).scr_lgd_downturn() and scr_ead_downturn()
estimate the observed downturn impact, and the LGD reference value, on
the training rows only, like the long-run averages; the hold-out stays
independent evidence.scr_export(), scr_sql(file = ) and the
classing lab functions follow the verbose key of the
object’s configuration, as the function that fitted it does.IV_RATIO_UNSTABLE, an advisory
warning, when the train IV of a proposal is below iv_min
and the hold-out/train IV ratio carries little information.scr_irb_params() lists the regulatory texts behind the
presets; users check the tables against the texts in force before any
regulatory use.inst/cheatsheet (PDF
and its HTML source).The IRB layer: from the scorecard to regulatory risk parameters, with the same contracts as the scorecard pipeline (one configuration, ledgers with mandatory reasons, hold-out revalidation with frozen bins, hardened workbooks, production SQL verified against DuckDB and SQLite). Regimes are parameter tables selected by a preset, never prose.
scr_irb_params() ships the numbers of three presets
("bcb", "basel3_final", "crr3"):
PD floors, LGD input floors, foundation LGD, standardised CCFs, asset
correlations, maturity rules, output floor and standardised risk
weights; the tables are editable and edits are recorded.scr_default() builds the default flag from a monthly
panel (days past due with absolute and relative materiality,
unlikeliness to pay, probation, restructuring, obligor-level pulling
effect); scr_default_rate() gives the default rates by
cohort, grade, segment and exposure, with the long-run average and its
benchmark.scr_bin_continuous() bins drivers against a bounded
continuous target (LGD, CCF) and returns an object with the shape of the
engine’s, so OptimalBinningWoE::obwoe_apply() and
obwoe_sql() reproduce the bin means in R and in every SQL
dialect; hold-out revalidation with frozen cut points and PSI.scr_config() gains the keys of stages 8 to 12
(default_*, pd_*, lgd_*,
ccf_*, framework, capital_*,
ecl_*), all registered in scr_config_keys()
and validated.scr_demo_panel, scr_demo_lgd,
scr_demo_lgd_cashflows, scr_demo_rates,
scr_demo_ead and scr_demo_portfolio are new
demonstration data.scr_master_scale(), scr_calibrate()
(intercept shift, log-odds (a, b), scaling, quasi-moment
matching; a new alignment, the scorecard untouched),
scr_grades() (geometric, quantile or supplied grades,
merges below the minimum counts, monotone repair recorded),
scr_moc() (estimation error computed; other categories with
a mandatory reason), scr_pd() (floors from the preset),
scr_migration(), scr_pd_validate() (Jeffreys,
binomial, normal, Hosmer-Lemeshow, multi-period, AUC against the initial
value, PSI, migration bandwidths, concentration; traffic lights),
scr_pd_pit_ttc(), with predict(),
scr_apply(), scr_sql() (grade and PD as a
CASE on the score) and scr_export()
methods.scr_workout() (discounted recoveries and costs,
cures, merged re-defaults, extrapolated incomplete workouts, named
funnel rules), scr_lgd() (cure stage on the binary engine,
severity stage on the continuous binner with a fractional logit or a
beta regression, hold-out revalidation, pools),
scr_lgd_downturn(), scr_lgd_floor(),
scr_elbe(), scr_lgd_validate(), with
scr_apply(), scr_sql() (both stages, pool
CASE, floored result) and scr_export()
methods.scr_ead_data() (realised conversion factors under
a fixed, cohort or variable horizon; conversion factor below and limit
factor above a utilisation threshold; named funnel rules),
scr_ead() (driver bins with admission rules, pools,
estimation-error margin, standardised floor),
scr_ead_downturn(), scr_ead_validate(), with
scr_apply(), scr_sql() and
scr_export() methods.scr_el(),
scr_irb_rw() (the risk-weight function with correlations,
size adjustment, maturity, floors and the defaulted case),
scr_sa_rw(), scr_capital() (reconciliation by
segment, output floor, provisions shortfall and excess, floors impact,
sensitivity grid, concentration), scr_pd_stress(),
scr_ecl() (survival-weighted 12-month and lifetime expected
credit loss with stages and scenarios), with scr_sql()
(constants per pool, no normal quantile at run time) and
scr_export() methods.scr_irb_rw() and scr_capital() read the
supervisory LGD of the foundation approach from
params$lgd_firb through a claim type;
scr_sa_rw() and scr_capital() apply the
non-granular retail weight with granular = FALSE; the
Hosmer-Lemeshow light of scr_pd_validate() is green when
every grade sits on the conservative side (the PD above the observed
rate), since the statistic is two-sided.betareg (Suggests) powers the beta severity engine of
scr_lgd().scr_sql() on a scorecard gains
what = "all" (bin label, WOE and points of every variable
next to the exact score and the whole-points score) and
keep_columns (key columns carried into the output), for a
deployment that reports the band of each variable with the score.scr_iv() ignores
NA for every group type; scr_classing_read()
validates the separator and the spec carries it into
scr_classing_import(); the TOO_MANY_BINS
screening rule can fire (the screen reads max_bins);
scr_psi() stores and prints its thresholds;
scr_default_rate() reports one long-run mean and benchmarks
an optional lra_adjusted; one asset_class
configuration key replaces pd_asset_class and
capital_asset_class; scr_lgd_downturn() always
records a reason; the traffic-light convention is red at or below the
first threshold in PD, LGD and EAD; scr_apply() on an
scr_ead takes what; scr_fetch()
gains verbose and scr_run() follows
config$verbose; the scr_demo columns carry
English names (vl_partial_*, vl_noise_*,
vl_constant, vl_near_const,
vl_duplicate, vl_redundant,
vl_late, ds_region, ds_band,
ds_channel, ds_high_card).data.table) and read
the columns without copying them. The kernels are tested against their
reference R implementations.scr_bin() (Pearson or Spearman):
each WOE column is ranked once and the correlation matrix comes from one
BLAS cross-product, instead of ranking both columns of every pair. The
greedy sweep gives exactly the same result as
OptimalBinningWoE::obwoe_prune(), and is about 50 times
faster at 150 columns.O(n log n) (Knight’s algorithm). It replaces an
O(n^2) Kendall computation inside a 200-resample bootstrap.
The EAD version is also exact now: it no longer groups the prediction
into 60 quantile buckets.scr_ecl() streams the survival-weighted loss row by row
and applies the scenario shocks on the fly. Memory is O(n)
whatever the term (the matrix version built several n x T
copies, about 2.9 GB each at n = 1e6, T = 360). Results are
the same to 1e-15.scr_metrics() ranks the scores once; each bootstrap
resample then re-tabulates counts, with no sort and no grouping by a
double key. The cut-off sweep sorts once per sample. The monitor
tabulates the base once for every period. Default rates by cohort use
one rolling join. Every LGD cash-flow aggregation and EAD reference date
is vectorised.config$nthread.scr_triage() and the R pre-processing of
scr_apply() reserve column slots before adding columns, so
they no longer fail past about 1024 columns..Random.seed is restored on exit, and a bootstrap advances
the user’s stream only by the replicate seeds it draws. Results for a
given seed are unchanged.scr_split():
integer64 columns
are read correctly;scr_bin(): under
allow_derived_final = FALSE, derived flags leave before the
redundancy pruning, so a flag can no longer remove a real column. The
Rcpp subset-proxy warnings of
OptimalBinningWoE::obwoe_gains_score() are muffled; its
values are correct, and the fix belongs upstream.scr_config() validates every key of stages 0 to 7.min_sum_hessian_in_leaf. The xgboost
API is detected from xgb.train() (it works with xgboost
3).obwoe_sql() does. A row that falls in no fitted bin
takes the points of WOE 0, in R and in SQL.scr_metrics() refuses a factor or a non-0/1 outcome,
and counts are kept in double to avoid integer overflow;scr_psi() leaves bands empty in both samples out of the
index and out of the degrees of freedom;scr_monitor() keeps undated rows as a period;scr_default() could assign one unit’s flags to another
under locales where the grouping order differed from the C-locale sort;
it is fixed, and the state machine is now vectorised;PSI_ACTION gate of the LGD and EAD drivers compared
against a flag scr_psi() never returns, so it never fired;
it now does;K degrees of
freedom;sd(DR_t - PD_t)
(BCBS WP 14);scr_moc() no longer edits the caller’s ledger;scr_ead_data() paired default dates with facilities
after a sort, so unsorted input put defaults on the wrong facility; it
is fixed;scr_ead() no longer needs data.table 1.15;ccf_measure = "lf" keeps its pools;ead + drawings - recovered;scr_elbe() uplift starts at zero at the date of
default;scr_lgd_downturn(), scr_lgd_floor() and
scr_ead_validate() no longer modify their input.PD = 1e-5 below that
point, where 1 - 1.5 b approaches zero and the risk weight
exploded or turned negative without a PD floor;scr_monitoring_plan() is the monitoring contract:
created by scr_scorecard(), written to the
Monitoring_Plan sheet, and read back by
scr_monitor(plan = ) (a table or the strategy workbook),
which now takes its PSI/CSI thresholds, alpha and
min_events_per_period from it.scr_scorecard() stores the hold-out bin index of every
variable, so the Stability_CSI_Timeline sheet is a real
timeline by vintage without a scr_monitor() object.options(scorecraft.parallel = "fork" | "psock" | "serial")
selects the parallel backend; results are identical under the
three.scr_triage() is parallel by column as well.NULL that surfaces later as
a subscript error), warnings raised in a worker are re-raised in the
parent, PSOCK workers run with a single data.table thread, and a
data.table returned by a worker is re-allocated so that
:= works on it.options(scorecraft.fork_mem_fraction = 0.75) caps the
fork workers by the memory available on Linux (Inf to
disable), since forked workers duplicate the parent heap once the
garbage collector runs.First release. A production-grade scorecard engine for binary targets, built on ‘OptimalBinningWoE’: audit funnel, single configuration, named relaxation, first-class scale alignment, cut-off strategy and hardened deliverables.
scr_select() and scr_scorecard():
scr_split(), scr_triage(),
scr_bin(), scr_model(),
scr_align(),
scr_cutoff()/scr_strategy()/scr_reject().scr_align() is a first-class stage: banded log-odds
regression on the raw score composed with the PDO map,
odds_orientation recorded, applied to any engine. It runs
automatically inside scr_scorecard().scr_sql() emits production SQL in fourteen dialects: a
pre-processing CTE, the WOE/BIN transformation from the authoritative
cut points and, for a scorecard, the exact score plus whole points from
the bin index. R-SQL equivalence is verified by test against DuckDB and
SQLite.scr_bin() is parallelised by column
(nthread), with the fits merged; the result is identical to
the serial one.scr_metrics() always reports a bootstrap confidence
interval for AUC/KS/Gini; scr_psi() reports the fixed
threshold next to the sample-size-adjusted critical value of Yurdakul
and Naranjo (2020).scr_scorecard(challenger = ) fits a tree challenger
(xgboost or lightgbm) on the same WOE columns,
aligned to the same scale, with
supports_scorecard = FALSE.scr_reject() implements honest reject inference:
population scope, band coverage and a 2x/4x/8x sensitivity band, never
parcelling by default.scr_monitor() recomputes PSI/CSI (with the signed
points shift) and the performance by vintage on new data; it never
schedules itself.scr_export() writes four hardened .xlsx
workbooks (selection, scorecard, validation, strategy), the SQL files
and a Markdown summary.scr_coarse_classing() opens a manual binning lab:
scr_classing_view(), scr_classing_propose()
(breaks, groups, merge, split, missing_to, other_to, reset),
scr_classing_accept()/scr_classing_discard()
with a mandatory reason, scr_classing_choose()
(keep/drop/force),
scr_classing_spec()/scr_classing_read()/scr_classing_import()
for a CSV/xlsx round trip, scr_classing_apply() to commit
into a new scr_result, and scr_decisions() for
the append-only ledger. Manual bins share the engine’s contract, so
scr_scorecard(), scr_apply() and
scr_sql() follow them unchanged; the funnel gains
provenance.scr_connect() accepts any DBI driver next to an ODBC
DSN, and scr_fetch() samples server-side with a
dialect-aware random expression.