Pandas Density Plots: DataFrame.plot.density() & plot.kde() Analysis

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.

What is Kernel Density Estimation (KDE)? 🔝

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:

  • No Bin Boundaries: Avoids the arbitrary histogram bin placement artifacts where moving bin edges changes the visual shape.
  • Total Probability Integral: The total area under the density curve equals 1.0 (100% probability).
  • Peak Interpretation: Peaks in the curve correspond to clusters of high concentration (modes) within your numeric variable.

Create a basic density plot with plot.density() 🔝

Both df.plot.density() and df.plot.kde() are interchangeable aliases. Let us examine student scores in Mathematics:

DataFrame density plot showing continuous score probability distribution
Smooth Kernel Density Estimation curve of test scores
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

Tuning curve smoothness with bw_method 🔝

The bw_method (bandwidth) parameter controls how wide each Gaussian kernel is. Bandwidth balances curve smoothness vs fidelity to individual data points:

  • Default (Scott’s Rule): Automated statistical heuristic based on sample size and standard deviation.
  • Small bandwidth (e.g. 0.1–0.2): Highly sensitive, showing narrow peaks around individual observations (risks overfitting noise).
  • Large bandwidth (e.g. 0.8–1.5): Oversmoothed curve showing broad global trends (may mask bimodal clusters).
# Fine-tuned bandwidth adjustment
df.plot.kde(bw_method=0.3, figsize=(6, 3.5), title="KDE with bw_method=0.3")
plt.show()

Overlay multiple series with line styles and colors 🔝

When comparing distributions across multiple metrics or categories, customize line colors and dash patterns (style) to distinguish overlapping curves:

Density plot with dash-dot line style
style=['-.'] (Dash-dot line)
Density plot with dotted line style
style=[':'] (Dotted line)
Density plot with custom color
Custom color palette
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()

Dual-scale comparison with secondary_y and mark_right 🔝

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:

Density plot with secondary_y=True
secondary_y=True (Dual Y-axis)
Density plot with secondary_y=False
secondary_y=False (Shared single axis)

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:

Legend shows (right) annotation
mark_right=True (Shows (right) label)
Legend omits (right) annotation
mark_right=False (Clean legend entry)
multi_df.plot.density(
    secondary_y=["ENG"],
    mark_right=True,
    figsize=(6.5, 4),
    title="Dual-Scale Density Comparison"
)
plt.show()

Customise gridlines, rotation, and font sizes 🔝

Fine-tune grid visibility, axis label rotations, and typography:

Density plot with grid=True
grid=True (Reference lines)
Density plot with grid=False
grid=False (Clean backdrop)
Density plot with rot=45
rot=45 (Angled labels)
Density plot with rot=180
rot=180 (Vertical inverted)
Density plot with fontsize=20
fontsize=20 (Large readable labels)
df.plot.density(
    grid=True,
    rot=45,
    fontsize=12,
    figsize=(6, 3.5),
    title="Configured Density Plot"
)
plt.show()

Save density plots to file using savefig() 🔝

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!")

Practical workflow: order delivery time comparison across shipping tiers 🔝

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

Exercise: transaction basket value distribution 🔝

Given 1,000 customer transaction values across two retail store branches, perform these tasks:

  1. Create a DataFrame with Branch_A (mean $65, std $15) and Branch_B (mean $90, std $25).
  2. Assert that Branch_B has a higher mean transaction value than Branch_A.
  3. Plot both distributions on a single KDE chart using bw_method=0.3, grid=True, and title "Retail Basket Value Density by Branch".

Exercise solution 🔝

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

Practice in Google Colab 🔝

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.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer