Skip to content

Add kw arg to normalize kernel in distance weights. #791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
353 changes: 353 additions & 0 deletions docs/user-guide/weights/kernels.ipynb

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions libpysal/_kernels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import numpy

Check warning on line 1 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L1

Added line #L1 was not covered by tests


def _triangular(distances, bandwidth):
u = distances/bandwidth
u = 1-numpy.clip(u, 0,1)
return u/bandwidth

Check warning on line 7 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L4-L7

Added lines #L4 - L7 were not covered by tests

def _parabolic(distances, bandwidth):
u = numpy.clip(distances / bandwidth, 0, 1)
return 0.75 * (1 - u**2)

Check warning on line 11 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L9-L11

Added lines #L9 - L11 were not covered by tests


def _gaussian(distances, bandwidth):
u = distances / bandwidth
exponent_term = -0.5 * (u**2)
c = 1 / (bandwidth * numpy.sqrt(2 * numpy.pi))
return c * numpy.exp(exponent_term)

Check warning on line 18 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L14-L18

Added lines #L14 - L18 were not covered by tests


def _bisquare(distances, bandwidth):
u = numpy.clip(distances / bandwidth, 0, 1)
return (15 / 16) * (1 - u**2) ** 2

Check warning on line 23 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L21-L23

Added lines #L21 - L23 were not covered by tests


def _cosine(distances, bandwidth):
u = numpy.clip(distances / bandwidth, 0, 1)
return (numpy.pi / 4) * numpy.cos(numpy.pi / 2 * u)

Check warning on line 28 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L26-L28

Added lines #L26 - L28 were not covered by tests


def _exponential(distances, bandwidth):
u = distances / bandwidth
return numpy.exp(-u)

Check warning on line 33 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L31-L33

Added lines #L31 - L33 were not covered by tests


def _boxcar(distances, bandwidth):
r = (distances < bandwidth).astype(int)
return r

Check warning on line 38 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L36-L38

Added lines #L36 - L38 were not covered by tests


def _identity(distances, _):
return distances

Check warning on line 42 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L41-L42

Added lines #L41 - L42 were not covered by tests


_kernel_functions = {

Check warning on line 45 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L45

Added line #L45 was not covered by tests
"triangular": _triangular,
"parabolic": _parabolic,
"gaussian": _gaussian,
"bisquare": _bisquare,
"cosine": _cosine,
"boxcar": _boxcar,
"discrete": _boxcar,
"exponential": _exponential,
"identity": _identity,
None: _identity,
}


def _kernel(

Check warning on line 59 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L59

Added line #L59 was not covered by tests
distances,
bandwidth,
kernel="gaussian",
):
"""
distances: array-like
bandwidth float
kernel: string or callable

"""

if callable(kernel):
k = kernel(distances, bandwidth)

Check warning on line 72 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L71-L72

Added lines #L71 - L72 were not covered by tests
else:
kernel = kernel.lower()
k = _kernel_functions[kernel](distances, bandwidth)

Check warning on line 75 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L74-L75

Added lines #L74 - L75 were not covered by tests

return k

Check warning on line 77 in libpysal/_kernels.py

View check run for this annotation

Codecov / codecov/patch

libpysal/_kernels.py#L77

Added line #L77 was not covered by tests
16 changes: 13 additions & 3 deletions libpysal/weights/distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,8 @@ class Kernel(W):
bandwidth : float
or array-like (optional)
the bandwidth :math:`h_i` for the kernel.
fixed : binary
If true then :math:`h_i=h \\forall i`. If false then
fixed : bool
If True then :math:`h_i=h \\forall i`. If False then
bandwidth is adaptive across observations.
k : int
the number of nearest neighbors to use for determining
Expand Down Expand Up @@ -424,6 +424,10 @@ class Kernel(W):
eps : float
adjustment to ensure knn distance range is closed on the
knnth observations
normalize : bool
If True (default) Gaussian kernel is normalized to integrate to 1.
If False K(0)=1.


Attributes
----------
Expand Down Expand Up @@ -530,8 +534,10 @@ def __init__(
diagonal=False,
distance_metric="euclidean",
radius=None,
normalize=True,
**kwargs,
):
self._normalize = normalize
if radius is not None:
distance_metric = "arc"
if isKDTree(data):
Expand Down Expand Up @@ -685,7 +691,9 @@ def _eval_kernel(self):
)
z.append(zi)
zs = z
# functions follow Anselin and Rey (2010) table 5.4

# functions follow Anselin and Rey (2014) Modern Spatial Econometircs in Practice. Pg 78

if self.function == "triangular":
self.kernel = [1 - zi for zi in zs]
elif self.function == "uniform":
Expand All @@ -697,6 +705,8 @@ def _eval_kernel(self):
elif self.function == "gaussian":
c = np.pi * 2
c = c ** (-0.5)
if self._normalize is False:
c = 1
self.kernel = [c * np.exp(-(zi**2) / 2.0) for zi in zs]
else:
print(("Unsupported kernel function", self.function))
Expand Down
8 changes: 8 additions & 0 deletions libpysal/weights/tests/test_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,14 @@ def test_from_geodataframe(self):
for k, v in list(w[self.known_wi5 - 1].items()):
np.testing.assert_allclose(v, self.known_w5[k + 1], rtol=RTOL)

def test_w_normalize(self):
wg = d.Kernel.from_array(self.points, function='gaussian')
np.testing.assert_allclose(wg.weights[0], [0.3989422804014327, 0.35206533556593145, 0.3412334260702758], rtol=RTOL)
wgun = d.Kernel.from_array(self.points, function='gaussian', normalize=False)
np.testing.assert_allclose(wgun.weights[0], [1.0, 0.8824969246470149, 0.8553453540369604], rtol=RTOL)



##########################
# Function/User tests #
##########################
Expand Down