Skip to content

MixedGWR

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

MixedGWR

Fit a semiparametric GWR with global and local coefficients.

Property Value
Type class
Import from pygwrx.models import MixedGWR
Signature MixedGWR(kernel: 'Union[str, Callable]' = 'bisquare', bandwidth: 'Union[float, int, str, None]' = 'aicc', bandwidth_method: 'str' = 'aicc', adaptive: 'bool' = True, local_vars: 'VariableSpec' = None, global_vars: 'VariableSpec' = None, intercept_fixed: 'bool' = True, ridge: 'float' = 0.0, fit_intercept: 'bool' = True, bandwidth_range: 'Optional[tuple[float, float]]' = None, optimization_method: 'str' = 'golden_section', distance_metric: 'str' = 'euclidean', verbose: 'bool' = False) -> 'None'
Maintained example examples/models/08_mixed_gwr.py

MixedGWR

MixedGWR(
    kernel: Union[str, Callable] = "bisquare",
    bandwidth: Union[float, int, str, None] = "aicc",
    bandwidth_method: str = "aicc",
    adaptive: bool = True,
    local_vars: VariableSpec = None,
    global_vars: VariableSpec = None,
    intercept_fixed: bool = True,
    ridge: float = 0.0,
    fit_intercept: bool = True,
    bandwidth_range: Optional[tuple[float, float]] = None,
    optimization_method: str = "golden_section",
    distance_metric: str = "euclidean",
    verbose: bool = False,
)

Bases: BaseSpatialRegressor

Fit a semiparametric GWR with global and local coefficients.

Mixed GWR partitions explanatory variables into globally constant and geographically varying groups. The implementation follows the partial- regression algorithm used by GWmodel::gwr.mixed rather than an iterative backfitting algorithm.

Parameters:

Name Type Description Default
kernel Union[str, Callable]

Spatial kernel name or callable.

'bisquare'
bandwidth Union[float, int, str, None]

Fixed distance, adaptive neighbour count, or one of "cv", "aic", "aicc", or "bic". Automatic selection uses the corresponding full-GWR bandwidth as the mixed model bandwidth, matching the published Dublin workflow.

'aicc'
bandwidth_method str

Selection method used when bandwidth=None or the legacy token "adaptive" is supplied.

'aicc'
adaptive bool

Whether numeric bandwidth is a neighbour count.

True
local_vars VariableSpec

Feature indices or DataFrame column names with local coefficients. If omitted with global_vars, all features are local.

None
global_vars VariableSpec

Feature indices or names with global coefficients. If only one variable group is supplied, the other is its complement.

None
intercept_fixed bool

Whether the fitted intercept is global. If false, the intercept varies locally.

True
ridge float

Non-negative regularization applied explicitly to local and global normal equations. The default 0.0 reproduces the unregularized reference algorithm, with deterministic pseudo-inverse fallback.

0.0
fit_intercept bool

Whether to fit an intercept.

True
bandwidth_range Optional[tuple[float, float]]

Optional search range for automatic bandwidth selection.

None
optimization_method str

Bandwidth optimizer passed to the shared selector.

'golden_section'
distance_metric str

Distance metric used by the shared distance utility.

'euclidean'
verbose bool

Whether to print fit progress.

False

Attributes:

Name Type Description
coef_local_

Local coefficients with shape (n_samples, n_local_vars).

coef_global_

Constant coefficients for the global feature variables.

intercept_

Scalar global intercept or a vector of local intercepts.

coef_

Full coefficient surface in original feature order.

enp_

Effective parameter count trace(S) when diagnostics are enabled.

References

Fotheringham, A. S., Brunsdon, C., & Charlton, M. (2002). Geographically Weighted Regression. Wiley.

Source code in src/pygwrx/models/mixed_gwr.py
def __init__(
    self,
    kernel: Union[str, Callable] = "bisquare",
    bandwidth: Union[float, int, str, None] = "aicc",
    bandwidth_method: str = "aicc",
    adaptive: bool = True,
    local_vars: VariableSpec = None,
    global_vars: VariableSpec = None,
    intercept_fixed: bool = True,
    ridge: float = 0.0,
    fit_intercept: bool = True,
    bandwidth_range: Optional[tuple[float, float]] = None,
    optimization_method: str = "golden_section",
    distance_metric: str = "euclidean",
    verbose: bool = False,
) -> None:
    super().__init__(
        kernel=kernel,
        bandwidth=bandwidth,
        bandwidth_method=bandwidth_method,
        fit_intercept=fit_intercept,
        distance_metric=distance_metric,
        adaptive=adaptive,
        bandwidth_range=bandwidth_range,
        optimization_method=optimization_method,
        verbose=verbose,
    )
    if not isinstance(intercept_fixed, (bool, np.bool_)):
        raise TypeError("intercept_fixed must be boolean.")
    if isinstance(ridge, (bool, np.bool_)):
        raise TypeError("ridge must be a real scalar, not bool.")
    ridge_value = float(ridge)
    if not np.isfinite(ridge_value) or ridge_value < 0:
        raise ValueError("ridge must be finite and non-negative.")

    self.local_vars = local_vars
    self.global_vars = global_vars
    self.intercept_fixed = bool(intercept_fixed)
    self.ridge = ridge_value
    self._reset_mixed_state()

fit

fit(
    X: Union[ndarray, DataFrame],
    y: Union[ndarray, Series],
    coords: Union[ndarray, DataFrame],
    compute_enp: bool = True,
) -> "MixedGWR"

Fit the mixed global/local coefficient model.

Source code in src/pygwrx/models/mixed_gwr.py
def fit(
    self,
    X: Union[np.ndarray, pd.DataFrame],
    y: Union[np.ndarray, pd.Series],
    coords: Union[np.ndarray, pd.DataFrame],
    compute_enp: bool = True,
) -> "MixedGWR":
    """Fit the mixed global/local coefficient model."""
    self._reset_mixed_state()
    try:
        if not isinstance(compute_enp, (bool, np.bool_)):
            raise TypeError("compute_enp must be boolean.")

        X_arr, y_arr = validate_data(X, y)
        coords_arr = validate_coords(coords)
        if X_arr.shape[0] != coords_arr.shape[0]:
            raise ValueError(
                "X, y, and coords must contain the same number of rows."
            )
        self.n_samples_ = X_arr.shape[0]
        self.n_features_in_ = X_arr.shape[1]
        self.feature_names_in_ = (
            np.asarray(X.columns, dtype=object)
            if isinstance(X, pd.DataFrame)
            else None
        )
        self._partition_variables(X_arr.shape[1])

        self.X_train_ = X_arr.copy()
        self.y_train_ = y_arr.copy()
        self.coords_train_ = coords_arr.copy()
        self.kernel_func_ = get_kernel_function(self.kernel)
        self.bandwidth_ = self._resolve_mixed_bandwidth(X_arr, y_arr, coords_arr)

        X_local, X_global = self._build_designs(X_arr)
        distances = compute_distance_matrix(
            coords_arr,
            coords_arr,
            metric=self.distance_metric,
        )
        local_design_coef, global_design_coef = fit_mixed_gwr_core(
            X_local,
            X_global,
            y_arr,
            self.bandwidth_,
            self.kernel_func_,
            distances,
            adaptive=self.adaptive,
            ridge=self.ridge,
        )
        self._coef_local_design_ = local_design_coef.copy()
        self._coef_global_design_ = global_design_coef.copy()
        self._X_local_design_train_ = X_local.copy()
        self._X_global_design_train_ = X_global.copy()

        self.fitted_values_ = np.einsum("ij,ij->i", X_local, local_design_coef) + (
            X_global @ global_design_coef if X_global.shape[1] else 0.0
        )
        self.residuals_ = y_arr - self.fitted_values_

        if self.fit_intercept and self.intercept_fixed:
            self.intercept_ = float(global_design_coef[0])
            self.coef_global_ = global_design_coef[1:].copy()
            self.coef_local_ = local_design_coef.copy()
        elif self.fit_intercept:
            self.intercept_ = local_design_coef[:, 0].copy()
            self.coef_local_ = local_design_coef[:, 1:].copy()
            self.coef_global_ = global_design_coef.copy()
        else:
            self.intercept_ = 0.0
            self.coef_local_ = local_design_coef.copy()
            self.coef_global_ = global_design_coef.copy()

        self.coef_ = np.empty((X_arr.shape[0], X_arr.shape[1]), dtype=float)
        self.coef_[:, self.local_var_indices_] = self.coef_local_
        if self.global_var_indices_.size:
            self.coef_[:, self.global_var_indices_] = self.coef_global_[None, :]

        if compute_enp:
            self.hat_matrix_ = compute_mixed_gwr_hat_matrix(
                X_local,
                X_global,
                self.bandwidth_,
                self.kernel_func_,
                distances,
                adaptive=self.adaptive,
                ridge=self.ridge,
            )
            self.enp_ = float(np.trace(self.hat_matrix_))
            self.trace_StS_ = float(np.sum(self.hat_matrix_**2))
            self.diagnostics_ = compute_diagnostics(
                y_arr,
                self.fitted_values_,
                hat_matrix=self.hat_matrix_,
                compute_gwr_stats=True,
            )
        else:
            self.hat_matrix_ = None
            self.enp_ = None
            self.trace_StS_ = None
            self.diagnostics_ = compute_diagnostics(
                y_arr,
                self.fitted_values_,
                n_features=X_local.shape[1] + X_global.shape[1],
            )

        self.aic_ = float(self.diagnostics_["aic"])
        self.aicc_ = float(self.diagnostics_["aicc"])
        self.bic_ = float(self.diagnostics_["bic"])
        self._mark_fitted()
        return self
    except Exception:
        self._reset_mixed_state()
        raise

predict

predict(
    X: Union[ndarray, DataFrame],
    coords: Union[ndarray, DataFrame],
) -> np.ndarray

Predict at new locations without modifying fitted training state.

Source code in src/pygwrx/models/mixed_gwr.py
def predict(
    self,
    X: Union[np.ndarray, pd.DataFrame],
    coords: Union[np.ndarray, pd.DataFrame],
) -> np.ndarray:
    """Predict at new locations without modifying fitted training state."""
    X_arr, coords_arr = self._validate_prediction_inputs(X, coords)
    if (
        self.X_train_ is None
        or self.y_train_ is None
        or self.coords_train_ is None
        or self._coef_global_design_ is None
        or self._X_local_design_train_ is None
        or self._X_global_design_train_ is None
        or self.kernel_func_ is None
        or self.bandwidth_ is None
    ):
        raise RuntimeError("Stored MixedGWR training state is incomplete.")

    X_local_test, X_global_test = self._build_designs(X_arr)
    target_distances = compute_distance_matrix(
        coords_arr,
        self.coords_train_,
        metric=self.distance_metric,
    )
    training_distances = compute_distance_matrix(
        self.coords_train_,
        self.coords_train_,
        metric=self.distance_metric,
    )
    local_coefficients, global_coefficients = fit_mixed_gwr_core(
        self._X_local_design_train_,
        self._X_global_design_train_,
        self.y_train_,
        self.bandwidth_,
        self.kernel_func_,
        training_distances,
        target_distances=target_distances,
        adaptive=self.adaptive,
        ridge=self.ridge,
    )
    # Re-estimation is deterministic and should reproduce the stored global vector.
    if not np.allclose(
        global_coefficients, self._coef_global_design_, rtol=1e-10, atol=1e-10
    ):
        raise RuntimeError(
            "Stored and recomputed global coefficients are inconsistent."
        )
    return np.einsum("ij,ij->i", X_local_test, local_coefficients) + (
        X_global_test @ global_coefficients if X_global_test.shape[1] else 0.0
    )

test_spatial_variation

test_spatial_variation() -> dict

Return descriptive variation of fitted local coefficients.

This method is descriptive and is not a formal hypothesis test.

Source code in src/pygwrx/models/mixed_gwr.py
def test_spatial_variation(self) -> dict:
    """Return descriptive variation of fitted local coefficients.

    This method is descriptive and is not a formal hypothesis test.
    """
    self._check_is_fitted()
    return {
        "local_var_indices": self.local_var_indices_.copy(),
        "global_var_indices": self.global_var_indices_.copy(),
        "local_coef_variance": np.var(self.coef_local_, axis=0),
        "coef_global": self.coef_global_.copy(),
    }

Runnable examples used on this page

examples/models/08_mixed_gwr.py
# SPDX-FileCopyrightText: 2026 Jinghao Hu
# SPDX-License-Identifier: MIT

"""Fit a semiparametric Mixed GWR with global and local predictors."""

# 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 mixed_regression, print_model_result

from pygwrx import MixedGWR

X, y, coords = mixed_regression()
model = MixedGWR(
    bandwidth=28,
    adaptive=True,
    global_vars=["global_x"],
    local_vars=["local_x"],
    intercept_fixed=True,
).fit(X, y, coords, compute_enp=False)
print_model_result(model)
print("global_coefficients=", model.coef_global_)
print("predictions=", model.predict(X.iloc[:3], coords.iloc[:3]))