data_fit_gof.py 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. """Data preparation, constrained MAP fitting, prediction, and fit diagnostics."""
  2. import numpy as np
  3. from .core import (
  4. load_xy,
  5. make_trimmed_dataset,
  6. sigmoid,
  7. softplus,
  8. theta_max,
  9. unpack,
  10. dE_full,
  11. P_with,
  12. make_priors,
  13. init_phi,
  14. neg_post,
  15. fit_bayes,
  16. x_at_p,
  17. slope_at_x,
  18. )
  19. def goodness_of_fit(X, y, theta_hat, threshold=0.5):
  20. """Return conditional Bernoulli fit measures for a fitted CB model."""
  21. X = np.asarray(X, float).ravel()
  22. y = np.asarray(y, int).ravel()
  23. p = np.clip(np.asarray(P_with(theta_hat, X), float), 1e-12, 1.0 - 1e-12)
  24. pred = (p >= threshold).astype(int)
  25. log_loss = -float(np.mean(y * np.log(p) + (1 - y) * np.log1p(-p)))
  26. return {
  27. "n": int(y.size),
  28. "events": int(y.sum()),
  29. "log_loss": log_loss,
  30. "brier_score": float(np.mean((y - p) ** 2)),
  31. "accuracy": float(np.mean(pred == y)),
  32. "sensitivity": float(np.mean(pred[y == 1] == 1)) if np.any(y == 1) else np.nan,
  33. "specificity": float(np.mean(pred[y == 0] == 0)) if np.any(y == 0) else np.nan,
  34. }
  35. def fit_full_trim(percentile=95, **fit_kwargs):
  36. """Load, trim, and fit the FULL and TRIM constrained Bayesian models."""
  37. X, y = load_xy(perc=percentile)
  38. ds = make_trimmed_dataset(X, y)
  39. result = {"data": ds}
  40. for key, x_key, y_key in (
  41. ("FULL", "X_orig", "y_orig"),
  42. ("TRIM", "X_trim", "y_trim"),
  43. ):
  44. theta, optimizer = fit_bayes(ds[x_key], ds[y_key], **fit_kwargs)
  45. result[key] = {
  46. "theta": theta,
  47. "optimizer": optimizer,
  48. "gof": goodness_of_fit(ds[x_key], ds[y_key], theta),
  49. }
  50. return result