Skip to content

Metrics

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

Conceptual guide

compute_r_squared

Compute the coefficient of determination, R².

Property Value
Type function
Import from pygwrx.core import compute_r_squared
Signature compute_r_squared(y_true: 'np.ndarray', y_pred: 'np.ndarray') -> 'float'
Maintained example examples/core/05_metrics.py

compute_r_squared

compute_r_squared(
    y_true: ndarray, y_pred: ndarray
) -> float

Compute the coefficient of determination, R².

R² is bounded above by 1 but may be negative. For a constant response, this function returns 1 for exact prediction and 0 otherwise, matching the finite convention commonly used by machine-learning libraries.

Source code in src/pygwrx/core/metrics.py
def compute_r_squared(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """Compute the coefficient of determination, R².

    R² is bounded above by 1 but may be negative. For a constant response,
    this function returns 1 for exact prediction and 0 otherwise, matching
    the finite convention commonly used by machine-learning libraries.
    """
    y_true_arr, y_pred_arr = _validate_targets(y_true, y_pred)

    residuals = y_true_arr - y_pred_arr
    centered = y_true_arr - np.mean(y_true_arr)

    ss_res = float(np.dot(residuals, residuals))
    ss_tot = float(np.dot(centered, centered))

    if np.isclose(ss_tot, 0.0, rtol=0.0, atol=np.finfo(float).eps):
        return (
            1.0
            if np.isclose(
                ss_res,
                0.0,
                rtol=0.0,
                atol=np.finfo(float).eps,
            )
            else 0.0
        )

    return float(1.0 - ss_res / ss_tot)

compute_adjusted_r_squared

Compute GWR adjusted R² from residual effective degrees of freedom.

Property Value
Type function
Import from pygwrx.core import compute_adjusted_r_squared
Signature compute_adjusted_r_squared(y_true: 'np.ndarray', y_pred: 'np.ndarray', edf: 'float') -> 'float'
Maintained example examples/core/05_metrics.py

compute_adjusted_r_squared

compute_adjusted_r_squared(
    y_true: ndarray, y_pred: ndarray, edf: float
) -> float

Compute GWR adjusted R² from residual effective degrees of freedom.

Formula

Adj R² = 1 - (1 - R²) * (n - 1) / (EDF - 1)

Here EDF is normally

n - 2 * trace(S) + trace(S.T @ S)

Source code in src/pygwrx/core/metrics.py
def compute_adjusted_r_squared(
    y_true: np.ndarray,
    y_pred: np.ndarray,
    edf: float,
) -> float:
    """Compute GWR adjusted R² from residual effective degrees of freedom.

    Formula
    -------
    Adj R² = 1 - (1 - R²) * (n - 1) / (EDF - 1)

    Here EDF is normally:
        n - 2 * trace(S) + trace(S.T @ S)
    """
    y_true_arr, y_pred_arr = _validate_targets(y_true, y_pred)
    edf_value = _validate_nonnegative_scalar(edf, "edf")

    if edf_value <= 1.0:
        return np.nan

    n = y_true_arr.size
    r2 = compute_r_squared(y_true_arr, y_pred_arr)

    return float(1.0 - (1.0 - r2) * (n - 1.0) / (edf_value - 1.0))

compute_aic

Compute Gaussian GWR AIC using trace(S) as the complexity term.

Property Value
Type function
Import from pygwrx.core import compute_aic
Signature compute_aic(y_true: 'np.ndarray', y_pred: 'np.ndarray', n_params: 'float') -> 'float'
Maintained example examples/core/05_metrics.py

compute_aic

compute_aic(
    y_true: ndarray, y_pred: ndarray, n_params: float
) -> float

Compute Gaussian GWR AIC using trace(S) as the complexity term.

Formula

AIC = n*log(RSS/n) + n*log(2π) + n + 2*(trace(S) + 1)

Notes

This is a Gaussian RSS-based criterion. It must not be used for Poisson, Binomial, Gamma, or other non-Gaussian GWGLM families.

Source code in src/pygwrx/core/metrics.py
def compute_aic(
    y_true: np.ndarray,
    y_pred: np.ndarray,
    n_params: float,
) -> float:
    """Compute Gaussian GWR AIC using trace(S) as the complexity term.

    Formula
    -------
    AIC = n*log(RSS/n) + n*log(2π) + n + 2*(trace(S) + 1)

    Notes:
        This is a Gaussian RSS-based criterion. It must not be used for Poisson,
        Binomial, Gamma, or other non-Gaussian GWGLM families.
    """
    y_true_arr, y_pred_arr = _validate_targets(y_true, y_pred)
    n_params_value = _validate_nonnegative_scalar(n_params, "n_params")

    n = y_true_arr.size
    rss = _safe_rss_for_log(_residual_sum_of_squares(y_true_arr, y_pred_arr))

    return float(
        n * np.log(rss / n) + n * np.log(2.0 * np.pi) + n + 2.0 * (n_params_value + 1.0)
    )

compute_aicc

Compute Gaussian GWR corrected AIC (AICc). Compute the corrected Akaike information criterion for Gaussian GWR.

Property Value
Type function
Import from pygwrx.core import compute_aicc
Signature compute_aicc(y_true: 'np.ndarray', y_pred: 'np.ndarray', n_params: 'float') -> 'float'
Maintained example examples/core/05_metrics.py

compute_aicc

compute_aicc(
    y_true: ndarray, y_pred: ndarray, n_params: float
) -> float

Compute Gaussian GWR corrected AIC (AICc). Compute the corrected Akaike information criterion for Gaussian GWR.

Formula

AICc = n*log(RSS/n) + n*log(2π) + n*(n + trace(S)) / (n - 2 - trace(S))

Returns infinity when the correction denominator is not positive.

Source code in src/pygwrx/core/metrics.py
def compute_aicc(
    y_true: np.ndarray,
    y_pred: np.ndarray,
    n_params: float,
) -> float:
    """Compute Gaussian GWR corrected AIC (AICc). Compute the corrected Akaike information
    criterion for Gaussian GWR.

    Formula
    -------
    AICc = n*log(RSS/n) + n*log(2π)
           + n*(n + trace(S)) / (n - 2 - trace(S))

    Returns infinity when the correction denominator is not positive.
    """
    y_true_arr, y_pred_arr = _validate_targets(y_true, y_pred)
    n_params_value = _validate_nonnegative_scalar(n_params, "n_params")

    n = y_true_arr.size
    rss = _safe_rss_for_log(_residual_sum_of_squares(y_true_arr, y_pred_arr))
    denominator = n - 2.0 - n_params_value

    if denominator <= 0.0:
        return np.inf

    return float(
        n * np.log(rss / n)
        + n * np.log(2.0 * np.pi)
        + n * (n + n_params_value) / denominator
    )

compute_bic

Compute Gaussian GWR BIC using trace(S).

Property Value
Type function
Import from pygwrx.core import compute_bic
Signature compute_bic(y_true: 'np.ndarray', y_pred: 'np.ndarray', trace_S: 'float') -> 'float'
Maintained example examples/core/05_metrics.py

compute_bic

compute_bic(
    y_true: ndarray, y_pred: ndarray, trace_S: float
) -> float

Compute Gaussian GWR BIC using trace(S).

Formula

BIC = n*log(RSS/n) + n*log(2π) + n + log(n)*(trace(S) + 1)

Notes

This is a Gaussian RSS-based criterion and is not suitable for non-Gaussian GWGLM families.

Source code in src/pygwrx/core/metrics.py
def compute_bic(
    y_true: np.ndarray,
    y_pred: np.ndarray,
    trace_S: float,
) -> float:
    """Compute Gaussian GWR BIC using trace(S).

    Formula
    -------
    BIC = n*log(RSS/n) + n*log(2π) + n
          + log(n)*(trace(S) + 1)

    Notes:
        This is a Gaussian RSS-based criterion and is not suitable for
        non-Gaussian GWGLM families.
    """
    y_true_arr, y_pred_arr = _validate_targets(y_true, y_pred)
    trace_s_value = _validate_nonnegative_scalar(trace_S, "trace_S")

    n = y_true_arr.size
    rss = _safe_rss_for_log(_residual_sum_of_squares(y_true_arr, y_pred_arr))

    return float(
        n * np.log(rss / n)
        + n * np.log(2.0 * np.pi)
        + n
        + np.log(n) * (trace_s_value + 1.0)
    )

compute_local_r_squared

Compute local weighted R² values. Compute a locally weighted coefficient of determination.

Property Value
Type function
Import from pygwrx.core import compute_local_r_squared
Signature compute_local_r_squared(y_true: 'np.ndarray', y_pred: 'np.ndarray', weights: 'np.ndarray') -> 'np.ndarray'
Maintained example examples/core/05_metrics.py

compute_local_r_squared

compute_local_r_squared(
    y_true: ndarray, y_pred: ndarray, weights: ndarray
) -> np.ndarray

Compute local weighted R² values. Compute a locally weighted coefficient of determination.

Local R² is bounded above by 1 but may be negative when local predictions are worse than the local weighted-mean baseline.

Source code in src/pygwrx/core/metrics.py
def compute_local_r_squared(
    y_true: np.ndarray,
    y_pred: np.ndarray,
    weights: np.ndarray,
) -> np.ndarray:
    """Compute local weighted R² values. Compute a locally weighted coefficient of
    determination.

    Local R² is bounded above by 1 but may be negative when local predictions
    are worse than the local weighted-mean baseline.
    """
    y_true_arr, y_pred_arr = _validate_targets(y_true, y_pred)
    n_samples = y_true_arr.size

    try:
        weights_arr = np.asarray(weights, dtype=float)
    except (TypeError, ValueError) as exc:
        raise TypeError("weights must be numeric array-like data.") from exc

    expected_shape = (n_samples, n_samples)
    if weights_arr.shape != expected_shape:
        raise ValueError(
            f"weights must have shape {expected_shape}; got {weights_arr.shape}."
        )

    if not np.all(np.isfinite(weights_arr)):
        raise ValueError("weights must contain only finite values.")

    if np.any(weights_arr < 0.0):
        raise ValueError("weights must be non-negative.")

    residuals_sq = (y_true_arr - y_pred_arr) ** 2
    local_r2 = np.full(n_samples, np.nan, dtype=float)

    for i in range(n_samples):
        w = weights_arr[i]
        sum_w = float(np.sum(w))

        if sum_w <= np.finfo(float).eps:
            # No local information is available at this location.
            continue

        y_mean_weighted = float(np.dot(w, y_true_arr) / sum_w)
        tss_weighted = float(np.dot(w, (y_true_arr - y_mean_weighted) ** 2))
        rss_weighted = float(np.dot(w, residuals_sq))

        if np.isclose(
            tss_weighted,
            0.0,
            rtol=0.0,
            atol=np.finfo(float).eps,
        ):
            local_r2[i] = (
                1.0
                if np.isclose(
                    rss_weighted,
                    0.0,
                    rtol=0.0,
                    atol=np.finfo(float).eps,
                )
                else 0.0
            )
        else:
            local_r2[i] = 1.0 - rss_weighted / tss_weighted

    return local_r2

compute_effective_parameters

Return trace(S), the first common effective-parameter convention.

Property Value
Type function
Import from pygwrx.core import compute_effective_parameters
Signature compute_effective_parameters(hat_matrix: 'np.ndarray') -> 'float'
Maintained example examples/core/05_metrics.py

compute_effective_parameters

compute_effective_parameters(hat_matrix: ndarray) -> float

Return trace(S), the first common effective-parameter convention.

Notes

A second convention is 2*trace(S) - trace(S.T @ S), returned by :func:compute_enp. Keeping these definitions explicit avoids mixing incompatible EDF/ENP conventions.

Source code in src/pygwrx/core/metrics.py
def compute_effective_parameters(hat_matrix: np.ndarray) -> float:
    """Return trace(S), the first common effective-parameter convention.

    Notes:
        A second convention is 2*trace(S) - trace(S.T @ S), returned by
        :func:`compute_enp`. Keeping these definitions explicit avoids mixing
        incompatible EDF/ENP conventions.
    """
    matrix = _validate_hat_matrix(hat_matrix)
    return float(np.trace(matrix))

compute_diagnostics

Compute diagnostic statistics for a Gaussian GWR-style model.

Property Value
Type function
Import from pygwrx.core import compute_diagnostics
Signature compute_diagnostics(y_true: 'np.ndarray', y_pred: 'np.ndarray', hat_matrix: 'Optional[np.ndarray]' = None, n_features: 'Optional[int]' = None, compute_gwr_stats: 'bool' = False, *, trace_S: 'Optional[float]' = None, trace_StS: 'Optional[float]' = None) -> 'Dict[str, float]'
Maintained example examples/core/05_metrics.py

compute_diagnostics

compute_diagnostics(
    y_true: ndarray,
    y_pred: ndarray,
    hat_matrix: Optional[ndarray] = None,
    n_features: Optional[int] = None,
    compute_gwr_stats: bool = False,
    *,
    trace_S: Optional[float] = None,
    trace_StS: Optional[float] = None
) -> Dict[str, float]

Compute diagnostic statistics for a Gaussian GWR-style model.

Parameters:

Name Type Description Default
y_true ndarray

Observed values.

required
y_pred ndarray

Fitted values.

required
hat_matrix Optional[ndarray]

GWR hat matrix. When supplied, trace(S), trace(S'S), EDF, and ENP are derived from this matrix.

None
n_features Optional[int]

Backward-compatible parameter-count fallback used when no hat matrix is available. In the current PyGWRx model implementations this value is the number of columns in the fitted design matrix, so an intercept already present in that matrix must NOT be added again.

None
compute_gwr_stats bool

Include trace_S, trace_StS, edf, enp_v1, enp_v2, and compatibility key enp in the returned dictionary.

False
trace_S Optional[float]

Precomputed trace of the smoother matrix. Supply together with trace_StS when the full hat matrix is intentionally not stored.

None
trace_StS Optional[float]

Precomputed trace of S.T @ S. Supply together with trace_S to retain GWR complexity diagnostics without an n x n matrix.

None
Notes
  • Information criteria here are Gaussian RSS-based criteria.
  • Models without a reliable hat matrix receive only a parameter-count approximation of complexity.
  • For Poisson/Binomial/Gamma GWGLM, use family-specific log-likelihood and deviance diagnostics instead of this function.
Source code in src/pygwrx/core/metrics.py
def compute_diagnostics(
    y_true: np.ndarray,
    y_pred: np.ndarray,
    hat_matrix: Optional[np.ndarray] = None,
    n_features: Optional[int] = None,
    compute_gwr_stats: bool = False,
    *,
    trace_S: Optional[float] = None,
    trace_StS: Optional[float] = None,
) -> Dict[str, float]:
    """Compute diagnostic statistics for a Gaussian GWR-style model.

    Args:
        y_true: Observed values.
        y_pred: Fitted values.
        hat_matrix: GWR hat matrix. When supplied, trace(S), trace(S'S), EDF, and ENP are
            derived from this matrix.
        n_features: Backward-compatible parameter-count fallback used when no hat matrix
            is available. In the current PyGWRx model implementations this value
            is the number of columns in the fitted design matrix, so an intercept
            already present in that matrix must NOT be added again.
        compute_gwr_stats: Include trace_S, trace_StS, edf, enp_v1, enp_v2, and compatibility key
            enp in the returned dictionary.
        trace_S: Precomputed trace of the smoother matrix. Supply together with
            ``trace_StS`` when the full hat matrix is intentionally not stored.
        trace_StS: Precomputed trace of ``S.T @ S``. Supply together with
            ``trace_S`` to retain GWR complexity diagnostics without an ``n x n`` matrix.

    Notes:
        - Information criteria here are Gaussian RSS-based criteria.
        - Models without a reliable hat matrix receive only a parameter-count
          approximation of complexity.
        - For Poisson/Binomial/Gamma GWGLM, use family-specific log-likelihood and
          deviance diagnostics instead of this function.
    """
    y_true_arr, y_pred_arr = _validate_targets(y_true, y_pred)
    n = y_true_arr.size

    residuals = y_true_arr - y_pred_arr
    rss = float(np.dot(residuals, residuals))

    diagnostics: Dict[str, float] = {
        "r2": compute_r_squared(y_true_arr, y_pred_arr),
        "rss": rss,
        "rmse": float(np.sqrt(np.mean(residuals**2))),
        "mae": float(np.mean(np.abs(residuals))),
    }

    if (trace_S is None) != (trace_StS is None):
        raise ValueError("trace_S and trace_StS must be supplied together.")
    if hat_matrix is not None and trace_S is not None:
        raise ValueError("Supply either hat_matrix or trace_S/trace_StS, not both.")

    if hat_matrix is not None:
        matrix = _validate_hat_matrix(hat_matrix, n_samples=n)
        trace_stats = compute_trace_statistics(matrix)
        trace_s = trace_stats["trace_S"]
        trace_sts = trace_stats["trace_StS"]

    elif trace_S is not None:
        trace_s = _validate_nonnegative_scalar(trace_S, "trace_S")
        trace_sts = _validate_nonnegative_scalar(trace_StS, "trace_StS")

    else:
        trace_s = None
        trace_sts = None

    if trace_s is not None and trace_sts is not None:

        edf_v2 = compute_edf(n, trace_s, trace_sts)
        enp_v1 = trace_s
        enp_v2 = compute_enp(trace_s, trace_sts)

        diagnostics.update(
            {
                # Backward compatibility: historically this key meant trace(S).
                "effective_params": enp_v1,
                "adj_r2": compute_adjusted_r_squared(
                    y_true_arr,
                    y_pred_arr,
                    edf=edf_v2,
                ),
                "aic": compute_aic(y_true_arr, y_pred_arr, n_params=trace_s),
                "aicc": compute_aicc(y_true_arr, y_pred_arr, n_params=trace_s),
                "bic": compute_bic(y_true_arr, y_pred_arr, trace_S=trace_s),
            }
        )

        if compute_gwr_stats:
            diagnostics.update(
                {
                    "trace_S": trace_s,
                    "trace_StS": trace_sts,
                    # Explicitly expose both conventions.
                    "enp_v1": enp_v1,
                    "edf_v1": float(n - enp_v1),
                    "enp_v2": enp_v2,
                    "edf_v2": edf_v2,
                    # Compatibility keys preserve the original GWmodel-style
                    # v2 convention used by existing PyGWRx code.
                    "enp": enp_v2,
                    "edf": edf_v2,
                }
            )

    elif n_features is not None:
        # IMPORTANT: Existing PyGWRx callers pass the fitted design-matrix
        # column count here; several already include an intercept. Therefore
        # do not add another +1 in metrics.py.
        n_params = _validate_nonnegative_scalar(n_features, "n_features")
        residual_df = n - n_params

        diagnostics["effective_params"] = n_params
        diagnostics["adj_r2"] = (
            float(1.0 - (1.0 - diagnostics["r2"]) * (n - 1.0) / (residual_df - 1.0))
            if residual_df > 1.0
            else np.nan
        )
        diagnostics["aic"] = compute_aic(
            y_true_arr,
            y_pred_arr,
            n_params=n_params,
        )
        diagnostics["aicc"] = compute_aicc(
            y_true_arr,
            y_pred_arr,
            n_params=n_params,
        )
        diagnostics["bic"] = compute_bic(
            y_true_arr,
            y_pred_arr,
            trace_S=n_params,
        )

    else:
        # Complexity is unknown; do not invent a one-parameter model.
        diagnostics.update(
            {
                "effective_params": np.nan,
                "adj_r2": np.nan,
                "aic": np.nan,
                "aicc": np.nan,
                "bic": np.nan,
            }
        )

    return diagnostics

compute_trace_statistics

Compute trace(S) and trace(S'S) from a validated hat matrix.

Property Value
Type function
Import from pygwrx.core import compute_trace_statistics
Signature compute_trace_statistics(hat_matrix: 'np.ndarray') -> 'Dict[str, float]'
Maintained example examples/core/05_metrics.py

compute_trace_statistics

compute_trace_statistics(
    hat_matrix: ndarray,
) -> Dict[str, float]

Compute trace(S) and trace(S'S) from a validated hat matrix.

Source code in src/pygwrx/core/metrics.py
def compute_trace_statistics(hat_matrix: np.ndarray) -> Dict[str, float]:
    """Compute trace(S) and trace(S'S) from a validated hat matrix."""
    matrix = _validate_hat_matrix(hat_matrix)

    trace_s = float(np.trace(matrix))
    trace_sts = float(np.sum(matrix * matrix))

    return {
        "trace_S": trace_s,
        "trace_StS": trace_sts,
    }

compute_edf

Compute residual effective degrees of freedom using the GWmodel convention.

Property Value
Type function
Import from pygwrx.core import compute_edf
Signature compute_edf(n: 'int', trace_S: 'float', trace_StS: 'float') -> 'float'
Maintained example examples/core/05_metrics.py

compute_edf

compute_edf(
    n: int, trace_S: float, trace_StS: float
) -> float

Compute residual effective degrees of freedom using the GWmodel convention.

EDF_v2 = n - 2*trace(S) + trace(S'S)

Source code in src/pygwrx/core/metrics.py
def compute_edf(n: int, trace_S: float, trace_StS: float) -> float:
    """Compute residual effective degrees of freedom using the GWmodel convention.

    EDF_v2 = n - 2*trace(S) + trace(S'S)
    """
    if isinstance(n, (bool, np.bool_)) or not isinstance(n, (int, np.integer)):
        raise TypeError("n must be a positive integer.")
    if n <= 0:
        raise ValueError("n must be greater than zero.")

    trace_s_value = _validate_nonnegative_scalar(trace_S, "trace_S")
    trace_sts_value = _validate_nonnegative_scalar(trace_StS, "trace_StS")

    return float(n - 2.0 * trace_s_value + trace_sts_value)

compute_enp

Compute the GWmodel-style effective number of parameters. Compute the effective parameter count using the GWmodel convention.

Property Value
Type function
Import from pygwrx.core import compute_enp
Signature compute_enp(trace_S: 'float', trace_StS: 'float') -> 'float'
Maintained example examples/core/05_metrics.py

compute_enp

compute_enp(trace_S: float, trace_StS: float) -> float

Compute the GWmodel-style effective number of parameters. Compute the effective parameter count using the GWmodel convention.

ENP_v2 = 2*trace(S) - trace(S'S)

Source code in src/pygwrx/core/metrics.py
def compute_enp(trace_S: float, trace_StS: float) -> float:
    """Compute the GWmodel-style effective number of parameters. Compute the effective
    parameter count using the GWmodel convention.

    ENP_v2 = 2*trace(S) - trace(S'S)
    """
    trace_s_value = _validate_nonnegative_scalar(trace_S, "trace_S")
    trace_sts_value = _validate_nonnegative_scalar(trace_StS, "trace_StS")

    return float(2.0 * trace_s_value - trace_sts_value)

Runnable examples used on this page

examples/core/05_metrics.py
# SPDX-FileCopyrightText: 2026 Jinghao Hu
# SPDX-License-Identifier: MIT

"""Calculate every public model-fit and effective-parameter metric."""

# 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))

import numpy as np

from pygwrx.core import (
    compute_adjusted_r_squared,
    compute_aic,
    compute_aicc,
    compute_bic,
    compute_diagnostics,
    compute_edf,
    compute_effective_parameters,
    compute_enp,
    compute_local_r_squared,
    compute_r_squared,
    compute_trace_statistics,
)

y = np.array([1.0, 2.0, 2.8, 4.2, 5.0])
yhat = np.array([1.1, 1.9, 3.0, 4.0, 4.9])
hat = np.eye(5) * 0.4
weights = np.vstack([np.linspace(1.0, 0.2, 5)] * 5)
trace = compute_trace_statistics(hat)
print("r2=", compute_r_squared(y, yhat))
print("adjusted_r2=", compute_adjusted_r_squared(y, yhat, edf=3.0))
print("aic=", compute_aic(y, yhat, n_params=2.0))
print("aicc=", compute_aicc(y, yhat, n_params=2.0))
print("bic=", compute_bic(y, yhat, trace_S=2.0))
print("local_r2=", compute_local_r_squared(y, yhat, weights))
print("effective_parameters=", compute_effective_parameters(hat))
print("trace_statistics=", trace)
print("edf=", compute_edf(5, trace["trace_S"], trace["trace_StS"]))
print("enp=", compute_enp(trace["trace_S"], trace["trace_StS"]))
print(
    "diagnostics=",
    compute_diagnostics(y, yhat, hat, n_features=1, compute_gwr_stats=True),
)