Use DataFrame.plot.density() or its alias DataFrame.plot.kde() to generate smooth Kernel Density Estimation (KDE) curves. Unlike histograms that divide continuous variables into arbitrary discrete bins, density plots compute a non-parametric probability density function (PDF), providing continuous, smooth representations of metric distribution, modality, and skewness.
Kernel Density Estimation places a continuous mathematical kernel (typically a Gaussian normal bell curve) over every observation point and sums all kernels together into a single continuous curve. Key properties include:
Both df.plot.density() and df.plot.kde() are interchangeable aliases. Let us examine student scores in Mathematics:
import pandas as pd
import matplotlib.pyplot as plt
my_dict = {
"MATH": [30, 33, 32, 32, 31, 34, 32]
}
df = pd.DataFrame(data=my_dict)
print(df)
# Plot smooth density curve
df.plot.density(figsize=(6, 3.5), title="Math Marks Density Estimation")
plt.tight_layout()
plt.show()
Expected console output:
MATH
0 30
1 33
2 32
3 32
4 31
5 34
6 32
The bw_method (bandwidth) parameter controls how wide each Gaussian kernel is. Bandwidth balances curve smoothness vs fidelity to individual data points:
# Fine-tuned bandwidth adjustment
df.plot.kde(bw_method=0.3, figsize=(6, 3.5), title="KDE with bw_method=0.3")
plt.show()
When comparing distributions across multiple metrics or categories, customize line colors and dash patterns (style) to distinguish overlapping curves:
multi_df = pd.DataFrame({
"MATH": [30, 33, 32, 32, 31, 34, 32],
"ENG": [28, 30, 29, 31, 27, 32, 29]
})
# Compare two distributions with distinct line styles
multi_df.plot.density(
style=["-", "--"],
color=["#1f77b4", "#d62728"],
figsize=(6.5, 3.5),
title="Math vs English Score Densities"
)
plt.show()
When two series have radically different magnitudes or variance (causing one curve to dwarf the other on a shared Y-axis), place one series on an independent secondary Y-axis with secondary_y=True:
When secondary_y=True is active, Pandas appends (right) to the column legend by default. Control this behavior with mark_right=True or mark_right=False:
multi_df.plot.density(
secondary_y=["ENG"],
mark_right=True,
figsize=(6.5, 4),
title="Dual-Scale Density Comparison"
)
plt.show()
Fine-tune grid visibility, axis label rotations, and typography:
df.plot.density(
grid=True,
rot=45,
fontsize=12,
figsize=(6, 3.5),
title="Configured Density Plot"
)
plt.show()
Export your KDE curves to vector or raster image files for publication:
ax = df.plot.density(figsize=(6.5, 4))
plt.title("Probability Density Export")
plt.tight_layout()
plt.savefig("density_distribution_report.png", dpi=300)
plt.close()
print("Exported density_distribution_report.png successfully!")
In logistics optimization, analyzing transit time distributions reveals service consistency and late delivery risks. Let us simulate 1,000 package deliveries across Standard and Express shipping:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
n_orders = 1000
std_days = np.random.normal(5.2, 1.4, n_orders)
exp_days = np.random.normal(2.1, 0.6, n_orders)
df_shipping = pd.DataFrame({
"Standard_Shipping_Days": np.clip(std_days, 1.0, 12.0),
"Express_Shipping_Days": np.clip(exp_days, 0.5, 5.0)
})
print(f"Standard Median Days: {df_shipping['Standard_Shipping_Days'].median():.2f}")
print(f"Express Median Days: {df_shipping['Express_Shipping_Days'].median():.2f}")
# Assert statistical integrity
assert len(df_shipping) == 1000
assert df_shipping["Express_Shipping_Days"].mean() < df_shipping["Standard_Shipping_Days"].mean()
# Plot comparative density curves
ax = df_shipping.plot.density(
style=["-", "--"],
color=["#2b5c8f", "#d95f02"],
bw_method=0.35,
figsize=(7.5, 4.5),
title="Delivery Lead Time Probability Densities by Tier"
)
plt.xlabel("Delivery Time (Days)")
plt.ylabel("Probability Density")
plt.grid(True, linestyle="--", alpha=0.6)
plt.tight_layout()
plt.show()
Expected console output:
Standard Median Days: 5.24
Express Median Days: 2.14
Given 1,000 customer transaction values across two retail store branches, perform these tasks:
Branch_A (mean $65, std $15) and Branch_B (mean $90, std $25).Branch_B has a higher mean transaction value than Branch_A.bw_method=0.3, grid=True, and title "Retail Basket Value Density by Branch".import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(202)
n = 1000
branch_a = np.clip(np.random.normal(65, 15, n), 10, 150)
branch_b = np.clip(np.random.normal(90, 25, n), 15, 200)
df_retail = pd.DataFrame({
"Branch_A": branch_a,
"Branch_B": branch_b
})
# Assert statistical expectations
assert len(df_retail) == 1000
assert df_retail["Branch_B"].mean() > df_retail["Branch_A"].mean()
print(f"Branch A Mean: ${df_retail['Branch_A'].mean():.2f}")
print(f"Branch B Mean: ${df_retail['Branch_B'].mean():.2f}")
df_retail.plot.kde(
bw_method=0.3,
style=["-", "--"],
figsize=(7, 4),
title="Retail Basket Value Density by Branch",
grid=True
)
plt.xlabel("Basket Value ($)")
plt.ylabel("Density")
plt.tight_layout()
plt.show()
Expected console output:
Branch A Mean: $64.40
Branch B Mean: $90.20
Experiment with bandwidth tuning, dual-axis density curves, and retail distributions in our interactive Google Colab practice notebook:
Open in Google Colab View on GitHub
Run the density estimation examples interactively, experiment with bandwidth tuning, test multi-curve comparisons, and verify the practice exercise directly in Google Colab. All sample datasets are embedded directly in the notebook.
Reference: Pandas DataFrame.plot.density official documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.