Skip to content

SGTWR

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

Conceptual guide

SGTWR

Spatiotemporal geographically weighted regression with similarity.

Property Value
Type class
Import from pygwrx.models import SGTWR
Signature SGTWR(spatial_bandwidth: 'SelectionValue' = 'aicc', *, temporal_bandwidth: 'SelectionValue' = 'aicc', adaptive: 'bool' = True, alpha: 'SelectionValue' = 'aicc', similarity_vars: 'Optional[Sequence[Union[int, str]]]' = None, standardize_similarity: 'bool' = True, spatial_bandwidth_candidates: 'Optional[Sequence[Number]]' = None, temporal_bandwidth_candidates: 'Optional[Sequence[Number]]' = None, alpha_candidates: 'Optional[Sequence[Number]]' = None, causal: 'bool' = False, time_unit: 'str' = 'auto', fit_intercept: 'bool' = True, distance_metric: 'str' = 'euclidean', sigma2_v1: 'bool' = True, ridge: 'float' = 0.0, store_weights: 'bool' = True, verbose: 'bool' = False) -> 'None'
Maintained example examples/models/16_sgtwr.py

SGTWR

SGTWR(
    spatial_bandwidth: SelectionValue = "aicc",
    *,
    temporal_bandwidth: SelectionValue = "aicc",
    adaptive: bool = True,
    alpha: SelectionValue = "aicc",
    similarity_vars: Optional[
        Sequence[Union[int, str]]
    ] = None,
    standardize_similarity: bool = True,
    spatial_bandwidth_candidates: Optional[
        Sequence[Number]
    ] = None,
    temporal_bandwidth_candidates: Optional[
        Sequence[Number]
    ] = None,
    alpha_candidates: Optional[Sequence[Number]] = None,
    causal: bool = False,
    time_unit: str = "auto",
    fit_intercept: bool = True,
    distance_metric: str = "euclidean",
    sigma2_v1: bool = True,
    ridge: float = 0.0,
    store_weights: bool = True,
    verbose: bool = False
)

Spatiotemporal geographically weighted regression with similarity.

The published spatiotemporal weight is

.. math::

W_{ST,ij}=\exp\left[-\frac{1}{2}\left(
\left(\frac{d^S_{ij}}{h^S_i}\right)^2+
\left(\frac{d^T_{ij}}{h^T}\right)^2\right)\right].

Static attribute similarity follows SGWR:

.. math::

W_{S,ij}=\exp\left[-\left(
\frac{1}{m}\sum_{k=1}^{m}|z_{ik}-z_{jk}|\right)^2\right].

The final local weight is

.. math::

W_{ij}=\alpha W_{ST,ij}+(1-\alpha)W_{S,ij}.

The paper uses a genetic algorithm to tune the spatial neighbour count, temporal bandwidth, and mixing coefficient. pyGWRx uses a deterministic AICc candidate search so fitted results are reproducible and testable.

Parameters:

Name Type Description Default
spatial_bandwidth SelectionValue

Fixed spatial distance or adaptive neighbour count. "aicc" selects from spatial_bandwidth_candidates.

'aicc'
temporal_bandwidth SelectionValue

Positive temporal bandwidth. "aicc" selects from temporal_bandwidth_candidates.

'aicc'
adaptive bool

Interpret spatial_bandwidth as a neighbour count.

True
alpha SelectionValue

Spatiotemporal contribution in [0, 1]. "aicc" selects from alpha_candidates.

'aicc'
similarity_vars Optional[Sequence[Union[int, str]]]

Predictor names or indices used for similarity. None uses every predictor.

None
standardize_similarity bool

Standardize selected variables before taking absolute attribute differences. This matches the paper's Z-score preprocessing and the standardized SGWR implementation.

True
spatial_bandwidth_candidates Optional[Sequence[Number]]

Optional spatial candidates for AICc selection.

None
temporal_bandwidth_candidates Optional[Sequence[Number]]

Optional temporal candidates for AICc selection.

None
alpha_candidates Optional[Sequence[Number]]

Optional mixing candidates for AICc selection.

None
causal bool

Exclude source observations later than a regression time.

False
time_unit str

Numeric unit or datetime conversion convention used by GTWR.

'auto'
fit_intercept bool

Include a local intercept.

True
distance_metric str

Spatial distance metric supported by pyGWRx.

'euclidean'
sigma2_v1 bool

Use n - trace(S) for residual variance when true.

True
ridge float

Non-negative stabilization added to slope normal equations. The intercept is not penalized.

0.0
store_weights bool

Store component and combined weight matrices.

True
verbose bool

Print selected parameters and AICc.

False
References

Li, M., Du, W., Yu, S., Hong, Z., Zhang, D., He, Y., & De, L. (2025). SGTWR Model with Spatial-Temporal Heterogeneity and Attribute Similarity for Urban Traffic Carbon Emission Driver Analysis. Sustainability, 17(23), 10773.

Source code in src/pygwrx/models/sgtwr.py
def __init__(
    self,
    spatial_bandwidth: SelectionValue = "aicc",
    *,
    temporal_bandwidth: SelectionValue = "aicc",
    adaptive: bool = True,
    alpha: SelectionValue = "aicc",
    similarity_vars: Optional[Sequence[Union[int, str]]] = None,
    standardize_similarity: bool = True,
    spatial_bandwidth_candidates: Optional[Sequence[Number]] = None,
    temporal_bandwidth_candidates: Optional[Sequence[Number]] = None,
    alpha_candidates: Optional[Sequence[Number]] = None,
    causal: bool = False,
    time_unit: str = "auto",
    fit_intercept: bool = True,
    distance_metric: str = "euclidean",
    sigma2_v1: bool = True,
    ridge: float = 0.0,
    store_weights: bool = True,
    verbose: bool = False,
) -> None:
    self.spatial_bandwidth = spatial_bandwidth
    self.temporal_bandwidth = temporal_bandwidth
    self.adaptive = self._boolean(adaptive, "adaptive")
    self.alpha = alpha
    self.similarity_vars = similarity_vars
    self.standardize_similarity = self._boolean(
        standardize_similarity,
        "standardize_similarity",
    )
    self.spatial_bandwidth_candidates = spatial_bandwidth_candidates
    self.temporal_bandwidth_candidates = temporal_bandwidth_candidates
    self.alpha_candidates = alpha_candidates
    self.causal = self._boolean(causal, "causal")
    self.time_unit = self._time_unit(time_unit)
    self.fit_intercept = self._boolean(fit_intercept, "fit_intercept")
    self.distance_metric = self._distance_metric(distance_metric)
    self.sigma2_v1 = self._boolean(sigma2_v1, "sigma2_v1")
    self.ridge = self._nonnegative(ridge, "ridge")
    self.store_weights = self._boolean(store_weights, "store_weights")
    self.verbose = self._boolean(verbose, "verbose")
    self._reset_fit_state()

fit

fit(
    X: Union[ndarray, DataFrame],
    y: Union[ndarray, Series],
    coords: Union[ndarray, DataFrame],
    times: object,
) -> "SGTWR"

Fit SGTWR at observed space-time locations.

Source code in src/pygwrx/models/sgtwr.py
def fit(
    self,
    X: Union[np.ndarray, pd.DataFrame],
    y: Union[np.ndarray, pd.Series],
    coords: Union[np.ndarray, pd.DataFrame],
    times: object,
) -> "SGTWR":
    """Fit SGTWR at observed space-time locations."""
    self._reset_fit_state()
    try:
        X_arr, names = self._coerce_X(X)
        y_arr = self._coerce_y(y, X_arr.shape[0])
        coords_arr = validate_coords(coords)
        if coords_arr.shape[0] != X_arr.shape[0]:
            raise ValueError("X and coords must contain the same number of rows.")
        times_arr = self._fit_times(times)
        if times_arr.size != X_arr.shape[0]:
            raise ValueError("times must contain one value per row of X.")

        self.feature_names_ = names
        self.similarity_indices_ = self._resolve_similarity_indices(
            names,
            X_arr.shape[1],
        )
        self.similarity_feature_names_ = tuple(
            names[index] for index in self.similarity_indices_
        )
        similarity_X = self._fit_similarity_scaler(
            X_arr[:, self.similarity_indices_]
        )
        similarity = self._similarity_weights(similarity_X, similarity_X)
        X_design = add_intercept(X_arr) if self.fit_intercept else X_arr.copy()
        (
            spatial_bandwidth,
            temporal_bandwidth,
            alpha,
        ) = self._select_parameters(
            X_design,
            y_arr,
            coords_arr,
            times_arr,
            similarity,
        )
        spatiotemporal = self._spatiotemporal_weights(
            coords_arr,
            times_arr,
            coords_arr,
            times_arr,
            spatial_bandwidth,
            temporal_bandwidth,
        )
        combined = self._combine_weights(
            spatiotemporal,
            similarity,
            alpha,
            query_times=times_arr,
            source_times=times_arr,
        )
        local_fit = self._fit_weight_matrix(
            X_design,
            y_arr,
            combined,
            X_design,
            compute_covariance=True,
        )
        residuals = y_arr - local_fit.fitted_values
        diagnostics = compute_diagnostics(
            y_arr,
            local_fit.fitted_values,
            hat_matrix=local_fit.hat_matrix,
            compute_gwr_stats=True,
        )
        trace_s = float(diagnostics["trace_S"])
        trace_sts = float(diagnostics["trace_StS"])
        denominator = (
            y_arr.size - trace_s
            if self.sigma2_v1
            else y_arr.size - 2.0 * trace_s + trace_sts
        )
        if denominator <= 0.0:
            raise ValueError(
                "SGTWR residual effective degrees of freedom are not positive; "
                "increase either bandwidth or simplify the design."
            )
        sigma2 = float(np.dot(residuals, residuals) / denominator)
        if local_fit.covariance_diagonal is None:
            raise RuntimeError("SGTWR covariance factors were not computed.")
        parameter_se = np.sqrt(
            np.maximum(
                local_fit.covariance_diagonal * sigma2,
                0.0,
            )
        )
        parameter_t = np.divide(
            local_fit.parameters,
            parameter_se,
            out=np.full_like(local_fit.parameters, np.nan),
            where=parameter_se > 0.0,
        )

        self.X_train_ = X_arr.copy()
        self.X_design_ = X_design
        self.y_train_ = y_arr.copy()
        self.coords_train_ = coords_arr.copy()
        self.times_train_ = times_arr.copy()
        self.spatial_bandwidth_ = spatial_bandwidth
        self.temporal_bandwidth_ = temporal_bandwidth
        self.alpha_ = alpha
        self.parameters_ = local_fit.parameters
        if self.fit_intercept:
            self.intercept_ = local_fit.parameters[:, 0]
            self.coef_ = local_fit.parameters[:, 1:]
            self.intercept_se_ = parameter_se[:, 0]
            self.coef_se_ = parameter_se[:, 1:]
            self.intercept_t_ = parameter_t[:, 0]
            self.coef_t_ = parameter_t[:, 1:]
        else:
            self.intercept_ = np.zeros(y_arr.size, dtype=float)
            self.coef_ = local_fit.parameters
            self.intercept_se_ = np.zeros(y_arr.size, dtype=float)
            self.coef_se_ = parameter_se
            self.intercept_t_ = np.full(y_arr.size, np.nan)
            self.coef_t_ = parameter_t
        self.coefficients_ = self.coef_
        self.fitted_values_ = local_fit.fitted_values
        self.residuals_ = residuals
        self.hat_matrix_ = local_fit.hat_matrix
        self.influence_ = np.diag(local_fit.hat_matrix)
        self.parameter_covariance_diagonal_ = local_fit.covariance_diagonal
        self.parameter_standard_errors_ = parameter_se
        self.parameter_t_values_ = parameter_t
        self.sigma2_ = sigma2
        self.diagnostics_ = diagnostics
        if self.store_weights:
            self.spatiotemporal_weights_ = spatiotemporal
            self.similarity_weights_ = similarity
            self.combined_weights_ = combined
        self._is_fitted = True
    except Exception:
        self._reset_fit_state()
        raise
    if self.verbose:
        print(
            "SGTWR fitted: "
            f"spatial_bandwidth={self.spatial_bandwidth_}, "
            f"temporal_bandwidth={self.temporal_bandwidth_:.6g}, "
            f"alpha={self.alpha_:.4f}, "
            f"AICc={self.diagnostics_['aicc']:.6f}"
        )
    return self

predict_result

predict_result(
    X: Union[ndarray, DataFrame],
    coords: Union[ndarray, DataFrame],
    times: object,
) -> SGTWRPredictionResult

Recalibrate SGTWR at new space-time locations.

Source code in src/pygwrx/models/sgtwr.py
def predict_result(
    self,
    X: Union[np.ndarray, pd.DataFrame],
    coords: Union[np.ndarray, pd.DataFrame],
    times: object,
) -> SGTWRPredictionResult:
    """Recalibrate SGTWR at new space-time locations."""
    self._require_fitted()
    if (
        self.X_train_ is None
        or self.X_design_ is None
        or self.y_train_ is None
        or self.coords_train_ is None
        or self.times_train_ is None
        or self.spatial_bandwidth_ is None
        or self.temporal_bandwidth_ is None
        or self.alpha_ is None
    ):
        raise RuntimeError("SGTWR training state is incomplete.")
    X_arr, _ = self._coerce_X(X, expected_names=self.feature_names_)
    coords_arr = validate_coords(coords)
    times_arr = self._predict_times(times)
    if X_arr.shape[0] != coords_arr.shape[0] or X_arr.shape[0] != times_arr.size:
        raise ValueError(
            "X, coords, and times must contain the same number of rows."
        )
    query_design = add_intercept(X_arr) if self.fit_intercept else X_arr.copy()
    training_similarity = self._transform_similarity(self.X_train_)
    query_similarity = self._transform_similarity(X_arr)
    spatiotemporal = self._spatiotemporal_weights(
        coords_arr,
        times_arr,
        self.coords_train_,
        self.times_train_,
        self.spatial_bandwidth_,
        self.temporal_bandwidth_,
    )
    similarity = self._similarity_weights(
        query_similarity,
        training_similarity,
    )
    combined = self._combine_weights(
        spatiotemporal,
        similarity,
        self.alpha_,
        query_times=times_arr,
        source_times=self.times_train_,
    )
    local_fit = self._fit_weight_matrix(
        self.X_design_,
        self.y_train_,
        combined,
        query_design,
        compute_covariance=False,
    )
    if self.fit_intercept:
        intercept = local_fit.parameters[:, 0]
        coef = local_fit.parameters[:, 1:]
    else:
        intercept = np.zeros(X_arr.shape[0], dtype=float)
        coef = local_fit.parameters
    return SGTWRPredictionResult(
        predictions=local_fit.fitted_values,
        coef=coef,
        intercept=intercept,
        coords=coords_arr.copy(),
        times=times_arr.copy(),
        feature_names=self.feature_names_,
    )

predict

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

Return SGTWR predictions at new space-time locations.

Source code in src/pygwrx/models/sgtwr.py
def predict(
    self,
    X: Union[np.ndarray, pd.DataFrame],
    coords: Union[np.ndarray, pd.DataFrame],
    times: object,
) -> np.ndarray:
    """Return SGTWR predictions at new space-time locations."""
    return self.predict_result(X, coords, times).predictions

get_results

get_results() -> pd.DataFrame

Return fitted values and local coefficients as a DataFrame.

Source code in src/pygwrx/models/sgtwr.py
def get_results(self) -> pd.DataFrame:
    """Return fitted values and local coefficients as a DataFrame."""
    self._require_fitted()
    if (
        self.fitted_values_ is None
        or self.coords_train_ is None
        or self.times_train_ is None
        or self.coef_ is None
        or self.intercept_ is None
    ):
        raise RuntimeError("SGTWR fitted results are incomplete.")
    frame = SGTWRPredictionResult(
        predictions=self.fitted_values_,
        coef=self.coef_,
        intercept=self.intercept_,
        coords=self.coords_train_,
        times=self.times_train_,
        feature_names=self.feature_names_,
    ).to_frame()
    frame["residual"] = self.residuals_
    return frame

SGTWRPredictionResult

Detailed predictions from a fitted SGTWR model.

Property Value
Type class
Import from pygwrx.models import SGTWRPredictionResult
Signature SGTWRPredictionResult(predictions: 'np.ndarray', coef: 'np.ndarray', intercept: 'np.ndarray', coords: 'np.ndarray', times: 'np.ndarray', feature_names: 'Tuple[str, ...]') -> None
Maintained example examples/models/16_sgtwr.py

SGTWRPredictionResult dataclass

SGTWRPredictionResult(
    predictions: ndarray,
    coef: ndarray,
    intercept: ndarray,
    coords: ndarray,
    times: ndarray,
    feature_names: Tuple[str, ...],
)

Detailed predictions from a fitted SGTWR model.

to_frame

to_frame() -> pd.DataFrame

Return predictions and local parameters as a DataFrame.

Source code in src/pygwrx/models/sgtwr.py
def to_frame(self) -> pd.DataFrame:
    """Return predictions and local parameters as a DataFrame."""
    data: Dict[str, np.ndarray] = {
        "coord_0": self.coords[:, 0],
        "coord_1": self.coords[:, 1],
        "time": self.times,
        "prediction": self.predictions,
        "intercept": self.intercept,
    }
    for index, name in enumerate(self.feature_names):
        data[f"coef_{name}"] = self.coef[:, index]
    return pd.DataFrame(data)

Runnable examples used on this page

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

"""Fit similarity and geographically-temporally weighted regression."""

# 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, temporal_regression

from pygwrx import SGTWR, SGTWRPredictionResult

X, y, coords, times = temporal_regression(n=48, p=3)
model = SGTWR(
    spatial_bandwidth=24,
    temporal_bandwidth=2.0,
    adaptive=True,
    alpha=0.5,
    similarity_vars=["x1", "x2"],
    store_weights=True,
).fit(X, y, coords, times)
print_model_result(model)
print("combined_weights_shape=", model.combined_weights_.shape)
result = model.predict_result(X.iloc[:3], coords.iloc[:3], times[:3])
assert isinstance(result, SGTWRPredictionResult)
print(result.to_frame())