Appendix A. Condensed Reproducible Pipeline
The following listing presents the condensed core pipeline (Python 3.10) underlying the analyses reported in
Section 2 and
Section 3. It covers dependency configuration (A.1), large-scale data ingestion via Polars (A.2), target binarization (A.3), bivariate correlation with Benjamini–Hochberg FDR correction (A.4), stratified partitioning and Random Oversampling restricted to the calibration set (A.5), hyperparameter-complete model definitions for all six classifiers (A.6), a unified evaluation function returning the metrics reported in
Table 3 (A.7), holdout evaluation (A.8), Logistic Regression coefficient extraction (A.9), 5-fold stratified cross-validation with 95% confidence intervals computed as the fold-score mean ± 1.96 × SD (A.10), external verification on flood.csv (A.11), and, new to this revision, the probability calibration analysis underlying
Section 3.4 - Brier score, five-bin reliability diagram, the threshold-sensitivity scan across [0.10, 0.90], and a Platt-scaling recalibration routine (A.12). The listing is self-contained and deterministic under RANDOM_STATE = 100; executing it against the Resurrectum Diluvium files reproduces
Table 3,
Table 4,
Table 5,
Table 6,
Table 7 and
Table 8 exactly. Visualization routines, figure export, and ancillary diagnostic outputs are omitted for conciseness.
# =============================================================================
# Flood-Risk Index Classification Using Machine Learning
# Resurrectum Diluvium Benchmark
# Python 3.10 · Google Colab · RANDOM_STATE = 100
# =============================================================================
# ── A.1 Dependencies ─────────────────────────────────────────────────────────
import warnings; warnings.filterwarnings('ignore')
import numpy as np
import pandas as pd
import polars as pl
from scipy.stats import spearmanr, pearsonr
from statsmodels.stats.multitest import multipletests
from sklearn.model_selection import (train_test_split, StratifiedKFold,
cross_val_score)
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import (RandomForestClassifier,
GradientBoostingClassifier)
from sklearn.metrics import (accuracy_score, f1_score, roc_auc_score,
matthews_corrcoef, cohen_kappa_score,
precision_score, recall_score)
from imblearn.over_sampling import RandomOverSampler
import lightgbm as lgb
import catboost as cb
RANDOM_STATE = 100
np.random.seed(RANDOM_STATE)
# ── A.2 Data Ingestion ────────────────────────────────────────────────────────
TRAIN_CSV = '/content/train.csv' # Kaggle: naiyakhalid/flood-prediction-dataset
FLOOD_CSV = '/content/flood.csv'
df_raw = pl.read_csv(TRAIN_CSV, infer_schema_length=0,
ignore_errors=True, rechunk=True).to_pandas()
if 'id' in df_raw.columns:
df_raw.drop(columns=['id'], inplace=True)
for col in df_raw.columns:
if df_raw[col].dtype == 'object':
df_raw[col] = pd.to_numeric(df_raw[col], errors='coerce')
PREDICTOR_COLS = [c for c in df_raw.columns if c != 'FloodProbability']
TARGET_CONT = 'FloodProbability'
TARGET_BIN = 'FloodOccurrence'
THRESHOLD = 0.50
# ── A.3 Target Binarisation ───────────────────────────────────────────────────
df_raw[TARGET_BIN] = (df_raw[TARGET_CONT] >= THRESHOLD).astype(int)
X = df_raw[PREDICTOR_COLS].values
y = df_raw[TARGET_BIN].values
# ── A.4 Bivariate Correlation with BH-FDR Correction ─────────────────────────
rho_vals, p_vals = zip(*[
spearmanr(df_raw[c], df_raw[TARGET_CONT]) for c in PREDICTOR_COLS
])
_, p_bh, _, _ = multipletests(p_vals, method='fdr_bh')
corr_df = pd.DataFrame({
'Variable' : PREDICTOR_COLS,
'Spearman_rho': np.round(rho_vals, 4),
'p_BH_FDR' : np.round(p_bh, 6)
}).sort_values('Spearman_rho', ascending=False)
# ── A.5 Train/Test Split and Oversampling ────────────────────────────────────
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state=RANDOM_STATE, stratify=y)
ros = RandomOverSampler(random_state=RANDOM_STATE)
X_bal, y_bal = ros.fit_resample(X_train, y_train)
scaler = StandardScaler()
X_bal_s = scaler.fit_transform(X_bal)
X_test_s = scaler.transform(X_test)
# ── A.6 Model Definitions ────────────────────────────────────────────────────
models = {
'Logistic Regression': LogisticRegression(
C=1.0, solver='lbfgs', max_iter=3000, n_jobs=-1,
random_state=RANDOM_STATE),
'Decision Tree': DecisionTreeClassifier(
max_depth=5, min_samples_leaf=30, criterion='gini',
random_state=RANDOM_STATE),
'Random Forest': RandomForestClassifier(
n_estimators=200, random_state=RANDOM_STATE, n_jobs=-1),
'Gradient Boosting': GradientBoostingClassifier(
n_estimators=200, learning_rate=0.10, max_depth=4,
subsample=0.8, random_state=RANDOM_STATE),
'LightGBM': lgb.LGBMClassifier(
n_estimators=500, max_depth=6, learning_rate=0.05,
num_leaves=63, subsample=0.8, colsample_bytree=0.8,
min_child_samples=20, random_state=RANDOM_STATE,
verbose=-1, n_jobs=-1),
'CatBoost': cb.CatBoostClassifier(
iterations=500, depth=6, learning_rate=0.05,
l2_leaf_reg=3, random_seed=RANDOM_STATE,
verbose=0, eval_metric='AUC', early_stopping_rounds=50),
}
# LR and scaler-dependent models use standardised matrices
scaled_models = {'Logistic Regression'}
# ── A.7 Evaluation Function ──────────────────────────────────────────────────
def evaluate(name, model, X_tr, y_tr, X_te, y_te):
model.fit(X_tr, y_tr)
y_pred = model.predict(X_te)
y_proba = (model.predict_proba(X_te)[:, 1]
if hasattr(model, 'predict_proba')
else model.decision_function(X_te))
return {
'Model' : name,
'Accuracy' : round(accuracy_score(y_te, y_pred), 4),
'Precision' : round(precision_score(y_te, y_pred), 4),
'Recall' : round(recall_score(y_te, y_pred), 4),
'F1' : round(f1_score(y_te, y_pred), 4),
'MCC' : round(matthews_corrcoef(y_te, y_pred),4),
'Kappa' : round(cohen_kappa_score(y_te, y_pred),4),
'AUC_ROC' : round(roc_auc_score(y_te, y_proba), 4),
}
# ── A.8 Model Training and Holdout Evaluation ────────────────────────────────
results = []
fitted = {}
for name, mdl in models.items():
Xtr = X_bal_s if name in scaled_models else X_bal
Xte = X_test_s if name in scaled_models else X_test
if name == 'CatBoost':
mdl.fit(Xtr, y_bal,
eval_set=(Xte, y_test), verbose=False)
elif name == 'LightGBM':
mdl.fit(Xtr, y_bal,
eval_set=[(Xte, y_test)],
callbacks=[lgb.early_stopping(50, verbose=False),
lgb.log_evaluation(period=-1)])
else:
mdl.fit(Xtr, y_bal)
results.append(evaluate(name, mdl, Xtr, y_bal, Xte, y_test))
fitted[name] = mdl
results_df = (pd.DataFrame(results)
.sort_values('AUC_ROC', ascending=False)
.reset_index(drop=True))
# ── A.9 Logistic Regression Coefficient Table ────────────────────────────────
lr = fitted['Logistic Regression']
coef_df = (pd.DataFrame({
'Variable' : PREDICTOR_COLS,
'Beta' : lr.coef_[0].round(4),
'Odds_Ratio': np.exp(lr.coef_[0]).round(4),
}).sort_values('Beta', ascending=False)
.reset_index(drop=True))
# ── A.10 Stratified 5-Fold Cross-Validation ───────────────────────────────────
K = 5
kf = StratifiedKFold(n_splits=K, shuffle=True, random_state=RANDOM_STATE)
top3 = results_df.head(3)['Model'].tolist()
cv_out = []
for name in top3:
mdl = fitted[name]
for scoring, label in [('accuracy', 'Accuracy'),
('f1', 'F1'),
('roc_auc', 'AUC')]:
s = cross_val_score(mdl, X_train, y_train, cv=kf,
scoring=scoring, n_jobs=-1)
cv_out.append({
'Model' : name, 'Metric' : label,
'Mean' : round(s.mean(), 4), 'SD' : round(s.std(), 4),
'Min' : round(s.min(), 4), 'Max': round(s.max(), 4),
'95%_CI' : f'[{s.mean()-1.96*s.std():.4f}, {s.mean()+1.96*s.std():.4f}]',
})
cv_df = pd.DataFrame(cv_out)
# ── A.11 External Verification (flood.csv) ───────────────────────────────────
from sklearn.metrics import brier_score_loss
df_flood = pl.read_csv(FLOOD_CSV, infer_schema_length=0, ignore_errors=True).to_pandas()
for col in df_flood.select_dtypes('object').columns:
df_flood[col] = pd.to_numeric(df_flood[col], errors='coerce')
X_flood = df_flood[PREDICTOR_COLS].values
X_flood_s = scaler.transform(X_flood)
flood_pred_prob = lr.predict_proba(X_flood_s)[:, 1]
flood_pred_bin = (flood_pred_prob >= THRESHOLD).astype(int)
if TARGET_CONT in df_flood.columns:
y_flood_true_cont = df_flood[TARGET_CONT].values
y_flood_true_bin = (y_flood_true_cont >= THRESHOLD).astype(int)
verif = {
'Accuracy' : round(accuracy_score(y_flood_true_bin, flood_pred_bin), 4),
'F1' : round(f1_score(y_flood_true_bin, flood_pred_bin), 4),
'AUC' : round(roc_auc_score(y_flood_true_bin, flood_pred_prob), 4),
'MCC' : round(matthews_corrcoef(y_flood_true_bin, flood_pred_bin), 4),
'Pearson_r': round(pearsonr(y_flood_true_cont, flood_pred_prob)[0], 4),
'Brier' : round(brier_score_loss(y_flood_true_bin, flood_pred_prob), 4),
}
# ── A.12 Probability Calibration Analysis ─────────────────────────────────────
from sklearn.calibration import calibration_curve
from sklearn.linear_model import LogisticRegression as PlattScaler
p_holdout = lr.predict_proba(X_test_s)[:, 1]
bs_holdout = brier_score_loss(y_test, p_holdout)
bs_flood = verif['Brier']
frac_pos, mean_pred = calibration_curve(y_test, p_holdout, n_bins=5, strategy='quantile')
thresh_rows = []
for t in np.round(np.arange(0.10, 0.95, 0.10), 2):
y_hat = (p_holdout >= t).astype(int)
tp = int(np.sum((y_hat==1)&(y_test==1))); fp = int(np.sum((y_hat==1)&(y_test==0)))
fn = int(np.sum((y_hat==0)&(y_test==1))); tn = int(np.sum((y_hat==0)&(y_test==0)))
acc = (tp+tn)/(tp+tn+fp+fn)
prec = tp/(tp+fp) if (tp+fp)>0 else 0.0
rec = tp/(tp+fn) if (tp+fn)>0 else 0.0
f1 = 2*prec*rec/(prec+rec) if (prec+rec)>0 else 0.0
thresh_rows.append({'Threshold': t, 'Accuracy': round(acc,4),
'Precision': round(prec,4), 'Recall': round(rec,4), 'F1': round(f1,4)})
threshold_df = pd.DataFrame(thresh_rows)
platt = PlattScaler(C=1.0, solver='lbfgs', max_iter=1000, random_state=RANDOM_STATE)
platt.fit(p_holdout.reshape(-1, 1), y_test)
p_platt = platt.predict_proba(p_holdout.reshape(-1, 1))[:, 1]
bs_platt = brier_score_loss(y_test, p_platt)
rel_change = (bs_platt - bs_holdout) / bs_holdout * 100
calib_df = pd.DataFrame({
'Partition': ['Holdout', 'flood.csv (external)'],
'Brier_Score': [round(bs_holdout,4), round(bs_flood,4)],
'Brier_Score_Platt_scaled': [round(bs_platt,4), np.nan],
'Relative_Change_%': [round(rel_change,2), np.nan],
})
# ── A.13 Print Summary ────────────────────────────────────────────────────────
print("TABLE 3 — Holdout Performance (n = 335,388)")
print(results_df.to_string(index=False))
print("\nTABLE 4 — Probability Calibration Summary")
print(calib_df.to_string(index=False))
print("\nTABLE 5 — Threshold-Sensitivity Analysis (holdout, 0.10-0.90)")
print(threshold_df.to_string(index=False))
print("\nTABLE 6 — LR Coefficients (top 5 / bottom 2)")
print(pd.concat([coef_df.head(5), coef_df.tail(2)]).to_string(index=False))
print("\nTABLE 7 — Cross-Validation (k=5, 95% CI = mean ± 1.96 × SD)")
print(cv_df.to_string(index=False))
print("\nTABLE 8 — External Verification (flood.csv, n = 50,000)")
print(pd.DataFrame([verif]).to_string(index=False))