Locally Compensated Ridge GWR (LCRGWR)¶
Task: continuous-response local regression with location-specific collinearity diagnosis and ridge compensation
Core mechanism: apply a positive local ridge parameter only where the unpenalised local condition number exceeds a threshold
Required inputs: predictor matrix X, numeric response y, coordinates coords
Independent-target prediction: supported with target-specific condition numbers and ridge terms
API reference GWR manual GWLasso manual
What problem LCRGWR solves¶
GWR fits many local regressions from overlapping weighted samples. Even when predictors are not severely collinear globally, a predictor can become locally constant or locally redundant inside particular neighbourhoods. The resulting local normal equations can be unstable, producing large coefficient variance, abrupt sign reversals and extreme local estimates.
LCRGWR first diagnoses the unpenalised weighted design at every location. When the local condition number exceeds cn_thresh, it calculates a location-specific ridge value intended to reduce the condition number toward the threshold.
With locally normalised singular values \(d_{\max}\) and \(d_{\min}\), the classical compensation rule is
where \(\kappa^*\) is cn_thresh. Locations below the threshold retain the baseline lambda_ridge, usually zero.
Pre- and post-compensation condition numbers are different diagnostics
condition_numbers_ describes the unpenalised local design and therefore remains high at locations that required compensation. Use compensated_condition_numbers_ and penalized_system_condition_numbers_ to inspect the implied and actual post-penalty systems.
When to use LCRGWR¶
Use LCRGWR when:
- the response is continuous and GWR is otherwise appropriate;
- coefficient surfaces show instability associated with local predictor correlation;
- local condition numbers exceed a pre-specified diagnostic threshold;
- retaining all predictors is scientifically preferable to local variable deletion;
- a transparent location-specific stabilisation map is required.
Do not use it merely because ordinary GWR coefficients vary. Spatial variation and numerical collinearity are different phenomena.
| Main objective | Better starting point |
|---|---|
| Reduce influence of response outliers | RGWR |
| Select a sparse subset of predictors at each location | GWLasso |
| Assign known variables to global and local groups | MixedGWR |
| Allow each predictor a different bandwidth | MGWR |
| Diagnose collinearity without changing estimates | Fit GWR and use local collinearity diagnostics first. |
What pyGWRx implements¶
The class follows the classical GWR-LCR workflow and the GWmodel::gwr.lcr conventions, with one explicit consistency improvement: pyGWRx constructs the hat matrix from the actual penalised estimator, so smoother traces, information criteria, influence and standard errors correspond to the ridge-adjusted fit.
The implementation provides:
- strict leave-one-out CV bandwidth selection for automatic LCRGWR;
- fixed or adaptive spatial bandwidths;
- a constant baseline ridge term through
lambda_ridge; - threshold-triggered local compensation through
lambda_adjust=True; - pre-compensation, formula-implied post-compensation and actual penalised-system condition numbers;
- location-specific lambda values and masks;
- optional final-bandwidth LOOCV residuals;
- target-location predictions and target local diagnostics.
The intercept is penalised together with the slopes, matching the reference GWmodel convention.
Installation¶
Self-contained example¶
import numpy as np
import pandas as pd
from pygwrx import LCRGWR
rng = np.random.default_rng(21)
n = 90
coords = rng.uniform(0.0, 100.0, size=(n, 2))
x1 = rng.normal(size=n)
# Strong overall correlation plus a spatially varying disturbance.
x2 = 0.97 * x1 + 0.08 * rng.normal(size=n) + 0.002 * coords[:, 0]
x3 = rng.normal(size=n)
X = pd.DataFrame({"x1": x1, "x2": x2, "x3": x3})
y = 2.0 + 1.4 * x1 - 1.0 * x2 + 0.6 * x3 + rng.normal(0.0, 0.3, size=n)
model = LCRGWR(
kernel="bisquare",
bandwidth="cv",
adaptive=True,
cn_thresh=30.0,
lambda_ridge=0.0,
lambda_adjust=True,
).fit(
X,
y,
coords,
compute_hat_matrix=False,
compute_cv=True,
)
print("bandwidth:", model.bandwidth_)
print("compensated locations:", model.locally_compensated_mask_.sum())
print("maximum original CN:", model.condition_numbers_.max())
print("maximum local lambda:", model.local_lambda_.max())
print(model.to_frame().head())
Target-location prediction and diagnostics are separate:
X_new = X.iloc[:3].copy()
coords_new = np.array([[20.0, 20.0], [50.0, 50.0], [80.0, 80.0]])
pred = model.predict_result(X_new, coords_new)
local_diag = model.get_local_diagnostics(coords_new)
print(pred.to_frame())
print(local_diag)
Constructor¶
LCRGWR(
kernel="bisquare",
bandwidth="cv",
bandwidth_method="cv",
adaptive=False,
bandwidth_range=None,
optimization_method="golden_section",
lambda_ridge=0.0,
lambda_adjust=True,
cn_thresh=30.0,
fit_intercept=True,
distance_metric="euclidean",
sigma2_v1=True,
verbose=False,
)
Constructor parameters¶
Spatial and bandwidth parameters¶
| Parameter | Default | Meaning and use |
|---|---|---|
kernel |
"bisquare" |
Spatial kernel used by every local penalised regression. Compact support can expose locally unsupported predictors more sharply. |
bandwidth |
"cv" |
Numeric fixed/adaptive bandwidth, "cv", or None. Classical automatic LCRGWR selection is strict leave-one-out CV. |
bandwidth_method |
"cv" |
Automatic criterion when bandwidth=None. Only "cv" is supported for the classical LCR algorithm. |
adaptive |
False |
Interprets numeric bandwidth as a neighbour count when true. Adaptive neighbourhoods are often useful under uneven sampling density. |
bandwidth_range |
None |
Optional CV search bounds. Check whether selection reaches a boundary. |
optimization_method |
"golden_section" |
"golden_section", "brent", or "grid". Grid search provides the clearest sensitivity trace. |
fit_intercept |
True |
Adds a local intercept, which is included in the penalised design under the reference convention. |
distance_metric |
"euclidean" |
Defines spatial proximity and fixed-bandwidth units. |
sigma2_v1 |
True |
Residual variance convention for local standard errors. |
Ridge and condition-number parameters¶
| Parameter | Default | Meaning | How to choose and what can go wrong |
|---|---|---|---|
lambda_ridge |
0.0 |
Non-negative ridge value applied at every location before optional compensation. | Keep zero for classical threshold-only LCRGWR. A positive value creates globally present local ridge regularisation and means ridge_applied_mask_ may be true even below the condition threshold. |
lambda_adjust |
True |
Enables threshold-triggered replacement of lambda_ridge with a location-specific compensation value. |
Set false only for a controlled constant-ridge comparison. With false and lambda_ridge=0, the model is effectively unpenalised under the LCR fitting conventions. |
cn_thresh |
30.0 |
Desired maximum local condition number used by the compensation formula. | Values around 20–30 are common diagnostic conventions, not universal laws. Pre-specify or sensitivity-test the threshold. A lower threshold applies ridge at more locations and increases bias. Must be greater than one. |
verbose |
False |
Prints bandwidth and fit information. | Useful for CV and threshold diagnostics. |
How condition numbers are calculated¶
pyGWRx follows the GWmodel/Belsley-style convention used by LCRGWR:
- multiply the design columns by local weights;
- normalise columns by their local Euclidean norms;
- obtain singular values;
- calculate largest / smallest singular value.
A locally constant or unsupported predictor can produce a zero smallest singular value and an infinite pre-compensation condition number. That is a substantive warning about local information support, not merely a numerical inconvenience.
Fitting¶
model.fit(
X,
y,
coords,
compute_hat_matrix=True,
compute_local_r2=True,
compute_inference=True,
compute_cv=True,
verbose=None,
)
| Fit argument | Default | Meaning and guidance |
|---|---|---|
compute_hat_matrix |
True |
Stores the complete penalised smoother matrix. Traces remain available when false. Disable for larger samples unless matrix entries are required. |
compute_local_r2 |
True |
Computes local R². It does not diagnose collinearity and should be read alongside condition-number maps. |
compute_inference |
True |
Computes penalised-estimator covariance factors, local SEs and t values. Ridge introduces bias, so ordinary unpenalised inferential interpretations require caution. |
compute_cv |
True |
Computes final-bandwidth leave-one-out residuals, squared contributions and total CV score. Disable when those outputs are unnecessary after a supplied bandwidth. |
verbose |
None |
Per-fit verbosity override. |
Main fitted attributes¶
| Attribute | Meaning |
|---|---|
condition_numbers_ / local_condition_numbers_ |
Unpenalised local condition numbers. |
local_lambda_ / local_lambdas_ |
Ridge value actually used at every calibration location. |
compensated_condition_numbers_ |
Condition numbers implied by the classical compensation formula. |
penalized_system_condition_numbers_ |
Numerical condition numbers of the actual penalised normal systems. |
locally_compensated_mask_ |
Locations above cn_thresh where adaptive compensation was applied. |
ridge_applied_mask_ |
All locations with positive final lambda, including constant baseline ridge. |
design_scales_ |
Global design-column scales used by the estimator. |
cv_residuals_, cv_contributions_ |
Final-bandwidth LOOCV residuals and their squared contributions. |
bandwidth_cv_score_ |
Sum of squared LOOCV residuals. |
bandwidth_selection_result_ |
Search value, score, evaluations, convergence and method. |
coef_, intercept_, fitted_values_, residuals_ |
Penalised local fit results. |
to_frame() exports coefficient results together with local condition numbers, lambdas and compensation masks. get_local_diagnostics(coords) computes the same diagnostic family at arbitrary target coordinates.
Interpreting the outputs¶
Diagnose before celebrating smoother coefficients¶
A ridge penalty usually reduces coefficient variance and extreme values. That visual stability is not proof that the original scientific specification was sound. Inspect which predictors are locally unsupported and whether the bandwidth or design should change.
Compare three condition-number fields¶
condition_numbers_: severity of the original local design problem;compensated_condition_numbers_: result implied by the classical singular-value formula;penalized_system_condition_numbers_: condition of the actual matrix solved by pyGWRx.
They answer different questions and need not be numerically identical.
Interpret lambda spatially¶
A clustered high-lambda region can indicate:
- locally redundant predictor patterns;
- insufficient variation within the chosen bandwidth;
- boundary effects;
- sparse sampling;
- a predictor whose spatial support differs from the shared GWR bandwidth.
Ridge changes the estimand¶
Penalised coefficients are biased toward zero. Compare signs, magnitudes and uncertainty with ordinary GWR, but do not describe LCR estimates as if the penalty had no inferential consequence.
LCRGWR versus GWLasso¶
| Feature | LCRGWR | GWLasso |
|---|---|---|
| Main problem | Numerical instability from local collinearity | Local shrinkage and variable selection |
| Penalty | L2 ridge | L1 Lasso |
| Activation | Condition-number threshold or constant lambda | Fixed or locally CV-selected alpha |
| Coefficients exactly zero | Generally no | Yes |
| Keeps all predictors | Yes | Not necessarily |
| Primary diagnostic | Condition numbers and local lambda | Selection frequency, active sets and local alpha |
Common mistakes¶
| Mistake | Correction |
|---|---|
Expecting condition_numbers_ to fall below the threshold |
It records the pre-compensation design. Inspect post-compensation fields. |
Treating cn_thresh=30 as universally optimal |
Justify and sensitivity-test thresholds such as 20, 25 and 30. |
| Using LCRGWR to address response outliers | Use RGWR and residual diagnostics. |
| Interpreting ridge-stabilised t values exactly like ordinary GWR t values | Acknowledge penalisation bias and focus on stability/sensitivity. |
| Ignoring infinite local condition numbers | Check locally constant predictors, bandwidth support and data preparation. |
Setting a positive lambda_ridge while claiming threshold-only compensation |
Report the constant baseline penalty explicitly. |
Disabling compute_cv and then expecting cv_residuals_ |
Leave it enabled when final LOOCV diagnostics are required. |
| Comparing target predictions without target diagnostic maps | Use get_local_diagnostics() to show target condition numbers and lambdas. |
What to report¶
Report:
- all ordinary GWR spatial settings;
- local condition-number definition;
cn_thresh,lambda_ridgeandlambda_adjust;- selected bandwidth and CV search details;
- number and spatial pattern of compensated locations;
- distributions/maps of original, compensated and actual system condition numbers;
- local lambda distribution;
- coefficient comparison with ordinary GWR;
- ridge-aware inference caveats;
- final-bandwidth CV and spatial validation results;
- pyGWRx version.
References¶
- Wheeler, D. C. (2007). Diagnostic tools and a remedial method for collinearity in geographically weighted regression. Environment and Planning A, 39(10), 2464–2481.
10.1068/a38325 - Gollini, I., Lu, B., Charlton, M., Brunsdon, C., & Harris, P. (2015). GWmodel: An R Package for Exploring Spatial Heterogeneity Using Geographically Weighted Models. Journal of Statistical Software, 63(17), 1–50.
10.18637/jss.v063.i17