Menu

Boxplot in R: boxplot() by Group with Examples

How to make a box plot in R with boxplot() - reading the box and whiskers, the formula interface for group comparisons, styling, and finding the numbers behind it.

This page includes runnable editors - edit, run, and see output instantly.

How to Read a Box Plot

A box plot compresses a whole distribution into five numbers and draws them as one compact glyph. Reading it takes thirty seconds to learn, so let's do that first - every piece has a precise meaning:

  • The heavy line inside the box is the median: half the data sits below it, half above.
  • The box itself spans the interquartile range (IQR) - from the first quartile (25% of the data below) to the third quartile (75% below). The middle half of your data lives inside the box.
  • The whiskers are the lines extending out from the box. Each reaches to the most extreme data point that is still within 1.5 × IQR of the box edge - so they cover "the ordinary range" of the data, not the full range.
  • Individual points beyond the whiskers are values more than 1.5 × IQR from the box: flagged as suspected outliers, drawn one by one so you can count them.

A tall box means spread-out data; a median line sitting off-center inside the box means skew; a trail of points past one whisker means a heavy tail on that side. A histogram shows the same distribution in more detail - but the box plot's superpower is that a dozen of them fit side by side, which makes it the plot for comparing groups.

A Boxplot of One Variable

The function is boxplot(), and for a single numeric vector it's one call:

scores <- c(52, 55, 58, 60, 61, 63, 64, 66, 68, 70, 72, 95)

boxplot(scores,
        main = "Test scores",
        ylab = "Score")

The picture this draws: a box from 59 to 69 with the median line at 63.5, whiskers reaching down to 52 and up to 72, and one lone point floating at 95 - the outlier that a mean-and-standard-deviation summary would have quietly absorbed. That instant "one value is not like the others" is what box plots are for.

(As on every page in this chapter, plotting calls are static snippets - the runner below shows text output only. Run them locally to see the drawing.)

Comparing Groups: the Formula Interface

The real everyday use is one box per group, and for that boxplot() accepts a formula: y ~ group, read as "y broken down by group". R's built-in ToothGrowth dataset - tooth length in 60 guinea pigs given vitamin C via orange juice (OJ) or ascorbic acid (VC) - is the classic demo:

boxplot(len ~ supp,
        data = ToothGrowth,
        main = "Tooth growth by supplement",
        xlab = "Supplement",
        ylab = "Tooth length")

Two boxes appear on a shared vertical scale: the OJ box sits noticeably higher than the VC box, with its median around 22 versus about 19. Because both boxes share one axis, the comparison is honest by construction - no chance of two plots with different scales flattering one group. The grouping column should be a factor (or character vector); each level becomes one box.

Formulas nest, too: boxplot(len ~ supp * dose, data = ToothGrowth) draws six boxes, one per supplement-and-dose combination. When a group difference looks real in the boxes, the natural next question - is it more than noise? - is what a t-test answers.

Labels, Colors, and Horizontal Boxplots

The styling arguments follow base plotting conventions. names labels the boxes, col fills them - pass a vector to color each group differently:

boxplot(len ~ supp,
        data  = ToothGrowth,
        names = c("Orange juice", "Ascorbic acid"),
        col   = c("orange", "lightblue"),
        main  = "Tooth growth by supplement",
        ylab  = "Tooth length")

Two more switches earn their keep:

  • horizontal = TRUE rotates the whole plot so the boxes run along the horizontal axis. Do this whenever group names are long - horizontal labels stay readable where vertical ones collide.
  • notch = TRUE carves a notch around each median; when two groups' notches don't overlap, their medians plausibly differ. Treat it as a visual hint, not a test.

The Numbers Behind the Box

Everything the plot draws comes from numbers you can print. quantile() gives the five-number summary, and boxplot.stats() gives you exactly what the plot uses - including the outliers. This one you can run right here:

Note the two are cousins, not twins: quantile() reports the true minimum and maximum, while s$stats ends at the whiskers - the most extreme points within the 1.5 × IQR fence. The gap between them is precisely s$out, here the lone 95. When someone asks "which rows are those outlier dots?", x[x %in% boxplot.stats(x)$out] - or a filter on the fence values - answers it.

Boxplots with ggplot2

In ggplot2 the grouping goes into the aesthetic mapping instead of a formula:

library(ggplot2)

ggplot(ToothGrowth, aes(x = supp, y = len, fill = supp)) +
    geom_boxplot() +
    labs(title = "Tooth growth by supplement",
         x = "Supplement", y = "Tooth length")

Same two boxes, with a legend and theme for free. ggplot2's version scales better when the plot grows - facet by dose, overlay the raw points with geom_jitter(width = 0.1), and the code stays declarative. For a one-off comparison during analysis, boxplot(y ~ g, data = df) remains the fastest thing you can type.

What You Take Away

  • Box anatomy: median line, box = IQR, whiskers = most extreme points within 1.5 × IQR, dots beyond = suspected outliers.
  • boxplot(x) for one variable; boxplot(y ~ group, data = df) for side-by-side group comparison on one honest shared scale.
  • Style with names, a col vector per group, and horizontal = TRUE when labels are long.
  • boxplot.stats(x)$stats and $out are the plot as numbers - use them to extract the outliers the plot points at.
  • ggplot2: geom_boxplot() with the group mapped to x or fill.

Next up: the scatter plot - from one variable's distribution to the relationship between two.

Frequently Asked Questions

How do you make a boxplot in R?

Call boxplot(x) on a numeric vector for a single box, or use the formula interface boxplot(y ~ group, data = df) to get one box per group side by side. Both ship with base R - no packages needed.

What do the parts of a box plot mean?

The heavy line inside the box is the median. The box spans the interquartile range, from the first quartile to the third. The whiskers reach out to the most extreme data points within 1.5 times the IQR of the box, and anything beyond the whiskers is drawn as an individual point - a suspected outlier.

How do you make a boxplot by group in R?

Use a formula: boxplot(len ~ supp, data = ToothGrowth) draws one box per level of supp. The grouping variable should be a factor or character column; each level becomes one box on a shared scale.

How do you find the outliers a boxplot shows?

boxplot.stats(x)$out returns exactly the values the plot would draw as points beyond the whiskers, and boxplot.stats(x)$stats gives the five numbers behind the box - whisker ends, hinges, and median.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED