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.
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.
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.
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.