scorecraft (development version)
- New score studies, computed from one pass over the scored rows into a table of counts per score value:
-
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_togives policy-friendly cuts, andn_bootmeasures 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 withby; the thresholds are an editable table fromscr_rag_plan(). -
scr_apply(),scr_sql()andscr_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 intier_label, andnumbered = FALSEgives 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. - The three write a workbook with
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. - The six write a workbook with
scr_export().
-
- New configuration keys for the score studies (stage 13):
study_bands,study_level,tier_min_pct,tier_min_eventsandtier_max_bins. -
scr_pd_validate()gives the light"grey"to a row without a testable result (a missing p-value) instead ofNA, and the overall light is"grey", not"green", when no row has a testable result.scr_ead_validate()does the same and adds the overalllight. Inscr_lgd_validate(), a summary row over pools or drivers that are all grey is now grey, not green. -
scr_monitor()and the CSI timelines ofscr_export()compute the CSI asscr_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()freezescrossing$cuton 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 ofscr_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.breaksgiven as a number of intervals now also gets a cut, taken on the evaluated sample. - The rank-order diagnostics of
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 thanscore_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()andscr_psi()assign the bands as integer indices, without a factor per row. The DeLong standard error ofscr_pd_validate()is computed from the counts and can differ in the last digits (about 1e-16). No other result changes. - The cheat sheet gains a third page on the score studies.
scorecraft 0.3.1
-
scr_strategy()reports the event and non-event distributions of each band (pct_event,pct_nonevent,odds_eventandlog_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_goodis the revenue of an event and the decisions are"target","review"and"skip". Credit and fraud results are unchanged. -
scr_score_gains()addspct_event,pct_noneventandwoe. Underhigher_is_riskier,oddsandlog_oddsare now events per non-event, the orientation of the scale, solog_oddsrises with the score in both directions. - The points table and the
Variable_Gains_IVsheet ofscr_export()addpct_eventandpct_noneventper bin. - Documentation, messages and comments use American English spelling. Column names, configuration keys and data values are unchanged (for example
utilisation,realisedand the"grey"traffic light).
scorecraft 0.3.0
-
scr_lgd_downturn()andscr_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 theverbosekey of the object’s configuration, as the function that fitted it does. - The classing lab raises
IV_RATIO_UNSTABLE, an advisory warning, when the train IV of a proposal is belowiv_minand 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. - Three vignettes: Get started, coarse classing, and scaling, alignment and challengers. The PD, LGD/EAD and capital guides are articles on the package website.
- A two-page cheat sheet ships in
inst/cheatsheet(PDF and its HTML source).
scorecraft 0.2.0
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, soOptimalBinningWoE::obwoe_apply()andobwoe_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 inscr_config_keys()and validated. -
scr_demo_panel,scr_demo_lgd,scr_demo_lgd_cashflows,scr_demo_rates,scr_demo_eadandscr_demo_portfolioare new demonstration data. - PD:
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(), withpredict(),scr_apply(),scr_sql()(grade and PD as aCASEon the score) andscr_export()methods. - LGD:
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(), withscr_apply(),scr_sql()(both stages, poolCASE, floored result) andscr_export()methods. - EAD:
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(), withscr_apply(),scr_sql()andscr_export()methods. - Expected loss and capital:
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), withscr_sql()(constants per pool, no normal quantile at run time) andscr_export()methods. -
scr_irb_rw()andscr_capital()read the supervisory LGD of the foundation approach fromparams$lgd_firbthrough aclaimtype;scr_sa_rw()andscr_capital()apply the non-granular retail weight withgranular = FALSE; the Hosmer-Lemeshow light ofscr_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 ofscr_lgd(). -
scr_sql()on a scorecard gainswhat = "all"(bin label, WOE and points of every variable next to the exact score and the whole-points score) andkeep_columns(key columns carried into the output), for a deployment that reports the band of each variable with the score. - After the documentation audit:
scr_iv()ignoresNAfor every group type;scr_classing_read()validates the separator and the spec carries it intoscr_classing_import(); theTOO_MANY_BINSscreening rule can fire (the screen readsmax_bins);scr_psi()stores and prints its thresholds;scr_default_rate()reports one long-run mean and benchmarks an optionallra_adjusted; oneasset_classconfiguration key replacespd_asset_classandcapital_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 anscr_eadtakeswhat;scr_fetch()gainsverboseandscr_run()followsconfig$verbose; thescr_democolumns 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).
Compiled kernels and big tables
- The package now compiles C++ code (‘Rcpp’ and ‘RcppArmadillo’). Thin internal R wrappers validate the input and call the kernels, which take plain vectors and matrices (never a
data.table) and read the columns without copying them. The kernels are tested against their reference R implementations. - Redundancy pruning in
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 asOptimalBinningWoE::obwoe_prune(), and is about 50 times faster at 150 columns. - Somers’ D of LGD and EAD models is counted exactly in
O(n log n)(Knight’s algorithm). It replaces anO(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 isO(n)whatever the term (the matrix version built severaln x Tcopies, about 2.9 GB each atn = 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. - Kernel threads follow
config$nthread.
Correctness and numerical stability
- Wide tables:
scr_triage()and the R pre-processing ofscr_apply()reserve column slots before adding columns, so they no longer fail past about 1024 columns. - Random numbers: seeds are local to the call.
.Random.seedis 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():- the target is checked for 0/1 before integer coercion (0.5 was truncated to 0);
- text dates (as DBI returns them) and
integer64columns are read correctly; - rows with a missing date are reported.
-
scr_bin(): underallow_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 ofOptimalBinningWoE::obwoe_gains_score()are muffled; its values are correct, and the fix belongs upstream. -
scr_config()validates every key of stages 0 to 7. - LightGBM receives
min_sum_hessian_in_leaf. The xgboost API is detected fromxgb.train()(it works with xgboost 3). - SQL string literals follow the dialect: backslashes are escaped only in MySQL, Spark/Hive/Databricks and BigQuery. A line break in a name can no longer escape an SQL comment. The scorecard’s SQL quotes identifiers as
obwoe_sql()does. A row that falls in no fitted bin takes the points of WOE 0, in R and in SQL. - Metrics and monitoring:
-
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; - a classing spec survives the CSV and xlsx round trips;
- an export cannot be written outside the given directory.
-
- PD:
-
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; - the
PSI_ACTIONgate of the LGD and EAD drivers compared against a flagscr_psi()never returns, so it never fired; it now does; - Hosmer-Lemeshow on fixed PDs uses
Kdegrees of freedom; - the multi-period test is scaled by
sd(DR_t - PD_t)(BCBS WP 14); - the AUC test uses the DeLong variance;
-
scr_moc()no longer edits the caller’s ledger; - point-in-time PDs accept a grade at zero.
-
- LGD and EAD:
-
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; - a cure recovers
ead + drawings - recovered; - the
scr_elbe()uplift starts at zero at the date of default; -
scr_lgd_downturn(),scr_lgd_floor()andscr_ead_validate()no longer modify their input.
-
- Capital and ECL:
- the maturity adjustment is held at
PD = 1e-5below that point, where1 - 1.5 bapproaches zero and the risk weight exploded or turned negative without a PD floor; - a missing SME sales figure gets no firm-size adjustment (CRE31.9);
- the ECL exit probability is capped at one, the LGD floored at zero, and scenario shocks and input lengths are validated.
- the maturity adjustment is held at
Scorecard pipeline hardening
-
scr_monitoring_plan()is the monitoring contract: created byscr_scorecard(), written to theMonitoring_Plansheet, and read back byscr_monitor(plan = )(a table or the strategy workbook), which now takes its PSI/CSI thresholds, alpha andmin_events_per_periodfrom it. -
scr_scorecard()stores the hold-out bin index of every variable, so theStability_CSI_Timelinesheet is a real timeline by vintage without ascr_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. - Workers never fail silently on any backend: a worker error is re-thrown with the failing item, a worker killed by the system is reported as such (instead of a
NULLthat 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 adata.tablereturned 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 (Infto disable), since forked workers duplicate the parent heap once the garbage collector runs.
scorecraft 0.1.0
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.
- Seven stages, each an exported function, chained by
scr_select()andscr_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_orientationrecorded, applied to any engine. It runs automatically insidescr_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 (xgboostorlightgbm) on the same WOE columns, aligned to the same scale, withsupports_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.xlsxworkbooks (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 newscr_result, andscr_decisions()for the append-only ledger. Manual bins share the engine’s contract, soscr_scorecard(),scr_apply()andscr_sql()follow them unchanged; the funnel gainsprovenance. -
scr_connect()accepts any DBI driver next to an ODBC DSN, andscr_fetch()samples server-side with a dialect-aware random expression.
