13  Interval

How far does each group reach, from its lowest value to its highest? interval draws a span from a low value to a high one at each position: a whisker, capped at both ends. It is the mark behind error bars and min–max ranges. A bar measures from a baseline up to a value. An interval spans a low value and a high value instead: it has no baseline, and it needs both before it can draw. Whiskers stand up by default and lie down when the categories are on y; see Whiskers on their side.

Almost everyone calls this an error bar, and the name hides a choice no drawing can show. Three different quantities get drawn as the same picture: a standard deviation, a standard error, and a confidence interval. On the same data, one can be several times longer than another. Nothing in the drawing says which one it is, so a reader without the caption is guessing at a number’s meaning.

gog makes the sentence name the statistic instead. interval * range spans the observed extremes, interval * confidence(0.95) computes the interval and names its level, and interval * bounds(lower, upper) takes two columns you computed yourself. The mark is the same whisker in all three; what differs is the transform, and the transform is written down. That is the law of Explicit Over Implicit, shown in one mark: the name error bar can hide the choice, and a written sentence cannot.

13.1 A range per group

How far do the petal lengths of each species reach, from the shortest to the longest? One whisker per species answers it, and a whisker needs a low value and a high value to span. The low value and the high value come from a transform, not from two extra channels. range reduces y to its minimum and maximum within each x group, and interval spans between them:

iris_flowers: first 5 of 150 rows
sepal_length sepal_width petal_length species
5.1 3.5 1.4 setosa
4.9 3.0 1.4 setosa
4.7 3.2 1.3 setosa
4.6 3.1 1.5 setosa
5.0 3.6 1.4 setosa
data(iris_flowers) + interval * range + x(species) + y(petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Range of petal length per species")
(data(iris_flowers) + interval * range + x(col.species) + y(col.petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Range of petal length per species"))
data(iris_flowers) + interval * range + x(:species) + y(:petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Range of petal length per species")
plot(data(iris_flowers), layer(interval, range), x(col.species),
  y(col.petal_length), x_label("Species"), y_label("Petal length (cm)"),
  title("Range of petal length per species"))
setosa versicolor virginica 2 4 6 Range of petal length per species Petal length (cm) Species

“Given the iris flowers: intervals derived by range, x is species, y is petal length.”

Read it exactly like bar * mean. Here the transform gives a range, while mean gives one number. You name the one variable whose spread you are asking about; the transform invents the two extents and the mark spans them.

13.2 The middle half of each group

A minimum and a maximum each come from a single row, so one unusual flower can set the whole whisker. To show the middle of a group instead of its extremes, give range two quantiles:

data(iris_flowers) + interval * range(0.25, 0.75) + x(species) + y(petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Middle half of petal length per species")
(data(iris_flowers) + interval * range(0.25, 0.75) + x(col.species) + y(col.petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Middle half of petal length per species"))
data(iris_flowers) + interval * range(0.25, 0.75) + x(:species) +
  y(:petal_length) + x_label("Species") + y_label("Petal length (cm)") +
  title("Middle half of petal length per species")
plot(data(iris_flowers), layer(interval, range(0.25, 0.75)),
  x(col.species), y(col.petal_length), x_label("Species"),
  y_label("Petal length (cm)"),
  title("Middle half of petal length per species"))
setosa versicolor virginica 2 3 4 5 6 Middle half of petal length per species Petal length (cm) Species

“Given the iris flowers: intervals derived by range from 0.25 to 0.75, x is species, y is petal length.”

This spans the first quartile to the third, which is the interquartile range. range(0.1, 0.9) keeps the middle 80%, and any other pair works the same way. Bare range is range(0, 1), so the plain sentence above is unchanged. Transforms gives the full account of the two arguments.

13.3 Three layers instead of one mark

A box draws five numbers in one mark, and you take all five or none. Draw the band and the full range as two layers, and the median as a third. You can restyle any of the three layers, drop one, or replace one:

data(iris_flowers) +
  interval * range + style(color = "lightgray") +
  interval * range(0.25, 0.75) + style(color = "steelblue", size = 8) +
  point * median + style(color = "white", size = 3) +
  x(species) + y(petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Petal length per species, drawn in three layers")
(data(iris_flowers) +
  interval * range + style(color = "lightgray") +
  interval * range(0.25, 0.75) + style(color = "steelblue", size = 8) +
  point * median + style(color = "white", size = 3) +
  x(col.species) + y(col.petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Petal length per species, drawn in three layers"))
data(iris_flowers) + interval * range + style(color = "lightgray") +
  interval * range(0.25, 0.75) + style(color = "steelblue", size = 8) +
  point * median + style(color = "white", size = 3) + x(:species) +
  y(:petal_length) + x_label("Species") + y_label("Petal length (cm)") +
  title("Petal length per species, drawn in three layers")
plot(data(iris_flowers), layer(interval, range),
  style({ color: "lightgray" }), layer(interval, range(0.25, 0.75)),
  style({ color: "steelblue", size: 8 }), layer(point, median),
  style({ color: "white", size: 3 }), x(col.species), y(col.petal_length),
  x_label("Species"), y_label("Petal length (cm)"),
  title("Petal length per species, drawn in three layers"))
setosa versicolor virginica 2 4 6 Petal length per species, drawn in three layers Petal length (cm) Species

“Given the iris flowers: intervals derived by range, and also intervals derived by range from 0.25 to 0.75, and also points derived by median, x is species, y is petal length.”

The gray whisker is the full range, the thick blue band is the middle half, and the white dot is the median. The three layers name the three statistics. A box plot draws the same three and names none of them.

This is not a box redrawn, and the difference is at the ends. A box stops its whiskers at a cutoff computed from the quartiles, and draws every point beyond that cutoff as an outlier. Drawn as a box, setosa shows four outlier dots that are absent here, because these whiskers run to the true minimum and maximum. What the layers give you instead is a plot you can change. Widen the band to range(0.1, 0.9) and the plot reports a different statistic, with the same marks. Drop the gray layer and the y axis covers the band rather than the extremes. Add a third layer with another pair of quantiles, and you have the fan chart drawn over categories instead of along a line. A box is still the shorter sentence when you want the five numbers and the outlier rule together.

13.4 A confidence interval, with a center

How well is the average petal length of each species known? That is a different question from how far the flowers spread, and it needs a different statistic. range shows the full spread. For the uncertainty of the mean, use confidence. It computes a t-interval for each group’s mean (a t-interval widens when the group is small) and keeps that mean as the center. The interval then draws a dot at that center: a pointrange.

data(iris_flowers) + interval * confidence(0.95) + x(species) + y(petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("95% CI of mean petal length")
(data(iris_flowers) + interval * confidence(0.95) + x(col.species) + y(col.petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("95% CI of mean petal length"))
data(iris_flowers) + interval * confidence(0.95) + x(:species) +
  y(:petal_length) + x_label("Species") + y_label("Petal length (cm)") +
  title("95% CI of mean petal length")
plot(data(iris_flowers), layer(interval, confidence(0.95)),
  x(col.species), y(col.petal_length), x_label("Species"),
  y_label("Petal length (cm)"), title("95% CI of mean petal length"))
setosa versicolor virginica 2 3 4 5 95% CI of mean petal length Petal length (cm) Species

“Given the iris flowers: intervals derived by confidence at 95%, x is species, y is petal length.”

The level is a parameter, so confidence(0.99) draws wider than confidence(0.95).

13.5 The spread of the data

How far is a typical flower from the average of its species? That is a question about the spread of the data, not about how well the average is known. The opening of this chapter named a standard deviation, a standard error and a confidence interval. confidence draws the third. The first is deviation:

data(iris_flowers) + interval * deviation + x(species) + y(petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Mean petal length, with one standard deviation")
(data(iris_flowers) + interval * deviation + x(col.species) + y(col.petal_length) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("Mean petal length, with one standard deviation"))
data(iris_flowers) + interval * deviation + x(:species) +
  y(:petal_length) + x_label("Species") + y_label("Petal length (cm)") +
  title("Mean petal length, with one standard deviation")
plot(data(iris_flowers), layer(interval, deviation), x(col.species),
  y(col.petal_length), x_label("Species"), y_label("Petal length (cm)"),
  title("Mean petal length, with one standard deviation"))
setosa versicolor virginica 2 3 4 5 6 Mean petal length, with one standard deviation Petal length (cm) Species

“Given the iris flowers: intervals derived by deviation, x is species, y is petal length.”

deviation(2) reaches two standard deviations on each side of the mean, so that band is four standard deviations wide. The default is one each side. Like confidence it carries a center, so it draws a pointrange rather than a bare whisker.

The standard error has no transform of its own, and that is deliberate. confidence computes it internally and reports the interval, which is what a reader needs from it.

deviation and confidence look alike and answer different questions, so here they are on the same fifty flowers per species, sharing one y scale:

(data(iris_flowers) + interval * deviation + x(species) + y(petal_length) +
   y_label("Petal length (cm)") + title("Spread of the data")) |
  (data(iris_flowers) + interval * confidence + x(species) + y(petal_length) +
     y_label("") + title("Uncertainty of the mean"))
((data(iris_flowers) + interval * deviation + x(col.species) + y(col.petal_length) +
   y_label("Petal length (cm)") + title("Spread of the data")) |
  (data(iris_flowers) + interval * confidence + x(col.species) + y(col.petal_length) +
     y_label("") + title("Uncertainty of the mean")))
(data(iris_flowers) + interval * deviation + x(:species) +
  y(:petal_length) + y_label("Petal length (cm)") +
  title("Spread of the data")) |
  (data(iris_flowers) + interval * confidence + x(:species) +
  y(:petal_length) + y_label("") + title("Uncertainty of the mean"))
beside(plot(data(iris_flowers), layer(interval, deviation),
  x(col.species), y(col.petal_length), y_label("Petal length (cm)"),
  title("Spread of the data")),
  plot(data(iris_flowers), layer(interval, confidence), x(col.species),
  y(col.petal_length), y_label(""), title("Uncertainty of the mean")))
setosa versicolor virginica 2 4 6 Spread of the data Petal length (cm) Species setosa versicolor virginica Uncertainty of the mean Species

“Given the iris flowers: intervals derived by deviation, x is species, y is petal length, beside intervals derived by confidence, x is species, y is petal length.”

The bands on the right are less than a third as long, on identical data, and the ratio is the same in all three species. The two numbers do not contradict each other. deviation describes how far the flowers are from their average, and measuring more of them does not make that band shorter. confidence describes how precisely the average itself is known. More flowers make it shorter: four times as many flowers make it half as long. Fifty flowers per species is what sets that ratio here.

So choose by the sentence you would write under the plot. “A versicolor petal is usually within half a centimeter of its species average” describes the left panel. “The average versicolor petal is 4.26 cm, and the interval reaches 0.13 cm on either side” describes the right one. Reporting one and meaning the other is the mistake the name error bar allows, and the reason gog asks you to name the statistic.

Which band is wider is not fixed, either. confidence narrows as the group gets larger. On fifty flowers it is under a third of the spread; on three flowers it is the wider of the two.

Whether a center exists is decided by the statistic, not by a setting. confidence and deviation have a mean and range does not, so the statistic chooses the geometry: a bare error bar or a pointrange. A setting can hide an existing center (below), but no setting adds one to range. See Transforms for the whole range family.

13.6 Why interval needs a transform

bar needs no transform, bar + x + y, because a bar measures from a baseline the grammar already supplies: zero. An interval has no such default. A low and a high are two numbers, and a plain y() supplies one. So the shortest interval that draws, its minimum syllable, includes a transform that produces a range. Ask for an interval without one and the engine says so, rather than drawing an empty panel. What it refuses shows that refusal.

This is the design choice that keeps the channel vocabulary small. The obvious alternative would add ymin and ymax as two more channels meaningful to a single mark, and a box plot would need five channels. Instead a transform invents the extents, exactly as bin invents a count, so the channel set never grows. The same low and high rows are what box and ribbon draw from. When you already have the extents, from a fitted model or a published standard error, bounds(lower, upper) supplies them without computing anything: interval * bounds(lower, upper) draws one whisker per row.

13.7 Whiskers on their side

Whiskers lie down for the reasons bars do: the names are long, or there are many of them. A list of rows is then easier to scan than a row of columns. An interval reads its orientation off the bindings, the way a bar and a box do. Put the categories on y and the measured column on x, and the whiskers lie down:

data(iris_flowers) + interval * confidence(0.95) + x(petal_length) + y(species) +
  x_label("Petal length (cm)") + y_label("Species") +
  title("The same 95% CI, lying down")
(data(iris_flowers) + interval * confidence(0.95) + x(col.petal_length) + y(col.species) +
  x_label("Petal length (cm)") + y_label("Species") +
  title("The same 95% CI, lying down"))
data(iris_flowers) + interval * confidence(0.95) + x(:petal_length) +
  y(:species) + x_label("Petal length (cm)") + y_label("Species") +
  title("The same 95% CI, lying down")
plot(data(iris_flowers), layer(interval, confidence(0.95)),
  x(col.petal_length), y(col.species), x_label("Petal length (cm)"),
  y_label("Species"), title("The same 95% CI, lying down"))
2 3 4 5 virginica versicolor setosa The same 95% CI, lying down Species Petal length (cm)

The caps turn with the whisker, since a cap always crosses the span at its end, and the center dot stays at the center. Everything else in this chapter is unchanged: pattern, color and dodge behave the same whichever axis carries the categories, and so do the two settings the later sections introduce.

This is the usual shape for a coefficient plot, where each row is a named estimate and the reader scans down a column of intervals. Estimates like these normally arrive already computed, from a model rather than from raw observations. That is what bounds is for: it reads two columns you already have as the low and the high, and computes nothing. With the terms on y, the measured axis is the one bounds invents, so there is no x() to write:

coefs: all 5 rows
term lo hi
Age 0.02 0.18
Education 0.31 0.55
Experience 0.11 0.29
Region: North -0.24 0.06
Region: South -0.05 0.21
data(coefs) + interval * bounds(lo, hi) + y(term) +
  x_label("Effect on log wage") +
  title("A coefficient plot, from bounds you already have")
(data(coefs) + interval * bounds(col.lo, col.hi) + y(col.term) +
  x_label("Effect on log wage") +
  title("A coefficient plot, from bounds you already have"))
data(coefs) + interval * bounds(:lo, :hi) + y(:term) +
  x_label("Effect on log wage") +
  title("A coefficient plot, from bounds you already have")
plot(data(coefs), layer(interval, bounds(col.lo, col.hi)), y(col.term),
  x_label("Effect on log wage"),
  title("A coefficient plot, from bounds you already have"))
-0.2 0.0 0.2 0.4 Region: South Region: North Experience Education Age A coefficient plot, from bounds you already have Term Effect on log wage

“Given the coefs table: intervals from lo to hi, y is term.”

The two intervals that cross zero cover positive and negative values alike, so the data does not settle whether those two effects are positive or negative.

An interval needs a number to span, so two categorical axes are refused, as they are on a bar and a box. What it refuses shows that refusal.

13.8 Splitting by color

A color says which whisker is which without reading the axis. It is also the only way to put a second category on the same axis. color splits an interval into one whisker per group, the same categorical split line and bar make. The statistic is computed inside each group, so each gets its own interval, its own hue, and a legend:

data(iris_flowers) + interval * confidence(0.95) + x(species) + y(petal_length) +
  color(species) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("95% CI of mean petal length, by species")
(data(iris_flowers) + interval * confidence(0.95) + x(col.species) + y(col.petal_length) +
  color(col.species) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("95% CI of mean petal length, by species"))
data(iris_flowers) + interval * confidence(0.95) + x(:species) +
  y(:petal_length) + color(:species) + x_label("Species") +
  y_label("Petal length (cm)") +
  title("95% CI of mean petal length, by species")
plot(data(iris_flowers), layer(interval, confidence(0.95)),
  x(col.species), y(col.petal_length), color(col.species),
  x_label("Species"), y_label("Petal length (cm)"),
  title("95% CI of mean petal length, by species"))
setosa versicolor virginica 2 3 4 5 95% CI of mean petal length, by species Petal length (cm) Species Species setosa versicolor virginica

“Given the iris flowers: intervals derived by confidence at 95%, x is species, y is petal length, color by species.”

When the split is a second category, so several whiskers share one x, they overlap, and dodge sets them side by side, the same collision modifier that separates grouped bars and boxes:

gm_eras: first 5 of 284 rows
country continent year life population gdp era
Afghanistan Asia 1957 30.332 9240934 820.8530 1957
Afghanistan Asia 2007 43.828 31889923 974.5803 2007
Albania Europe 1957 59.280 1476505 1942.2842 1957
Albania Europe 2007 76.423 3600523 5937.0295 2007
Algeria Africa 1957 45.685 10270856 3013.9760 1957
data(gm_eras) + interval * range * dodge + x(continent) + y(life) + color(era) +
  y_label("Life expectancy range") +
  title("Life expectancy range per continent, 1957 vs 2007")
(data(gm_eras) + interval * range * dodge + x(col.continent) + y(col.life) + color(col.era) +
  y_label("Life expectancy range") +
  title("Life expectancy range per continent, 1957 vs 2007"))
data(gm_eras) + interval * range * dodge + x(:continent) + y(:life) +
  color(:era) + y_label("Life expectancy range") +
  title("Life expectancy range per continent, 1957 vs 2007")
plot(data(gm_eras), layer(interval, range, dodge), x(col.continent),
  y(col.life), color(col.era), y_label("Life expectancy range"),
  title("Life expectancy range per continent, 1957 vs 2007"))
Asia Europe Africa Americas Oceania 40 60 80 Life expectancy range per continent, 1957 vs 2007 Life expectancy range Continent Era 1957 2007

“Given the gapminder eras: intervals derived by range and dodge, x is continent, y is life, color by era.”

Width and opacity stay settings. One whisker is a single stroke, so there is no row for size or opacity to vary over, and style() sets them for the layer:

data(iris_flowers) + interval * range + x(species) + y(petal_length) +
  style(color = "tomato", size = 2.5) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("A heavier, one-color range")
(data(iris_flowers) + interval * range + x(col.species) + y(col.petal_length) +
  style(color = "tomato", size = 2.5) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("A heavier, one-color range"))
data(iris_flowers) + interval * range + x(:species) + y(:petal_length) +
  style(color = "tomato", size = 2.5) + x_label("Species") +
  y_label("Petal length (cm)") + title("A heavier, one-color range")
plot(data(iris_flowers), layer(interval, range), x(col.species),
  y(col.petal_length), style({ color: "tomato", size: 2.5 }),
  x_label("Species"), y_label("Petal length (cm)"),
  title("A heavier, one-color range"))
setosa versicolor virginica 2 4 6 A heavier, one-color range Petal length (cm) Species

“Given the iris flowers: intervals derived by range, x is species, y is petal length, colored tomato, with size 2.5.”

13.9 Caps, or a bare linerange

Whether a whisker ends in a crossbar is a matter of style, and a crowded plot often reads more cleanly without them. The end caps, the short crossbars, are decoration, not data; they only emphasize where a whisker stops. So whether to draw them is a setting: style(caps = FALSE) drops them for a bare linerange, and caps = TRUE (the default) keeps the capped error bar.

data(iris_flowers) + interval * confidence(0.95) + x(species) + y(petal_length) +
  color(species) + style(caps = FALSE) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("The same CI, drawn as a linerange")
(data(iris_flowers) + interval * confidence(0.95) + x(col.species) + y(col.petal_length) +
  color(col.species) + style(caps = False) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("The same CI, drawn as a linerange"))
data(iris_flowers) + interval * confidence(0.95) + x(:species) +
  y(:petal_length) + color(:species) + style(caps = false) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("The same CI, drawn as a linerange")
plot(data(iris_flowers), layer(interval, confidence(0.95)),
  x(col.species), y(col.petal_length), color(col.species),
  style({ caps: false }), x_label("Species"),
  y_label("Petal length (cm)"),
  title("The same CI, drawn as a linerange"))
setosa versicolor virginica 2 3 4 5 The same CI, drawn as a linerange Petal length (cm) Species Species setosa versicolor virginica

“Given the iris flowers: intervals derived by confidence at 95%, x is species, y is petal length, color by species, with caps off.”

Because caps are geometry only interval has, style(caps = ) on any other mark is refused with direction, the same way a bar’s border_size is refused on a line. caps combines with range, confidence and deviation, and with the color split. So error bar, linerange, pointrange and their colored versions are one mark, one transform and one setting.

13.10 Hiding the center dot

A pointrange’s dot is the mean; sometimes you want the confidence whiskers without it, a plain error bar. style(center = FALSE) drops the dot, and center = TRUE (the default) keeps it. Like caps, it only hides: it cannot add a center to range, which has none to draw.

data(iris_flowers) + interval * confidence(0.95) + x(species) + y(petal_length) +
  color(species) + style(center = FALSE) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("The same CI, without its center dots")
(data(iris_flowers) + interval * confidence(0.95) + x(col.species) + y(col.petal_length) +
  color(col.species) + style(center = False) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("The same CI, without its center dots"))
data(iris_flowers) + interval * confidence(0.95) + x(:species) +
  y(:petal_length) + color(:species) + style(center = false) +
  x_label("Species") + y_label("Petal length (cm)") +
  title("The same CI, without its center dots")
plot(data(iris_flowers), layer(interval, confidence(0.95)),
  x(col.species), y(col.petal_length), color(col.species),
  style({ center: false }), x_label("Species"),
  y_label("Petal length (cm)"),
  title("The same CI, without its center dots"))
setosa versicolor virginica 2 3 4 5 The same CI, without its center dots Petal length (cm) Species Species setosa versicolor virginica

“Given the iris flowers: intervals derived by confidence at 95%, x is species, y is petal length, color by species, with center off.”

center and caps are the interval’s two settings, one for its middle and one for its ends, so a confidence interval draws four ways. A capped pointrange is the default. A capped bare error bar is center = FALSE. A linerange with a dot is caps = FALSE, and a plain linerange is both FALSE. Like style(caps = ), style(center = ) belongs to interval alone; on any other mark it is refused with direction.

13.11 A dashed whisker

A dash separates one layer from another without a second color, and it survives a print in black and white. A whisker is a stroke, so it takes style(pattern = ) the same way a line does. This is the settable rule: a dash patterns every path-stroke mark (line, step, interval, path, rule). The dash is drawn on the whisker span only. The caps and the center dot stay solid, since a dashed crossbar is hard to read as a cap:

data(iris_flowers) + interval * confidence(0.95) + x(species) + y(petal_length) +
  color(species) + style(pattern = "dashed") +
  x_label("Species") + y_label("Petal length (cm)") +
  title("A dashed 95% CI")
(data(iris_flowers) + interval * confidence(0.95) + x(col.species) + y(col.petal_length) +
  color(col.species) + style(pattern = "dashed") +
  x_label("Species") + y_label("Petal length (cm)") +
  title("A dashed 95% CI"))
data(iris_flowers) + interval * confidence(0.95) + x(:species) +
  y(:petal_length) + color(:species) + style(pattern = "dashed") +
  x_label("Species") + y_label("Petal length (cm)") +
  title("A dashed 95% CI")
plot(data(iris_flowers), layer(interval, confidence(0.95)),
  x(col.species), y(col.petal_length), color(col.species),
  style({ pattern: "dashed" }), x_label("Species"),
  y_label("Petal length (cm)"), title("A dashed 95% CI"))
setosa versicolor virginica 2 3 4 5 A dashed 95% CI Petal length (cm) Species Species setosa versicolor virginica

“Given the iris flowers: intervals derived by confidence at 95%, x is species, y is petal length, color by species, with pattern dashed.”

The five-number summary has its own mark, box, and the same low and high pair drawn as a continuous fill is the ribbon band. See Transforms for where range, confidence and deviation sit among the others.

13.12 What you can set

A setting changes how every whisker looks without reading a column, and each mark takes its own. These are an interval’s, with the values each accepts:

Setting Value
style(color = ) any CSS color name or hex
style(opacity = ) 0 to 1
style(size = ) pixels
style(pattern = ) solid, dashed, dotted
style(caps = ) TRUE, FALSE
style(center = ) TRUE, FALSE

caps and center are the interval’s own settings, and both only ever hide. caps turns the end crossbars off, leaving a bare linerange; center hides the dot that confidence supplies. Neither can add geometry the statistic did not produce, so style(center = TRUE) on interval * range changes nothing: a range has no center. The plot below reads gm_continents, three continents across every year from 1952 to 2007, so each interval pools twelve years of countries.

gm_continents: first 5 of 1056 rows
country continent year life population gdp
Afghanistan Asia 1952 28.801 8425333 779.4453
Afghanistan Asia 1957 30.332 9240934 820.8530
Afghanistan Asia 1962 31.997 10267083 853.1007
Afghanistan Asia 1967 34.020 11537966 836.1971
Afghanistan Asia 1972 36.088 13079460 739.9811
data(gm_continents) + interval * confidence(0.99) + x(continent) + y(life) +
  style(caps = FALSE, size = 3, color = "darkslateblue", pattern = "dotted") +
  y_label("Life expectancy") + title("A bare, thick, dotted 99% interval")
(data(gm_continents) + interval * confidence(0.99) + x(col.continent) + y(col.life) +
  style(caps = False, size = 3, color = "darkslateblue", pattern = "dotted") +
  y_label("Life expectancy") + title("A bare, thick, dotted 99% interval"))
data(gm_continents) + interval * confidence(0.99) + x(:continent) +
  y(:life) +
  style(caps = false, size = 3, color = "darkslateblue", pattern = "dotted") +
  y_label("Life expectancy") + title("A bare, thick, dotted 99% interval")
plot(data(gm_continents), layer(interval, confidence(0.99)),
  x(col.continent), y(col.life),
  style({ caps: false, size: 3, color: "darkslateblue",
  pattern: "dotted" }), y_label("Life expectancy"),
  title("A bare, thick, dotted 99% interval"))
Asia Europe Americas 60 65 70 A bare, thick, dotted 99% interval Life expectancy Continent

One number is missing from that table, and the transform carries it: confidence(0.99) widens the interval from the default 0.95. That belongs to the transform rather than to style(), because it changes what is computed, not how it is drawn. The test is whether the numbers change: they do here, so it is not a setting.

The grid of every mark and every setting shows which other marks share this list. What a mark maps rather than sets is its row on the companion grid.

13.13 What it refuses

An interval is a stroke, however much it looks like a bar, so its pattern is a dash and a hatch is refused:

gapminder_2007: first 5 of 142 rows
country continent year life population gdp
Afghanistan Asia 2007 43.828 31889923 974.5803
Albania Europe 2007 76.423 3600523 5937.0295
Algeria Africa 2007 72.301 33333216 6223.3675
Angola Africa 2007 42.731 12420476 4797.2313
Argentina Americas 2007 75.320 40301927 12779.3796
data(gapminder_2007) + interval * range + x(continent) + y(life) +
  style(pattern = "hatch")
(data(gapminder_2007) + interval * range + x(col.continent) + y(col.life) +
  style(pattern = "hatch"))
data(gapminder_2007) + interval * range + x(:continent) + y(:life) +
  style(pattern = "hatch")
plot(data(gapminder_2007), layer(interval, range), x(col.continent),
  y(col.life), style({ pattern: "hatch" }))
Error:
! gog: `style(pattern = "hatch")` is not a stroke pattern. Use "solid" (the default), "dashed", or "dotted".
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

That refusal is worth explaining, because a capped range looks like a shape with an inside. The engine draws it as a stroke, which is why the whisker takes a dash and not a hatch.

A bare interval is the second refusal. A plain y() supplies one number, and a span needs two, as Why interval needs a transform explained. The engine says so rather than drawing an empty panel:

data(iris_flowers) + interval + x(species) + y(petal_length)
data(iris_flowers) + interval + x(col.species) + y(col.petal_length)
data(iris_flowers) + interval + x(:species) + y(:petal_length)
plot(data(iris_flowers), interval, x(col.species), y(col.petal_length))
Error:
! gog: `interval` draws a span from a low value to a high one, but nothing here produces those extents. Add a range transform — `interval * range + x(group) + y(value)` draws the min–max range per group, or `interval * bounds(lo, hi)` a pre-computed one.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

The message lists the transforms that produce a range. Two categorical axes are the third refusal. An interval reads its orientation off the bindings, so one axis has to be continuous:

data(gapminder_2007) + interval * range + x(continent) + y(continent)
data(gapminder_2007) + interval * range + x(col.continent) + y(col.continent)
data(gapminder_2007) + interval * range + x(:continent) + y(:continent)
plot(data(gapminder_2007), layer(interval, range), x(col.continent),
  y(col.continent))
Error:
! gog: `interval` has categorical columns on both axes — `x(continent)` and `y(continent)` — so there is nothing for it to span. One axis must be a number: that is the column the low and high extents come from.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

That continuous column is where the low and the high come from. Two categories leave the mark nothing to span.