ggplot2 scale_size() in R: Control Point and Bubble Size
The scale_size() function in ggplot2 maps a numeric variable to point area so larger values draw bigger points. It is the standard scale behind bubble plots and any chart that uses the size aesthetic.
scale_size(range = c(1, 10)) # min, max in mm scale_size(range = c(2, 12), name = "Population") # tune legend title scale_size_area(max_size = 15) # 0 maps to size 0 scale_radius(range = c(1, 10)) # map to radius, not area scale_size_continuous(breaks = c(10, 100, 1000)) # explicit legend breaks scale_size_manual(values = c(2, 4, 8)) # discrete level to size
Need explanation? Read on for examples and pitfalls.
What scale_size() does in one sentence
scale_size() maps a continuous numeric variable to the AREA of points so visual size grows with value. Area scaling (not radius) matches how humans perceive size, so it is the right default for bubble plots.
Syntax and arguments
scale_size(name = waiver(), breaks = waiver(), labels = waiver(), limits = NULL, range = c(1, 6), trans = "identity", guide = "legend"). The range argument is the most-used one: a length-2 numeric vector giving the minimum and maximum point size in millimetres.
The default range = c(1, 6) is conservative. A wider range like c(2, 12) exaggerates size differences and is usually what you want for a clear bubble plot.
range for bubble plots; defaults look flat. scale_size(range = c(2, 14)) is a good starting point when bubbles need to be visually distinct.Five common patterns
1. Basic bubble plot
The size = hp mapping inside aes() tells ggplot which variable controls the bubble. scale_size() then defines how that variable becomes a drawing size.
2. Honest zero with scale_size_area()
scale_size_area() forces a value of zero to map to size zero, which scale_size() does not guarantee. Use this whenever zero is a meaningful data point.
3. Radius instead of area
scale_radius() maps the variable to point radius. This exaggerates differences (a 2x value becomes a 4x area), so prefer scale_size() unless you have a specific reason.
scale_radius() overstates differences visually. A value twice as large appears four times as big in area. Use scale_size() or scale_size_area() for accurate perception.4. Custom legend breaks and labels
Without explicit breaks, ggplot picks tick values automatically. For a polished plot, pass breaks to control which sizes show in the legend.
5. Discrete levels with scale_size_manual()
Use scale_size_manual() when the size variable is a factor and you want to assign specific sizes by hand.
scale_size() controls how a variable maps to size; geom_point(size = 3) sets a fixed size for ALL points. If you set both, ggplot uses the aesthetic mapping and ignores the fixed value.scale_size() vs scale_size_area() vs scale_radius()
| Function | Maps value to | Zero handling | Best for |
|---|---|---|---|
scale_size() |
Point area | Not pinned to 0 | Standard bubble plots |
scale_size_area() |
Point area | 0 maps to size 0 | Counts, populations, where 0 is meaningful |
scale_radius() |
Point radius | Not pinned to 0 | Rare; only when radius comparison matters |
scale_size_manual() |
Discrete levels | Manual | Factor variable with hand-picked sizes |
scale_size_continuous() |
Point area | Same as scale_size() |
Alias; identical behaviour |
Common pitfalls
Pitfall 1: bubbles too small to see. The default range = c(1, 6) is conservative. If bubbles look flat, widen it: scale_size(range = c(2, 14)).
Pitfall 2: confusing size = hp (inside aes()) with size = 3 (outside). Inside aes() it maps a variable; outside it sets a fixed size. Mixing them silently overrides the mapping.
Pitfall 3: overlap. Large bubbles overlap and hide each other. Add alpha = 0.5 to geom_point() so overlaps stay visible, or use position_jitter() for clustered data.
Try it yourself
Try it: Build a bubble plot of airquality with Wind on x, Temp on y, and Ozone controlling bubble size. Widen the range to c(2, 12) and label the legend "Ozone (ppb)". Save the plot to ex_bubble.
Click to reveal solution
Explanation: Map Ozone to size inside aes(), then call scale_size() to widen the range and rename the legend. Add alpha so overlapping bubbles stay readable.
Related ggplot2 functions
After mastering scale_size(), look at:
geom_point(): the main geom that uses the size aestheticscale_size_area(): enforces a 0 value to 0 size mappingscale_radius(): maps to radius instead of areascale_color_gradient(): continuous color scaling (often paired with size)aes(): where you mapsize = variablein the first place
For full theory, see the official ggplot2 scale_size reference.
FAQ
What is the difference between scale_size and scale_size_area in ggplot2?
Both map a variable to point area. scale_size() does NOT guarantee that a value of zero produces a size of zero; the smallest value in the data takes the lower end of range. scale_size_area() forces zero to map to zero size and uses max_size instead of range. Use scale_size_area() when zero values are meaningful in your data (e.g., counts, populations).
How do I make bubbles bigger in ggplot2?
Pass a wider range to scale_size(). The default is range = c(1, 6). For a more dramatic bubble plot, try range = c(2, 14) or even c(3, 20). You can also use scale_size_area(max_size = 20) if you want zero to map to zero.
Why is scale_size based on area instead of radius?
Humans judge circle size by area, not radius. If you map a variable to radius, a 2x value becomes a 4x area, which exaggerates differences. ggplot maps to area by default so visual size matches numeric value. Use scale_radius() only if you have a specific need.
How do I change the legend title for scale_size?
Pass name = "Your Title" to scale_size(), or use labs(size = "Your Title") on the plot. Both work; name keeps the change local to the scale.
Can I use scale_size with a discrete variable?
Yes, use scale_size_manual(values = c("a" = 2, "b" = 6)) to map factor levels to explicit sizes. The default scale_size() expects a continuous variable and will warn if you pass a factor.