Skip to content

Model summaries

This page documents 3 public symbols. Each entry includes its purpose, import path, full API docstring, and the maintained example that exercises it.

Conceptual guide

DiagnosticSummary

Normalized global diagnostics for one fitted estimator.

Property Value
Type class
Import from pygwrx.diagnostics import DiagnosticSummary
Signature DiagnosticSummary(model_name: 'str', n_samples: 'Optional[int]', n_features: 'Optional[int]', family: 'Optional[str]', metrics: 'Mapping[str, float]', conditional_metrics: 'Tuple[str, ...]' = ()) -> None
Maintained example examples/diagnostics/01_model_and_residual_diagnostics.py

DiagnosticSummary dataclass

DiagnosticSummary(
    model_name: str,
    n_samples: Optional[int],
    n_features: Optional[int],
    family: Optional[str],
    metrics: Mapping[str, float],
    conditional_metrics: Tuple[str, ...] = (),
)

Normalized global diagnostics for one fitted estimator.

to_series

to_series(name: Optional[str] = None) -> pd.Series

Return a labeled :class:pandas.Series.

Source code in src/pygwrx/diagnostics/model.py
def to_series(self, name: Optional[str] = None) -> pd.Series:
    """Return a labeled :class:`pandas.Series`."""
    values: Dict[str, object] = {
        "model": self.model_name,
        "n_samples": self.n_samples,
        "n_features": self.n_features,
        "family": self.family,
    }
    values.update(self.metrics)
    return pd.Series(values, name=name or self.model_name)

diagnostics_frame

Return one row of normalized global diagnostics per model.

Property Value
Type function
Import from pygwrx.diagnostics import diagnostics_frame
Signature diagnostics_frame(models: 'Iterable[Any]', labels: 'Optional[Sequence[str]]' = None) -> 'pd.DataFrame'
Maintained example examples/diagnostics/01_model_and_residual_diagnostics.py

diagnostics_frame

diagnostics_frame(
    models: Iterable[Any],
    labels: Optional[Sequence[str]] = None,
) -> pd.DataFrame

Return one row of normalized global diagnostics per model.

Source code in src/pygwrx/diagnostics/model.py
def diagnostics_frame(
    models: Iterable[Any], labels: Optional[Sequence[str]] = None
) -> pd.DataFrame:
    """Return one row of normalized global diagnostics per model."""
    model_list = list(models)
    if not model_list:
        raise ValueError("models must contain at least one fitted estimator.")
    if labels is not None and len(labels) != len(model_list):
        raise ValueError("labels must contain one entry per model.")
    records = []
    for index, model in enumerate(model_list):
        summary = model_diagnostic_summary(model)
        record = summary.to_series().to_dict()
        record["label"] = labels[index] if labels is not None else summary.model_name
        records.append(record)
    frame = pd.DataFrame(records).set_index("label")
    preferred = [
        "model",
        "n_samples",
        "n_features",
        "family",
        "r2",
        "adj_r2",
        "rmse",
        "mae",
        "aic",
        "aicc",
        "bic",
        "enp",
        "edf",
    ]
    columns = [name for name in preferred if name in frame.columns]
    columns.extend(name for name in frame.columns if name not in columns)
    return frame.loc[:, columns]

model_diagnostic_summary

Normalize global diagnostics exposed by any supported fitted model.

Property Value
Type function
Import from pygwrx.diagnostics import model_diagnostic_summary
Signature model_diagnostic_summary(model: 'Any') -> 'DiagnosticSummary'
Maintained example examples/diagnostics/01_model_and_residual_diagnostics.py

model_diagnostic_summary

model_diagnostic_summary(model: Any) -> DiagnosticSummary

Normalize global diagnostics exposed by any supported fitted model.

Source code in src/pygwrx/diagnostics/model.py
def model_diagnostic_summary(model: Any) -> DiagnosticSummary:
    """Normalize global diagnostics exposed by any supported fitted model."""
    require_fitted(model)
    raw: Dict[str, Any] = dict(getattr(model, "diagnostics_", None) or {})
    attribute_aliases = {
        "r2_": "r2",
        "adjusted_r2_": "adjusted_r2",
        "rss_": "rss",
        "aic_": "aic",
        "aicc_": "aicc",
        "bic_": "bic",
        "trace_S_": "trace_S",
        "trace_StS_": "trace_StS",
        "effective_params_": "effective_params",
        "effective_n_params_": "effective_params",
        "effective_df_": "edf",
        "cv_score_": "cv_score",
        "bandwidth_cv_score_": "bandwidth_cv_score",
        "alpha_score_": "alpha_score",
        "sigma2_": "sigma2",
        "sigma_": "sigma",
    }
    for attribute, alias in attribute_aliases.items():
        value = getattr(model, attribute, None)
        if value is not None:
            raw.setdefault(alias, value)

    y = training_response(model)
    fitted = fitted_values(model)
    if y is not None and fitted is not None and y.size == fitted.size:
        residual = y - fitted
        raw.setdefault("rss", float(np.dot(residual, residual)))
        raw.setdefault("rmse", float(np.sqrt(np.mean(residual**2))))
        raw.setdefault("mae", float(np.mean(np.abs(residual))))

    metrics: Dict[str, float] = {}
    for canonical, aliases in _ALIAS_GROUPS.items():
        for alias in aliases:
            if alias in raw:
                value = _finite_scalar(raw[alias])
                if value is not None:
                    metrics[canonical] = value
                    break

    # Preserve additional scalar diagnostics without duplicating aliases.
    consumed = {alias for aliases in _ALIAS_GROUPS.values() for alias in aliases}
    for key, value in raw.items():
        if key in consumed or key in metrics:
            continue
        scalar = _finite_scalar(value)
        if scalar is not None:
            metrics[str(key)] = scalar

    n_samples = getattr(model, "n_samples_", None)
    if n_samples is None and y is not None:
        n_samples = int(y.size)
    n_features = getattr(model, "n_features_in_", None)
    family = getattr(model, "family", None)
    if family is not None and not isinstance(family, str):
        family = family.__class__.__name__

    conditional = tuple(
        name for name in ("aicc", "enp") if f"conditional_{name}" in raw
    )
    return DiagnosticSummary(
        model_name=model.__class__.__name__,
        n_samples=None if n_samples is None else int(n_samples),
        n_features=None if n_features is None else int(n_features),
        family=None if family is None else str(family),
        metrics=metrics,
        conditional_metrics=conditional,
    )

Runnable examples used on this page

examples/diagnostics/01_model_and_residual_diagnostics.py
# SPDX-FileCopyrightText: 2026 Jinghao Hu
# SPDX-License-Identifier: MIT

"""Extract global and local diagnostics from fitted models."""

# Allow this script to run directly from any working directory.
import sys
from pathlib import Path

_PROJECT_ROOT = Path(__file__).resolve().parents[2]
_EXAMPLES_ROOT = _PROJECT_ROOT / "examples"
_SRC_ROOT = _PROJECT_ROOT / "src"
for _path in (_SRC_ROOT, _EXAMPLES_ROOT):
    if str(_path) not in sys.path:
        sys.path.insert(0, str(_path))

from _common import spatial_regression

from pygwrx import GWR
from pygwrx.diagnostics import (
    DiagnosticSummary,
    InfluenceThresholds,
    diagnostics_frame,
    influence_thresholds,
    local_diagnostic_frame,
    model_diagnostic_summary,
)

X, y, coords = spatial_regression(n=42, p=2)
first = GWR(bandwidth=22, adaptive=True, kernel="bisquare").fit(X, y, coords)
second = GWR(bandwidth=24, adaptive=True, kernel="gaussian").fit(X, y, coords)
summary = model_diagnostic_summary(first)
thresholds = influence_thresholds(first)
assert isinstance(summary, DiagnosticSummary)
assert isinstance(thresholds, InfluenceThresholds)
print(summary.to_series())
print(thresholds)
print(local_diagnostic_frame(first).head())
print(diagnostics_frame([first, second], labels=["bisquare", "gaussian"]))