Source code for safeai.cramer

"""
Lorenz/concordance Cramer-von Mises utilities.

The functions in this module implement the Lorenz-curve and concordance-curve
building blocks used by SAFE-AI metrics. They are based on the identity between
weighted concordance-curve divergence and the rank-based Cramer-von Mises
formulation discussed in:

https://www.worldscientific.com/doi/abs/10.1142/S0218202526420030
"""

import numpy as np

[docs] def lorenz_curve(y): """ Compute the Lorenz curve for a given array. Parameters ---------- y : array-like Input values Returns ------- np.ndarray Normalized cumulative sum (Lorenz curve) """ y = np.asarray(y, dtype=float).reshape(-1) y = y[~np.isnan(y)] if len(y) == 0: return np.array([]) y_sorted = np.sort(y) cum = np.cumsum(y_sorted) sum_y = cum[-1] if sum_y == 0: return np.full_like(cum, np.nan) return cum / sum_y
[docs] def concordance_curve(y, yhat): """ Compute the concordance curve between true and predicted values. Parameters ---------- y : array-like True values yhat : array-like Predicted values Returns ------- np.ndarray Concordance curve """ y = np.asarray(y, dtype=float).reshape(-1) yhat = np.asarray(yhat, dtype=float).reshape(-1) mask = ~np.isnan(y) & ~np.isnan(yhat) y = y[mask] yhat = yhat[mask] if len(y) == 0: return np.array([]) ord_idx = np.argsort(yhat, kind='stable') cum = np.cumsum(y[ord_idx]) return cum / cum[-1]
[docs] def gini_via_lorenz(y): """ Calculate Gini coefficient. Parameters ---------- y : array-like Input values Returns ------- float Gini coefficient """ y = np.asarray(y, dtype=float).reshape(-1) l = lorenz_curve(y) n = len(l) if n == 0: return np.nan u = np.linspace(1 / n, 1, n) return 2 * np.mean(np.abs(u - l))
[docs] def cvm1_concordance_weighted(y, yhat): """ Weighted Cramer von Mises distance between Lorenz and Concordance curves. Parameters ---------- y : array-like True values yhat : array-like Predicted values Returns ------- float Weighted CvM distance """ y = np.asarray(y, dtype=float).reshape(-1) yhat = np.asarray(yhat, dtype=float).reshape(-1) mask = ~np.isnan(y) & ~np.isnan(yhat) y = y[mask] yhat = yhat[mask] n = len(y) if n == 0: return np.nan # Lorenz curve ord_y = np.argsort(y, kind='stable') l = np.cumsum(y[ord_y]) / np.sum(y) # Concordance curve ord_yhat = np.argsort(yhat, kind='stable') c = np.cumsum(y[ord_yhat]) / np.sum(y) # Weights weights = y[ord_y] / np.sum(y) return np.sum(np.abs(c - l) * weights)