Skip to content

RGWR

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

RGWR

Classical robust geographically weighted regression.

Property Value
Type class
Import from pygwrx.models import RGWR
Signature RGWR(kernel: 'Union[str, Callable[[np.ndarray, float], np.ndarray]]' = 'gaussian', bandwidth: 'Union[float, int, str, None]' = 'cv', bandwidth_method: 'str' = 'cv', adaptive: 'bool' = False, bandwidth_range: 'Optional[Tuple[float, float]]' = None, optimization_method: 'str' = 'golden_section', fit_intercept: 'bool' = True, distance_metric: 'str' = 'euclidean', sigma2_v1: 'bool' = True, method: 'str' = 'automatic', max_iter: 'int' = 20, tol: 'float' = 1e-05, cut1: 'float' = 2.0, cut2: 'float' = 3.0, cut_filter: 'float' = 3.0, verbose: 'bool' = False) -> 'None'
Maintained example examples/models/03_rgwr.py

RGWR

RGWR(
    kernel: Union[
        str, Callable[[ndarray, float], ndarray]
    ] = "gaussian",
    bandwidth: Union[float, int, str, None] = "cv",
    bandwidth_method: str = "cv",
    adaptive: bool = False,
    bandwidth_range: Optional[Tuple[float, float]] = None,
    optimization_method: str = "golden_section",
    fit_intercept: bool = True,
    distance_metric: str = "euclidean",
    sigma2_v1: bool = True,
    method: str = "automatic",
    max_iter: int = 20,
    tol: float = 1e-05,
    cut1: float = 2.0,
    cut2: float = 3.0,
    cut_filter: float = 3.0,
    verbose: bool = False,
)

Bases: GWR

Classical robust geographically weighted regression.

RGWR first calibrates a standard Gaussian GWR using the requested kernel and bandwidth. It then applies one of the robust procedures implemented by the R package GWmodel:

"automatic" Repeatedly combine the spatial kernel weights with a global residual weight vector. Standardized residual magnitudes below cut1 receive weight 1, values between cut1 and cut2 receive a smooth bisquare transition, and values above cut2 receive weight 0.

"filtered" Compute GWmodel-style studentized residuals from the initial GWR hat matrix, exclude observations whose absolute residual exceeds cut_filter, and refit once.

Parameters:

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

Spatial kernel name or callable accepted by :class:GWR.

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

Numeric bandwidth or automatic-selection criterion. Robust fitting uses the bandwidth selected by the initial standard GWR.

'cv'
bandwidth_method str

Criterion used when bandwidth=None.

'cv'
adaptive bool

Whether bandwidths represent nearest-neighbour counts.

False
bandwidth_range Optional[Tuple[float, float]]

Optional lower and upper bandwidth search bounds.

None
optimization_method str

One-dimensional bandwidth search method.

'golden_section'
fit_intercept bool

Whether to include a local intercept.

True
distance_metric str

Distance metric used by the spatial kernel.

'euclidean'
sigma2_v1 bool

Residual-variance convention used for final GWR inference.

True
method str

Robust procedure, either "automatic" or "filtered".

'automatic'
max_iter int

Maximum number of automatic robust refits.

20
tol float

Relative mean-squared-error tolerance for automatic convergence.

1e-05
cut1 float

Lower standardized-residual threshold for automatic reweighting.

2.0
cut2 float

Upper standardized-residual threshold for automatic reweighting.

3.0
cut_filter float

Absolute studentized-residual threshold for filtered RGWR.

3.0
verbose bool

Whether to print fit progress.

False

Attributes:

Name Type Description
robust_weights_

Observation-level residual weights used in the final robust refit. Filtered weights are exactly 0 or 1.

outlier_mask_

Boolean mask identifying zero-weight observations.

downweighted_mask_

Boolean mask identifying observations with final robust weights below 1.

n_iter_

Number of robust refits after the initial standard GWR.

converged_

Whether the automatic relative-MSE criterion was reached. Filtered RGWR is marked converged after its single refit.

weight_history_

Robust weight vectors used by successive calibrations, beginning with the all-ones initial GWR weights.

mse_history_

Initial and robust-refit mean squared residuals.

convergence_history_

Relative MSE changes for automatic RGWR.

initial_studentized_residuals_

GWmodel-style studentized residuals from the initial standard GWR. Populated for filtered RGWR.

robust_residual_scores_

Final residuals divided by the root mean squared residual, matching the automatic weight-score definition.

Notes

The robust weights are observation-level weights shared by every local calibration. At location :math:s_i, the effective weights are

.. math::

w_{ij}^{\mathrm{effective}}
= w_{ij}^{\mathrm{spatial}} r_j,

where :math:r_j is the final residual weight for observation j.

Bandwidth selection is intentionally performed on the initial standard GWR, matching the standard GWmodel workflow in which gwr.robust is supplied a bandwidth selected by bw.gwr.

References

Harris, P., Fotheringham, A. S., and Juggins, S. (2010). Robust geographically weighted regression: a technique for quantifying spatial relationships between freshwater acidification critical loads and catchment attributes. Annals of the Association of American Geographers, 100(2), 286-306.

Lu, B., Harris, P., Charlton, M., and Brunsdon, C. (2014). The GWmodel R package: further topics for exploring spatial heterogeneity using geographically weighted models. Geo-spatial Information Science, 17(2), 85-101.

Source code in src/pygwrx/models/rgwr.py
def __init__(
    self,
    kernel: Union[str, Callable[[np.ndarray, float], np.ndarray]] = "gaussian",
    bandwidth: Union[float, int, str, None] = "cv",
    bandwidth_method: str = "cv",
    adaptive: bool = False,
    bandwidth_range: Optional[Tuple[float, float]] = None,
    optimization_method: str = "golden_section",
    fit_intercept: bool = True,
    distance_metric: str = "euclidean",
    sigma2_v1: bool = True,
    method: str = "automatic",
    max_iter: int = 20,
    tol: float = 1.0e-5,
    cut1: float = 2.0,
    cut2: float = 3.0,
    cut_filter: float = 3.0,
    verbose: bool = False,
) -> None:
    super().__init__(
        kernel=kernel,
        bandwidth=bandwidth,
        bandwidth_method=bandwidth_method,
        adaptive=adaptive,
        bandwidth_range=bandwidth_range,
        optimization_method=optimization_method,
        fit_intercept=fit_intercept,
        distance_metric=distance_metric,
        sigma2_v1=sigma2_v1,
        verbose=verbose,
    )
    self.method = self._normalize_method(method)
    self.max_iter = self._validate_positive_integer(max_iter, "max_iter")
    self.tol = self._validate_positive_float(tol, "tol")
    self.cut1 = self._validate_nonnegative_float(cut1, "cut1")
    self.cut2 = self._validate_positive_float(cut2, "cut2")
    self.cut_filter = self._validate_positive_float(cut_filter, "cut_filter")
    if self.cut2 <= self.cut1:
        raise ValueError("cut2 must be greater than cut1.")
    self._reset_robust_state()

fit

fit(
    X: Union[ndarray, DataFrame],
    y: Union[ndarray, Series],
    coords: Union[ndarray, DataFrame],
    *,
    compute_hat_matrix: bool = True,
    compute_local_r2: bool = True,
    compute_inference: bool = True,
    compute_hat_matrix_flag: Optional[bool] = None,
    verbose: Optional[bool] = None
) -> "RGWR"

Fit robust GWR and return the estimator.

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

Whether to retain the final robust hat matrix.

True
compute_local_r2 bool

Whether to compute local coefficients of determination.

True
compute_inference bool

Whether to retain parameter covariance factors, standard errors, and t values.

True
compute_hat_matrix_flag Optional[bool]

Compatibility alias for compute_hat_matrix.

None
verbose Optional[bool]

Optional per-fit override of the estimator verbosity.

None

Returns:

Type Description
'RGWR'

The fitted robust estimator.

Raises:

Type Description
RuntimeError

If robust filtering leaves too few usable observations for local calibration.

Source code in src/pygwrx/models/rgwr.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 = True,
    compute_local_r2: bool = True,
    compute_inference: bool = True,
    compute_hat_matrix_flag: Optional[bool] = None,
    verbose: Optional[bool] = None,
) -> "RGWR":
    """Fit robust GWR and return the estimator.

    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: Whether to retain the final robust hat matrix.
        compute_local_r2: Whether to compute local coefficients of
            determination.
        compute_inference: Whether to retain parameter covariance factors,
            standard errors, and t values.
        compute_hat_matrix_flag: Compatibility alias for
            ``compute_hat_matrix``.
        verbose: Optional per-fit override of the estimator verbosity.

    Returns:
        The fitted robust estimator.

    Raises:
        RuntimeError: If robust filtering leaves too few usable
            observations for local calibration.
    """
    self._validate_robust_parameters()
    if compute_hat_matrix_flag is not None:
        if not isinstance(compute_hat_matrix_flag, (bool, np.bool_)):
            raise TypeError("compute_hat_matrix_flag must be boolean or None.")
        compute_hat_matrix = bool(compute_hat_matrix_flag)
    for name, value in (
        ("compute_hat_matrix", compute_hat_matrix),
        ("compute_local_r2", compute_local_r2),
        ("compute_inference", compute_inference),
    ):
        if not isinstance(value, (bool, np.bool_)):
            raise TypeError(f"{name} must be boolean.")
    self._reset_fit_state()

    try:
        # Filtered RGWR needs the complete initial S matrix to reproduce the
        # studentized residual definition in GWmodel::gwr.basic.
        initial_hat_matrix = bool(compute_hat_matrix) or self.method == "filtered"
        super().fit(
            X,
            y,
            coords,
            compute_hat_matrix=initial_hat_matrix,
            compute_local_r2=bool(compute_local_r2),
            compute_inference=bool(compute_inference),
            compute_hat_matrix_flag=None,
            verbose=verbose,
        )

        self.initial_fitted_values_ = self.fitted_values_.copy()
        self.initial_residuals_ = self.residuals_.copy()
        self.initial_diagnostics_ = dict(self.diagnostics_ or {})
        initial_mse = float(np.mean(self.initial_residuals_**2))
        self.mse_history_ = [initial_mse]
        self.weight_history_ = [np.ones(self.n_samples_, dtype=float)]

        X_design = (
            add_intercept(self.X_train_) if self.fit_intercept else self.X_train_
        )

        if self.method == "filtered":
            studentized = self._gwmodel_studentized_residuals()
            self.initial_studentized_residuals_ = studentized.copy()
            robust_weights = (
                np.isfinite(studentized) & (np.abs(studentized) < self.cut_filter)
            ).astype(float)
            self.robust_weights_ = robust_weights
            self.weight_history_.append(robust_weights.copy())
            self._commit_robust_fit(
                X_design,
                compute_hat_matrix=bool(compute_hat_matrix),
                compute_local_r2=bool(compute_local_r2),
                compute_inference=bool(compute_inference),
            )
            final_mse = float(np.mean(self.residuals_**2))
            self.mse_history_.append(final_mse)
            self.n_iter_ = 1
            self.converged_ = True
        else:
            current_mse = initial_mse
            candidate_weights, scores = self._automatic_weights(
                self.initial_residuals_, current_mse
            )
            self.robust_residual_scores_ = scores.copy()

            for iteration in range(self.max_iter):
                self.robust_weights_ = candidate_weights.copy()
                self.weight_history_.append(self.robust_weights_.copy())
                self._commit_robust_fit(
                    X_design,
                    compute_hat_matrix=bool(compute_hat_matrix),
                    compute_local_r2=bool(compute_local_r2),
                    compute_inference=bool(compute_inference),
                )

                new_mse = float(np.mean(self.residuals_**2))
                self.mse_history_.append(new_mse)
                if new_mse <= np.finfo(float).eps:
                    relative_change = 0.0
                else:
                    relative_change = abs(current_mse - new_mse) / new_mse
                self.convergence_history_.append(float(relative_change))
                self.n_iter_ = iteration + 1

                next_weights, scores = self._automatic_weights(
                    self.residuals_, new_mse
                )
                self.robust_residual_scores_ = scores.copy()
                self.robust_scale_ = float(np.sqrt(max(new_mse, 0.0)))

                if self.verbose:
                    print(
                        "RGWR automatic iteration "
                        f"{self.n_iter_}: relative MSE change="
                        f"{relative_change:.6g}"
                    )
                if relative_change <= self.tol:
                    self.converged_ = True
                    break

                current_mse = new_mse
                candidate_weights = next_weights

            if not self.converged_:
                warnings.warn(
                    "RGWR automatic reweighting reached max_iter before the "
                    "relative-MSE tolerance was satisfied.",
                    RuntimeWarning,
                    stacklevel=2,
                )

        if self.robust_weights_ is None:
            raise RuntimeError("The final robust weight vector is unavailable.")
        self.outlier_mask_ = np.isclose(self.robust_weights_, 0.0)
        self.downweighted_mask_ = self.robust_weights_ < 1.0
        final_mse = float(np.mean(self.residuals_**2))
        self.robust_scale_ = float(np.sqrt(max(final_mse, 0.0)))
        if final_mse <= np.finfo(float).eps:
            self.robust_residual_scores_ = np.zeros(self.n_samples_, dtype=float)
        else:
            self.robust_residual_scores_ = self.residuals_ / np.sqrt(final_mse)
        self.robust_method_ = self.method
        self._mark_fitted()
        return self
    except Exception:
        self._reset_fit_state()
        raise

to_frame

to_frame() -> pd.DataFrame

Return standard GWR results plus robust diagnostics.

Source code in src/pygwrx/models/rgwr.py
def to_frame(self) -> pd.DataFrame:
    """Return standard GWR results plus robust diagnostics."""
    frame = super().to_frame()
    if self.robust_weights_ is not None:
        frame["robust_weight"] = self.robust_weights_
        frame["downweighted"] = self.downweighted_mask_
        frame["robust_outlier"] = self.outlier_mask_
    if self.robust_residual_scores_ is not None:
        frame["robust_residual_score"] = self.robust_residual_scores_
    if self.initial_studentized_residuals_ is not None:
        frame["initial_studentized_residual"] = self.initial_studentized_residuals_
    return frame

summary

summary() -> str

Return the standard GWR summary with robust-fit information.

Source code in src/pygwrx/models/rgwr.py
def summary(self) -> str:
    """Return the standard GWR summary with robust-fit information."""
    base_summary = super().summary()
    lines = base_summary.splitlines()
    for index, line in enumerate(lines):
        if "Gaussian Geographically Weighted Regression" in line:
            lines[index] = "Robust Geographically Weighted Regression (RGWR)"
            break

    if lines and set(lines[-1]) == {"="}:
        closing = lines.pop()
    else:
        closing = "=" * 78
    lines.extend(
        [
            "",
            "Robust calibration",
            "-" * 78,
            f"Method: {self.method}",
            f"Robust refits: {self.n_iter_}",
            f"Converged: {self.converged_}",
            f"Downweighted observations: "
            f"{int(np.count_nonzero(self.downweighted_mask_))}",
            f"Zero-weight outliers: {int(np.count_nonzero(self.outlier_mask_))}",
            f"Minimum robust weight: {float(np.min(self.robust_weights_)):.6f}",
            closing,
        ]
    )
    return "\n".join(lines)

Runnable examples used on this page

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

"""Fit robust GWR in automatic down-weighting mode."""

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

from pygwrx import RGWR

X, y, coords = spatial_regression()
y = y.copy()
y[[2, 20]] += np.array([5.0, -4.0])
model = RGWR(bandwidth=24, adaptive=True, max_iter=8).fit(X, y, coords)
print_model_result(model)
print("robust_weights=", model.robust_weights_[:8])
print("predictions=", model.predict(X.iloc[:3], coords.iloc[:3]))