HOWTO · Matplotlib

How to Create Density Plot in Matplotlib

Estimate a density function from sample data with gaussian_kde() from scipy.stats and draw it with Matplotlib, or use kdeplot() from seaborn and kind='density' in pandas.DataFrame.plot().

On this page

A density plot shows a smoothed estimate of how one-dimensional sample data is distributed. In Python the usual route is kernel density estimation (KDE): estimate a density function from the observations with gaussian_kde() from the scipy.stats module, evaluate it on a grid of x values, and draw the resulting curve with Matplotlib. You can reach the same result with less code through kdeplot() from the seaborn package or by setting kind="density" in pandas.DataFrame.plot(). The examples below use NumPy, SciPy, and Matplotlib; the Seaborn and pandas variants additionally need those packages installed, and the pandas plot requires SciPy because it calls gaussian_kde internally.

Create a density plot with scipy.stats.gaussian_kde

gaussian_kde builds the estimate by placing a kernel — a small bump shaped like a Gaussian — on top of each observation and averaging the bumps. The result is a callable density function: pass it a grid of x positions and it returns the estimated density at each position, which is exactly the line Matplotlib draws.

First, fit the estimate on the sample and evaluate it on a grid:

import numpy as np
from scipy.stats import gaussian_kde

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
density = gaussian_kde(data)
x = np.linspace(-2, 10, 300)
y = density(x)

print(f"Peak estimated density: {y.max():.4f} at x = {x[np.argmax(y)]:.2f}")
Peak estimated density: 0.2635 at x = 3.14

The program fits gaussian_kde on the 18 sample values and evaluates the estimate at 300 evenly spaced positions between -2 and 10; the estimate peaks near x = 3, where most observations cluster. Choose the grid from the range of your own data, for example from a little below the minimum to a little above the maximum; the -2 to 10 window here only suits this sample.

The plotting script below pins Matplotlib’s non-interactive Agg backend so it runs identically with or without a display attached, and silences the library’s logging so a first-run font message stays out of standard error. Run this to see which backend is selected:

import logging
import matplotlib
matplotlib.use("Agg")
logging.getLogger("matplotlib").setLevel(logging.ERROR)
import matplotlib.pyplot as plt

print("Backend:", matplotlib.get_backend())
Backend: Agg

Now draw the evaluated curve with the complete script:

import logging
import matplotlib
matplotlib.use("Agg")
logging.getLogger("matplotlib").setLevel(logging.ERROR)
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import gaussian_kde

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
density = gaussian_kde(data)
x = np.linspace(-2, 10, 300)
y = density(x)

plt.plot(x, y)
plt.title("Density Plot of the data")
plt.xlabel("Value")
plt.ylabel("Estimated density")

print("Plotted", len(plt.gca().get_lines()), "density line")
Plotted 1 density line

plt.plot() draws the estimated density as one line on the current axes, and plt.gca().get_lines() reports the single line it added. In an interactive session you can drop the matplotlib.use("Agg") call and add plt.show() after the plotting calls to open the figure window.

Estimated density curve produced by scipy.stats gaussian_kde for the sample data

The rendered curve is the estimated density: higher where observations are concentrated, lower where they are sparse. Because it is an estimate, treat its shape as a view of this sample rather than a measurement of the true distribution.

Adjust the KDE bandwidth

The bandwidth controls how much the estimate is smoothed. A large bandwidth widens each kernel and produces a flatter, smoother curve that can hide real structure; a small bandwidth narrows the kernels, so the curve follows the sample closely and may show wiggles that are only noise in this particular dataset. By default gaussian_kde picks the bandwidth with Scott’s rule; you can also pass "scott" or "silverman" explicitly, or supply a scalar through the bw_method argument, which sets the kernel width relative to the data’s spread. The same adjustment is available later through the public set_bandwidth() method, so there is no need to call private helpers.

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import gaussian_kde

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
density = gaussian_kde(data, bw_method=0.25)
x = np.linspace(-2, 10, 300)
y = density(x)

plt.plot(x, y)
plt.title("Density Plot with a smaller bandwidth")
plt.xlabel("Value")
plt.ylabel("Estimated density")

print(f"Peak estimated density: {y.max():.4f}")
Peak estimated density: 0.3913

With bw_method=0.25 each kernel is narrower than the default, so the curve rises higher at its peak and tracks the clustering of the sample more tightly. Raise the value to smooth more, lower it to smooth less, and compare shapes rather than treating any single bandwidth as universally correct.

Estimated density curve computed with a smaller bandwidth, showing a taller peak

Create a density plot with Seaborn

seaborn wraps the same idea in one call: kdeplot() estimates the density internally and draws it on a Matplotlib axes. Pass the sample through x, and scale the automatically chosen bandwidth with bw_adjust — values above 1 smooth more, values below 1 smooth less. The older distplot() function is deprecated; use kdeplot() for the curve alone or displot(kind="kde") for the figure-level variant.

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import seaborn as sns

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
ax = sns.kdeplot(x=data, bw_adjust=0.5)
ax.set_title("Density Plot of the data")
ax.set_xlabel("Value")

print("kdeplot drew", len(ax.get_lines()), "density line")
kdeplot drew 1 density line

kdeplot returns the Matplotlib Axes it drew on, so titles and labels are applied with the usual axes methods; add plt.show() to display the figure window interactively.

Density curve drawn by seaborn kdeplot for the sample data

Create a density plot with pandas

When the sample already lives in a DataFrame or Series, pandas.DataFrame.plot can produce the curve directly: pass kind="density" (the alias kind="kde" also works). pandas computes the estimate with gaussian_kde, so SciPy must be installed for this plot type.

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
df = pd.DataFrame(data, columns=["value"])
ax = df.plot(kind="density")
ax.set_title("Density Plot of the data")
ax.set_xlabel("Value")

print("pandas labeled the axis:", ax.get_ylabel())
pandas labeled the axis: Density

Calling the method on a DataFrame plots one curve per column and labels the y-axis Density automatically; call it on a single Series for one named variable. As with the SciPy approach, the smoothness follows the estimator’s bandwidth, which pandas derives from the data.