
Image by Author | Midjourney & Canva
When we talk about data, we’re really talking about stories about people, behavior, choices, and patterns. And distributions are one of the best ways to tell those stories. In simple terms, a distribution shows us how values are spread across a dataset.
Distributions matter because they shape how we understand the world. For instance, a dataset might have the same average income for two countries, but completely different spreads. One might have a massive wealth gap, while the other is more equal. That difference doesn’t show up in a simple mean or median, but it jumps out the moment you visualize the distribution.
Before we can start exploring these visualizations ourselves, we need to get our environment ready.

A sampling of different distribution types
Setting Up Your Environment
If you’re working in a fresh Python environment (like Jupyter Notebook, VS Code, or even Google Colab), the first thing to do is install the right libraries. You’ll only need a handful of them, and they cover everything from basic data manipulation to plotting beautiful, insightful charts.
Here’s a one-liner that gets everything we need:
pip install matplotlib seaborn plotly numpy pandas
Let me break down what each of these does and why we’re using them:
- Matplotlib: The OG of Python plotting. It gives you full control over your visuals and is great for creating static, publication-quality charts.
- Seaborn: Built on top of Matplotlib, but makes statistical plots a breeze. If you want quick, clean visuals with less code, Seaborn’s your friend.
- Plotly adds interactivity to your charts. Its hover tooltips, zooming, and responsive visuals are perfect for demos and dashboards.
- NumPy: The backbone of numerical computing in Python. We’ll use it to generate synthetic datasets and handle basic math under the hood.
- Pandas: For working with tabular data like CSVs or DataFrames.
Generating Sample Data
Before we get into plotting anything, we need some data to work with. While you can always use real-world datasets like Titanic or Iris, I find it super helpful to start with synthetic data. This gives you full control over the shape of the distribution and makes it easier to understand what’s happening visually.
We’ll use NumPy to generate three types of distributions: normal, skewed, and bimodal. These are some of the most common patterns you’ll encounter in the wild, and they’re a great way to get comfortable with different shapes and what they imply.
import numpy as np
import pandas as pd
# Set a seed so results are reproducible
np.random.seed(42)
# Normal distribution (e.g., heights, test scores)
normal_data = np.random.normal(loc=50, scale=10, size=1000)
# Skewed distribution (e.g., income, time-on-site)
skewed_data = np.random.exponential(scale=2.0, size=1000)
# Bimodal distribution (e.g., test scores from two different groups)
bimodal_data = np.concatenate([
np.random.normal(loc=40, scale=5, size=500),
np.random.normal(loc=70, scale=5, size=500)
])
In this setup:
- The normal_data simulates a classic bell curve, centered around 50, with most values spread within 10 units on either side
- The skewed_data is heavily right-skewed, which means you’ll see a long tail of larger values. This is pretty common in things like income data, where most people earn a similar amount, but a few outliers earn way more
- The bimodal_data creates two clusters, one around 40 and another around 70. It’s what you’d get if, say, two very different user groups took the same test
After generating this data, you can throw it into a pandas DataFrame for easier handling when plotting:
df = pd.DataFrame({
"normal": normal_data,
"skewed": skewed_data,
"bimodal": bimodal_data
})
This makes it much easier to pass around columns when using libraries like Seaborn or Plotly later on.
Matplotlib
Let’s kick things off with Matplotlib, since it’s the foundation of most plotting libraries in Python. It might feel a bit low-level at first, but it gives you full control over how your charts look and behave. And once you get the hang of it, you’ll appreciate the flexibility.
We’re going to start simple and build up. Our goal here is to create a basic histogram that shows how values are distributed, then we’ll layer in some styling and a density curve to make it more insightful.
Here’s a minimal example using the normal distribution we generated earlier:
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 5))
plt.hist(df["normal"], bins=30, color="skyblue", edgecolor="black")
plt.title("Normal Distribution (Matplotlib)")
plt.xlabel("Value")
plt.ylabel("Frequency")
plt.grid(True)
plt.show()
This gives you a classic histogram, where the x-axis shows the range of values and the y-axis shows how many times values fall into each bin. In this case, since the data is normally distributed, you should see a nice bell-shaped curve.

Normal distribution Matplotlib
If you want to take things a step further, you can overlay a density curve to smooth things out and help the viewer grasp the shape of the distribution more clearly. For that, we can use scipy.stats.gaussian_kde:
from scipy.stats import gaussian_kde
import numpy as np
data = df["normal"]
density = gaussian_kde(data)
x_vals = np.linspace(min(data), max(data), 1000)
density_vals = density(x_vals)
plt.figure(figsize=(8, 5))
plt.hist(data, bins=30, color="lightgray", edgecolor="black", density=True)
plt.plot(x_vals, density_vals, color="blue", linewidth=2)
plt.title("Normal Distribution with Density Curve")
plt.xlabel("Value")
plt.ylabel("Density")
plt.grid(True)
plt.show()

Normal distribution with density curve
Now, instead of just the bars, you get a smooth line that reflects the distribution of the data.
Seaborn
Seaborn is built on top of Matplotlib, but it simplifies a lot of the syntax and gives you cleaner, more informative plots with much less effort. It also plays really well with pandas DataFrames, which makes it perfect for data exploration.
Let’s start by recreating the histogram we built earlier, but this time using Seaborn’s histplot:
import seaborn as sns
sns.set(style="whitegrid")
plt.figure(figsize=(8, 5))
sns.histplot(data=df, x="normal", bins=30, color="mediumseagreen", kde=False)
plt.title("Normal Distribution (Seaborn)")
plt.xlabel("Value")
plt.ylabel("Frequency")
plt.show()
That’s already cleaner, right? The default styling in Seaborn adds gridlines, padding, and font adjustments that make the plot feel more polished out of the box.

Normal distribution with Seaborn
Now, let’s take advantage of one of Seaborn’s best features: combining histograms and KDE plots in one line.
plt.figure(figsize=(8, 5))
sns.histplot(data=df, x="normal", bins=30, kde=True, color="mediumpurple")
plt.title("Normal Distribution with KDE (Seaborn)")
plt.xlabel("Value")
plt.ylabel("Density")
plt.show()

Normal distribution with KDE
Setting kde=True adds that smooth density curve we previously built manually in Matplotlib. This saves time and gives you a quick sense of the distribution’s shape.
Another super handy tool is displot, which can create faceted or layered charts with very little code. For example, if you wanted to look at multiple distributions side by side:
sns.displot(df[["normal", "skewed", "bimodal"]], kind="kde", fill=True, height=5, aspect=1.5)
plt.title("Comparing Distribution Shapes (Seaborn)")
plt.xlabel("Value")

Comparing distribution shapes
This gives you overlapping density plots for each dataset so you can easily compare how their shapes differ. Visuals like this are great for spotting differences in spread, skew, and symmetry.
The bottom line is, if you want fast, attractive, and statistically meaningful plots without dealing with too many custom settings, Seaborn is the sweet spot.
Plotly
So far, we’ve examined Matplotlib for control and Seaborn for convenience. Now, it’s time to level things up with interactivity. Plotly lets you create interactive charts right inside your notebook or browser. You can hover over data points, zoom into specific areas, and even export charts directly from the interface.
Let’s say you want to visualize the skewed distribution we generated earlier. With Plotly, it’s as easy as this:
import plotly.express as px
fig = px.histogram(df, x="skewed", nbins=30, title="Skewed Distribution (Plotly)",
labels={"skewed": "Value"}, opacity=0.7)
fig.update_layout(bargap=0.1)
fig.show()
As soon as you run this, you’ll be able to hover over each bar to see exact values, pan around, zoom in, and even save the plot as a PNG with one click. No extra setup required.

Skewed distribution Plotly
If you’re working with multiple distributions and want to compare them interactively, you can reshape your data into long format and use color to differentiate them. Something like this:
df_long = df.melt(var_name="Distribution", value_name="Value")
fig = px.histogram(df_long, x="Value", color="Distribution",
barmode="overlay", nbins=40,
title="Comparing Distributions (Plotly)")
fig.update_layout(bargap=0.1)
fig.show()
This creates an overlapping histogram with different colors for normal, skewed, and bimodal distributions. You can toggle each one on and off in the legend to isolate them visually.

Comparing distributions Plotly
Plotly’s power really shines when you’re presenting your findings or building something interactive for others to explore. It’s best suited for dashboards, reports, or anything where engagement and visual clarity are top priority.
Comparing Techniques
The truth is, there’s no one-size-fits-all answer. Each tool has its strengths, and depending on what you’re trying to achieve, one might be a better fit than the others.
Let’s break it down:
| Tool | Ease of Use | Customizability | Interactivity | Best For |
|---|---|---|---|---|
| Matplotlib | Medium | High | No | Full control, publication-ready plots |
| Seaborn | High | Moderate | No | Quick, beautiful statistical visuals |
| Plotly | High | Moderate | Yes | Interactive dashboards and presentations |
Conclusion
Visualizing distributions is not all about nice-looking charts. It’s really about getting to know your data better. And this could be whether you are cleaning up a messy dataset, prepping for a machine learning project, or just trying to figure out what’s really going on; these kinds of plots help you see the bigger picture.
But here’s the thing: even small choices on how you plot can change how people read your data. If you’re creating something that others will look at, keep it simple and clear. Use consistent colors, label things properly, and don’t overload the chart with stuff that doesn’t need to be there. Let the data speak for itself.
Also, don’t stop at the examples in this guide. We worked with synthetic data here, but the real power shows up when you start using these techniques on your own projects and real-world datasets. That’s where it gets interesting.
