import numpy as np import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.base import BaseEstimator, TransformerMixin from sklearn.feature_selection import mutual_info_classif np.random.seed(42) n = 5000 df = pd.DataFrame({ "avg_transaction_value": np.random.normal(150, 40, n), "num_transactions_30d": np.random.poisson(12, n), "account_age_days": np.random.exponential(400, n), "device_risk_score": np.random.beta(2, 5, n), }) df["target"] = (df["device_risk_score"] * 3 + np.random.normal(0, 0.3, n) > 1.2).astype(int) # this is the kind of feature that sneaks into real datasets constantly - # a flag count that got backfilled after the outcome was already known df["post_hoc_flag_count"] = df["target"] * np.random.poisson(2, n) X = df.drop(columns=["target"]) y = df["target"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)