# mgwr: A Python Implementation of Multiscale Geographically Weighted Regression for Investigating Process Spatial Heterogeneity and Scale

^{1}

^{2}

^{3}

^{4}

^{*}

## Abstract

**:**

## 1. Introduction

## 2. Source Code and Datasets

#### 2.1. Source Code and Installation

`pip install mgwr`

`pip install https://github.com/pysal/mgwr/archive/master.zip`

`>>> import numpy as np`

`>>> import pandas as pd`

`>>> import libpysal as ps`

`>>> from mgwr.gwr import GWR, MGWR`

`>>> from mgwr.sel_bw import Sel_BW`

`>>> from mgwr.utils import compare_surfaces, truncate_colormap`

`>>> import geopandas as gp`

`>>> import matplotlib.pyplot as plt`

`>>> import matplotlib as mpl`

#### 2.2. Datasets

#### 2.2.1. Georgia Dataset

`#Load Georgia dataset and generate plot of Georgia counties (Figure 1)`

`>>> georgia = gp.read_file(ps.examples.get_path(‘G_utm.shp’))`

`>>> fig, ax = plt.subplots(figsize = (10, 10))`

`>>> georgia.plot(ax=ax, **{‘edgecolor’: ‘black’, ‘facecolor’: ‘white’})`

`>>> georgia.centroid.plot(ax = ax, c = ‘black’)`

`>>> plt.savefig(‘georgia_shp’)`

`>>> plt.show()`

#### 2.2.2. Berlin Airbnb Dataset

`#Load Berlin dataset and generate plot of properties (Figure 2)`

`>>> prenz = gp.read_file(ps.examples.get_path(‘prenzlauer.zip’))`

`>>> prenz_bound = gp.read_file(ps.examples.get_path(‘prenz_bound.zip’))`

`>>> fig, ax = plt.subplots(figsize = (10, 10))`

`>>> prenz_bound.plot(ax = ax, **{‘edgecolor’: ‘black’, ‘facecolor’: ‘white’})`

`>>> prenz.plot(ax = ax, markersize = 10, **{‘edgecolor’: ‘black’,`

`‘facecolor’: ‘black’})`

`>>> plt.savefig(‘prenz’)`

`>>> plt.show()`

## 3. GWR Functionality

`#Prepare Georgia dataset inputs`

`>>> g_y = georgia[‘PctBach’].values.reshape((-1, 1))`

`>>> g_X = georgia[[‘PctFB’, ‘PctBlack’, ‘PctRural’]].values`

`>>> u = georgia[‘X’]`

`>>> v = georgia[‘Y’]`

`>>> g_coords = list(zip(u, v))`

`#Prepare Berlin dataset inputs`

`#Take the logarithm of the price variable to correct for skewing`

`>>> b_y = np.log(prenz[‘price’].values.reshape((-1, 1)))`

`>>> b_X = prenz[[‘review_sco’,`

`‘accommodat’,`

`‘bathrooms’]].values`

`>>> u = prenz[‘X’]`

`>>> v = prenz[‘Y’]`

`>>> b_coords = list(zip(u, v))`

#### 3.1. Distance-Weighting Scheme

#### 3.1.1. Kernel Functions

`kernel`input parameter where the option is available (see Table 3).

#### 3.1.2. Kernel Types

#### 3.2. Bandwidth Selection

`#Examples of optimal bandwidth~selection`

`#Instantiate bandwidth selection object`

`>>> selector = Sel_BW(g_coords, g_y, g_X)`

`#Default golden section search using AICc criterion`

`>>> bw = selector.search()`

`>>> print(bw)`

`117.0`

`#Interval search using AICc criterion`

`>>> bw = selector.search(search_method = ‘interval’,`

`interval = 2,`

`bw_min = 101,`

`bw_max = 150)`

`>>> print(bw)`

`117`

#### 3.3. Model Calibration

`GWR`model object. Then, the fit method for the GWR object is called to fit the model. An important input that must be specified for GWR calibration is the bandwidth parameter, which can be chosen via the optimal bandwidth selection routine discussed above.

`#Calibrate a GWR model for Georgia dataset using computationally selected~bandwidth`

`>>> gwr_selector = Sel_BW(g_coords, g_y, g_X)`

`>>> gwr_bw = gwr_selector.search()`

`>>> print(gwr_bw)`

`117.0`

`>>> gwr_model = GWR(g_coords, g_y, g_X, gwr_bw)`

`>>> gwr_results = gwr_model.fit()`

`>>> print(gwr_results.resid_ss)`

`1650.85969828`

`#Calibrate a GWR model for the Georgia dataset`

`#using a manually set~bandwidth`

`>>> gwr_model = GWR(g_coords, g_y, g_X, 117)`

`>>> gwr_results = gwr_model.fit()`

`>>> print(gwr_results.resid_ss)`

`1650.85969828`

`#Exploring spatial heterogeneity by manually varying~bandwidth`

`>>> fig, ax = plt.subplots(2, 3, figsize = (10, 6))`

`>>> bws = (x for x in range(25, 175, 25))`

`>>> vmins = []`

`>>> vmaxs = []`

`>>> for row in range(2):`

`for col in range(3):`

`bw = next(bws)`

`gwr_model = GWR(g_coords, g_y, g_X, bw)`

`gwr_results = gwr_model.fit()`

`georgia[‘rural’] = gwr_results.params[:, -1]`

`georgia.plot(‘rural’, ax = ax[row, col])`

`ax[row,col].set_title(‘Bandwidth: ’ + str(bw))`

`ax[row,col].get_xaxis().set_visible(False)`

`ax[row,col].get_yaxis().set_visible(False)`

`vmins.append(georgia[‘rural’].min())`

`vmaxs.append(georgia[‘rural’].max())`

`>>> sm = plt.cm.ScalarMappable(norm=plt.Normalize(vmin=min(vmins), vmax=max(vmaxs)))`

`>>> fig.tight_layout()`

`>>> fig.subplots_adjust(right=0.9)`

`>>> cax = fig.add_axes([0.92, 0.14, 0.03, 0.75])`

`>>> sm._A = []`

`>>> cbar = fig.colorbar(sm, cax=cax)`

`>>> cbar.ax.tick_params(labelsize=10)`

`>>> plt.savefig(‘explore’)`

`>>> plt.show()`

#### 3.4. Probability Models

from spglm.family import Poisson, Binomialand then it is necessary to set family = Poisson() or family = Binomial() when instantiating a Sel_BW or GWR object. Generally, it is not necessary to import or specify a Gaussian family object since it is the default behavior across mgwr.

#### 3.5. Model Diagnostics

#### 3.5.1. Model Fit

`#Global model~fit`

`>>> gwr_selector = Sel_BW(g_coords, g_y, g_X)`

`>>> gwr_bw = gwr_selector.search()`

`>>> print(gwr_bw)`

`117.0`

`>>> gwr_model = GWR(g_coords, g_y, g_X, gwr_bw)`

`>>> gwr_results = gwr_model.fit()`

`>>> print(gwr_results.aic)`

`848.915407053`

`>>> print(gwr_results.aicc)`

`851.350292784`

`>>> print(gwr_results.R2)`

`0.678074266959`

`#Local model fit`

`>>> georgia[‘R2’] = gwr_results.localR2`

`>>> georgia.plot(‘R2’, legend = True)`

`>>> ax = plt.gca()`

`>>> ax.get_xaxis().set_visible(False)`

`>>> ax.get_yaxis().set_visible(False)`

`>>> plt.savefig(’local_R2’)`

`>>> plt.show()`

#### 3.5.2. Inference on Individual Parameter Estimates

`#Visualizing hypothesis tests for significance of parameter estimates`

`>>> gwr_selector = Sel_BW(g_coords, g_y, g_X)`

`>>> gwr_bw = gwr_selector.search()`

`>>> print(gwr_bw)`

`117.0`

`>>> gwr_model = GWR(g_coords, g_y, g_X, gwr_bw)`

`>>> gwr_results = gwr_model.fit()`

`#default behavior using corrected alpha`

`>>> filter_tc = gwr_results.filter_tvals()`

`#without correction using common alpha`

`>>> filter_t = gwr_results.filter_tvals(alpha = 0.05)`

`>>> georgia[‘fb’] = gwr_results.params[:, 1]`

`>>> georgia[‘fb_t’] = filter_t[:, 1]`

`>>> georgia[‘fb_tc’] = filter_tc[:, 1]`

`>>> fig, ax = plt.subplots(1, 3, figsize = (12, 3))`

`>>> georgia.plot(‘fb’,`

`**{‘edgecolor’: ‘black’,`

`‘alpha’: .65,`

`‘linewidth’: .5},`

`ax = ax[0],`

`legend=True)`

`>>> ax[0].get_xaxis().set_visible(False)`

`>>> ax[0].get_yaxis().set_visible(False)`

`>>> ax[0].set_title(’Parameter estimates’)`

`>>> georgia.plot(‘fb’,`

`**{‘edgecolor’: ‘black’,`

`‘alpha’: .65,`

`‘linewidth’: .5},`

`ax = ax[1],`

`legend=True)`

`>>> georgia[filter_t[:, 1] == 0].plot(color = ‘grey’,`

`ax = ax[1],`

`**{‘edgecolor’: ’black’,`

`‘linewidth’: .5})`

`>>> ax[1].get_xaxis().set_visible(False)`

`>>> ax[1].get_yaxis().set_visible(False)`

`>>> ax[1].set_title(’Composite’)`

`>>> georgia.plot(‘fb’,`

`**{‘edgecolor’: ‘black’,`

`‘alpha’: .65,`

`‘linewidth’: .5},`

`ax = ax[2],`

`legend=True)`

`>>> georgia[filter_tc[:, 1] == 0].plot(color = ‘grey’,`

`ax = ax[2],`

`**{‘edgecolor’: ’black’,`

`‘linewidth’: .5})`

`>>> ax[2].get_xaxis().set_visible(False)`

`>>> ax[2].get_yaxis().set_visible(False)`

`>>> ax[2].set_title(‘Composite with correction’)`

`plt.savefig(‘testing’)`

`plt.show()`

#### 3.5.3. Inference on Surface of Parameter Estimates

`#Visualizing hypothesis tests for significance of parameter~estimates`

`#Manually set bandwidth to 50 and fit`

`>>> gwr_model = GWR(g_coords, g_y, g_X, 50)`

`>>> gwr_results = gwr_model.fit()`

`#100 iterations`

`>>> p_vals_100 = gwr_results.spatial_variability(gwr_selector, 100)`

`>>> print(p_vals_100)`

`[ 0.153 0.019 0.026 0.155]`

`#default is 1000 iterations`

`>>> p_vals_1000 = gwr_results.spatial_variability(gwr_selector)`

`>>> print(p_vals_1000)`

`[ 0.12 0.03 0.04 0.14]`

`#2000 iterations`

`>>> p_vals_2000 = gwr_results.spatial_variability(gwr_selector, 2000)`

`>>> print(p_vals_2000)`

`[ 0.1515 0.0195 0.023 0.146 ]`

#### 3.5.4. Local Multicollinearity

`>>> gwr_selector = Sel_BW(g_coords, g_y, g_X)`

`>>> gwr_bw = gwr_selector.search()`

`>>> print(gwr_bw)`

`117.0`

`>>> gwr_model = GWR(g_coords, g_y, g_X, gwr_bw)`

`>>> gwr_results = gwr_model.fit()`

`>>> LCC, VIF, CN, VDP = gwr_results.local_collinearity()`

`>>> names = [‘Foreign Born vs. African American’,`

`‘Foreign Born vs. Rural’,`

`‘African American vs. Rural’]`

`>>> fig, ax = plt.subplots(1, 3, figsize = (12, 4))`

`>>> for col in range(3):`

`georgia[‘vif’] = LCC[:, col]`

`georgia.plot(‘vif’, ax = ax[col], legend = True)`

`ax[col].set_title(‘LCC: ’ + names[col])`

`ax[col].get_xaxis().set_visible(False)`

`ax[col].get_yaxis().set_visible(False)`

`>>> names = [‘Foreign Born’, ‘African American’, ‘Rural’]`

`>>> fig, ax = plt.subplots(1, 3, figsize = (12, 4))`

`>>> for col in range(3):`

`georgia[‘vif’] = VIF[:, col]`

`georgia.plot(‘vif’, ax = ax[col], legend = True)`

`ax[col].set_title(‘VIF: ’ + names[col])`

`ax[col].get_xaxis().set_visible(False)`

`ax[col].get_yaxis().set_visible(False)`

`>>> fig, ax = plt.subplots(1, 1, figsize = (4, 4))`

`>>> georgia[‘cn’] = CN`

`>>> georgia.plot(‘cn’, legend = True, ax = ax)`

`>>> ax.set_title(‘Condition Number’)`

`>>> ax.get_xaxis().set_visible(False)`

`>>> ax.get_yaxis().set_visible(False)`

`>>> names = [‘Intercept’, ‘Foreign Born’, ‘African American’, ‘Rural’]`

`>>> fig, ax = plt.subplots(1, 4, figsize = (16, 4))`

`>>> for col in range(4):`

`georgia[‘vdp’] = VDP[:, col]`

`georgia.plot(‘vdp’, ax = ax[col], legend = True)`

`ax[col].set_title(‘VDP: ’ + names[col])`

`ax[col].get_xaxis().set_visible(False)`

`ax[col].get_yaxis().set_visible(False)`

#### 3.6. Out-of-Sample Spatial Prediction

`# Out-of-sample prediction using~GWR`

`#Split data into calibration and prediction sets`

`>>> np.random.seed(908)`

`>>> sample = np.random.choice(range(159), 10)`

`>>> mask = np.ones_like(g_y, dtype = bool).flatten()`

`>>> mask[sample] = False`

`>>> cal_coords = np.array(g_coords)[mask]`

`>>> cal_y = g_y[mask]`

`>>> cal_X = g_X[mask]`

`>>> pred_coords = np.array(g_coords)[~mask]`

`>>> pred_y = g_y[~mask]`

`>>> pred_X = g_X[~mask]`

`#Calibrate GWR model`

`>>> gwr_selector = Sel_BW(cal_coords, cal_y, cal_X)`

`>>> gwr_bw = gwr_selector.search(bw_min = 2)`

`>>> print(gwr_bw)`

`109.0`

`>>> model = GWR(cal_coords, cal_y, cal_X, gwr_bw)`

`>>> gwr_results = model.fit()`

`#Make predictions`

`>>> pred_results = model.predict(pred_coords, pred_X)`

`#Check correlation between known and predicted values`

`>>> corr = np.corrcoef(pred_results.predictions.flatten(),`

`pred_y.flatten())[0][1]`

`print(corr)`

`0.914249268428`

## 4. MGWR Functionality

#### 4.1. Standardizing the Variables

`#Standardize~variables`

`#Georgia dataset`

`>>> g_X = (g_X - g_X.mean(axis = 0)) / g_X.std(axis = 0)`

`>>> g_y = (g_y - g_y.mean(axis = 0)) / g_y.std(axis = 0)`

`#Standardize Berlin dataset`

`>>> b_X = (b_X - b_X.mean(axis = 0)) / b_X.std(axis = 0)`

`>>> b_y = (b_y - b_y.mean(axis = 0)) / b_y.std(axis = 0)`

#### 4.2. Bandwidth Selection and Model Calibration

`#Example of MGWR calibration (Berlin data)`

`>>> mgwr_selector = Sel_BW(b_coords, b_y, b_X, multi = True)`

`>>> mgwr_bw = mgwr_selector.search()`

`>>> print(mgwr_bw)`

`[191.0, 1279.0, 79.0, 2200.0]`

`>>> mgwr_results = MGWR(b_coords, b_y, b_X, mgwr_selector).fit()`

#### 4.3. Manually Setting Covariate-Specific Bandwidths

`#Example of manual bandwidth selection in~MGWR`

`#Apply the same bandwidth to all variables`

`>>> mgwr_selector = Sel_BW(b_coords, b_y, b_X, multi = True)`

`>>> mgwr_bw = mgwr_selector.search(multi_bw_min = [500],`

`multi_bw_max = [500])`

`>>> print(mgwr_bw)`

`[500.0, 500.0, 500.0, 500.0]`

`>>> mgwr_results = MGWR(b_coords, b_y, b_X, mgwr_selector).fit()`

`#Unique manual bandwidths`

`>>> mgwr_selector = Sel_BW(b_coords, b_y, b_X, multi = True)`

`>>> mgwr_bw = mgwr_selector.search(multi_bw_min = [150, 500, 750, 1000],`

`multi_bw_max = [150, 500, 750, 1000])`

`>>> print(mgwr_bw)`

`[150.0, 500.0, 750.0, 1000.0]`

`>>> mgwr_results = MGWR(b_coords, b_y, b_X, mgwr_selector).fit()`

#### 4.4. Model Fit

#### 4.5. Inference on Parameter Estimates

`#First set up model`

`>>> mgwr_selector = Sel_BW(b_coords, b_y, b_X, multi = True)`

`>>> mgwr_bw = mgwr_selector.search()`

`>>> mgwr_results = MGWR(b_coords, b_y, b_X, mgwr_selector).fit()`

`#Covariate-specific ENP`

`>>> print(mgwr_results.ENP_j)`

`[31.89989861, 4.77588266, 73.79013919, 1.40343481]`

`#Covrariate-specific adjusted alpha at 95% CI`

`>>> print(mgwr_results.adj_alpha_j[:, 1])`

`[ 0.0015674 0.01046927 0.0006776 0.03562688]`

`#Covariate-specific adjusted critical t-value`

`>>> print(mgwr_results.critical_tval())`

`[ 3.16585816 2.56212889 3.40333525 2.10245302]`

`>>> mgwr_filtered_t = mgwr_results.filter_tvals()`

#### 4.5.1. The Georgia Dataset

`#Calibrate GWR using standardized~data`

`>>> gwr_selector = Sel_BW(g_coords, g_y, g_X)`

`>>> gwr_bw = gwr_selector.search()`

`print(gwr_bw)`

`117.0`

`>>> gwr_model = GWR(g_coords, g_y, g_X, gwr_bw)`

`>>> gwr_results = gwr_model.fit()`

`#Prepare GWR results for~mapping`

`#Add GWR parameters to GeoDataframe`

`>>> georgia[‘gwr_intercept’] = gwr_results.params[:, 0]`

`>>> georgia[‘gwr_fb’] = gwr_results.params[:, 1]`

`>>> georgia[‘gwr_aa’] = gwr_results.params[:, 2]`

`>>> georgia[‘gwr_rural’] = gwr_results.params[:, 3]`

`#Obtain t-vals filtered based on multiple testing correction`

`>>> gwr_filtered_t = gwr_results.filter_tvals()`

`#Calibrate MGWR~model`

`>>> mgwr_selector = Sel_BW(g_coords, g_y, g_X, multi = True)`

`>>> mgwr_bw = mgwr_selector.search(multi_bw_min = [2])`

`print(mgwr_bw)`

`[92.0, 101.0, 136.0, 158.0]`

`>>> mgwr_results = MGWR(g_coords, g_y, g_X, mgwr_selector).fit()`

`#Prepare MGWR results for~mapping`

`#Add MGWR parameters to GeoDataframe`

`>>> georgia[‘mgwr_intercept’] = mgwr_results.params[:, 0]`

`>>> georgia[‘mgwr_fb’] = mgwr_results.params[:, 1]`

`>>> georgia[‘mgwr_aa’] = mgwr_results.params[:, 2]`

`>>> georgia[‘mgwr_rural’] = mgwr_results.params[:, 3]`

`#Obtain t-vals filtered based on multiple testing correction`

`>>> mgwr_filtered_t = mgwr_results.filter_tvals()`

`>>> kwargs1 = {‘edgecolor’: ‘black’, ‘alpha’: .65}`

`>>> kwargs2 = {‘edgecolor’: ‘black’}`

`>>> compare_surfaces(georgia, ‘gwr_intercept’, ‘mgwr_intercept’,`

`gwr_filtered_t[:, 0], gwr_bw, mgwr_filtered_t[:, 0],`

`mgwr_bw[0], ‘Intercept’, kwargs1, kwargs2,`

`savefig = ‘g1’)`

`>>> compare_surfaces(georgia, ‘gwr_fb’, ‘mgwr_fb’, gwr_filtered_t[:, 1],`

`gwr_bw, mgwr_filtered_t[:, 1], mgwr_bw[1],`

`‘Foreign Born’, kwargs1, kwargs2, savefig = ‘g2’)`

`>>> compare_surfaces(georgia, ‘gwr_aa’, ‘mgwr_aa’, gwr_filtered_t[:, 2],`

`gwr_bw, mgwr_filtered_t[:, 2], mgwr_bw[2],`

`‘African American’, kwargs1, kwargs2, savefig = ‘g3’)`

`>>> compare_surfaces(georgia, ‘gwr_rural’, ‘mgwr_rural’, gwr_filtered_t[:, 3],`

`gwr_bw, mgwr_filtered_t[:, 3], mgwr_bw[3],`

`‘Rural’, kwargs1, kwargs2, savefig = ‘g4’)`

#### 4.5.2. The Berlin Dataset

`#Calibrate GWR using standardized~data`

`>>> gwr_selector = Sel_BW(b_coords, b_y, b_X)`

`>>> gwr_bw = gwr_selector.search()`

`>>> print(gwr_bw)`

`191.0`

`>>> gwr_model = GWR(b_coords, b_y, b_X, gwr_bw)`

`>>> gwr_results = gwr_model.fit()`

`#Prepare GWR results for~mapping`

`#Add GWR parameters to GeoDataframe`

`>>> prenz[‘gwr_intercept’] = gwr_results.params[:, 0]`

`>>> prenz[‘gwr_score’] = gwr_results.params[:, 1]`

`>>> prenz[‘gwr_accom’] = gwr_results.params[:, 2]`

`>>> prenz[‘gwr_baths’] = gwr_results.params[:, 3]`

`#Obtain t-vals filtered based on multiple testing correction`

`>>> gwr_filtered_t = gwr_results.filter_tvals()`

`#Calibrate MGWR~model`

`>>> mgwr_selector = Sel_BW(b_coords, b_y, b_X, multi = True)`

`>>> mgwr_bw = mgwr_selector.search(multi_bw_min = [2])`

`>>> print(mgwr_bw)`

`[190.0, 1279.0, 79.0, 2200.0]`

`>>> mgwr_results = MGWR(b_coords, b_y, b_X, mgwr_selector).fit()`

`#Prepare MGWR results for~mapping`

`#Add MGWR parameters to GeoDataframe`

`>>> prenz[‘mgwr_intercept’] = mgwr_results.params[:, 0]`

`>>> prenz[‘mgwr_score’] = mgwr_results.params[:, 1]`

`>>> prenz[‘mgwr_accom’] = mgwr_results.params[:, 2]`

`>>> prenz[‘mgwr_baths’] = mgwr_results.params[:, 3]`

`#Obtain t-vals filtered based on multiple testing correction`

`>>> mgwr_filtered_t = mgwr_results.filter_tvals()`

`>>> kwargs1 = {‘edgecolor’: ‘lightgrey’, ‘markersize’: 175}`

`>>> kwargs2 = {‘facecolor’: ‘lightgrey’, ‘markersize’: 175}`

`>>> compare_surfaces(prenz, ‘gwr_intercept’, ‘mgwr_intercept’,`

`gwr_filtered_t[:, 0], gwr_bw,`

`mgwr_filtered_t[:, 0], mgwr_bw[0],`

`‘Intercept’, kwargs1, kwargs2, savefig = ‘b1’)`

`>>> compare_surfaces(prenz, ‘gwr_score’, ‘mgwr_score’, gwr_filtered_t[:, 1],`

`gwr_bw, mgwr_filtered_t[:, 1], mgwr_bw[1],`

`‘Review Score’, kwargs1, kwargs2, savefig = ‘b2’)`

`>>> compare_surfaces(prenz, ‘gwr_accom’, ‘mgwr_accom’, gwr_filtered_t[:, 2],`

`gwr_bw, mgwr_filtered_t[:, 2], mgwr_bw[2],`

`‘Accommodates’, kwargs1, kwargs2, savefig = ‘b3’)`

`>>> compare_surfaces(prenz, ‘gwr_baths’, ‘mgwr_baths’, gwr_filtered_t[:, 3],`

`gwr_bw, mgwr_filtered_t[:, 3], mgwr_bw[3],`

`‘Baths’, kwargs1, kwargs2, savefig = ‘b4’)`

#### 4.6. Local Multicollinearity

`#Prepare GWR/MGWR condition number for mapping`

`>>> gwr_lc = gwr_results.local_collinearity()`

`>>> mgwr_lc = mgwr_results.local_collinearity()`

`>>> prenz[‘gwr_cn’] = gwr_lc[2]`

`>>> prenz[‘mgwr_cn’] = mgwr_lc[0]`

`>>> fig, axes = plt.subplots(nrows = 1, ncols = 2, figsize = (10, 5))`

`>>> ax0 = axes[0]`

`>>> ax0.set_title(‘GWR Condition Number’, fontsize = 10)`

`>>> ax1 = axes[1]`

`>>> ax1.set_title(‘MGWR Condition Number’, fontsize = 10)`

`>>> cmap = mpl.cm.RdYlBu`

`>>> vmin = np.min([prenz[’gwr_cn’].min(), prenz[‘mgwr_cn’].min()])`

`>>> vmax = np.max([prenz[’gwr_cn’].max(), prenz[‘mgwr_cn’].max()])`

`>>> if (vmin < 0) & (vmax < 0):`

`cmap = truncate_colormap(cmap, 0.0, 0.5)`

`>>> elif (vmin > 0) & (vmax > 0):`

`cmap = truncate_colormap(cmap, 0.5, 1.0)`

`>>> sm = plt.cm.ScalarMappable(cmap = cmap,`

`norm = plt.Normalize(vmin = vmin,`

`vmax = vmax))`

`>>> prenz.plot(‘gwr_cn’, cmap = sm.cmap, ax = ax0,`

`vmin = vmin, vmax = vmax,`

`**{‘edgecolor’: ‘lightgrey’,`

`‘alpha’: .95,`

`‘linewidth’: .75})`

`>>> prenz.plot(‘mgwr_cn’, cmap = cmap, ax = ax1,`

`vmin = vmin, vmax = vmax,`

`**{‘edgecolor’: ‘lightgrey’,`

`‘alpha’: .95,`

`‘linewidth’: .75})`

`>>> fig.tight_layout()`

`>>> fig.subplots_adjust(right = 0.9)`

`>>> cax = fig.add_axes([0.92, 0.14, 0.03, 0.75])`

`>>> sm._A = []`

`>>> cbar = fig.colorbar(sm, cax = cax)`

`>>> cbar.ax.tick_params(labelsize = 10)`

`>>> ax0.get_xaxis().set_visible(False)`

`>>> ax0.get_yaxis().set_visible(False)`

`>>> ax1.get_xaxis().set_visible(False)`

`>>> ax1.get_yaxis().set_visible(False)`

`>>> plt.savefig(’compare_collin’)`

`>>> plt.show()`

## 5. Additional Features

#### 5.1. Computational Efficiency

**mgwr**.

#### 5.2. Accessibility

## 6. Conclusions

## Author Contributions

## Funding

## Conflicts of Interest

## References

- Tobler, W.R. A Computer Movie Simulating Urban Growth in the Detroit Region. Econ. Geogr.
**1970**, 46, 234. [Google Scholar] [CrossRef] - Fotheringham, A.S.; Brunsdon, C.; Charlton, M. Geographically Weighted Regression: The Analysis of Spatially Varying Relationships; John Wiley & Sons: Hoboken, NJ, USA, 2002. [Google Scholar]
- Fotheringham, A.S.; Yang, W.; Kang, W. Multi-Scale Geographically Weighted Regression. Ann. Am. Assoc. Geogr.
**2017**, 107, 1247–1265. [Google Scholar] - Environmental Systems Research Institute (ESRI). ArcMap 10.3 Spatial Analyst Toolbox; ESRI: Redlands, CA, USA, 2018. [Google Scholar]
- Bivand, R.; Yu, D.; Nakaya, T.; Garcia-Lopez, M.A.
**spgwr**: Geographically Weighted Regression, R package version 0.6-32; 2017. [Google Scholar] - Wheeler, D.
**gwrr**: Fits Geographically Weighted Regression Models with Diagnostic Tools, R package version 0.2-1; 2013. [Google Scholar] - Yu, H.; Fotheringham, A.S.; Li, Z.; Oshan, T.; Kang, W.; Wolf, L.J. Inference in multiscale geographically weighted regression. Geogr. Anal.
**2019**. [Google Scholar] [CrossRef] - Lu, B.; Harris, P.; Charlton, M.; Brundson, C.; Nayaka, T.; Gollini, I.
**GWmodel**: Geographically-Weighted Models, R package version 2.0-5; 2018. [Google Scholar] - Lu, B.; Brunsdon, C.; Charlton, M.; Harris, P. Geographically weighted regression with parameter-specific distance metrics. Int. J. Geogr. Inf. Sci.
**2017**, 31, 982–998. [Google Scholar] [CrossRef] - Li, Z.; Fotheringham, A.S.; Li, W.; Oshan, T. Fast Geographically Weighted Regression (FastGWR): A Scalable Algorithm to Investigate Spatial Process Heterogeneity in Millions of Observations. Int. J. Geogr. Inf. Sci.
**2018**. [Google Scholar] [CrossRef] - Griffith, D.A. Spatial-filtering-based contributions to a critique of geographically weighted regression (GWR). Environ. Plan. A
**2008**, 40, 2751–2769. [Google Scholar] [CrossRef] - Da Silva, A.R.; Fotheringham, A.S. The Multiple Testing Issue in Geographically Weighted Regression: The Multiple Testing Issue in GWR. Geogr. Anal.
**2015**. [Google Scholar] [CrossRef] - Wheeler, D.; Tiefelsdorf, M. Multicollinearity and correlation among local regression coefficients in geographically weighted regression. J. Geogr. Syst.
**2005**, 7, 161–187. [Google Scholar] [CrossRef] - Belsey, D.A.; Kuh, E.; Welsch, R.E. Regression Diagnostics: Identifying Influential Data and Sources of Collinearity; Wiley: New York, NY, USA, 1980. [Google Scholar]
- O’brien, R.M. A Caution Regarding Rules of Thumb for Variance Inflation Factors. Qual. Quant.
**2007**, 41, 673–690. [Google Scholar] [CrossRef] - Wheeler, D.C. Diagnostic Tools and a Remedial Method for Collinearity in Geographically Weighted Regression. Environ. Plan. A
**2007**, 39, 2464–2481. [Google Scholar] [CrossRef] - Fotheringham, A.S.; Oshan, T.M. Geographically weighted regression and multicollinearity: Dispelling the myth. J. Geogr. Syst.
**2016**, 18, 303–329. [Google Scholar] [CrossRef] - Oshan, T.M.; Fotheringham, A.S. A Comparison of Spatially Varying Regression Coefficient Estimates Using Geographically Weighted and Spatial-Filter-Based Techniques: A Comparison of Spatially Varying Regression. Geogr. Anal.
**2017**. [Google Scholar] [CrossRef] - Murakami, D.; Lu, B.; Harris, P.; Brunsdon, C.; Charlton, M.; Nakaya, T.; Griffith, D.A. The importance of scale in spatially varying coefficient modeling. arXivt
**2017**, arXiv:1709.08764. [Google Scholar] [CrossRef] - Harris, P.; Fotheringham, A.S.; Crespo, R.; Charlton, M. The Use of Geographically Weighted Regression for Spatial Prediction: An Evaluation of Models Using Simulated Data Sets. Math. Geosci.
**2010**, 42, 657–680. [Google Scholar] [CrossRef] - Lu, B.; Yang, W.; Ge, Y.; Harris, P. Improvements to the calibration of a geographically weighted regression with parameter-specific distance metrics and bandwidths. Comput. Environ. Urban Syst.
**2018**. [Google Scholar] [CrossRef] - Comber, A.; Chi, K.; Quang Huy, M.; Nguyen, Q.; Lu, B.; Huu Phe, H.; Harris, P. Distance metric choice can both reduce and induce collinearity in geographically weighted regression. Environ. Plan. B Urban Anal. City Sci.
**2018**. [Google Scholar] [CrossRef]

**Figure 1.**The 159 counties within the state of Georgia. Note: basemap and scalebar added using additional code.

**Figure 2.**2203 rental properties in the Prenzlauer Berg neighborhood of Berlin. Note: basemap and scalebar added using additional code.

**Figure 3.**Examples of exponential kernels (

**top**), Guassian kernels (

**middle**), and bisquare kernels (

**bottom**) for a small, medium, and large bandwidth parameter.

**Figure 5.**Spatial heterogeneity of the percent rural parameter surface for the Georgia dataset using different bandwidths.

**Figure 6.**Spatial variation of local ${R}^{2}$ model fit statistic for the Georgia dataset. Model fit is highest in the north, and worst in the Southwest.

**Figure 7.**Parameter estimates for foreign born variable (

**left**), composite of significant and insignificant (grey) parameter estimates without correction for multiple dependent hypothesis tests (

**middle**) and with correction for multiple dependent hypothesis tests (

**right**).

**Figure 12.**Parameter estimates for geographically weighted regression (GWR) (

**left**) and multiscale GWR (MGWR) (

**right**) for Georgia dataset.

**Figure 17.**Graphical user interface main window for desktop software built on top of mgwr implementation.

**Figure 18.**Summary of MGWR calibration using graphical user interface that includes global and local model diagnostics.

Short Name | Description |
---|---|

PctBach | Percentage of the population with a bachelor’s degree or higher |

PctFB | Percentage of the population that was born in a foreign country |

PctBlack | Percentage of the population that identifies as African American |

PctRural | Percentage of the population that is classified as living in a rural area |

Short Name | Description |
---|---|

Log price | Logged price of rental unit |

Score | Cumulative review score from previous customers for each rental unit |

Accommodates | Number of individuals a rental unit can accommodate |

Bathrooms | Number of bathrooms in each rental unit |

Function | Specification | Input Parameter |
---|---|---|

Gaussian | ${w}_{ij}=exp(-\frac{1}{2}{(\frac{{d}_{ij}}{b})}^{2})$ | kernel=‘gaussian’ |

Exponential | ${w}_{ij}=exp(-(\frac{|{d}_{ij}|}{b}))$ | kernel=‘exponential’ |

Bi-square | ${w}_{ij}=\left(\right)open="\{"\; close>\begin{array}{cc}{(1-{({d}_{ij}/b)}^{2})}^{2}\hfill & \mathrm{if}|{d}_{ij}|b\hfill \\ 0\hfill & \mathrm{otherwise}\hfill \end{array}$ | kernel=‘bisquare’ |

Name | Input Parameter |
---|---|

Cross-validation (CV) | criterion=‘CV’ |

Akaike information criterion (AIC) | criterion = ‘AIC’ |

Corrected AIC (AICc) | criterion = ‘AICc’ |

Bayesian information criterion (BIC) | criterion = ‘BIC’ |

© 2019 by the authors. Licensee MDPI, Basel, Switzerland. This article is an open access article distributed under the terms and conditions of the Creative Commons Attribution (CC BY) license (http://creativecommons.org/licenses/by/4.0/).

## Share and Cite

**MDPI and ACS Style**

Oshan, T.M.; Li, Z.; Kang, W.; Wolf, L.J.; Fotheringham, A.S.
mgwr: A *Python* Implementation of Multiscale Geographically Weighted Regression for Investigating Process Spatial Heterogeneity and Scale. *ISPRS Int. J. Geo-Inf.* **2019**, *8*, 269.
https://doi.org/10.3390/ijgi8060269

**AMA Style**

Oshan TM, Li Z, Kang W, Wolf LJ, Fotheringham AS.
mgwr: A *Python* Implementation of Multiscale Geographically Weighted Regression for Investigating Process Spatial Heterogeneity and Scale. *ISPRS International Journal of Geo-Information*. 2019; 8(6):269.
https://doi.org/10.3390/ijgi8060269

**Chicago/Turabian Style**

Oshan, Taylor M., Ziqi Li, Wei Kang, Levi J. Wolf, and A. Stewart Fotheringham.
2019. "mgwr: A *Python* Implementation of Multiscale Geographically Weighted Regression for Investigating Process Spatial Heterogeneity and Scale" *ISPRS International Journal of Geo-Information* 8, no. 6: 269.
https://doi.org/10.3390/ijgi8060269