sgwr 0.0.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sgwr-0.0.11/LICENSE.txt +14 -0
- sgwr-0.0.11/PKG-INFO +122 -0
- sgwr-0.0.11/README.md +91 -0
- sgwr-0.0.11/setup.cfg +4 -0
- sgwr-0.0.11/setup.py +40 -0
- sgwr-0.0.11/sgwr/__init__.py +3 -0
- sgwr-0.0.11/sgwr/alpha.py +362 -0
- sgwr-0.0.11/sgwr/diagnostics.py +92 -0
- sgwr-0.0.11/sgwr/fastsgwr/FastSGWR.py +706 -0
- sgwr-0.0.11/sgwr/fastsgwr/__init__.py +0 -0
- sgwr-0.0.11/sgwr/fastsgwr/__main__.py +56 -0
- sgwr-0.0.11/sgwr/fastsgwr/diagnostics.py +92 -0
- sgwr-0.0.11/sgwr/fastsgwr/sgwr_mpi.py +40 -0
- sgwr-0.0.11/sgwr/sgwr.py +758 -0
- sgwr-0.0.11/sgwr/summary.py +122 -0
- sgwr-0.0.11/sgwr.egg-info/PKG-INFO +122 -0
- sgwr-0.0.11/sgwr.egg-info/SOURCES.txt +19 -0
- sgwr-0.0.11/sgwr.egg-info/dependency_links.txt +1 -0
- sgwr-0.0.11/sgwr.egg-info/entry_points.txt +2 -0
- sgwr-0.0.11/sgwr.egg-info/requires.txt +9 -0
- sgwr-0.0.11/sgwr.egg-info/top_level.txt +1 -0
sgwr-0.0.11/LICENSE.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright 2025 GIBD (M. Naser Lessani)
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
8
|
+
|
|
9
|
+
### Citation
|
|
10
|
+
|
|
11
|
+
If you use this tool in your research, please cite the following publications:
|
|
12
|
+
|
|
13
|
+
1. Lessani, M. Naser, and Zhenlong Li. "SGWR: similarity and geographically weighted regression." International Journal of Geographical Information Science 38, no. 7 (2024): 1232-1255.
|
|
14
|
+
2. Lessani, M. Naser, and Zhenlong Li. "Enhancing the Computational Efficiency of the SGWR Model and Introducing Its Software Implementation." Annals of GIS (2025).
|
sgwr-0.0.11/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sgwr
|
|
3
|
+
Version: 0.0.11
|
|
4
|
+
Summary: Python implementation of SGWR and Fast SGWR (MPI-enhanced)
|
|
5
|
+
Home-page: https://github.com/Lessani252/FastSGWR
|
|
6
|
+
Author: M. Naser Lessani (GIBD)
|
|
7
|
+
Author-email: naserlessani252@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE.txt
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: pandas
|
|
14
|
+
Requires-Dist: scikit-learn
|
|
15
|
+
Requires-Dist: click
|
|
16
|
+
Requires-Dist: mpi4py
|
|
17
|
+
Requires-Dist: scipy
|
|
18
|
+
Requires-Dist: spglm
|
|
19
|
+
Requires-Dist: argparse
|
|
20
|
+
Requires-Dist: matplotlib
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
FastSGWR: "Enhancing the Computational Efficiency of the SGWR Model and Introducing Its Software Implementation." This article along the python packages (parallel & sequential), and a Graphic User Interface (GUI) tool named 'SGWR Model' is developed based on this article :SGWR: similarity and geographically weighted regression (https://doi.org/10.1080/13658816.2024.2342319).
|
|
33
|
+
|
|
34
|
+
Author info
|
|
35
|
+
------------
|
|
36
|
+
- Code Author: M. Naser Lessani (GIBD)
|
|
37
|
+
- Realeased Year: 2025
|
|
38
|
+
- Affiliation: Geoinformation and Big Data Research Laboratory (GIBD), Department of Geography, The Pennsylvania State University, University Park, PA, USA
|
|
39
|
+
|
|
40
|
+
Overview
|
|
41
|
+
--------
|
|
42
|
+
The SGWR (Similarity and Geographically Weighted Regression) model is a novel local spatial regression model that extends the conventional GWR by incorporating both geographical proximity and attribute similarity into a composite spatial weight matrix. The combination of spatial and attribute-based weights is governed by a parameter alpha, which is optimized based on AICc measure.
|
|
43
|
+
|
|
44
|
+
This Python package includes:
|
|
45
|
+
- MPI-enabled parallel implementations of the SGWR
|
|
46
|
+
- Serial version of the SGWR model
|
|
47
|
+
- Support for Gaussian and bi-square kernels
|
|
48
|
+
- Also, supports a combination of adaptive bisquare and gaussian
|
|
49
|
+
|
|
50
|
+
Key Features
|
|
51
|
+
------------
|
|
52
|
+
- Parallel Computation with MPI: Achieve substantial runtime reductions using multiple CPU cores.
|
|
53
|
+
- Alpha Optimization: Automatically tune the contribution of similarity and spatial proximity.
|
|
54
|
+
- Kernel Flexibility: Choose between Gaussian (default) and bi-square kernels.
|
|
55
|
+
- Also users have the ability to either chose standardize or don't standardize their data
|
|
56
|
+
- Evaluation Metrics: Outputs include R², adjusted R², AICc, and other metrics
|
|
57
|
+
|
|
58
|
+
Installation Requirements
|
|
59
|
+
-------------------------
|
|
60
|
+
Ensure the following dependencies are installed:
|
|
61
|
+
|
|
62
|
+
Standard Python Libraries:
|
|
63
|
+
- os
|
|
64
|
+
- argparse
|
|
65
|
+
- datetime
|
|
66
|
+
- math
|
|
67
|
+
- copy
|
|
68
|
+
- typing (Optional)
|
|
69
|
+
- itertools (combinations)
|
|
70
|
+
|
|
71
|
+
MPI and CLI:
|
|
72
|
+
- mpi4py
|
|
73
|
+
- click
|
|
74
|
+
|
|
75
|
+
Numerical and Data Handling:
|
|
76
|
+
- numpy
|
|
77
|
+
- pandas
|
|
78
|
+
- scipy (stats, linalg, spatial.distance)
|
|
79
|
+
|
|
80
|
+
Machine Learning and Metrics:
|
|
81
|
+
- scikit-learn (metrics)
|
|
82
|
+
|
|
83
|
+
Specialized Geospatial Modeling:
|
|
84
|
+
- spglm (family, glm, iwls, utils)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Data Format
|
|
88
|
+
-----------
|
|
89
|
+
Input data must be a CSV file with the following column order:
|
|
90
|
+
|
|
91
|
+
longitude, latitude, dependent_variable, independent_variable_1, ..., independent_variable_n
|
|
92
|
+
|
|
93
|
+
Categorical variables must be pre-processed into dummy variables.
|
|
94
|
+
Example: For a 3-class variable ("urban", "peri-urban", "rural"), create:
|
|
95
|
+
- urban_dummy: 1 if urban, else 0
|
|
96
|
+
- peri_urban_dummy: 1 if peri-urban, else 0
|
|
97
|
+
- Rural becomes the reference class (excluded)
|
|
98
|
+
|
|
99
|
+
Usage
|
|
100
|
+
-----
|
|
101
|
+
After preparing your dataset and ensuring all dependencies are installed, the model can be run via the command line:
|
|
102
|
+
|
|
103
|
+
MPI Commands (parallel):
|
|
104
|
+
----------------------------
|
|
105
|
+
- fastsgwr run -np x -data path_to_data (by default the kernel is Gaussian function, and doesn't standardize the data)
|
|
106
|
+
- fastsgwr run -np x -data path_to_data -standardize (using Gaussian function and standardize the input data)
|
|
107
|
+
- fastsgwr run -np x -data path_to_data -bisquare
|
|
108
|
+
- fastsgwr run -np x -data path_to_data -bisquare -standardize
|
|
109
|
+
- fastsgwr run -np x -data path_to_data -biga (adaptive bisquare and gaussian)
|
|
110
|
+
- fastsgwr run -np x -data path__to_data -gwr (run gwr as well in parallel)
|
|
111
|
+
|
|
112
|
+
- x: Number of cores
|
|
113
|
+
- path_to_data: Path to the CSV dataset
|
|
114
|
+
|
|
115
|
+
The output will be a CSV file saved in the same input directory, and containing local coefficients and performance metrics.
|
|
116
|
+
|
|
117
|
+
Citation
|
|
118
|
+
--------
|
|
119
|
+
If you use this package in your work, please cite the following articles:
|
|
120
|
+
|
|
121
|
+
1. Lessani, M. Naser, and Zhenlong Li. "SGWR: similarity and geographically weighted regression." International Journal of Geographical Information Science 38, no. 7 (2024): 1232-1255.
|
|
122
|
+
2. Lessani, M. Naser, and Zhenlong Li. "Enhancing the Computational Efficiency of the SGWR Model and Introducing Its Software Implementation." Annals of GIS (2025).
|
sgwr-0.0.11/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
FastSGWR: "Enhancing the Computational Efficiency of the SGWR Model and Introducing Its Software Implementation." This article along the python packages (parallel & sequential), and a Graphic User Interface (GUI) tool named 'SGWR Model' is developed based on this article :SGWR: similarity and geographically weighted regression (https://doi.org/10.1080/13658816.2024.2342319).
|
|
2
|
+
|
|
3
|
+
Author info
|
|
4
|
+
------------
|
|
5
|
+
- Code Author: M. Naser Lessani (GIBD)
|
|
6
|
+
- Realeased Year: 2025
|
|
7
|
+
- Affiliation: Geoinformation and Big Data Research Laboratory (GIBD), Department of Geography, The Pennsylvania State University, University Park, PA, USA
|
|
8
|
+
|
|
9
|
+
Overview
|
|
10
|
+
--------
|
|
11
|
+
The SGWR (Similarity and Geographically Weighted Regression) model is a novel local spatial regression model that extends the conventional GWR by incorporating both geographical proximity and attribute similarity into a composite spatial weight matrix. The combination of spatial and attribute-based weights is governed by a parameter alpha, which is optimized based on AICc measure.
|
|
12
|
+
|
|
13
|
+
This Python package includes:
|
|
14
|
+
- MPI-enabled parallel implementations of the SGWR
|
|
15
|
+
- Serial version of the SGWR model
|
|
16
|
+
- Support for Gaussian and bi-square kernels
|
|
17
|
+
- Also, supports a combination of adaptive bisquare and gaussian
|
|
18
|
+
|
|
19
|
+
Key Features
|
|
20
|
+
------------
|
|
21
|
+
- Parallel Computation with MPI: Achieve substantial runtime reductions using multiple CPU cores.
|
|
22
|
+
- Alpha Optimization: Automatically tune the contribution of similarity and spatial proximity.
|
|
23
|
+
- Kernel Flexibility: Choose between Gaussian (default) and bi-square kernels.
|
|
24
|
+
- Also users have the ability to either chose standardize or don't standardize their data
|
|
25
|
+
- Evaluation Metrics: Outputs include R², adjusted R², AICc, and other metrics
|
|
26
|
+
|
|
27
|
+
Installation Requirements
|
|
28
|
+
-------------------------
|
|
29
|
+
Ensure the following dependencies are installed:
|
|
30
|
+
|
|
31
|
+
Standard Python Libraries:
|
|
32
|
+
- os
|
|
33
|
+
- argparse
|
|
34
|
+
- datetime
|
|
35
|
+
- math
|
|
36
|
+
- copy
|
|
37
|
+
- typing (Optional)
|
|
38
|
+
- itertools (combinations)
|
|
39
|
+
|
|
40
|
+
MPI and CLI:
|
|
41
|
+
- mpi4py
|
|
42
|
+
- click
|
|
43
|
+
|
|
44
|
+
Numerical and Data Handling:
|
|
45
|
+
- numpy
|
|
46
|
+
- pandas
|
|
47
|
+
- scipy (stats, linalg, spatial.distance)
|
|
48
|
+
|
|
49
|
+
Machine Learning and Metrics:
|
|
50
|
+
- scikit-learn (metrics)
|
|
51
|
+
|
|
52
|
+
Specialized Geospatial Modeling:
|
|
53
|
+
- spglm (family, glm, iwls, utils)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Data Format
|
|
57
|
+
-----------
|
|
58
|
+
Input data must be a CSV file with the following column order:
|
|
59
|
+
|
|
60
|
+
longitude, latitude, dependent_variable, independent_variable_1, ..., independent_variable_n
|
|
61
|
+
|
|
62
|
+
Categorical variables must be pre-processed into dummy variables.
|
|
63
|
+
Example: For a 3-class variable ("urban", "peri-urban", "rural"), create:
|
|
64
|
+
- urban_dummy: 1 if urban, else 0
|
|
65
|
+
- peri_urban_dummy: 1 if peri-urban, else 0
|
|
66
|
+
- Rural becomes the reference class (excluded)
|
|
67
|
+
|
|
68
|
+
Usage
|
|
69
|
+
-----
|
|
70
|
+
After preparing your dataset and ensuring all dependencies are installed, the model can be run via the command line:
|
|
71
|
+
|
|
72
|
+
MPI Commands (parallel):
|
|
73
|
+
----------------------------
|
|
74
|
+
- fastsgwr run -np x -data path_to_data (by default the kernel is Gaussian function, and doesn't standardize the data)
|
|
75
|
+
- fastsgwr run -np x -data path_to_data -standardize (using Gaussian function and standardize the input data)
|
|
76
|
+
- fastsgwr run -np x -data path_to_data -bisquare
|
|
77
|
+
- fastsgwr run -np x -data path_to_data -bisquare -standardize
|
|
78
|
+
- fastsgwr run -np x -data path_to_data -biga (adaptive bisquare and gaussian)
|
|
79
|
+
- fastsgwr run -np x -data path__to_data -gwr (run gwr as well in parallel)
|
|
80
|
+
|
|
81
|
+
- x: Number of cores
|
|
82
|
+
- path_to_data: Path to the CSV dataset
|
|
83
|
+
|
|
84
|
+
The output will be a CSV file saved in the same input directory, and containing local coefficients and performance metrics.
|
|
85
|
+
|
|
86
|
+
Citation
|
|
87
|
+
--------
|
|
88
|
+
If you use this package in your work, please cite the following articles:
|
|
89
|
+
|
|
90
|
+
1. Lessani, M. Naser, and Zhenlong Li. "SGWR: similarity and geographically weighted regression." International Journal of Geographical Information Science 38, no. 7 (2024): 1232-1255.
|
|
91
|
+
2. Lessani, M. Naser, and Zhenlong Li. "Enhancing the Computational Efficiency of the SGWR Model and Introducing Its Software Implementation." Annals of GIS (2025).
|
sgwr-0.0.11/setup.cfg
ADDED
sgwr-0.0.11/setup.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
from os import path
|
|
3
|
+
import pathlib
|
|
4
|
+
|
|
5
|
+
working_directory = pathlib.Path(__file__).parent
|
|
6
|
+
|
|
7
|
+
with open(path.join(working_directory, 'README.md'), encoding='utf-8') as f:
|
|
8
|
+
long_description = f.read()
|
|
9
|
+
|
|
10
|
+
setup(
|
|
11
|
+
name='sgwr',
|
|
12
|
+
version='0.0.11',
|
|
13
|
+
url='https://github.com/Lessani252/FastSGWR',
|
|
14
|
+
author='M. Naser Lessani (GIBD)',
|
|
15
|
+
author_email='naserlessani252@gmail.com',
|
|
16
|
+
license='MIT',
|
|
17
|
+
description='Python implementation of SGWR and Fast SGWR (MPI-enhanced)',
|
|
18
|
+
long_description=long_description,
|
|
19
|
+
long_description_content_type='text/markdown',
|
|
20
|
+
packages=find_packages(include=['sgwr', 'sgwr.*']),
|
|
21
|
+
include_package_data=True,
|
|
22
|
+
install_requires=[
|
|
23
|
+
'numpy',
|
|
24
|
+
'pandas',
|
|
25
|
+
'scikit-learn',
|
|
26
|
+
'click',
|
|
27
|
+
'mpi4py',
|
|
28
|
+
'scipy',
|
|
29
|
+
'spglm',
|
|
30
|
+
'argparse',
|
|
31
|
+
'matplotlib'
|
|
32
|
+
],
|
|
33
|
+
python_requires='>=3.7',
|
|
34
|
+
|
|
35
|
+
entry_points={
|
|
36
|
+
'console_scripts': [
|
|
37
|
+
'fastsgwr=sgwr.fastsgwr.__main__:main',
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
)
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
""" Related to alpha optimization in SGWR model"""
|
|
2
|
+
""" Author: M. Naser Lessani. email:naserlessani252@gamil.com"""
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from spglm.family import Gaussian
|
|
6
|
+
from scipy.spatial.distance import cdist
|
|
7
|
+
|
|
8
|
+
class ALPHA(object):
|
|
9
|
+
def __init__(self, coords, y, X, data, family=Gaussian(),
|
|
10
|
+
offset=None, kernel='bisquare', bw=False, fixed=False, greedy=False, adaptiveBG=False, constant=True):
|
|
11
|
+
|
|
12
|
+
self.coords = coords
|
|
13
|
+
self.y = y
|
|
14
|
+
self.X = X
|
|
15
|
+
self.data = X
|
|
16
|
+
self.family = family
|
|
17
|
+
self.offset = offset
|
|
18
|
+
self.kernel = kernel
|
|
19
|
+
self.fixed = fixed
|
|
20
|
+
self.bw = bw
|
|
21
|
+
self.data = data
|
|
22
|
+
self.alphacurve = greedy
|
|
23
|
+
self.constant = constant
|
|
24
|
+
self.n = self.X.shape[0]
|
|
25
|
+
self.k = self.X.shape[1]
|
|
26
|
+
self.minbw = None
|
|
27
|
+
self.maxbw = None
|
|
28
|
+
self.adaptiveBG = adaptiveBG
|
|
29
|
+
self.search_params = {}
|
|
30
|
+
|
|
31
|
+
if self.adaptiveBG:
|
|
32
|
+
self.fixed = False
|
|
33
|
+
if self.constant:
|
|
34
|
+
self.X = np.hstack([np.ones((self.n, 1)), self.X]) ## adding the intercept
|
|
35
|
+
|
|
36
|
+
########
|
|
37
|
+
def set_search_range(self):
|
|
38
|
+
"""
|
|
39
|
+
Define the search range for the bandwidth in the golden section search.
|
|
40
|
+
"""
|
|
41
|
+
if self.fixed:
|
|
42
|
+
max_dist = np.max(np.array([np.max(cdist([self.coords[i]], self.coords))
|
|
43
|
+
for i in range(self.n)]))
|
|
44
|
+
self.maxbw = max_dist * 2
|
|
45
|
+
if self.minbw is None:
|
|
46
|
+
min_dist = np.min(
|
|
47
|
+
np.array([np.min(np.delete(cdist(self.coords[[i]], self.coords), i))
|
|
48
|
+
for i in range(self.n)]))
|
|
49
|
+
self.minbw = min_dist / 2
|
|
50
|
+
if self.minbw < 0.01: ##added Naser
|
|
51
|
+
self.minbw = 0.01
|
|
52
|
+
else:
|
|
53
|
+
self.maxbw = self.n
|
|
54
|
+
if self.minbw is None:
|
|
55
|
+
self.minbw = 40 + 2 * self.k
|
|
56
|
+
|
|
57
|
+
def _local_fit(self, i, y, X, bw):
|
|
58
|
+
# Compute distances for observation i
|
|
59
|
+
dist = cdist([self.coords[i]], self.coords).reshape(-1)
|
|
60
|
+
if self.fixed:
|
|
61
|
+
wi = np.exp(-0.5 * (dist / bw) ** 2).reshape(-1, 1)
|
|
62
|
+
else:
|
|
63
|
+
maxdis = np.partition(dist, int(bw) - 1)[int(bw) - 1] * 1.0000001
|
|
64
|
+
wg = dist / maxdis
|
|
65
|
+
wg[wg >= 1] = 1
|
|
66
|
+
wi = ((1 - (wg) ** 2) ** 2).reshape(-1, 1)
|
|
67
|
+
|
|
68
|
+
# Form the weighted design matrix and response vector
|
|
69
|
+
X_new = X * np.sqrt(wi)
|
|
70
|
+
Y_new = y * np.sqrt(wi)
|
|
71
|
+
|
|
72
|
+
# Regularization: add a tiny constant to the diagonal before inverting
|
|
73
|
+
epsilon = 1e-8
|
|
74
|
+
A = np.dot(X_new.T, X_new) + epsilon * np.eye(X_new.shape[1])
|
|
75
|
+
invA = np.linalg.inv(A)
|
|
76
|
+
temp = np.dot(invA, X_new.T)
|
|
77
|
+
hat = np.dot(X_new[i], temp[:, i])
|
|
78
|
+
yhat = np.sum(np.dot(X_new, temp[:, i]).reshape(-1, 1) * Y_new)
|
|
79
|
+
err = Y_new[i][0] - yhat
|
|
80
|
+
|
|
81
|
+
return err ** 2, hat
|
|
82
|
+
|
|
83
|
+
def golden_section(self, a, c, function):
|
|
84
|
+
delta = 0.38197
|
|
85
|
+
b = a + delta * np.abs(c - a)
|
|
86
|
+
d = c - delta * np.abs(c - a)
|
|
87
|
+
opt_bw = None
|
|
88
|
+
score = None
|
|
89
|
+
diff = 1.0e9
|
|
90
|
+
iters = 0
|
|
91
|
+
dict = {}
|
|
92
|
+
while np.abs(diff) > 1.0e-6 and iters < 200:
|
|
93
|
+
iters += 1
|
|
94
|
+
if not self.fixed:
|
|
95
|
+
b = np.round(b)
|
|
96
|
+
d = np.round(d)
|
|
97
|
+
|
|
98
|
+
if b in dict:
|
|
99
|
+
score_b = dict[b]
|
|
100
|
+
else:
|
|
101
|
+
score_b = function(b)
|
|
102
|
+
dict[b] = score_b
|
|
103
|
+
|
|
104
|
+
if d in dict:
|
|
105
|
+
score_d = dict[d]
|
|
106
|
+
else:
|
|
107
|
+
score_d = function(d)
|
|
108
|
+
dict[d] = score_d
|
|
109
|
+
|
|
110
|
+
if score_b <= score_d:
|
|
111
|
+
opt_score = score_b
|
|
112
|
+
opt_bw = b
|
|
113
|
+
c = d
|
|
114
|
+
d = b
|
|
115
|
+
b = a + delta * np.abs(c - a)
|
|
116
|
+
else:
|
|
117
|
+
opt_score = score_d
|
|
118
|
+
opt_bw = d
|
|
119
|
+
a = b
|
|
120
|
+
b = d
|
|
121
|
+
d = c - delta * np.abs(c - a)
|
|
122
|
+
|
|
123
|
+
diff = score_b - score_d
|
|
124
|
+
score = opt_score
|
|
125
|
+
return opt_bw
|
|
126
|
+
|
|
127
|
+
def sgwr_fit(self, y, X, bw):
|
|
128
|
+
if self.fixed:
|
|
129
|
+
bw = round(bw, 3)
|
|
130
|
+
RSS = 0
|
|
131
|
+
trS = 0
|
|
132
|
+
for i in range(self.n):
|
|
133
|
+
err2, hat = self._local_fit(i, y, X, bw)
|
|
134
|
+
RSS += err2
|
|
135
|
+
trS += hat
|
|
136
|
+
|
|
137
|
+
aicc = self.n * np.log((RSS) / (self.n)) + self.n * np.log(2 * np.pi) + self.n * (self.n + trS) / (
|
|
138
|
+
self.n - trS - 2.0)
|
|
139
|
+
|
|
140
|
+
print(f"BW: {bw:.3f} >>> AICc Score: {aicc:.3f}", flush=True)
|
|
141
|
+
|
|
142
|
+
return aicc
|
|
143
|
+
|
|
144
|
+
def fit(self, y=None, X=None):
|
|
145
|
+
if self.bw: ## if the bw is given by the users
|
|
146
|
+
alpha_optimization = self.optimal_alpha()
|
|
147
|
+
else: ## if the model requires to optimize the bandwidth
|
|
148
|
+
print('Bandwidth optimization using golden section based on AICc values...', flush=True)
|
|
149
|
+
if y is None:
|
|
150
|
+
y = self.y
|
|
151
|
+
X = self.X
|
|
152
|
+
|
|
153
|
+
self.set_search_range()
|
|
154
|
+
|
|
155
|
+
sgwr_func = lambda bw: self.sgwr_fit(y, X, bw)
|
|
156
|
+
|
|
157
|
+
self.bw = self.golden_section(self.minbw, self.maxbw, sgwr_func)
|
|
158
|
+
print('')
|
|
159
|
+
print(f'Optimal BW: {self.bw:.3f}')
|
|
160
|
+
# Save search params for spatial_variability compatibility
|
|
161
|
+
# return round(self.bw, 3)
|
|
162
|
+
if self.adaptiveBG:
|
|
163
|
+
self.fixed = True
|
|
164
|
+
alpha_optimization = self.optimal_alpha()
|
|
165
|
+
|
|
166
|
+
return alpha_optimization
|
|
167
|
+
|
|
168
|
+
def fit(self, y=None, X=None):
|
|
169
|
+
# If user provides a fixed bandwidth, skip optimization
|
|
170
|
+
if self.bw:
|
|
171
|
+
alpha_optimization = self.optimal_alpha()
|
|
172
|
+
else:
|
|
173
|
+
print('Bandwidth optimization using golden section based on AICc values...', flush=True)
|
|
174
|
+
|
|
175
|
+
if y is None:
|
|
176
|
+
y = self.y
|
|
177
|
+
X = self.X
|
|
178
|
+
|
|
179
|
+
self.set_search_range()
|
|
180
|
+
sgwr_func = lambda bw: self.sgwr_fit(y, X, bw)
|
|
181
|
+
|
|
182
|
+
self.bw = self.golden_section(self.minbw, self.maxbw, sgwr_func)
|
|
183
|
+
|
|
184
|
+
print(f'\nOptimal BW: {self.bw:.3f}')
|
|
185
|
+
|
|
186
|
+
if self.adaptiveBG:
|
|
187
|
+
self.fixed = True
|
|
188
|
+
|
|
189
|
+
alpha_optimization = self.optimal_alpha()
|
|
190
|
+
|
|
191
|
+
return alpha_optimization
|
|
192
|
+
|
|
193
|
+
def alpha_fit(self, i, bw, bt_value):
|
|
194
|
+
###
|
|
195
|
+
data_point = self.data.iloc[i, :].values # fix: use .iloc and convert to np array
|
|
196
|
+
diff = np.abs(self.data.values - data_point)
|
|
197
|
+
combined = np.mean(diff, axis=1)
|
|
198
|
+
ws = np.exp(-combined ** 2)
|
|
199
|
+
ws = ws.reshape(-1, 1)
|
|
200
|
+
|
|
201
|
+
### Geographically weighted
|
|
202
|
+
dist = cdist([self.coords[i]], self.coords).reshape(-1)
|
|
203
|
+
|
|
204
|
+
# fixed gaussian
|
|
205
|
+
if self.fixed:
|
|
206
|
+
if self.adaptiveBG:
|
|
207
|
+
bw = np.partition(dist, int(bw) - 1)[int(bw) - 1] * 1.0000001
|
|
208
|
+
wg = np.exp(-0.5 * (dist / bw) ** 2).reshape(-1, 1)
|
|
209
|
+
else:
|
|
210
|
+
wg = np.exp(-0.5 * (dist / bw) ** 2).reshape(-1, 1)
|
|
211
|
+
# adaptive bisquare
|
|
212
|
+
else:
|
|
213
|
+
maxd = np.partition(dist, int(bw) - 1)[int(bw) - 1] * 1.0000001
|
|
214
|
+
zs = dist / maxd
|
|
215
|
+
zs[zs >= 1] = 1
|
|
216
|
+
wg = ((1 - (zs) ** 2) ** 2).reshape(-1, 1)
|
|
217
|
+
|
|
218
|
+
wi = bt_value * wg + (1 - bt_value) * ws
|
|
219
|
+
|
|
220
|
+
X_new = self.X * np.sqrt(wi)
|
|
221
|
+
Y_new = self.y * np.sqrt(wi)
|
|
222
|
+
temp = np.dot(np.linalg.inv(np.dot(X_new.T, X_new)), X_new.T)
|
|
223
|
+
hat = np.dot(X_new[i], temp[:, i])
|
|
224
|
+
yhat = np.sum(np.dot(X_new, temp[:, i]).reshape(-1, 1) * Y_new)
|
|
225
|
+
err = Y_new[i][0] - yhat
|
|
226
|
+
|
|
227
|
+
return err * err, hat
|
|
228
|
+
|
|
229
|
+
def alpha_optimization(self, bw, bt_value):
|
|
230
|
+
RSS = 0
|
|
231
|
+
trS = 0
|
|
232
|
+
for i in range(self.n):
|
|
233
|
+
err2, hat = self.alpha_fit(i, bw, bt_value)
|
|
234
|
+
RSS += err2
|
|
235
|
+
trS += hat
|
|
236
|
+
|
|
237
|
+
aicc = self.n * np.log((RSS) / (self.n)) + self.n * np.log(2 * np.pi) + self.n * (self.n + trS) / (
|
|
238
|
+
self.n - trS - 2.0)
|
|
239
|
+
|
|
240
|
+
return [aicc]
|
|
241
|
+
|
|
242
|
+
def greedy_fit(self):
|
|
243
|
+
print('')
|
|
244
|
+
print('Greedy optimization takes longer time than divide and conqur option!')
|
|
245
|
+
print('Alpha optimization using bandwidth:', round(self.bw, 3))
|
|
246
|
+
print('')
|
|
247
|
+
initial_candidates = [0.9, 0.01]
|
|
248
|
+
alpha_scores = {}
|
|
249
|
+
best_alpha = None
|
|
250
|
+
best_score = float('inf')
|
|
251
|
+
|
|
252
|
+
# Evaluate initial candidates
|
|
253
|
+
for alpha in initial_candidates:
|
|
254
|
+
if alpha < 0.02:
|
|
255
|
+
continue
|
|
256
|
+
aicc = self.alpha_optimization(self.bw, alpha)[0]
|
|
257
|
+
alpha_scores[alpha] = aicc
|
|
258
|
+
print(f'Alpha: {alpha:.3f} >>> AICc Score: {aicc:.3f}', flush=True)
|
|
259
|
+
if aicc < best_score:
|
|
260
|
+
best_score = aicc
|
|
261
|
+
best_alpha = alpha
|
|
262
|
+
else:
|
|
263
|
+
break
|
|
264
|
+
|
|
265
|
+
# Recursive search between best and neighbors
|
|
266
|
+
def recursive_search(low, high, depth=0):
|
|
267
|
+
if abs(high - low) < 0.01 or depth > 10:
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
mid = round((low + high) / 2, 3)
|
|
271
|
+
if mid in alpha_scores or mid < 0.02:
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
aicc = self.alpha_optimization(self.bw, mid)[0]
|
|
275
|
+
alpha_scores[mid] = aicc
|
|
276
|
+
print(f'Alpha: {mid:.3f} >>> AICc Score: {aicc:.3f}', flush=True)
|
|
277
|
+
|
|
278
|
+
nonlocal best_alpha, best_score
|
|
279
|
+
if aicc < best_score:
|
|
280
|
+
best_alpha = mid
|
|
281
|
+
best_score = aicc
|
|
282
|
+
# Keep searching both sides around this new best
|
|
283
|
+
recursive_search(low, mid, depth + 1)
|
|
284
|
+
recursive_search(mid, high, depth + 1)
|
|
285
|
+
else:
|
|
286
|
+
# Only search between previous best and mid
|
|
287
|
+
if mid < best_alpha:
|
|
288
|
+
recursive_search(mid, best_alpha, depth + 1)
|
|
289
|
+
else:
|
|
290
|
+
recursive_search(best_alpha, mid, depth + 1)
|
|
291
|
+
|
|
292
|
+
# Start search around neighbors
|
|
293
|
+
sorted_initial = sorted(initial_candidates)
|
|
294
|
+
best_index = sorted_initial.index(best_alpha)
|
|
295
|
+
if best_index > 0:
|
|
296
|
+
recursive_search(sorted_initial[best_index - 1], best_alpha)
|
|
297
|
+
if best_index < len(sorted_initial) - 1:
|
|
298
|
+
recursive_search(best_alpha, sorted_initial[best_index + 1])
|
|
299
|
+
|
|
300
|
+
return best_alpha
|
|
301
|
+
|
|
302
|
+
### this is coarse divide-and-conquer strategy with a step size of 0.1
|
|
303
|
+
def divid_fit(self):
|
|
304
|
+
print('')
|
|
305
|
+
print('Alpha optimization using bandwidth:', round(self.bw, 3))
|
|
306
|
+
print('')
|
|
307
|
+
initial_candidates = [0.7, 0.5, 0.1]
|
|
308
|
+
alpha_scores = {}
|
|
309
|
+
best_alpha = None
|
|
310
|
+
best_score = float('inf')
|
|
311
|
+
|
|
312
|
+
# Step 1: Evaluate initial candidates
|
|
313
|
+
for alpha in initial_candidates:
|
|
314
|
+
aicc = self.alpha_optimization(self.bw, alpha)[0]
|
|
315
|
+
alpha_scores[alpha] = aicc
|
|
316
|
+
print(f'Alpha: {alpha:.3f} >>> AICc Score: {aicc:.3f}', flush=True)
|
|
317
|
+
if aicc < best_score:
|
|
318
|
+
best_score = aicc
|
|
319
|
+
best_alpha = alpha
|
|
320
|
+
else:
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
# Step 2: Greedy search (strictly stop if score worsens)
|
|
324
|
+
def greedy_direction_search(start, direction, min_alpha=0.01):
|
|
325
|
+
nonlocal best_alpha, best_score
|
|
326
|
+
|
|
327
|
+
current = start
|
|
328
|
+
while True:
|
|
329
|
+
step = 0.1 if current > 0.1 else 0.02
|
|
330
|
+
next_alpha = round(current + direction * step, 3)
|
|
331
|
+
if next_alpha < min_alpha or next_alpha > 1.0 or next_alpha in alpha_scores:
|
|
332
|
+
break
|
|
333
|
+
|
|
334
|
+
aicc = self.alpha_optimization(self.bw, next_alpha)[0]
|
|
335
|
+
alpha_scores[next_alpha] = aicc
|
|
336
|
+
print(f'Alpha: {next_alpha:.3f} >>> AICc Score: {aicc:.3f}', flush=True)
|
|
337
|
+
|
|
338
|
+
if aicc < best_score:
|
|
339
|
+
best_alpha = next_alpha
|
|
340
|
+
best_score = aicc
|
|
341
|
+
current = next_alpha
|
|
342
|
+
else:
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
# Step 3: Explore both directions around best_alpha
|
|
346
|
+
greedy_direction_search(best_alpha, direction=-1) # Lower alphas
|
|
347
|
+
greedy_direction_search(best_alpha, direction=1) # Higher alphas
|
|
348
|
+
|
|
349
|
+
return best_alpha
|
|
350
|
+
|
|
351
|
+
def optimal_alpha(self):
|
|
352
|
+
if self.alphacurve:
|
|
353
|
+
self.best_alpha = self.greedy_fit()
|
|
354
|
+
print('')
|
|
355
|
+
print('Best alpha value (greedy):', self.best_alpha)
|
|
356
|
+
return round(self.bw, 3), self.best_alpha
|
|
357
|
+
|
|
358
|
+
else:
|
|
359
|
+
self.best_alpha = self.divid_fit()
|
|
360
|
+
print('')
|
|
361
|
+
print('Best alpha value (divide and conquer):', self.best_alpha)
|
|
362
|
+
return round(self.bw, 3), self.best_alpha
|