Source code for KDEpy.NaiveKDE

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Module for the NaiveKDE.
"""
import numbers
import itertools
import numpy as np
from KDEpy.BaseKDE import BaseKDE


[docs]class NaiveKDE(BaseKDE): """ This class implements a naive computation of a kernel density estimate. The advantages are that choices of bandwidth, norms, weights and grids are straightforward -- the user can do almost anything. The disadvantage is that computations are slow on more than a couple of thousand data points. Parameters ---------- kernel : str The kernel function. See cls._available_kernels.keys() for choices. bw : float, str or array-like Bandwidth or bandwidth selection method. If a float is passed, it is the standard deviation of the kernel. If a string it passed, it is the bandwidth selection method, see cls._bw_methods.keys() for choices. If an array-like it passed, it is the bandwidth of each point. norm : float The p-norm used to compute the distances in higher dimensions. Examples -------- >>> data = np.random.randn(2**10) >>> # (1) Automatic bw selection using Improved Sheather Jones >>> x, y = NaiveKDE(bw='ISJ').fit(data).evaluate() >>> # (2) Explicit choice of kernel and bw (standard deviation of kernel) >>> x, y = NaiveKDE(kernel='triweight', bw=0.5).fit(data).evaluate() >>> weights = data + 10 >>> # (3) Using a grid and weights for the data >>> y = NaiveKDE(kernel='epa', bw=0.5).fit(data, weights).evaluate(x) References ---------- - Silverman, B. W. Density Estimation for Statistics and Data Analysis. Boca Raton: Chapman and Hall, 1986. - Wand, M. P., and M. C. Jones. Kernel Smoothing. London ; New York: Chapman and Hall/CRC, 1995. - Scipy implementation, at ``scipy.stats.gaussian_kde``. """ def __init__(self, kernel="gaussian", bw=1, norm=2): super().__init__(kernel, bw) self.norm = norm
[docs] def fit(self, data, weights=None): """ Fit the KDE to the data. This validates the data and stores it. Computations are performed when the KDE is evaluated on a grid. Parameters ---------- data: array-like The data points. High dimensional data must have shape (obs, dims). weights: array-like One weight per data point. Must have shape (obs,). If None is passed, uniform weights are used. Returns ------- self Returns the instance. Examples -------- >>> data = [1, 3, 4, 7] >>> weights = [3, 4, 2, 1] >>> kde = NaiveKDE().fit(data, weights=None) >>> kde = NaiveKDE().fit(data, weights=weights) >>> x, y = kde() """ # Sets self.data super().fit(data, weights) return self
[docs] def evaluate(self, grid_points=None): """ Evaluate on grid points. Parameters ---------- grid_points: array-like, int, tuple or None A grid (mesh) to evaluate on. High dimensional grids must have shape (obs, dims). If an integer is passed, it's the number of grid points on an equidistant grid. If a tuple is passed, it's the number of grid points in each dimension. If None, a grid will be automatically created. Returns ------- y: array-like If a grid is supplied, `y` is returned. If no grid is supplied, a tuple (`x`, `y`) is returned. Examples -------- >>> kde = NaiveKDE().fit([1, 3, 4, 7]) >>> # Two ways to evaluate, either with a grid or without >>> x, y = kde.evaluate() >>> x, y = kde.evaluate(256) >>> y = kde.evaluate(x) """ # This method sets self.grid points and verifies it # NaiveKDE does not convert the bw to a scalar, since a vector is # allowed too. super().evaluate(grid_points, bw_to_scalar=False) # Create zeros on the grid points evaluated = np.zeros(self.grid_points.shape[0]) # For every data point, compute the kernel and add to the grid bw = self.bw if isinstance(bw, numbers.Number): bw = np.asfarray(np.ones(self.data.shape[0]) * bw) elif callable(bw): bw = np.asfarray(np.ones(self.data.shape[0]) * bw(self.data)) # TODO: Implementation w.r.t grid points for faster evaluation # See the SciPy evaluation for how this can be done weights = ( itertools.repeat(1 / self.data.shape[0]) if self.weights is None else self.weights ) for weight, data_point, bw in zip(weights, self.data, bw): x = self.grid_points - data_point evaluated += weight * self.kernel(x, bw=bw, norm=self.norm) return self._evalate_return_logic(evaluated, self.grid_points)
if __name__ == "__main__": import pytest # --durations=10 <- May be used to show potentially slow tests pytest.main(args=[".", "--doctest-modules", "-v"])