KDEpy: three kernel density estimators behind one Python API
Kernel Density Estimation in Python
At a glance
- What is it?
- KDEpy is a Python package for kernel density estimation that ships three different algorithms, a naive one, a tree based one and an FFT convolution based one, all behind the same fit and evaluate interface. That is the point of the library, letting you trade accuracy for speed and grid constraints without rewriting your analysis, at the cost of dependencies and a compiled Cython extension.
- Who is it for?
- KDEpy suits a data scientist who needs a kernel density estimate and wants the choice of algorithm rather than one fixed implementation, and who cares about evaluation on non-uniform grids or about very large samples. It is the wrong tool if a single estimate from a standard scientific stack is enough, and it is the wrong tool for variable bandwidth with a fast convolution, since FFTKDE does not support it.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 170 days ago.
- What is it written in?
- Mainly Jupyter Notebook, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One interface, three very different computations
KDEpy is a Python package that implements various kernel density estimators. The design decision is stated in a single sentence in the README, that three algorithms are implemented through the same API, and those three are NaiveKDE, TreeKDE and FFTKDE. That is unusual and it is the reason to pick this library over a generic statistics package. A kernel density estimate is not one computation but a family of them, and the members of that family differ in which inputs they accept and in what they cost. The naive estimator will do anything you ask but slowly. The FFT estimator is quick and constrained. The tree estimator sits between them and accepts grids that the FFT one cannot handle. Exposing all three behind the same fit and evaluate calls means the algorithm becomes a parameter of the analysis rather than a rewrite of it. The audience is a statistician or data scientist who has to produce a density estimate and wants to know which trade-off they are making, rather than a beginner who will use whatever the default happens to be.
The naive estimator is the reference, and it is slow
NaiveKDE is described as a naive computation, and it is the one to read first because it defines what the other two are optimising. It supports d-dimensional data, variable bandwidth, weighted data and many kernel functions, which makes it the most capable of the three in terms of what you may feed it. The price is stated without hedging, it is very slow on large data sets. The reason is structural. A naive evaluation of a kernel density estimate requires summing a kernel centred on every data point at every point of the output grid, so the work grows with the product of the sample size and the size of the grid. That is fine for a few hundred points and painful for a few hundred thousand. But the naive implementation is not a throwaway, it is the reference against which the fast paths are validated, and it is the only one of the three that handles variable bandwidth, which is a substantive statistical choice rather than an optimisation. If your problem needs a bandwidth that varies across the data, this is the class you are choosing.
The tree estimator trades a little accuracy for arbitrary grids
TreeKDE is a tree-based computation, and it supports the same features as the naive algorithm, including variable bandwidth and weighted data, but it is faster at the expense of small inaccuracy when using a kernel without finite support. That last clause is the one to understand before trusting it. A kernel with finite support, such as the Epanechnikov or the biweight, has compact tails, so there is a hard limit on how far each data point contributes. A kernel without finite support, the Gaussian among them, has infinite tails, and every point technically contributes a non-zero amount everywhere. A tree structure that prunes the traversal has to decide where to stop, and with an unbounded kernel that decision is approximate. The payoff is stated as good for evaluation on non-uniform, arbitrary grids, which is the case where a convolution method cannot help at all, because a fast Fourier transform assumes a regular sampling of the density. So TreeKDE exists for the situation where the data lives on an irregular grid, a geospatial density, a simulated distribution over a custom domain, and the accuracy cost is acceptable.
The FFT estimator trades flexibility for speed
FFTKDE is a very fast convolution-based computation, and the README says it outperforms other popular implementations, with a comparison page linked for the evidence. Its constraints are stated just as explicitly. It supports weighted d-dimensional data and many kernels, but not variable bandwidth. It must be evaluated on an equidistant grid, and the finer the grid the higher the accuracy. Data points may not be outside of the grid. Those three restrictions are the price of the convolution, which only makes sense when the output is sampled regularly and the bandwidth is constant across the domain, since a varying bandwidth would turn the sum back into a per-point operation. The practical workflow is therefore two-step. You choose a grid, usually wider than the data range, and you sample it finely enough that the density estimate is smooth, and then you accept a small quantisation error in exchange for a computation that scales. The code for the README's own example lives in the examples file in the documentation, and it is worth reading because the grid choice is visible there.
Kernel, bandwidth and weights in a single call
The API surface is small, and the README example shows all of it. You import the class you want and call fit on your data, optionally with weights, then call evaluate to get the coordinates and the density:
from KDEpy import FFTKDE
import matplotlib.pyplot as plt
customer_ages = [40, 56, 20, 35, 27, 24, 29, 37, 39, 46]
# Distribution of customers
x, y = FFTKDE(kernel="gaussian", bw="silverman").fit(customer_ages).evaluate()
plt.plot(x, y)
# Distribution of customer income (weight each customer by their income)
customer_income = [152, 64, 24, 140, 88, 64, 103, 148, 150, 132]
# The `bw` parameter can be manually set, e.g. `bw=5`
x, y = FFTKDE(bw="silverman").fit(customer_ages, weights=customer_income).evaluate()
plt.plot(x, y)The three parameters that matter are visible in that snippet. The kernel is chosen by name, the bandwidth can be a named rule such as silverman or a number you set yourself, and weights are passed to fit rather than to the constructor, so the same estimator can be reused with a different weighting. The chaining of fit and evaluate is a deliberate style, and it is what allows the same data to be estimated with two different weightings in three lines. The documentation carries more examples, and the package also exposes the estimators in a lower level API for cases where you want the internal steps.
Installing it, and the compiled extension underneath
KDEpy is on PyPI and installs with pip:
pip install KDEpyThe README also records a known installation problem on Ubuntu, where the fix is to install the Python development headers for your version, with the version number matching your interpreter. The reason a pure Python package needs that step is the Cython extension. The setup script in the repository builds a C extension from a pyx source file using Cython and numpy's include directories, so the installation compiles a small native module rather than running entirely in Python. The build requirements in the manifest make that explicit, listing setuptools, wheel, Cython with a version range and oldest-supported-numpy, and the runtime dependencies are short:
dependencies = [
"numpy>=1.14.2",
"scipy>=1.0.1,<2.0",
]The scipy pin is the one to look at, because the upper bound is below 2.0. In an environment that has already moved to a newer scipy, installing KDEpy means either pinning scipy back or working around the constraint, and that is a real adoption cost even though the library itself is small.
A stable package with a stated contribution policy
The project describes itself as stable code in widespread use by practitioners and in other packages, and the packaging supports that, with the development status classifier set to production and stable, and classifiers for Python 3.8 through 3.14. The release history is calm rather than busy, with v1.1.12 in April 2025 and v1.1.8 in October 2023, and the last push to the master branch was on 2026-05-06, so the branch is receiving occasional work rather than a continuous stream of releases. The licence is BSD-3-Clause, with the text in the LICENSE file, and the project hosts a Zenodo record with citation information and a DOI badge, which is what you need if you publish a paper that uses it. The contributing guidelines are unusually explicit about what a pull request should look like. Import as few external dependencies as possible. Use test driven development, with tests and docs for every method. Cite literature and implement recent methods. Unless it is a bottleneck computation, readability trumps speed. Employ object orientation, but resist the temptation to implement many methods, stick to the basics. Follow PEP8. Those rules describe the character of the codebase, which is a library that has chosen clarity over cleverness and would rather you read it than guess at it.
Editorial conclusion
KDEpy suits a data scientist who needs a kernel density estimate and wants the choice of algorithm rather than one fixed implementation, and who cares about evaluation on non-uniform grids or about very large samples. It is the wrong tool if a single estimate from a standard scientific stack is enough, and it is the wrong tool for variable bandwidth with a fast convolution, since FFTKDE does not support it. Before you adopt it, decide which of the three algorithms your data allows, read the comparison page the project links for its own benchmarks, and check that a scipy below 2.0 is acceptable in your environment.
Frequently asked questions
How do I install KDEpy?
Install it from PyPI with pip install KDEpy. If you have trouble on Ubuntu, the README suggests installing the development headers for your Python version with sudo apt install libpython3.X-dev, because the package builds a Cython extension.
Which of the three estimators should I use?
NaiveKDE supports d-dimensional data, variable bandwidth, weighted data and many kernels but is very slow on large data sets. TreeKDE supports the same features, is faster at the cost of small inaccuracy for kernels without finite support, and is good for non-uniform, arbitrary grids. FFTKDE is very fast but does not support variable bandwidth and must be evaluated on an equidistant grid.
How do I set the kernel, the bandwidth and the weights?
The kernel and the bandwidth are constructor arguments, for example FFTKDE(kernel="gaussian", bw="silverman"), and the bandwidth can also be a number such as bw=5. Weights are passed to the fit call, as in fit(customer_ages, weights=customer_income).
What does KDEpy depend on?
The runtime dependencies are numpy at 1.14.2 or newer and scipy at 1.0.1 or newer but below 2.0. The build additionally requires setuptools, wheel, Cython and oldest-supported-numpy, since a C extension is compiled from a pyx source file.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tommyod-kdepy)Was this useful?