Skip to content

Local collinearity

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

Conceptual guide

LocalCollinearityDiagnostics

Diagnose spatially varying multicollinearity in a fitted GWR model.

Property Value
Type class
Import from pygwrx.diagnostics import LocalCollinearityDiagnostics
Signature LocalCollinearityDiagnostics(gwr_model: Any, tolerance: float = 1e-10) -> None
Maintained example examples/diagnostics/02_inference_and_collinearity.py

LocalCollinearityDiagnostics

LocalCollinearityDiagnostics(
    gwr_model: Any, tolerance: float = 1e-10
)

Diagnose spatially varying multicollinearity in a fitted GWR model.

The diagnostic is intended for fitted, single-bandwidth spatial GWR-like estimators. It uses the model's training coordinates, kernel, fitted bandwidth, adaptive/fixed bandwidth setting, distance metric, and intercept setting.

Parameters:

Name Type Description Default
gwr_model Any

Fitted GWR-like model. The model must expose X_train_, coords_train_, bandwidth_, kernel, adaptive, distance_metric, fit_intercept, and _is_fitted.

required
tolerance float

Numerical tolerance used for constant columns, rank checks, and small singular values.

1e-10
Notes

The following quantities are computed at every calibration location:

  • local weighted correlations between predictor pairs;
  • variance inflation factors (VIF), excluding the intercept;
  • condition number (CN), using the actual local design matrix and therefore including the intercept when fit_intercept=True;
  • variance decomposition proportions (VDP), with axes ordered as (location, condition component, design variable).

This implementation does not silently approximate MGWR, GTWR, STWR, SGTWR, or other multiscale/spatiotemporal weighting schemes. Those models require model-specific diagnostics.

Source code in src/pygwrx/diagnostics/collinearity.py
def __init__(self, gwr_model: Any, tolerance: float = 1e-10) -> None:
    self.model = gwr_model
    self.tolerance = self._validate_tolerance(tolerance)
    self._cache: Optional[Dict[str, Any]] = None
    self._distance_matrix: Optional[np.ndarray] = None

    self._validate_model()

    self.X_features_ = np.asarray(self.model.X_train_, dtype=float)
    self.coords_ = np.asarray(self.model.coords_train_, dtype=float)
    self.n_samples_, self.n_features_ = self.X_features_.shape

    self.feature_names_ = self._resolve_feature_names()
    self.correlation_pairs_ = list(combinations(range(self.n_features_), 2))
    self.correlation_pair_names_ = [
        (self.feature_names_[left], self.feature_names_[right])
        for left, right in self.correlation_pairs_
    ]

    if bool(getattr(self.model, "fit_intercept", True)):
        self.X_design_ = np.column_stack(
            [np.ones(self.n_samples_, dtype=float), self.X_features_]
        )
        self.design_names_ = ["intercept"] + self.feature_names_
    else:
        self.X_design_ = self.X_features_.copy()
        self.design_names_ = list(self.feature_names_)

compute_local_correlations

compute_local_correlations() -> np.ndarray

Return local weighted correlations for every predictor pair.

Returns:

Type Description
ndarray

ndarray of shape (n_samples, n_feature_pairs): Pair order is available from correlation_pair_names_ and from diagnose()['correlation_pairs']. Correlations involving a locally constant predictor are reported as NaN.

Source code in src/pygwrx/diagnostics/collinearity.py
def compute_local_correlations(self) -> np.ndarray:
    """Return local weighted correlations for every predictor pair.

    Returns:
        ndarray of shape (n_samples, n_feature_pairs): Pair order is available from ``correlation_pair_names_`` and from
            ``diagnose()['correlation_pairs']``.  Correlations involving a
            locally constant predictor are reported as ``NaN``.
    """
    return self._compute_all()["local_correlations"].copy()

compute_vif

compute_vif() -> np.ndarray

Return local VIF values with shape (n_samples, n_features).

The intercept is excluded. Locally constant predictors and singular local correlation matrices are represented by np.inf rather than by deceptively finite pseudo-inverse values.

Source code in src/pygwrx/diagnostics/collinearity.py
def compute_vif(self) -> np.ndarray:
    """Return local VIF values with shape ``(n_samples, n_features)``.

    The intercept is excluded.  Locally constant predictors and singular
    local correlation matrices are represented by ``np.inf`` rather than
    by deceptively finite pseudo-inverse values.
    """
    return self._compute_all()["vif"].copy()

compute_condition_number

compute_condition_number() -> np.ndarray

Return the scaled local design-matrix condition number.

Source code in src/pygwrx/diagnostics/collinearity.py
def compute_condition_number(self) -> np.ndarray:
    """Return the scaled local design-matrix condition number."""
    return self._compute_all()["condition_number"].copy()

compute_vdp

compute_vdp() -> np.ndarray

Return variance decomposition proportions.

Returns:

Type Description
ndarray

ndarray of shape (n_samples, n_design_variables, n_design_variables): vdp[i, j, k] is the share of design variable k's variance associated with condition component j at location i. When an intercept is fitted, it is included as the first design variable and named "intercept".

Source code in src/pygwrx/diagnostics/collinearity.py
def compute_vdp(self) -> np.ndarray:
    """Return variance decomposition proportions.

    Returns:
        ndarray of shape (n_samples, n_design_variables, n_design_variables): ``vdp[i, j, k]`` is the share of design variable ``k``'s variance
            associated with condition component ``j`` at location ``i``.
            When an intercept is fitted, it is included as the first design
            variable and named ``"intercept"``.
    """
    return self._compute_all()["vdp"].copy()

diagnose

diagnose(verbose: bool = True) -> Dict[str, Any]

Run and summarize the complete local collinearity diagnostic.

Source code in src/pygwrx/diagnostics/collinearity.py
def diagnose(self, verbose: bool = True) -> Dict[str, Any]:
    """Run and summarize the complete local collinearity diagnostic."""
    if not isinstance(verbose, (bool, np.bool_)):
        raise TypeError("verbose must be a boolean.")

    computed = self._compute_all()
    vif = computed["vif"]
    cn = computed["condition_number"]

    severe_vif_cells = np.where(vif > 10.0)
    severe_vif_location_mask = np.any(vif > 10.0, axis=1)
    severe_cn_location_mask = cn > 30.0

    finite_cn = cn[np.isfinite(cn)]
    summary = {
        "max_vif": self._max_preserving_infinity(vif),
        "mean_vif": self._mean_preserving_infinity(vif),
        "max_cn": self._max_preserving_infinity(cn),
        "median_cn": (
            np.inf
            if np.isposinf(cn).any()
            else float(np.median(finite_cn)) if finite_cn.size else np.nan
        ),
        "pct_severe_vif_locations": float(
            np.mean(severe_vif_location_mask) * 100.0
        ),
        "pct_severe_vif_cells": float(np.mean(vif > 10.0) * 100.0),
        "pct_severe_cn_locations": float(np.mean(severe_cn_location_mask) * 100.0),
        "n_infinite_vif_locations": int(np.sum(np.any(np.isposinf(vif), axis=1))),
        "n_infinite_cn_locations": int(np.sum(np.isposinf(cn))),
    }
    # Backwards-compatible aliases, now with correct location semantics.
    summary["pct_severe_vif"] = summary["pct_severe_vif_locations"]
    summary["pct_severe_cn"] = summary["pct_severe_cn_locations"]

    diagnostics: Dict[str, Any] = {
        key: value.copy() if isinstance(value, np.ndarray) else value
        for key, value in computed.items()
    }
    diagnostics["severe_multicollinearity"] = {
        "vif_locations": np.flatnonzero(severe_vif_location_mask),
        "vif_cells": severe_vif_cells,
        "cn_locations": np.flatnonzero(severe_cn_location_mask),
    }
    diagnostics["summary"] = summary

    if verbose:
        self._print_summary(summary)

    return diagnostics

to_frame

to_frame() -> pd.DataFrame

Return row-wise collinearity diagnostics as a tidy table.

Source code in src/pygwrx/diagnostics/collinearity.py
def to_frame(self) -> pd.DataFrame:
    """Return row-wise collinearity diagnostics as a tidy table."""
    computed = self._compute_all()
    data: Dict[str, Any] = {
        "coord_0": self.coords_[:, 0],
        "coord_1": self.coords_[:, 1],
        "condition_number": computed["condition_number"],
        "effective_neighbors": computed["effective_neighbors"],
    }
    for index, name in enumerate(self.feature_names_):
        data[f"vif_{name}"] = computed["vif"][:, index]
    for index, (left, right) in enumerate(self.correlation_pair_names_):
        data[f"corr_{left}__{right}"] = computed["local_correlations"][:, index]
    return pd.DataFrame(data)

summary_frame

summary_frame() -> pd.DataFrame

Return the diagnostic summary as a one-row table.

Source code in src/pygwrx/diagnostics/collinearity.py
def summary_frame(self) -> pd.DataFrame:
    """Return the diagnostic summary as a one-row table."""
    return pd.DataFrame([self.diagnose(verbose=False)["summary"]])

Runnable examples used on this page

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

"""Use coefficient inference, multiple-testing correction, and collinearity tools."""

# 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 _common import collinear_regression

from pygwrx import GWR
from pygwrx.diagnostics import (
    LocalCollinearityDiagnostics,
    ParameterInference,
    adjust_pvalues,
    feature_names,
    parameter_inference,
    parameter_significance,
)

X, y, coords = collinear_regression(n=44)
model = GWR(bandwidth=24, adaptive=True).fit(X, y, coords)
view = parameter_inference(model, "x1")
assert isinstance(view, ParameterInference)
print("feature_names=", feature_names(model))
print("inference_label=", view.label)
print(parameter_significance(model, "x1", correction="bh").head())
print("adjusted=", adjust_pvalues(np.array([0.01, 0.04, 0.2, 0.8]), method="bh"))
collinearity = LocalCollinearityDiagnostics(model)
print(collinearity.summary_frame().head())
print("vif_shape=", collinearity.compute_vif().shape)
print("vdp_shape=", collinearity.compute_vdp().shape)
print("correlation_shape=", collinearity.compute_local_correlations().shape)
print("condition_numbers=", collinearity.compute_condition_number()[:5])
print("diagnosis_keys=", sorted(collinearity.diagnose(verbose=False)))