import numpy as np
from .diagnostics import aic, bic
from .mle import (
fit_burr,
fit_exponential,
fit_gamma,
fit_inverse_gamma,
fit_loglogistic,
fit_lognormal,
fit_pareto,
fit_paretoII,
fit_weibull,
)
from .moments import (
fit_exponential_moments,
fit_gamma_moments,
fit_lognormal_moments,
fit_pareto_moments,
fit_weibull_moments,
)
SEVERITY_MLE_FITTERS = {
"exponential": (fit_exponential, 1),
"gamma": (fit_gamma, 2),
"lognormal": (fit_lognormal, 2),
"pareto": (fit_pareto, 2),
"weibull": (fit_weibull, 2),
# FAM / ASTAM families (0.6.0); all support truncation/censoring
"paretoII": (fit_paretoII, 2),
"loglogistic": (fit_loglogistic, 2),
"inverse_gamma": (fit_inverse_gamma, 2),
"burr": (fit_burr, 3),
}
SEVERITY_MOMENT_FITTERS = {
"exponential": (fit_exponential_moments, 1),
"gamma": (fit_gamma_moments, 2),
"lognormal": (fit_lognormal_moments, 2),
"pareto": (fit_pareto_moments, 2),
"weibull": (fit_weibull_moments, 2),
}
[docs]
def fit_best_severity(data, candidates=None, method="mle", criterion="aic",
truncation=None, censored=None):
"""
Fit a set of severity models and return the best one by AIC or BIC.
Parameters
----------
data : array-like
Severity observations.
candidates : list of str, optional
Candidate model names. Defaults to all supported severity fitters.
method : {"mle", "moments"}
Fitting method.
criterion : {"aic", "bic"}
Selection criterion.
Returns
-------
dict
Dictionary with keys:
- "best_name"
- "best_model"
- "criterion"
- "method"
- "results"
Notes
-----
Models that fail to fit are skipped.
"""
data = np.asarray(data, dtype=float)
if data.size == 0:
raise ValueError("data must not be empty.")
if method not in {"mle", "moments"}:
raise ValueError("method must be 'mle' or 'moments'.")
if (truncation is not None or censored is not None) and method != "mle":
raise ValueError("truncation/censoring is only supported with method='mle'.")
if criterion not in {"aic", "bic"}:
raise ValueError("criterion must be 'aic' or 'bic'.")
fitters = SEVERITY_MLE_FITTERS if method == "mle" else SEVERITY_MOMENT_FITTERS
if candidates is None:
candidates = list(fitters.keys())
results = []
for name in candidates:
if name not in fitters:
raise ValueError(f"Unsupported candidate: {name}")
fitter, k = fitters[name]
try:
if truncation is not None or censored is not None:
model = fitter(data, truncation=truncation, censored=censored)
else:
model = fitter(data)
score = (
aic(model, data, k=k, truncation=truncation, censored=censored)
if criterion == "aic"
else bic(model, data, k=k, truncation=truncation, censored=censored)
)
results.append({
"name": name,
"model": model,
"k": k,
"score": float(score),
})
except Exception as exc:
results.append({
"name": name,
"model": None,
"k": k,
"score": float("inf"),
"error": str(exc),
})
valid = [r for r in results if np.isfinite(r["score"])]
if not valid:
error_details = "; ".join(
f'{r["name"]}: {r.get("error", "unknown error")}'
for r in results
)
raise RuntimeError(
f"No candidate models could be fit successfully. Errors: {error_details}"
)
best = min(valid, key=lambda r: r["score"])
return {
"best_name": best["name"],
"best_model": best["model"],
"criterion": criterion,
"method": method,
"results": results,
}