ggplot2 geom_hex() in R: 2D Hexagonal Density Bins

The geom_hex() function in ggplot2 plots 2D point density using HEXAGONAL bins. It is ideal for scatter plots with many overlapping points, providing a clearer view than transparent geom_point.

⚡ Quick Answer
ggplot(df, aes(x, y)) + geom_hex()
geom_hex(bins = 30)                     # control bin count
geom_hex() + scale_fill_viridis_c()
geom_bin2d()                             # rectangular bins alternative
geom_density_2d()                        # contour lines

Need explanation? Read on for examples and pitfalls.

📊 Is geom_hex() the right tool?
STARTmany points scatter, hexagonal binsgeom_hex()rectangular binsgeom_bin2d()contour lines (no fill)geom_density_2d()raw points with alphageom_point(alpha = 0.3)few pointsgeom_point() (no binning needed)

What geom_hex() does in one sentence

geom_hex() divides the (x, y) plane into hexagonal bins and colors each by the count of points falling in it. Best for dense scatter data where overlapping obscures patterns.

Syntax

geom_hex(bins = 30, binwidth = NULL, ...). Requires hexbin package.

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RHex density of diamonds carat vs price
library(ggplot2) ggplot(diamonds, aes(carat, price)) + geom_hex(bins = 50)

  
Tip
Install the hexbin package: install.packages("hexbin"). geom_hex requires it for the binning math.

Five common patterns

1. Standard hex density

RDiamonds carat vs price
ggplot(diamonds, aes(carat, price)) + geom_hex()

  

2. Custom bin count

RFiner bins
ggplot(diamonds, aes(carat, price)) + geom_hex(bins = 50)

  

3. Custom binwidth

RSpecific bin sizes
ggplot(df, aes(x, y)) + geom_hex(binwidth = c(0.1, 100)) #> bins of width 0.1 in x and 100 in y

  

4. Color scale

RViridis for perceptual uniform
ggplot(diamonds, aes(carat, price)) + geom_hex() + scale_fill_viridis_c()

  

5. With overlay

RHex density + LOESS
ggplot(diamonds, aes(carat, price)) + geom_hex(alpha = 0.7) + geom_smooth()

  
Key Insight
For 1000+ point scatter, geom_hex is much clearer than geom_point(alpha=0.3). Hex bins highlight density patterns that transparent points hide.

geom_hex() vs geom_bin2d() vs geom_point() with alpha

Function Bin shape Best for
geom_hex() Hexagonal Dense scatter with smooth visual
geom_bin2d() Rectangular Dense scatter, rectangle-friendly
geom_point(alpha = 0.3) None (raw points) Fewer points
geom_density_2d() Contour lines Smooth density

A practical workflow

Use geom_hex when scatter density is the question.

RInteractive R
ggplot(diamonds, aes(carat, price)) + geom_hex(bins = 40) + scale_fill_viridis_c() + scale_y_log10() + labs(x = "Carat", y = "Price (log)", fill = "Count")

  

Density on log-y for skewed price data.

Common pitfalls

Pitfall 1: forgetting hexbin package. geom_hex requires hexbin. Install if not already.

Pitfall 2: too few bins. Default 30 bins may be too coarse; try 50 or 100 for fine detail.

Warning
geom_hex() requires the hexbin package. Without it, the function errors. Always test imports.

Try it yourself

Try it: Plot a hex density of mpg vs hp from mtcars. Save to ex_plot.

RYour turn: mpg vs hp hex
ex_plot <- mtcars |> ggplot(aes(mpg, hp)) + # your code here

  
Click to reveal solution
RSolution
ex_plot <- ggplot(mtcars, aes(mpg, hp)) + geom_hex(bins = 15) + scale_fill_viridis_c()

  

Explanation: mtcars has only 32 points so use fewer bins. Hex shows density even with sparse data.

After mastering geom_hex, look at:

  • geom_bin2d(): rectangular bins alternative
  • geom_density_2d(): contour lines
  • geom_point(alpha): raw scatter
  • stat_density_2d(): density estimation

FAQ

What does geom_hex do in ggplot2?

geom_hex() plots 2D point density using hexagonal bins. Each bin is colored by the count of points within it.

Do I need a special package for geom_hex?

Yes. Install hexbin: install.packages("hexbin"). Without it, geom_hex errors.

What is the difference between geom_hex and geom_bin2d?

geom_hex uses hexagonal bins; geom_bin2d uses rectangular. Hexagons reduce visual artifacts at bin boundaries; rectangles are simpler.

How many bins should I use?

Default 30 is OK for medium data. For >10k points, try 50-100 for finer detail.

Should I use geom_hex or scatter with alpha?

For 1k+ overlapping points, geom_hex is clearer. For <1k, geom_point with alpha works well.