Download src/monitoring/drift_monitor.py from Ypeng12/quant-ai: direct link, hf CLI and curl.
- Browser
- Download file 2.44 kB
-
https://huggingface.co/spaces/Ypeng12/quant-ai/resolve/main/src/monitoring/drift_monitor.py
- Command line
-
hf download hf://spaces/Ypeng12/quant-ai/src/monitoring/drift_monitor.py
-
curl -L -o drift_monitor.py https://huggingface.co/spaces/Ypeng12/quant-ai/resolve/main/src/monitoring/drift_monitor.py
2.44 kB
| import numpy as np | |
| import pandas as pd | |
| from scipy import stats | |
| from typing import Dict, Any | |
| def calculate_psi(expected: np.ndarray, actual: np.ndarray, num_buckets: int = 10, eps: float = 1e-4) -> float: | |
| """ | |
| Calculates Population Stability Index (PSI): | |
| PSI = sum((Actual% - Expected%) * ln(Actual% / Expected%)) | |
| PSI < 0.10: Stable (Green) | |
| 0.10 <= PSI < 0.25: Moderate Drift (Yellow) | |
| PSI >= 0.25: Significant Drift (Red) | |
| """ | |
| expected = expected[~np.isnan(expected)] | |
| actual = actual[~np.isnan(actual)] | |
| if len(expected) < 20 or len(actual) < 20: | |
| return 0.0 | |
| percentiles = np.linspace(0, 100, num_buckets + 1) | |
| buckets = np.percentile(expected, percentiles) | |
| buckets[0] = -np.inf | |
| buckets[-1] = np.inf | |
| exp_counts, _ = np.histogram(expected, bins=buckets) | |
| act_counts, _ = np.histogram(actual, bins=buckets) | |
| exp_pct = exp_counts / len(expected) | |
| act_pct = act_counts / len(actual) | |
| exp_pct = np.clip(exp_pct, eps, 1.0) | |
| act_pct = np.clip(act_pct, eps, 1.0) | |
| psi_val = np.sum((act_pct - exp_pct) * np.log(act_pct / exp_pct)) | |
| return float(psi_val) | |
| class FeatureDriftMonitor: | |
| """ | |
| Monitors feature distribution drift between training reference window and current production window. | |
| """ | |
| def __init__(self, psi_threshold_warning: float = 0.10, psi_threshold_alert: float = 0.25): | |
| self.psi_threshold_warning = psi_threshold_warning | |
| self.psi_threshold_alert = psi_threshold_alert | |
| def audit_drift(self, ref_df: pd.DataFrame, cur_df: pd.DataFrame, feature_cols: list) -> Dict[str, Any]: | |
| results = {} | |
| for col in feature_cols: | |
| if col in ref_df.columns and col in cur_df.columns: | |
| ref_vals = ref_df[col].dropna().values | |
| cur_vals = cur_df[col].dropna().values | |
| psi_score = calculate_psi(ref_vals, cur_vals) | |
| ks_stat, p_val = stats.ks_2samp(ref_vals, cur_vals) | |
| status = "GREEN" | |
| if psi_score >= self.psi_threshold_alert: | |
| status = "RED" | |
| elif psi_score >= self.psi_threshold_warning: | |
| status = "YELLOW" | |
| results[col] = { | |
| "psi": psi_score, | |
| "ks_stat": float(ks_stat), | |
| "p_value": float(p_val), | |
| "status": status, | |
| } | |
| return results | |