Skip to content

MGWR

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

MGWR

Gaussian multiscale geographically weighted regression.

Property Value
Type class
Import from pygwrx.models import MGWR
Signature MGWR(kernel: 'Union[str, Callable[[np.ndarray, float], np.ndarray]]' = 'bisquare', bandwidths: 'BandwidthInput' = None, bandwidth_method: 'str' = 'aicc', adaptive: 'bool' = True, bandwidth_range: 'BandwidthRange' = None, bandwidth_ranges: 'BandwidthRanges' = None, init_bandwidth: 'Optional[Bandwidth]' = None, optimization_method: 'str' = 'golden_section', search_tol: 'float' = 1e-06, search_max_iter: 'int' = 200, max_iter: 'int' = 200, tol: 'float' = 1e-05, rss_score: 'bool' = False, bws_same_times: 'int' = 5, fit_intercept: 'bool' = True, distance_metric: 'str' = 'euclidean', sigma2_v1: 'bool' = True, verbose: 'bool' = False) -> 'None'
Maintained example examples/models/02_mgwr.py

MGWR

MGWR(
    kernel: Union[
        str, Callable[[ndarray, float], ndarray]
    ] = "bisquare",
    bandwidths: BandwidthInput = None,
    bandwidth_method: str = "aicc",
    adaptive: bool = True,
    bandwidth_range: BandwidthRange = None,
    bandwidth_ranges: BandwidthRanges = None,
    init_bandwidth: Optional[Bandwidth] = None,
    optimization_method: str = "golden_section",
    search_tol: float = 1e-06,
    search_max_iter: int = 200,
    max_iter: int = 200,
    tol: float = 1e-05,
    rss_score: bool = False,
    bws_same_times: int = 5,
    fit_intercept: bool = True,
    distance_metric: str = "euclidean",
    sigma2_v1: bool = True,
    verbose: bool = False,
)

Bases: BaseMultiscaleRegressor

Gaussian multiscale geographically weighted regression.

MGWR represents the response as a sum of spatially varying additive terms, with one bandwidth for the intercept and each predictor when an intercept is fitted. The model is calibrated by iteratively updating one term at a time while holding the remaining additive terms fixed.

Parameters:

Name Type Description Default
kernel Union[str, Callable[[ndarray, float], ndarray]]

Kernel name or callable accepting (distances, bandwidth).

'bisquare'
bandwidths BandwidthInput

Optional manual bandwidth or sequence of bandwidths. A scalar is applied to every parameter. A sequence must contain one value per fitted parameter, including the intercept when present. If None, variable-specific bandwidths are selected by backfitting.

None
bandwidth_method str

Criterion used for the initial GWR bandwidth and each variable-specific bandwidth search. Supported values are "cv", "aic", "aicc", and "bic".

'aicc'
adaptive bool

Interpret bandwidths as integer nearest-neighbour counts.

True
bandwidth_range BandwidthRange

Optional common search range for all parameters.

None
bandwidth_ranges BandwidthRanges

Optional parameter-specific search ranges. Supply one range per fitted parameter, including the intercept when present.

None
init_bandwidth Optional[Bandwidth]

Optional bandwidth for the initial single-bandwidth GWR fit. If None, it is selected automatically.

None
optimization_method str

One-dimensional bandwidth search method.

'golden_section'
search_tol float

Convergence tolerance used by variable-specific bandwidth searches.

1e-06
search_max_iter int

Maximum iterations for each variable-specific bandwidth search.

200
max_iter int

Maximum number of backfitting iterations.

200
tol float

Score-of-change convergence tolerance.

1e-05
rss_score bool

Use relative RSS change instead of smoothing-function change as the convergence score.

False
bws_same_times int

Stop repeating bandwidth searches after the complete bandwidth vector remains unchanged for this many iterations.

5
fit_intercept bool

Include a spatially varying intercept.

True
distance_metric str

Distance metric used to form spatial neighbourhoods.

'euclidean'
sigma2_v1 bool

Residual-variance convention. True uses RSS / (n - trace(S)); False uses RSS / (n - 2 trace(S) + trace(S'S)).

True
verbose bool

Print backfitting progress.

False

Attributes:

Name Type Description
bandwidths_ Optional[ndarray]

Final variable-specific bandwidth vector.

bandwidth_history_ Optional[Any]

Bandwidth vector from every backfitting iteration.

convergence_history_ Optional[Any]

Score of change from every iteration.

initial_bandwidth_ Optional[Any]

Initial single-bandwidth GWR bandwidth.

effective_params_by_variable_ Optional[Any]

Effective parameter count for each coefficient surface.

coef_ Optional[Any]

Local slope estimates with shape (n_samples, n_features).

intercept_ Optional[Any]

Local intercept estimates with shape (n_samples,).

parameter_standard_errors_ Optional[Any]

Local standard errors for all fitted parameters.

parameter_t_values_ Optional[Any]

Local t statistics for all fitted parameters.

converged_ Optional[Any]

Whether the backfitting score reached tol.

n_iter_ Optional[Any]

Number of completed backfitting iterations.

Notes

Out-of-sample MGWR prediction is intentionally not exposed because the widely used reference Python implementation does not provide a validated prediction algorithm for independently supplied target locations. Use fitted_values_ for calibration-location estimates.

References

Fotheringham, A. S., Yang, W., and Kang, W. (2017). Multiscale geographically weighted regression (MGWR). Annals of the American Association of Geographers, 107(6), 1247-1265.

Source code in src/pygwrx/models/mgwr.py
def __init__(
    self,
    kernel: Union[str, Callable[[np.ndarray, float], np.ndarray]] = "bisquare",
    bandwidths: BandwidthInput = None,
    bandwidth_method: str = "aicc",
    adaptive: bool = True,
    bandwidth_range: BandwidthRange = None,
    bandwidth_ranges: BandwidthRanges = None,
    init_bandwidth: Optional[Bandwidth] = None,
    optimization_method: str = "golden_section",
    search_tol: float = 1e-6,
    search_max_iter: int = 200,
    max_iter: int = 200,
    tol: float = 1e-5,
    rss_score: bool = False,
    bws_same_times: int = 5,
    fit_intercept: bool = True,
    distance_metric: str = "euclidean",
    sigma2_v1: bool = True,
    verbose: bool = False,
) -> None:
    if not isinstance(
        bandwidth_method, str
    ) or bandwidth_method.strip().lower() not in {
        "cv",
        "aic",
        "aicc",
        "bic",
    }:
        raise ValueError(
            "bandwidth_method must be one of 'cv', 'aic', 'aicc', or 'bic'."
        )
    if not isinstance(
        search_tol, (int, float, np.integer, np.floating)
    ) or isinstance(search_tol, (bool, np.bool_)):
        raise TypeError("search_tol must be a positive finite number.")
    if not np.isfinite(float(search_tol)) or float(search_tol) <= 0.0:
        raise ValueError("search_tol must be a positive finite number.")
    if not isinstance(search_max_iter, (int, np.integer)) or isinstance(
        search_max_iter, (bool, np.bool_)
    ):
        raise TypeError("search_max_iter must be an integer.")
    if int(search_max_iter) < 1:
        raise ValueError("search_max_iter must be at least 1.")
    if not isinstance(max_iter, (int, np.integer)) or isinstance(
        max_iter, (bool, np.bool_)
    ):
        raise TypeError("max_iter must be an integer.")
    if int(max_iter) < 1:
        raise ValueError("max_iter must be at least 1.")
    if not isinstance(tol, (int, float, np.integer, np.floating)) or isinstance(
        tol, (bool, np.bool_)
    ):
        raise TypeError("tol must be a positive finite number.")
    if not np.isfinite(float(tol)) or float(tol) <= 0.0:
        raise ValueError("tol must be a positive finite number.")
    if not isinstance(rss_score, (bool, np.bool_)):
        raise TypeError("rss_score must be boolean.")
    if not isinstance(bws_same_times, (int, np.integer)) or isinstance(
        bws_same_times, (bool, np.bool_)
    ):
        raise TypeError("bws_same_times must be an integer.")
    if int(bws_same_times) < 0:
        raise ValueError("bws_same_times must be non-negative.")
    if not isinstance(sigma2_v1, (bool, np.bool_)):
        raise TypeError("sigma2_v1 must be boolean.")

    # BaseGWR's single bandwidth is used only to validate and store the
    # initial GWR setting. Final MGWR scales are exposed as bandwidths_.
    super().__init__(
        kernel=kernel,
        bandwidth=(
            init_bandwidth if init_bandwidth is not None else bandwidth_method
        ),
        bandwidth_method=bandwidth_method,
        adaptive=adaptive,
        bandwidth_range=bandwidth_range,
        optimization_method=optimization_method,
        fit_intercept=fit_intercept,
        distance_metric=distance_metric,
        verbose=verbose,
    )
    self.bandwidths = bandwidths
    self.bandwidth_ranges = bandwidth_ranges
    self.init_bandwidth = init_bandwidth
    self.search_tol = float(search_tol)
    self.search_max_iter = int(search_max_iter)
    self.max_iter = int(max_iter)
    self.tol = float(tol)
    self.rss_score = bool(rss_score)
    self.bws_same_times = int(bws_same_times)
    self.sigma2_v1 = bool(sigma2_v1)
    self._reset_mgwr_state()

fit

fit(
    X: Union[ndarray, DataFrame],
    y: Union[ndarray, Series],
    coords: Union[ndarray, DataFrame],
    *,
    compute_hat_matrix: bool = False,
    store_partial_hat_matrices: bool = False,
    compute_inference: bool = True,
    n_chunks: int = 1,
    verbose: Optional[bool] = None
) -> "MGWR"

Fit the MGWR model and return self.

Parameters:

Name Type Description Default
X Union[ndarray, DataFrame]

Predictor matrix with shape (n_samples, n_features).

required
y Union[ndarray, Series]

Response vector with shape (n_samples,).

required
coords Union[ndarray, DataFrame]

Coordinates with shape (n_samples, 2).

required
compute_hat_matrix bool

Retain the complete model smoother matrix.

False
store_partial_hat_matrices bool

Retain one n x n smoother matrix per fitted parameter. This can require substantial memory.

False
compute_inference bool

Compute local standard errors and t statistics. Exact smoother traces are computed regardless of this setting.

True
n_chunks int

Number of column chunks used during exact inference.

1
verbose Optional[bool]

Optional per-fit override of the estimator's verbosity.

None

Returns:

Type Description
'MGWR'

The fitted model instance.

Source code in src/pygwrx/models/mgwr.py
def fit(
    self,
    X: Union[np.ndarray, pd.DataFrame],
    y: Union[np.ndarray, pd.Series],
    coords: Union[np.ndarray, pd.DataFrame],
    *,
    compute_hat_matrix: bool = False,
    store_partial_hat_matrices: bool = False,
    compute_inference: bool = True,
    n_chunks: int = 1,
    verbose: Optional[bool] = None,
) -> "MGWR":
    """Fit the MGWR model and return ``self``.

    Args:
        X: Predictor matrix with shape ``(n_samples, n_features)``.
        y: Response vector with shape ``(n_samples,)``.
        coords: Coordinates with shape ``(n_samples, 2)``.
        compute_hat_matrix: Retain the complete model smoother matrix.
        store_partial_hat_matrices: Retain one ``n x n`` smoother matrix
            per fitted parameter. This can require substantial memory.
        compute_inference: Compute local standard errors and t statistics.
            Exact smoother traces are computed regardless of this setting.
        n_chunks: Number of column chunks used during exact inference.
        verbose: Optional per-fit override of the estimator's verbosity.

    Returns:
        The fitted model instance.
    """
    for name, value in (
        ("compute_hat_matrix", compute_hat_matrix),
        ("store_partial_hat_matrices", store_partial_hat_matrices),
        ("compute_inference", compute_inference),
    ):
        if not isinstance(value, (bool, np.bool_)):
            raise TypeError(f"{name} must be boolean.")
    if verbose is not None:
        if not isinstance(verbose, (bool, np.bool_)):
            raise TypeError("verbose must be boolean or None.")
        self.verbose = bool(verbose)

    self._reset_fit_state()
    try:
        X_arr, y_arr, coords_arr = self._validate_inputs(X, y, coords)
        feature_names = (
            None
            if self.feature_names_in_ is None
            else self.feature_names_in_.copy()
        )
        self._store_training_data(X_arr, y_arr, coords_arr, copy=True)
        self.feature_names_in_ = feature_names
        X_design = (
            add_intercept(self.X_train_) if self.fit_intercept else self.X_train_
        )
        n_samples, n_parameters = X_design.shape
        self.kernel_func_ = get_kernel_function(self.kernel)
        distances = np.asarray(
            compute_distance_matrix(
                self.coords_train_,
                self.coords_train_,
                metric=self.distance_metric,
            ),
            dtype=float,
        )

        manual_bandwidths = self._resolve_manual_bandwidths(
            n_parameters=n_parameters,
            n_samples=n_samples,
        )
        bandwidth_ranges = self._resolve_bandwidth_ranges(
            n_parameters=n_parameters,
            n_samples=n_samples,
        )
        initial_bandwidth = self._resolve_initial_bandwidth(X_design, self.y_train_)
        self.initial_bandwidth_ = initial_bandwidth
        self.bandwidth_ = initial_bandwidth  # compatibility: initial GWR scale

        if self.verbose:
            kind = "adaptive neighbours" if self.adaptive else "fixed distance"
            print(
                f"Initializing MGWR with {kind} bandwidth="
                f"{self.initial_bandwidth_}."
            )

        backfit = self._backfit(
            X_design,
            self.y_train_,
            distances,
            initial_bandwidth,
            manual_bandwidths,
            bandwidth_ranges,
        )
        self.bandwidths_ = backfit.bandwidths.copy()
        self.bandwidth_history_ = backfit.bandwidth_history.copy()
        self.convergence_history_ = backfit.convergence_history.copy()
        self.n_iter_ = backfit.n_iter
        self.converged_ = backfit.converged
        self.parameter_contributions_ = backfit.contributions.copy()

        params = backfit.params
        if self.fit_intercept:
            self.intercept_ = params[:, 0].copy()
            self.coef_ = params[:, 1:].copy()
        else:
            self.intercept_ = np.zeros(n_samples, dtype=float)
            self.coef_ = params.copy()
        self.fitted_values_ = np.sum(backfit.contributions, axis=1)
        self.residuals_ = self.y_train_ - self.fitted_values_
        self.local_r2_ = None

        self.inference_enabled_ = bool(compute_inference)
        inference = self._compute_exact_inference(
            X_design,
            distances,
            n_chunks=n_chunks,
            store_hat_matrix=bool(compute_hat_matrix),
            store_partial_hat_matrices=bool(store_partial_hat_matrices),
            compute_covariance=self.inference_enabled_,
        )
        self.effective_params_by_variable_ = (
            inference.effective_params_by_variable.copy()
        )
        self.ENP_j_ = self.effective_params_by_variable_
        self.hat_matrix_ = inference.hat_matrix
        self.partial_hat_matrices_ = inference.partial_hat_matrices
        self.diagnostics_ = compute_diagnostics(
            self.y_train_,
            self.fitted_values_,
            compute_gwr_stats=True,
            trace_S=inference.trace_S,
            trace_StS=inference.trace_StS,
        )
        self._set_inference_results(inference, params)

        alpha = np.asarray([0.10, 0.05, 0.01], dtype=float)
        safe_enp = np.maximum(
            self.effective_params_by_variable_, np.finfo(float).eps
        )
        self.adjusted_alpha_by_variable_ = alpha / safe_enp[:, None]
        self.critical_t_values_ = student_t.ppf(
            1.0 - self.adjusted_alpha_by_variable_[:, 1] / 2.0,
            max(self.n_samples_ - 1, 1),
        )

        self._mark_fitted()
        return self
    except Exception:
        self._reset_fit_state()
        raise

predict

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

Reject unvalidated out-of-sample MGWR prediction.

Raises:

Type Description
NotImplementedError

Always. Use fitted_values_ for calibration locations.

Source code in src/pygwrx/models/mgwr.py
def predict(
    self,
    X: Union[np.ndarray, pd.DataFrame],
    coords: Union[np.ndarray, pd.DataFrame],
) -> np.ndarray:
    """Reject unvalidated out-of-sample MGWR prediction.

    Raises:
        NotImplementedError: Always. Use ``fitted_values_`` for calibration
            locations.
    """
    self._check_is_fitted()
    raise NotImplementedError(
        "Out-of-sample MGWR prediction is not implemented because a validated "
        "multiscale prediction operator is not yet part of the reference MGWR "
        "workflow. Use fitted_values_ for calibration locations."
    )

to_frame

to_frame() -> pd.DataFrame

Return calibration-location parameters and diagnostics.

Source code in src/pygwrx/models/mgwr.py
def to_frame(self) -> pd.DataFrame:
    """Return calibration-location parameters and diagnostics."""
    frame = super().to_frame()
    if self.intercept_se_ is not None:
        frame["intercept_se"] = self.intercept_se_
    if self.intercept_t_ is not None:
        frame["intercept_t"] = self.intercept_t_
    feature_names = (
        [str(name) for name in self.feature_names_in_]
        if self.feature_names_in_ is not None
        else [f"x{index}" for index in range(self.n_features_in_ or 0)]
    )
    if self.coef_se_ is not None:
        for index, name in enumerate(feature_names):
            frame[f"se_{name}"] = self.coef_se_[:, index]
    if self.coef_t_ is not None:
        for index, name in enumerate(feature_names):
            frame[f"t_{name}"] = self.coef_t_[:, index]
    for name, values in (
        ("influence", self.influence_),
        ("standardized_residual", self.standardized_residuals_),
        ("cooks_distance", self.cooks_distance_),
    ):
        if values is not None:
            frame[name] = values
    return frame

summary

summary() -> str

Return a stable text summary of MGWR calibration results.

Source code in src/pygwrx/models/mgwr.py
def summary(self) -> str:
    """Return a stable text summary of MGWR calibration results."""
    self._check_is_fitted()
    if self.bandwidths_ is None or self.effective_params_by_variable_ is None:
        raise RuntimeError("MGWR bandwidth and inference results are unavailable.")
    feature_names = (
        [str(name) for name in self.feature_names_in_]
        if self.feature_names_in_ is not None
        else [f"x{index}" for index in range(self.n_features_in_ or 0)]
    )
    parameter_names = (["intercept"] if self.fit_intercept else []) + feature_names
    params = (
        np.column_stack([self.intercept_, self.coef_])
        if self.fit_intercept
        else np.asarray(self.coef_)
    )

    lines = [
        "=" * 88,
        "Multiscale Geographically Weighted Regression (MGWR)",
        "=" * 88,
        f"Samples: {self.n_samples_}",
        f"Predictors: {self.n_features_in_}",
        f"Kernel: {self.kernel}",
        f"Bandwidth type: {'adaptive neighbours' if self.adaptive else 'fixed distance'}",
        f"Bandwidth criterion: {self.bandwidth_method.upper()}",
        f"Initial GWR bandwidth: {self.initial_bandwidth_}",
        f"Backfitting iterations: {self.n_iter_}",
        f"Converged: {self.converged_}",
        f"Final SOC: {self.convergence_history_[-1]:.8g}",
        "",
        "Variable-specific scales and coefficient distributions",
        "-" * 88,
        f"{'Variable':<20}{'Bandwidth':>12}{'ENP_j':>12}{'Min':>11}{'Median':>11}{'Mean':>11}{'Max':>11}",
    ]
    for index, name in enumerate(parameter_names):
        values = params[:, index]
        bandwidth = self.bandwidths_[index]
        bandwidth_text = (
            str(int(bandwidth)) if self.adaptive else f"{float(bandwidth):.6g}"
        )
        lines.append(
            f"{name:<20}{bandwidth_text:>12}"
            f"{self.effective_params_by_variable_[index]:>12.4f}"
            f"{np.min(values):>11.5f}{np.median(values):>11.5f}"
            f"{np.mean(values):>11.5f}{np.max(values):>11.5f}"
        )

    lines.extend(["", "MGWR diagnostics", "-" * 88])
    for label, key in (
        ("R-squared", "r2"),
        ("Adjusted R-squared", "adj_r2"),
        ("RSS", "rss"),
        ("RMSE", "rmse"),
        ("MAE", "mae"),
        ("AIC", "aic"),
        ("AICc", "aicc"),
        ("BIC", "bic"),
        ("trace(S)", "trace_S"),
        ("trace(S'S)", "trace_StS"),
        ("ENP v2", "enp_v2"),
        ("EDF v2", "edf_v2"),
    ):
        value = self.diagnostics_.get(key, np.nan) if self.diagnostics_ else np.nan
        lines.append(f"{label:<32}{value:>16.6f}")
    sigma2 = np.nan if self.sigma2_ is None else self.sigma2_
    lines.append(f"{'Residual variance (sigma^2)':<32}{sigma2:>16.6f}")
    lines.append("=" * 88)
    return "\n".join(lines)

Runnable examples used on this page

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

"""Fit MGWR with fixed variable-specific bandwidths."""

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

from pygwrx import MGWR

X, y, coords = spatial_regression(n=48, p=2)
model = MGWR(bandwidths=[24, 26, 28], adaptive=True, max_iter=8, tol=0.5).fit(
    X, y, coords, compute_inference=True
)
print_model_result(model)
try:
    model.predict(X.iloc[:2], coords.iloc[:2])
except NotImplementedError as exc:
    print("Expected MGWR prediction limitation:", exc)