KDE and Histograms: Visualizing Distributions
After completing this topic, you will be able to:
- Explain the difference between histograms and KDE plots.
- Visualize data distributions using Seaborn to understand the shape of your data.
Why Look at Distributions?
When analyzing data, the mean and standard deviation alone are not enough:
import numpy as np
data_a = np.array([50, 50, 50, 50, 50])data_b = np.array([10, 30, 50, 70, 90])
print(f"Mean of A: {data_a.mean()}, Mean of B: {data_b.mean()}") # Both are 50The means are the same, but the shape of the data is completely different. A is clustered in one place, while B is spread out. Visualizing the distribution reveals these differences.
Histograms: Viewing Frequency by Bar
A histogram divides data into bins and represents the number of data points in each bin with the height of a bar.
import matplotlib.pyplot as pltimport numpy as np
np.random.seed(42)data = np.random.normal(170, 10, 1000) # Mean 170, standard deviation 10, 1000 heights
plt.figure(figsize=(8, 4))plt.hist(data, bins=30, color='steelblue', edgecolor='white', alpha=0.7)plt.xlabel('Height (cm)')plt.ylabel('Frequency')plt.title('Height Distribution (Histogram)')plt.show()bins=30 means the entire range is divided into 30 bins. The shape changes depending on the number of bins:
fig, axes = plt.subplots(1, 3, figsize=(12, 3))for ax, b in zip(axes, [5, 30, 100]): ax.hist(data, bins=b, color='steelblue', edgecolor='white') ax.set_title(f'bins={b}')plt.tight_layout()plt.show()- Too few bins: Loses detail.
- Too many bins: Shows noise.
- Finding the right number of bins is a challenge in histograms.
KDE: Viewing Density with Smooth Curves
KDE (Kernel Density Estimation) is a smoothed version of the histogram. It places a small bell-shaped curve (kernel) on each data point and sums them up.
import seaborn as sns
plt.figure(figsize=(8, 4))sns.kdeplot(data, fill=True, color='steelblue', alpha=0.5)plt.xlabel('Height (cm)')plt.title('Height Distribution (KDE)')plt.show()Advantages of KDE:
- No need to choose the number of bins (because it's a continuous curve).
- Visually smooth β intuitively shows the shape of the distribution.
- Easy to compare two distributions β clear when overlaid.
Histograms + KDE Together
plt.figure(figsize=(8, 4))sns.histplot(data, bins=30, kde=True, color='steelblue', edgecolor='white', alpha=0.5, stat='density')plt.xlabel('Height (cm)')plt.title('Histogram + KDE')plt.show()Setting stat='density' changes the y-axis of the histogram from frequency to density, so that the scale matches the KDE curve.
Comparing Two Groups
np.random.seed(42)male = np.random.normal(175, 8, 500)female = np.random.normal(162, 7, 500)
plt.figure(figsize=(8, 4))sns.kdeplot(male, fill=True, label='Male', alpha=0.4)sns.kdeplot(female, fill=True, label='Female', alpha=0.4)plt.xlabel('Height (cm)')plt.legend()plt.title('Height Distribution Comparison by Gender')plt.show()KDE makes it intuitive to see the differences and overlap between the two groups by showing the overlapping parts transparently.
Various Distribution Shapes
np.random.seed(42)fig, axes = plt.subplots(1, 4, figsize=(16, 3))
# Normal distribution β bell-shaped, symmetricnormal = np.random.normal(0, 1, 1000)sns.kdeplot(normal, ax=axes[0], fill=True)axes[0].set_title('Normal Distribution')
# Right-skewed distribution β mostly small, with a few large values (income distribution)skewed = np.random.exponential(2, 1000)sns.kdeplot(skewed, ax=axes[1], fill=True)axes[1].set_title('Right-Skewed')
# Bimodal distribution β a mixture of two groupsbimodal = np.concatenate([np.random.normal(-2, 0.5, 500), np.random.normal(2, 0.5, 500)])sns.kdeplot(bimodal, ax=axes[2], fill=True)axes[2].set_title('Bimodal Distribution')
# Uniform distribution β all values have similar frequencyuniform = np.random.uniform(0, 10, 1000)sns.kdeplot(uniform, ax=axes[3], fill=True)axes[3].set_title('Uniform Distribution')
plt.tight_layout()plt.show()By looking at the shape of the distribution, you can immediately understand the characteristics of the data. If a bimodal distribution appears, you can gain the insight that "there are two groups mixed in the data."
Histogram vs. KDE Selection Criteria
| Histogram | KDE | |
|---|---|---|
| Accurate frequency | β Can confirm the number in each bin | β Density estimate |
| Distribution shape | Changes depending on the number of bins | β Consistent, smooth curve |
| Group comparison | Difficult to read when overlapping | β Transparent overlay makes it clear |
| Discrete data | β Suitable | β Suitable for continuous data |
| Small data | β Reflects the actual data | β οΈ Risk of excessive smoothing |
The safest approach is to use both together: check the actual distribution with a histogram and grasp the overall trend with a KDE.
The bandwidth Parameter in KDE
The most important setting in KDE is the bandwidth. It determines the width of the kernel placed on each data point:
fig, axes = plt.subplots(1, 3, figsize=(12, 3))bandwidths = [0.1, 0.5, 2.0]
for ax, bw in zip(axes, bandwidths): sns.kdeplot(data, bw_adjust=bw, ax=ax, fill=True) ax.set_title(f'bw_adjust={bw}')
plt.tight_layout()plt.show()- Small bandwidth (0.1): Reflects noise, jagged.
- Appropriate bandwidth (0.5ο½1.0): Captures the trend of the data well.
- Large bandwidth (2.0): Overly smoothed, loses fine details.
bw_adjust in Seaborn is the coefficient multiplied by the automatically calculated bandwidth. 1.0 is the default value and works well in most cases.
In Practice: Finding Outliers
Visualizing distributions is effective for detecting outliers:
np.random.seed(42)normal_data = np.random.normal(100, 15, 1000)outliers = np.array([200, 210, 220, -50])data_with_outliers = np.concatenate([normal_data, outliers])
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Check for outliers with a histogramaxes[0].hist(data_with_outliers, bins=50, color='steelblue', edgecolor='white')axes[0].set_title('Histogram β Outliers Visible in the Tail')
# Clearer with a box plotaxes[1].boxplot(data_with_outliers, vert=False)axes[1].set_title('Box Plot β Outliers Marked as Points')
plt.tight_layout()plt.show()If the tail of the KDE or histogram looks longer than expected, suspect outliers. Using it with a box plot allows for more accurate diagnosis.
Seaborn's displot: Unified Distribution Visualization
# displot provides histograms, KDEs, and ECDF in a single APIsns.displot(data, kind="hist", kde=True, bins=30, height=4, aspect=2)plt.show()
# ECDF β Cumulative Distribution Functionsns.displot(data, kind="ecdf", height=4, aspect=2)plt.show()displot is a unified interface introduced in Seaborn 0.11+. Select from "hist", "kde", or "ecdf" with the kind parameter, and you can easily compare by group with the hue parameter.
Visualizing distributions is the first step in data analysis. Patterns that are missed by just looking at the mean and standard deviation become apparent in a single graph.