Johns Hopkins micro-project

SecureBank fraud

First models on a time-split dataset, scored on the months they never saw.

Course
EN.705.603 Creating AI-Enabled Systems, Johns Hopkins, fall 2026
Domain
Card fraud detection, class imbalance, time-split validation
Stack
Python, scikit-learn, optbinning, pandas
Result
Gradient boosting at AP 0.83, 57 percent precision and 84 percent recall on December

Open the interactive binning explorer → Move the scorecard's splits yourself and watch the weight of evidence change.

01

The two metrics

The SecureBank case in EN.705.603 at Johns Hopkins asks for a card-fraud model that reaches 70 percent precision and 70 percent recall on unseen months. I ran this as the model selection pass. I picked two offline metrics tied to the requirements, trained three models on the same chronological split, and read the comparison for where the signal lives. Codex reviewed the first version adversarially and I reworked the leakage paths, the ablations, and the claims it flagged.

I score every model on average precision over the whole ranking and on precision and recall at one operating point. Average precision is my model selection number, since it does not depend on a threshold I have not chosen yet. The operating point is the number I'd show the bank. The alert threshold is set on the validation month at the lowest score that keeps precision at 70 percent, which is the analyst capacity constraint from my earlier posts turned into a rule. A model that never reaches 70 percent on November gets no qualifying operating point; I score it at a fixed 10 alerts a day instead and say so.

02

Dataset design

The split is the one I argued for in the dataset design week. I train on January 2019 through October 2020, choose the threshold on November, and score December once. The models train on the full training frame with balanced class weights; I kept the 10 percent downsample from my first pass as an ablation and it costs the boosted model 0.03 of average precision. Validation and test stay at the fraud rate among labelled rows, because precision on a rebalanced set is a number the bank never sees. The 10 percent of transactions with no label are out of every metric; their median amount and night share match the labelled rows, which is the only check I have that they are missing at random.

PartitionRowsFraudFraud rate
Train (Jan 2019 to Oct 2020)1,313,3606,0990.46%
Validation (Nov 2020)57,9192020.35%
Test (Dec 2020)111,5931950.17%

December doubles the volume and keeps the fraud count flat, so the base rate halves. Every precision number I report below was harder to earn than the validation month suggested.

03

Features

I built seventeen features, all computable at authorization. The amount bands and the gap sentinel were fixed before any fitting. The velocity features use every transaction, including the unlabelled ones, but only rows with a strictly earlier timestamp. The scorecard adds one cross bin, category by amount band.

Numeric: log_amt, amt_vs_prior_mean, n_prev_1h, n_prev_24h, log_secs_since_prev, first_txn, hour_sin, hour_cos, night, dow_sin, dow_cos, month_sin, month_cos, age, log_city_pop, home_merchant_km. Categorical: category, and cat_amt (category by amount band) in the scorecard.

Fraud rate by transaction amount band, log scale Column chart on a log scale. Everything under $200 sits near or below 0.2 percent; the rate climbs through $200 to $800 and peaks at 45.5 percent in the $800 to $1,200 band, then falls to 1.7 percent above $1,200. $200 to $1,200: where the fraud is 0.01% 0.1% 1% 10% 100% $0 to $50: 0.201% fraud, 1,379 of 685,955 rows0.20% $0 $50 to $200: 0.034% fraud, 190 of 565,058 rows0.03% $50 $200 to $300: 1.827% fraud, 598 of 32,723 rows1.83% $200 $300 to $500: 7.294% fraud, 1,065 of 14,601 rows7.29% $300 $500 to $800: 9.262% fraud, 762 of 8,227 rows9.26% $500 $800 to $1200: 45.549% fraud, 2,067 of 4,538 rows45.55% $800 $1200 to $up: 1.683% fraud, 38 of 2,258 rows1.68% $1,200+ transaction amount band, lower edge; fraud rate on the training rows
Fraud rate by amount band on the training rows. The $800 to $1,200 band runs 46 percent fraud; everything under $200 sits near zero. Amount is not monotonic.
Show the numbers
amount bandfraud ratefraud rowsrows
$0 to $500.201%1,379685,955
$50 to $2000.034%190565,058
$200 to $3001.827%59832,723
$300 to $5007.294%1,06514,601
$500 to $8009.262%7628,227
$800 to $120045.549%2,0674,538
$1200 to $up1.683%382,258
04

Three models, one comparison

I fit a logistic regression on the standardized features, a weight-of-evidence scorecard (optbinning bins each numeric feature on the training rows, the category and cross-bin columns get a smoothed WOE per level, and a logistic regression fits on the WOE columns), and a gradient boosting classifier.

December holdout: average precision, precision and recall for three models with 95 percent intervals Grouped columns. Gradient boosting leads on every measure: average precision 0.83, precision 57 percent and recall 84 percent at the November threshold, against 0.56, 50 and 58 for the scorecard and 0.10, 18 and 54 for logistic regression, which never qualified and is scored at 10 alerts a day. The 70 percent target line sits above every precision column. 0% 25% 50% 75% 100% average precision Gradient boosting: average precision 0.828 (95% interval 0.758 to 0.879)83% WOE scorecard: average precision 0.563 (95% interval 0.456 to 0.647)56% Logistic regression: average precision 0.105 (95% interval 0.062 to 0.151)10% precision at the point 70% target Gradient boosting: precision at the point 0.567 (95% interval 0.438 to 0.665)57% WOE scorecard: precision at the point 0.496 (95% interval 0.361 to 0.599)50% Logistic regression: precision at the point 0.175 (95% interval 0.107 to 0.242)18% recall at the point 70% target Gradient boosting: recall at the point 0.841 (95% interval 0.791 to 0.886)84% WOE scorecard: recall at the point 0.579 (95% interval 0.508 to 0.651)58% Logistic regression: recall at the point 0.538 (95% interval 0.447 to 0.619)54% December 2020; whiskers are 95 percent card-cluster bootstrap intervals
Gradient boostingWOE scorecardLogistic regression
December holdout. Whiskers are conditional card-cluster intervals.
Show the numbers
modelAP95% intervalprecision95% intervalrecall95% intervalalerts per daydollar recallreached 70% on Nov
Gradient boosting0.8280.76 to 0.880.5670.44 to 0.660.8410.79 to 0.899.30.94yes
WOE scorecard0.5630.46 to 0.650.4960.36 to 0.600.5790.51 to 0.657.40.77yes
Logistic regression0.1050.06 to 0.150.1750.11 to 0.240.5380.45 to 0.6219.40.83no
ModelDec APPrecisionRecallAlerts/dayDollar recallReached 70% precision on Nov
Gradient boosting0.8357%84%994%yes
WOE scorecard0.5650%58%777%yes
Logistic regression0.1018%54%1983%no, scored at 10 alerts a day
Precision and recall curves on the December holdout, with each model's operating point Three curves. Gradient boosting stays above 70 percent precision out to about 79 percent recall, the scorecard to about 48 percent, and the logistic regression never reaches 70 percent precision. Dots mark each model's operating point. meets both targets 0% 0% 20% 20% 40% 40% 60% 60% 80% 80% 100% 100% 70% precision 70% recall recall precision Gradient boosting at the November threshold: precision 0.567, recall 0.841 WOE scorecard at the November threshold: precision 0.496, recall 0.579 Logistic regression at the November threshold: precision 0.175, recall 0.538
Gradient boostingWOE scorecardLogistic regression
The full precision and recall curves with each operating point.
Show the numbers
modelprecision at the pointrecall at the pointscore thresholdoracle recall at 70% precisionqualifying point
Gradient boosting0.5670.8410.8770.79yes
WOE scorecard0.4960.5790.9950.48yes
Logistic regression0.1750.5380.9790.00no, 10 alerts a day

Gradient boosting reaches average precision 0.83 and, at the November threshold, 57 percent precision with 84 percent recall on 9 alerts a day, catching 94 percent of the fraud dollars. The scorecard reaches 0.56 and the raw logistic regression 0.10, and the logistic regression never qualifies. The whiskers are card-cluster bootstrap intervals with the model and threshold held fixed, so they cover the December card mix and nothing else. The precision miss against the 70 percent target has the halved base rate as one plausible cause; ROC AUC barely moves from November to December (0.999 to 0.998), but average precision drops from 0.93 to 0.83 and I have not separated prevalence from drift in the fraud mix.

05

Where the signal lives

Grouped permutation importance on the validation month, drop in average precision Horizontal bars for seven feature groups, ten repeats each. Shuffling the amount features costs 0.92 of average precision, category 0.81, the hour features 0.31, the cardholder features 0.13, velocity 0.06, and weekday, month and distance nothing. 0.00 0.25 0.50 0.75 1.00 drop in average precision when the group is shuffled amount amount (log_amt, amt_vs_prior_mean): AP drop 0.919 (sd 0.004 over 10 repeats) 0.919 category category: AP drop 0.811 (sd 0.016 over 10 repeats) 0.811 hour hour (hour_sin, hour_cos, night): AP drop 0.315 (sd 0.017 over 10 repeats) 0.315 cardholder cardholder (age, log_city_pop): AP drop 0.130 (sd 0.012 over 10 repeats) 0.130 velocity velocity (n_prev_1h, n_prev_24h, log_secs_since_prev, first_txn): AP drop 0.058 (sd 0.007 over 10 repeats) 0.058 weekday and month weekday and month: AP drop 0.004 (sd 0.001 over 10 repeats) 0.004 home_merchant_km home_merchant_km: AP drop -0.001 (sd 0.001 over 10 repeats) -0.001
Grouped permutation importance on November, ten repeats.
Show the numbers
feature groupAP dropsd over 10 repeats
amount (log_amt, amt_vs_prior_mean)0.9190.004
category0.8110.016
hour (hour_sin, hour_cos, night)0.3150.017
cardholder (age, log_city_pop)0.1300.012
velocity (n_prev_1h, n_prev_24h, log_secs_since_prev, first_txn)0.0580.007
weekday and month0.0040.001
home_merchant_km-0.0010.001

Shuffling feature groups together on November shows what the boosted model relies on. Amount costs 0.92 of average precision, category 0.81, hour 0.31, and nothing else more than 0.13. That is model reliance, not a statement about how the features combine, so I tested the combination directly.

Scorecard ablation: December average precision by feature set Horizontal bars for six scorecard feature sets against a dashed line at gradient boosting's 0.83. Amount alone scores 0.02, category alone 0.003, the two added 0.02, the category by amount cross bin alone 0.37, all main effects 0.11, and the scorecard with the cross bin 0.56. 0.0 0.2 0.4 0.6 0.8 December average precision, same split and seed amount only amount only: December AP 0.022, validation AP 0.0430.02 category only category only: December AP 0.003, validation AP 0.0090.00 amount plus category, additive amount plus category, additive: December AP 0.019, validation AP 0.0330.02 amount by category cross bin only amount by category cross bin only: December AP 0.369, validation AP 0.5130.37 main effects scorecard main effects scorecard: December AP 0.105, validation AP 0.2090.11 scorecard with cross bin scorecard with cross bin: December AP 0.563, validation AP 0.6540.56 gradient boosting 0.83
Scorecard ablation on December: one feature set at a time, same split and seed.
Show the numbers
feature setDecember APNovember APDecember ROC AUC
amount only0.0220.0430.921
category only0.0030.0090.684
amount plus category, additive0.0190.0330.923
amount by category cross bin only0.3690.5130.966
main effects scorecard0.1050.2090.965
scorecard with cross bin0.5630.6540.987
gradient boosting, 10 percent legitimate downsample0.7950.9220.997
gradient boosting, full training frame0.8280.9270.998

On the training rows, a $300 to $500 purchase runs 100 percent fraud in grocery_pos and 0 percent in travel, so the same amount means different things in different categories. The ablation makes the point in numbers. A scorecard on amount alone scores 0.02 on December, category alone 0.003, the two added together 0.02, and the single category-by-amount cross bin 0.37. All eleven main effects with category reach 0.11; adding the cross bin lifts that to 0.56. The tree model learns the same interaction and the rest on its own and scores 0.83. That gap is what I took from the exercise.

Information value per feature from the scorecard's bins Horizontal bars for 13 binned features. The category by amount cross bin has the highest information value at 8.29, above amount at 4.02 and amount against the card's prior mean at 3.32; hour is 1.86 and everything else is below 0.6. 0 2 4 6 8 information value, training months cat_amt cat_amt: information value 8.2908.29 amt amt: information value 4.0224.02 amt_vs_prior_mean amt_vs_prior_mean: information value 3.3203.32 hour hour: information value 1.8551.86 category category: information value 0.5720.57 log_secs_since_prev log_secs_since_prev: information value 0.4790.48 n_prev_1h n_prev_1h: information value 0.3630.36 n_prev_24h n_prev_24h: information value 0.1370.14 age age: information value 0.0390.04 month month: information value 0.0390.04 dow dow: information value 0.0220.02 city_pop city_pop: information value 0.0060.01 home_merchant_km home_merchant_km: information value 0.0010.00
Information value per feature from the scorecard fit on the full training frame.
Show the numbers
featureinformation value
cat_amt8.290
amt4.022
amt_vs_prior_mean3.320
hour1.855
category0.572
log_secs_since_prev0.479
n_prev_1h0.363
n_prev_24h0.137
age0.039
month0.039
dow0.022
city_pop0.006
home_merchant_km0.001

Open the interactive binning explorer → See the bins behind the scorecard and move the splits yourself.

06

Night and day

Night against day: precision and recall per model at the same December thresholds Grouped columns, night and day, three models each with a precision column and a lighter recall column. Gradient boosting holds 90 percent recall at night and 55 in the day; the scorecard falls from 66 to 13 percent recall; logistic regression precision falls to 3 percent in the day. Night has 164 fraud cases and day 31. 0% 25% 50% 75% 100% night 22:00 to 03:59 164 fraud cases Gradient boosting, night 22:00 to 03:59: precision 0.58 (254 alerts)58% Gradient boosting, night 22:00 to 03:59: recall 0.90 (254 alerts)90% WOE scorecard, night 22:00 to 03:59: precision 0.50 (219 alerts)50% WOE scorecard, night 22:00 to 03:59: recall 0.66 (219 alerts)66% Logistic regression, night 22:00 to 03:59: precision 0.33 (284 alerts)33% Logistic regression, night 22:00 to 03:59: recall 0.58 (284 alerts)58% day 04:00 to 21:59 31 fraud cases Gradient boosting, day 04:00 to 21:59: precision 0.49 (35 alerts)49% Gradient boosting, day 04:00 to 21:59: recall 0.55 (35 alerts)55% WOE scorecard, day 04:00 to 21:59: precision 0.44 (9 alerts)44% WOE scorecard, day 04:00 to 21:59: recall 0.13 (9 alerts)13% Logistic regression, day 04:00 to 21:59: precision 0.03 (316 alerts)3% Logistic regression, day 04:00 to 21:59: recall 0.32 (316 alerts)32% solid column precision, light column recall; December 2020, same thresholds
Gradient boostingWOE scorecardLogistic regression
The same December thresholds split by hour.
Show the numbers
modelnight precisionnight recallnight alertsday precisionday recallday alerts
Gradient boosting0.580.902540.490.5535
WOE scorecard0.500.662190.440.139
Logistic regression0.330.582840.030.32316

On the training rows 85 percent of the fraud sits on the 23 percent of volume between 22:00 and 04:00, a 1.7 percent rate against 0.09 percent by day. Gradient boosting holds 90 percent recall at night and 55 percent in the day on the same threshold. The scorecard raises 9 daytime alerts in the whole month. A model that only works at night is a model a fraudster learns to beat by waiting until morning, so I'd report the daytime slice to the bank separately.

07

What I would test next

  • Hold the threshold rule and re-fit monthly on a rolling window, to see whether the December precision drop is seasonal volume or drift in the fraud mix.
  • Give the scorecard the hour-by-category and amount-by-hour cross bins and check whether an explainable model can close the rest of the gap to the tree.
  • Replace the 70 percent precision rule with an alert budget per analyst per day and read recall off that, which is how the fraud operations teams I have worked with set thresholds.
  • Daytime fraud as its own evaluation slice, with its own threshold if the cost of a missed daytime case justifies more alerts.

Method notes: scikit-learn 1.9, optbinning 1.0, one run of 115 seconds on a laptop. Thresholds and bins come from the training and validation months only. Intervals resample cardholders, 1000 draws, model and threshold fixed. The oracle recall at 70 percent precision in the tables uses December labels and is an upper bound, not an operating result.