Density: hexbin & hist2d

Reviewed & published by Brayan K

A density plot bins many data points into a grid and colors each cell by how many points it holds, revealing where data piles up even when a scatter plot would collapse into one solid blob.

Part of the free Matplotlib course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll build hexagonal density maps with ax.hexbin(), square ones with ax.hist2d(), add a colorbar, tune the grid size and colormap, and learn when density beats a scatter.

What You'll Learn in This Lesson

1 Your First Hexbin Map

ax.hexbin(x, y) tiles the plot with hexagons and colors each one by how many points land inside it. The gridsize argument controls how many hexagons span the width — bigger numbers mean finer detail. Pass a cmap (colormap) to choose the color scheme. Always pair it with a colorbar so the colors mean something.

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(1)
# 20,000 points clustered near the center
x = rng.normal(0, 1, 20000)
y = rng.normal(0, 1, 20000)

fig, ax = plt.subplots()
hb = ax.hexbin(x, y, gridsize=30, cmap="viridis")
fig.colorbar(hb, ax=ax, label="Point count")

ax.set_title("Hexbin Density Map")
ax.set_xlabel("x")
ax.set_ylabel("y")

plt.show()
The chart this code draws: Bin 20,000 points into colored hexagons
What this code draws, rendered with Matplotlib.

What you'll see: a roughly circular cloud of hexagons that glow brightest in the middle and fade toward the edges, showing the data piles up near the center. The colorbar on the right tells you how many points each shade represents.

2 Square Bins With ax.hist2d()

ax.hist2d(x, y) is the two-dimensional version of a histogram. It lays a rectangular grid of square bins over the data and colors each by its count. Set the number of bins with bins=. It returns several values — the counts, the two sets of edges, and the image — and you pass that last image to fig.colorbar().

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(2)
# A diagonal correlated cloud
x = rng.normal(0, 1, 30000)
y = x * 0.8 + rng.normal(0, 0.5, 30000)

fig, ax = plt.subplots()
counts, xedges, yedges, im = ax.hist2d(x, y, bins=40, cmap="plasma")
fig.colorbar(im, ax=ax, label="Point count")

ax.set_title("2D Histogram (hist2d)")
ax.set_xlabel("x")
ax.set_ylabel("y")

plt.show()
The chart this code draws: Square bins reveal a diagonal correlation
What this code draws, rendered with Matplotlib.

What you'll see: a bright diagonal streak running from lower-left to upper-right, made of square cells, showing that x and y rise together. The brightest cells sit along the diagonal where most points fall, and the colorbar maps each shade to a count.

3 Why Density Beats Overplotting

With a few hundred points, a scatter plot is perfect. But push it to tens of thousands and the dots merge into one solid blob — you can't tell a busy region from a packed one. This is overplotting, and density plots are the cure. Put a scatter and a hexbin side by side and the difference is obvious.

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(7)
# Two overlapping clusters
x = np.concatenate([rng.normal(-1, 0.5, 25000),
                    rng.normal(1.5, 0.7, 25000)])
y = np.concatenate([rng.normal(0, 0.5, 25000),
                    rng.normal(1, 0.7, 25000)])

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))

# Left: overplotted scatter
ax1.scatter(x, y, s=2, alpha=0.1, color="black")
ax1.set_title("Scatter (overplotted)")

# Right: hexbin reveals two clusters
hb = ax2.hexbin(x, y, gridsize=35, cmap="inferno")
fig.colorbar(hb, ax=ax2, label="count")
ax2.set_title("Hexbin (clear)")

fig.suptitle("Density Beats Overplotting")
plt.tight_layout()
plt.show()
The chart this code draws: See structure that a scatter plot hides
What this code draws, rendered with Matplotlib.

What you'll see: on the left, a near-solid gray smear where you can barely guess the shape. On the right, the hexbin clearly shows two bright hot spots — the two clusters — with a colorbar quantifying how dense each one is.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to draw a hexbin map and attach a colorbar.

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(0)
x = rng.normal(0, 1, 15000)
y = rng.normal(0, 1, 15000)

fig, ax = plt.subplots()

# Make a hexbin with 25 hexagons across and the "magma" colormap
hb = ax.___(x, y, gridsize=25, cmap=___)

# Add a colorbar so the colors have meaning
fig.___(hb, ax=ax, label="count")

ax.set_title("My Hexbin Map")

plt.show()

📋 Common Errors & Quick Reference

❌ TypeError when calling fig.colorbar()

You passed the wrong object. For hist2d use the fourth return value (the image), not the counts.

❌ The map is one flat color

Your gridsize is too large or too small for the data. Try gridsize=30 and adjust until the structure appears.

❌ Low-density bins drown out the rest

Pass bins="log" to hexbin (or use a log norm) so a few crowded cells don't wash out everything else.

CallWhat It Does
ax.hexbin(x, y)Hexagonal density map
gridsize=30Hexagons across the width
ax.hist2d(x, y, bins=40)Square-binned density
cmap="viridis"Pick the color scheme
fig.colorbar(obj, ax=ax)Legend for the colors

🎯 Mini-Challenge: hexbin vs hist2d

Plot the same correlated dataset two ways — hexbin on the left, hist2d on the right — each with its own colorbar, so you can judge which binning you prefer.

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(42)
x = rng.normal(0, 1, 40000)
y = 0.6 * x + rng.normal(0, 0.6, 40000)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))

# Left: hexagonal bins
hb = ax1.hexbin(x, y, gridsize=35, cmap="viridis")
fig.colorbar(hb, ax=ax1, label="count")
ax1.set_title("hexbin")

# Right: square bins
counts, xe, ye, im = ax2.hist2d(x, y, bins=35, cmap="viridis")
fig.colorbar(im, ax=ax2, label="count")
ax2.set_title("hist2d")

fig.suptitle("Hexagonal vs Square Bins")
plt.tight_layout()
plt.show()
The chart this code draws: Compare hexbin and hist2d side by side
What this code draws, rendered with Matplotlib.
import matplotlib.pyplot as plt

xs = [1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 5, 5]
ys = [1, 2, 1, 2, 2, 3, 3, 4, 5, 5, 5, 4]

fig, ax = plt.subplots()
hb = ax.hexbin(xs, ys, gridsize=3, cmap="Blues")
bar = fig.colorbar(hb, ax=ax)

counts = hb.get_array()
print("Hexagons with at least one point:", int((counts > 0).sum()))
print("Points binned:", int(counts.sum()), "of", len(xs))
print("Busiest hexagon holds:", int(counts.max()))
print("Colormap:", hb.get_cmap().name)
print("Colourbar added an axes:", len(fig.axes) == 2)

# ✅ Expected output:
# Hexagons with at least one point: 5
# Points binned: 12 of 12
# Busiest hexagon holds: 4
# Colormap: Blues
# Colourbar added an axes: True
The chart this code draws: How many hexagons got a point, how many points were binned, and what the busiest one holds
What this code draws, rendered with Matplotlib.

❓ Frequently Asked Questions

Lesson complete — you can map dense data!

You built hexagonal and square density maps, tuned gridsize and colormaps, added colorbars, and saw first-hand why density plots reveal structure that an overplotted scatter hides.

Practice quiz

What does ax.hexbin(x, y) draw?

  • A line of best fit
  • A grid of hexagons colored by point count
  • A pie chart
  • A 3D surface

Answer: A grid of hexagons colored by point count. hexbin tiles the area with hexagons and colors each by how many points fall inside.

Which argument controls how many hexagons span the width?

  • bins
  • width
  • gridsize
  • hexcount

Answer: gridsize. gridsize sets how many hexagons span the width; bigger means finer detail.

What is the key difference between hexbin and hist2d?

  • hexbin uses hexagonal bins; hist2d uses square bins
  • hexbin is 3D
  • hist2d cannot show density
  • They are identical

Answer: hexbin uses hexagonal bins; hist2d uses square bins. Both show 2D density, but hexbin uses hexagons while hist2d uses square bins.

Which argument sets the number of bins for ax.hist2d()?

  • gridsize=
  • bins=
  • nbins=
  • cells=

Answer: bins=. ax.hist2d(x, y, bins=40) sets the number of square bins per axis.

ax.hist2d() returns four values. Which one goes to fig.colorbar()?

  • The counts
  • The x edges
  • The y edges
  • The image (the fourth value)

Answer: The image (the fourth value). Unpack counts, xedges, yedges, im — pass the image im to fig.colorbar().

How do you add a colorbar to a hexbin plot?

  • fig.colorbar(hb, ax=ax)
  • ax.colorbar(hb)
  • plt.legend(hb)
  • hb.colorbar()

Answer: fig.colorbar(hb, ax=ax). Capture hb = ax.hexbin(...) and pass it to fig.colorbar(hb, ax=ax).

When does a density plot beat a scatter plot?

  • For a few hundred points
  • When so many points overlap that a scatter becomes a solid blob
  • For categorical data
  • For a single point

Answer: When so many points overlap that a scatter becomes a solid blob. Density plots reveal structure when overplotting turns a scatter into a blob.

What problem do hexbin and hist2d solve?

  • Missing data
  • Overplotting of dense scatter points
  • Wrong axis labels
  • Slow rendering of lines

Answer: Overplotting of dense scatter points. They cure overplotting by binning many points into colored cells.

Why do hexagonal bins often look smoother than square bins?

  • They are larger
  • Hexagons tile more evenly and avoid square-corner artifacts
  • They use more colors
  • They ignore outliers

Answer: Hexagons tile more evenly and avoid square-corner artifacts. Hexagons tile more evenly than squares, avoiding the blocky corners of square bins.

Which keeps a few crowded cells from washing out the rest in hexbin?

  • gridsize=1
  • bins='log' (or a log norm)
  • alpha=0
  • cmap='gray'

Answer: bins='log' (or a log norm). Passing bins='log' (or a log norm) compresses the scale so dense cells don't dominate.

Continue this course

Frequently asked questions

What is a hexbin plot?

A hexbin plot divides the plotting area into a grid of hexagons and colors each one by how many data points fall inside it. It is a density map: instead of drawing thousands of overlapping dots, you see where points pile up. Call ax.hexbin(x, y) and brighter or darker hexagons mark the crowded regions.

What is the difference between hexbin and hist2d?

Both show 2D density, but hist2d uses square bins arranged in a rectangular grid, while hexbin uses hexagonal bins. Hexagons tile more evenly and avoid the visual artifacts of square corners, so hexbin often looks smoother. hist2d is the two-dimensional cousin of a regular histogram.

How do I add a colorbar to show what the colors mean?

Capture the object returned by the plotting call, then pass it to fig.colorbar(). For example hb = ax.hexbin(x, y); fig.colorbar(hb, ax=ax, label='counts'). The colorbar is a legend for color: it maps each shade to a count so readers know which regions are dense.

When should I use a density plot instead of a scatter plot?

Reach for hexbin or hist2d when you have so many points that a scatter plot turns into a solid blob — overplotting hides the structure. Density plots reveal where points concentrate even when millions overlap. For a few hundred points, a plain scatter is usually clearer.