Binary classification framework with Venn-ABERS conformal prediction, temporal validation, and automated feature engineering.
-
Advanced Calibration Methods
- π― Venn-ABERS (IVAP): Inductive conformal prediction with mathematical validity guarantees
- π Venn-ABERS (CVAP): Cross conformal β uses 100% of training data for calibration via OOF
- π Isotonic regression (sklearn standard)
- π Platt scaling (sigmoid)
- π Uncertainty quantification via prediction intervals [p0, p1]
-
Architecture
- β‘ LightGBM with automated hyperparameter tuning (Optuna, temporal CV)
- π§ Stateful feature engineering (no test leakage)
- π¬ Recursive Feature Elimination (performance-based, feature-engine)
- β±οΈ Temporal validation for time-series data (
TemporalGroupSplitter) - π Time-windowed target encoding (prevents leakage + concept drift)
- π¦ Scikit-learn compatible API with two-stage
fit()/calibrate()
# Clone the repository
git clone https://github.com/firefly1248/fraud-detection_demo_with_calibration.git
cd fraud-detection_demo_with_calibration
# Create environment with uv (recommended)
uv venv
source .venv/bin/activate
uv pip install -r requirements.txt
uv pip install -e .
# Or with poetry
poetry install
poetry shellfrom calibrated_clf.data_loader import load_fraud_data, create_time_groups
from calibrated_clf.model import CalibratedBinaryClassifier
# Load IEEE Fraud Detection data
df = load_fraud_data(sample_frac=0.1) # 10% sample for quick start
df['time_group'] = create_time_groups(df, n_bins=50)
# Prepare features
X = df.drop(columns=['isFraud', 'TransactionID', 'time_group'])
y = df['isFraud']
# Train with Venn-ABERS calibration
model = CalibratedBinaryClassifier(
variable_params={
'classifier__learning_rate': 0.05,
'classifier__max_depth': 6,
'classifier__n_estimators': 100,
'cat_encoder__strategy': 'target_encoder'
},
calibration_method='venn_abers',
calibration_params={'cal_size': 0.2}
)
# Fit model (feature engineering is automatic)
model.fit(X, y)
# Predict with uncertainty intervals
intervals = model.predict_proba_with_intervals(X_test)
print(f"Mean uncertainty: {intervals['interval_width'].mean():.4f}")
# Flag high-uncertainty predictions for manual review
uncertain = intervals['interval_width'] > 0.1
print(f"Uncertain predictions: {uncertain.sum()} / {len(X_test)}")Unlike standard calibration methods, Venn-ABERS provides prediction intervals with mathematical guarantees:
# Standard calibration (point estimates)
model_isotonic = CalibratedBinaryClassifier(
params, calibration_method='isotonic'
)
probas = model_isotonic.predict_proba(X_test) # Just probabilities
# Venn-ABERS IVAP β fast, reserves cal_size fraction for calibration
model_ivap = CalibratedBinaryClassifier(
params, calibration_method='venn_abers'
)
intervals = model_ivap.predict_proba_with_intervals(X_test)
# Returns: p_lower, p_upper, p_combined, interval_widthMultiCalibrationWrapper supports two usage patterns:
from calibrated_clf.calibration import MultiCalibrationWrapper
# fit() β wrapper handles the train/cal split internally
wrapper = MultiCalibrationWrapper(base_estimator=lgbm, method='venn_abers')
wrapper.fit(X_train, y_train)
# calibrate() β you supply the pre-split calibration set
wrapper = MultiCalibrationWrapper(base_estimator=fitted_lgbm, method='venn_abers')
wrapper.calibrate(X_cal, y_cal) # base_estimator must already be fittedUses all training data for calibration via k-fold out-of-fold predictions β no data wasted on a separate calibration split:
# CVAP: k+1 model fits, 100% of data contributes to calibration scores
wrapper = MultiCalibrationWrapper(
base_estimator=lgbm,
method='venn_abers',
venn_abers_mode='cross', # default is 'inductive' (IVAP)
cv_folds=5
)
wrapper.fit(X_train, y_train)| Mode | Cal set size | Model fits | Conformal validity |
|---|---|---|---|
IVAP (inductive) |
cal_size Γ n |
2 | β Yes |
CVAP (cross) |
100% Γ n (OOF) | cv_folds + 1 |
β Yes |
When to use Venn-ABERS:
- π₯ High-stakes decisions (medical diagnosis, fraud detection, loan approval)
- π Need uncertainty quantification beyond point estimates
- βοΈ Distribution-free guarantees regardless of data characteristics
- π¨ Alert systems where wide intervals trigger human review
- π Small datasets where CVAP avoids wasting data on calibration split
Built-in support for time-series cross-validation with TemporalGroupSplitter:
from calibrated_clf.validators import TemporalGroupSplitter
splitter = TemporalGroupSplitter(
n_splits=5,
val_unique_groups=5, # ~10% of data for validation
gap_unique_groups=2, # 2-bin gap prevents data leakage
train_accounts_share=0
)
for train_idx, val_idx in splitter.split(X, y, groups=df['time_group']):
# Train on past data, validate on future
model.fit(X.iloc[train_idx], y.iloc[train_idx])
predictions = model.predict_proba(X.iloc[val_idx])Why it matters:
- β Mimics production scenario (train on past, predict future)
- β Prevents data leakage with temporal gap
- β Realistic performance estimates
Detects dataset type and applies domain-specific features automatically:
Fraud Detection (13 features):
- Transaction amount: log transform, decimal patterns
- Card aggregations: mean/std per card
- Time features: hour, day, weekday
- Email/address matching
- Missing value indicators
No manual feature engineering required!
Calibration method comparison on held-out test set (temporal split, full 590K dataset):
| Method | AUC-PR | Brier Score | Log Loss | ECE |
|---|---|---|---|---|
| Uncalibrated | 0.5028 | 0.0229 | 0.0956 | 0.0029 |
| Isotonic | 0.4906 | 0.0231 | 0.0978 | 0.0064 |
| Venn-ABERS | 0.4933 | 0.0231 | 0.0955 | 0.0063 |
| Sigmoid | 0.5028 | 0.0239 | 0.1057 | 0.0152 |
After temporal CV hyperparameter tuning, LightGBM is already well-calibrated (ECE=0.003). Post-hoc calibration overfits to the calibration split and slightly hurts generalization on the out-of-time test set.
Reproduce: run
build_and_evaluate_model.ipynbon the full IEEE-CIS dataset.
Left: raw metric values. Right: normalised (1 = best) β higher bar means better performance on every axis. Generated by build_and_evaluate_model.ipynb.
Example showing isotonic vs Venn-ABERS calibration curves and uncertainty distributions.
Note: Run
build_and_evaluate_model.ipynbto regenerate both plots.
calibrated_clf/
βββ model.py # CalibratedBinaryClassifier (core)
βββ calibration.py # Venn-ABERS + multi-calibration wrapper
βββ data_loader.py # IEEE Fraud data loading & time groups
βββ validators.py # TemporalGroupSplitter for temporal validation
βββ train_model.py # Training pipeline with HP optimization
βββ model_optimisation.py # Optuna hyperparameter tuning
βββ feature_selection.py # Recursive feature elimination (custom)
βββ data_transformers.py # FraudFeatureEngineer, TimeWindowedTargetEncoder
βββ config.py # Fixed model parameters & random seed
Key Design Principles:
- β Scikit-learn compatible - Follows sklearn API conventions
- β Fully documented - NumPy/Google style docstrings everywhere
- β Type-safe - Complete type hints with type aliases
- β CI checked - black formatting enforced on every push
- β
Robust -
check_is_fittedguards, numerical stability (np.dividewithwhere)
- CLAUDE.md - Complete project guide for future development
- Docstrings - Every class and function fully documented with examples
class CalibratedBinaryClassifier(BaseEstimator, ClassifierMixin):
"""
Scikit-learn compatible binary classifier with advanced calibration.
Parameters
----------
variable_params : dict
Hyperparameters for the model pipeline
calibration_method : str, default='isotonic'
Calibration method: 'isotonic', 'venn_abers', 'sigmoid', or 'none'
calibration_params : dict, optional
Additional parameters for calibration
Examples
--------
>>> model = CalibratedBinaryClassifier(
>>> variable_params={'classifier__learning_rate': 0.05},
>>> calibration_method='venn_abers'
>>> )
>>> model.fit(X_train, y_train)
>>> intervals = model.predict_proba_with_intervals(X_test)
"""from calibrated_clf.train_model import train_model
model = train_model(
train_data=df,
target_column='isFraud',
with_hp_opt=True,
n_trials=100,
calibration_method='venn_abers'
)# SHAP values for feature importance
shap_values = model.calculate_shap_values(X_test)
top_features = shap_values.abs().mean().sort_values(ascending=False).head(10)
print("Top contributing features:")
print(top_features)For temporal data with concept drift, use TimeWindowedTargetEncoder to prevent both data leakage and outdated patterns:
from calibrated_clf.data_transformers import TimeWindowedTargetEncoder
from datetime import timedelta
# Encode categorical features using only recent past data
encoder = TimeWindowedTargetEncoder(
time_column='TransactionDT',
time_window=timedelta(days=30), # Only use last 30 days
cols=['card1', 'card2', 'ProductCD'],
smoothing=10.0, # Smoothing for rare categories
min_samples_leaf=20 # Min samples required in window
)
X_encoded = encoder.fit_transform(X_train, y_train)
# Unlike CatBoost encoder (uses all past data), this focuses on recent patterns
# Prevents: β Data leakage (future β past)
# β Concept drift (using outdated patterns)Why use time-windowed encoding?
- π Fraud patterns change over time - old data may be misleading
- π Balances preventing leakage with using relevant recent data
- π― Especially useful for financial fraud, user behavior, and seasonal patterns
- β‘ Performance: ~2-5 min for 590K transactions with 30-day window
- Samples: 590,540 transactions
- Features: 394 transaction + 41 identity = 435 total
- Target:
isFraud(3.5% positive class - highly imbalanced!) - Time Range: 182 days (TransactionDT in seconds)
- Missing Values: 45% (normal for this dataset, handled automatically)
Download: Kaggle IEEE-CIS Fraud Detection
Place files in ieee-fraud-detection/ directory:
ieee-fraud-detection/
βββ train_transaction.csv
βββ train_identity.csv
βββ test_transaction.csv
βββ test_identity.csv
python -c "
from calibrated_clf.data_loader import load_fraud_data
from calibrated_clf.data_transformers import FraudFeatureEngineer
df = load_fraud_data(sample_frac=0.01)
X = df.drop(columns=['isFraud'])
engineer = FraudFeatureEngineer()
X_eng = engineer.fit_transform(X, df['isFraud'])
print(f'Original: {X.shape[1]}, Engineered: {X_eng.shape[1]}')
"python calibrated_clf/data_loader.pyContributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Follow code style (black, type hints, docstrings)
- Add tests for new features
- Submit a pull request
Venn-ABERS & Conformal Prediction:
- Vovk et al. (2015) - Large-scale probabilistic predictors
- ip200/venn-abers GitHub
- Awesome Conformal Prediction
Fraud Detection:
Calibration:
If you use this framework in your research, please cite:
@software{calibrated_binary_classifier,
author = {Ekhlakov, Ilia},
title = {Calibrated Binary Classifier: ML with Venn-ABERS Conformal Prediction},
year = {2026},
url = {https://github.com/firefly1248/fraud-detection_demo_with_calibration}
}- β¨ Cutting-edge: Venn-ABERS conformal prediction (few implementations exist)
- π Well-documented: 650+ lines of professional docstrings
- π Type-safe: Complete type hints throughout
- π§ͺ Validated: Handles real-world fraud detection (590K transactions)
- π Educational: Clear examples and comprehensive guides
Author: Ilia Ekhlakov
Project Link: https://github.com/firefly1248/fraud-detection_demo_with_calibration
This project is licensed under the MIT License - see the LICENSE file for details.
Built with β€οΈ using Python, LightGBM, and Venn-ABERS
β Star this repo if you find it useful!

