def loo_pointwise_table(x: Any, data: pd.DataFrame | None = None) -> pd.DataFrame:
pointwise = getattr(x, "pointwise", None)
if pointwise is None and hasattr(x, "raw"):
pointwise = getattr(x.raw, "pointwise", None)
if pointwise is None:
raise GP3BayesError("`x` must contain pointwise LOO estimates.")
if isinstance(pointwise, pd.DataFrame):
out = pointwise.copy().reset_index(drop=True)
else:
arr = np.asarray(pointwise, float)
if arr.ndim == 1:
out = pd.DataFrame({"elpd_loo": arr})
elif arr.ndim == 2:
names = ["elpd_loo", "mcse_elpd_loo", "p_loo", "looic"][: arr.shape[1]]
out = pd.DataFrame(arr, columns=names)
else:
raise GP3BayesError("Pointwise LOO estimates have an unsupported shape.")
k = getattr(x, "pareto_k", None)
if k is None and "pareto_k" in out:
k = out["pareto_k"].to_numpy(float)
if k is None:
raise GP3BayesError("Pareto-k diagnostics are unavailable.")
k = np.asarray(k, float).reshape(-1)
if len(k) != len(out):
raise GP3BayesError("LOO pointwise and Pareto-k diagnostics differ in length.")
influence = getattr(x, "influence_pareto_k", None)
influence_arr = np.asarray(influence if influence is not None else k, float).reshape(-1)
result = pd.concat(
[
pd.DataFrame({"observation": np.arange(1, len(out) + 1)}),
out.drop(columns=["pareto_k"], errors="ignore"),
],
axis=1,
)
result["pareto_k"] = k
result["influence_pareto_k"] = influence_arr
result["flagged"] = ~np.isfinite(k) | (k >= 0.7)
result["severe"] = np.isfinite(k) & (k >= 1.0)
if data is not None:
if not isinstance(data, pd.DataFrame) or len(data) != len(result):
raise GP3BayesError("`data` must have one row per LOO observation.")
result = pd.concat([result.reset_index(drop=True), data.reset_index(drop=True)], axis=1)
return result