statistics.kde: Estimate Probability Densities

Published on: September 10, 2026
Reading time: 6 minutes
Data analytics chart for Python statistics.kde

statistics.kde brings kernel density estimation to Python’s standard library. It is useful when a mean, median, or histogram is not enough to describe how observations are distributed. Instead of splitting values into rigid bins, KDE builds a smooth function that highlights concentrations, gaps, and possible multiple modes.

This guide explains what kernel density estimation means, how to call statistics.kde, how bandwidth changes the result, how kernels differ, and how to use the returned function responsibly in exploratory analysis and production code.

What kernel density estimation does

A KDE places a small curve around every observation. Those curves are combined and normalized into a continuous density estimate. The result resembles a smoothed histogram, but its shape is controlled mainly by bandwidth rather than bin edges.

Suppose you record API latency. A histogram might use bins from 0–50, 50–100, and 100–150 milliseconds. Moving those boundaries can change the picture. KDE avoids fixed bins and instead asks how strongly each observation should influence nearby values.

Basic statistics.kde example

from statistics import kde

sample = [1.2, 1.5, 1.7, 2.0, 2.1, 2.2, 3.8, 4.0, 4.2]
density = kde(sample, h=0.35)

for x in [1.0, 1.5, 2.0, 2.5, 3.0, 4.0]:
    print(x, density(x))

The function returns another callable. Calling it with a number gives the estimated density at that location. The result is not the probability of that exact number. Probability is represented by area over an interval, while density is a local intensity and can be greater than one depending on scale.

Reading the curve

Evaluate the callable over an ordered grid to obtain points for a chart. High regions indicate where observations are concentrated. Low regions suggest sparse areas or gaps. In the sample above, the curve should reveal one cluster around 1–2.2 and another around 4.

This matters because a single average can be misleading. The average may fall near 2.5 even though few observations occur there. KDE preserves more of the sample’s shape.

Bandwidth is the key choice

The h argument controls smoothing. A bandwidth that is too small produces many narrow peaks and often follows noise. A bandwidth that is too large can merge separate groups and hide meaningful structure.

values = [10, 10.5, 11, 11.2, 14, 14.3, 15]

narrow = kde(values, h=0.2)
balanced = kde(values, h=0.8)
wide = kde(values, h=2.0)

There is no universal bandwidth. Scale, sample size, and the question being asked all matter. During exploration, compare several values. For automated decisions, validate the selected bandwidth against held-out data and domain knowledge.

Choosing a kernel

The optional kernel argument changes the shape placed around each observation. Documented choices include normal, logistic, sigmoid, rectangular, triangular, parabolic, and related forms. The normal kernel is a sensible starting point. Compact-support kernels can be useful when distant observations should contribute nothing.

normal = kde(sample, h=0.35, kernel="normal")
triangular = kde(sample, h=0.35, kernel="triangular")

Bandwidth usually has a stronger visual effect than switching between reasonable kernels, but the kernel still belongs in analysis metadata for reproducibility.

Building chart data

start = min(sample) - 1
end = max(sample) + 1
steps = 200

xs = [start + i * (end - start) / (steps - 1) for i in range(steps)]
ys = [density(x) for x in xs]

The standard-library function does not draw a chart. The generated arrays can be used with Matplotlib, a web chart, a spreadsheet, or an API response. For lazy sequence processing, see Python itertools.pairwise. For cumulative transformations, read Python itertools.accumulate.

Clean data first

KDE cannot repair bad input. Confirm units, remove invalid values, and investigate outliers before fitting the function. Mixing seconds with milliseconds creates an impressive but meaningless curve. Decide whether extreme points are measurement errors, valid rare events, or members of another population.

import math
from statistics import kde

raw = [1.2, None, 1.5, float("nan"), 2.0, 2.1]
clean = [x for x in raw if isinstance(x, (int, float)) and math.isfinite(x)]

if len(clean) < 2:
    raise ValueError("Not enough observations")

f = kde(clean, h=0.3)

For explicit model APIs, review Python dataclasses.KW_ONLY. For lightweight result objects, see Python SimpleNamespace.

KDE versus a histogram

Histograms are excellent for direct counts. KDE is excellent for a continuous view and for comparing shapes. Many reports should include both. A histogram shows how many observations are in each range, while KDE highlights the estimated structure.

Do not let a smooth curve hide a small sample. Always report sample size, bandwidth, kernel, and ideally the original observations or a supporting histogram.

Small samples and boundary bias

With very few values, every observation strongly affects the curve. KDE can still be calculated, but confidence should be limited. It describes the sample and does not prove that the wider population has the same peaks.

Boundaries are another concern. Durations and weights cannot be negative, yet symmetric kernels may assign some density below zero. That does not create negative observations; it shows that standard KDE may require transformation or boundary correction for constrained domains.

Anomaly scoring

A low-density score can identify unusual values. Fit the estimator on historical observations, evaluate a new value, and compare the score with a calibrated threshold.

history = [100, 102, 98, 101, 99, 103, 97, 100]
f = kde(history, h=2.0)
new_value = 118
score = f(new_value)

if score < 0.001:
    print("Unusual value; review it")

Low density is not automatically an error. It can be a legitimate rare event. Calibrate thresholds using known examples, record the score, preserve the training sample version, and log the bandwidth.

Testing the estimator

The callable is deterministic for a fixed sample and configuration, which makes property-based tests straightforward. Verify non-negative results, higher density near clusters, and expected changes when bandwidth changes.

def test_density_is_higher_near_sample():
    from statistics import kde
    f = kde([0.0, 0.1, 0.2, 0.3], h=0.2)
    assert f(0.15) > f(3.0)
    assert f(0.15) >= 0

Avoid strict equality for floating-point values unless necessary. Prefer tolerances and behavioral relationships.

Performance and architecture

Evaluating many grid points against a large sample can be expensive because each evaluation considers the observations. Cache repeated grids when appropriate, limit chart resolution, and measure before optimizing. Keep data cleaning, estimator construction, and presentation in separate functions.

For safe resource composition in analysis pipelines, Python contextlib.ExitStack provides useful patterns. It is especially helpful when input files, temporary directories, or optional resources are opened dynamically.

When external libraries are better

statistics.kde is a strong choice for teaching, scripts, prototypes, and projects that prefer the standard library. Scientific systems may require multidimensional KDE, observation weights, automatic bandwidth selection, advanced integration, or optimized vector operations. SciPy, NumPy, pandas, statsmodels, and scikit-learn remain important for those cases.

The official Python statistics documentation defines the current API and supported kernels. For broader statistical interpretation, the NIST Engineering Statistics Handbook is a reliable reference.

Production checklist

Record the Python version because this API is available only in modern releases. Validate finite numeric input. Keep units consistent. Compare bandwidths before choosing one. Store kernel and bandwidth with results. Report sample size. Do not call point density a probability. Recalibrate anomaly thresholds when the underlying population changes.

Monitoring is also important. A density model fitted months ago may no longer represent current traffic, prices, or user behavior. Track drift and rebuild the estimator when the distribution changes.

Practical helper

from statistics import kde

def build_density(values, bandwidth, kernel="normal"):
    clean = [float(value) for value in values]
    if len(clean) < 2:
        raise ValueError("At least two observations are required")
    if bandwidth <= 0:
        raise ValueError("bandwidth must be positive")
    return kde(clean, h=bandwidth, kernel=kernel)

This wrapper centralizes validation and makes the chosen configuration explicit. A larger application can extend it with finite-value checks, unit metadata, logging, and sample versioning.

Conclusion

statistics.kde makes distribution analysis available without an external dependency. It can reveal clusters, compare shapes, support exploratory charts, and provide simple anomaly scores. Its value depends on disciplined input validation and a justified bandwidth.

Start by evaluating a small sample on a grid and comparing several bandwidths. Then add tests, documentation, monitoring, and clear communication of limitations. A KDE should not merely look smooth; it should answer a defined question with reproducible settings.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Code and file structure managed with Python contextlib.ExitStack
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    contextlib.ExitStack: Manage Dynamic Resources

    Learn Python contextlib.ExitStack to manage dynamic resources, cleanup callbacks, optional contexts, and exceptions safely.

    Ler mais

    Tempo de leitura: 4 minutos
    09/09/2026
    Software developer creating text templates with Python string.Template
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    string.Template: Safe and Simple Text Templates

    Learn Python string.Template for configurable messages, placeholder validation, safe mappings, previews, and maintainable text rendering.

    Ler mais

    Tempo de leitura: 6 minutos
    09/09/2026
    Synchronized team representing Python asyncio.Barrier
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Barrier: Synchronize Tasks in Phases

    Learn Python asyncio.Barrier to synchronize tasks in phases, coordinate pipelines, avoid races, and handle cancellation safely.

    Ler mais

    Tempo de leitura: 5 minutos
    08/09/2026
    Software developer building models with Python dataclasses.KW_ONLY
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    dataclasses.KW_ONLY: Require Keyword-Only Arguments

    Learn Python dataclasses.KW_ONLY to require named arguments, prevent ambiguous calls, and evolve public APIs more safely.

    Ler mais

    Tempo de leitura: 5 minutos
    08/09/2026
    Software developer using Python operator.methodcaller
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    operator.methodcaller: Call Methods in Pipelines

    Learn Python operator.methodcaller for map, sorted, callbacks, arguments, reusable transformations, and clearer declarative pipelines.

    Ler mais

    Tempo de leitura: 5 minutos
    07/09/2026
    Open folder representing files and directories in Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pathlib.Path.walk: Traverse and Filter Directories

    Learn how to traverse directories with pathlib.Path.walk in Python, filter files, skip folders, handle errors, and avoid common pitfalls.

    Ler mais

    Tempo de leitura: 5 minutos
    07/09/2026