Powered by AppSignal & Oban Pro

Box plots: geom_boxplot

notebooks/geom_boxplot.livemd

Box plots: geom_boxplot

Mix.install([
  {:giggly, path: Path.join(__DIR__, "..")},
  {:kino, "~> 0.19"}
  ])

alias Giggly.Plot

mpg = Giggly.Datasets.mpg()
diamonds = Giggly.Datasets.diamonds()

render = fn plot ->
  plot
  |> Giggly.to_svg()
  |> IO.iodata_to_binary()
  |> Kino.Image.new(:svg)
end

About this notebook

This notebook follows the examples on ggplot2's reference page for geom_boxplot(), which covers stat_boxplot() too, in the order the page gives them. Each example shows ggplot2's code first and then Giggly's. An example Giggly cannot draw yet keeps its ggplot2 code and names what it is waiting for, so the page is also a list of what remains.

A box plot shows the distribution of a continuous column in five numbers: the median, two hinges at the first and third quartiles, and two whiskers, each reaching to the furthest value within 1.5 times the inter-quartile range of its hinge. Every value beyond a whisker is drawn as a point of its own, an outlier. stat_boxplot() computes the five numbers for each group, and geom_boxplot() draws them, or draws numbers computed beforehand. Giggly has Giggly.Plot.geom_boxplot/3, which computes the five numbers for each group itself, takes ggplot2's options as keyword options (notch:, varwidth:, outlier_colour: and the look of each part), and sets boxes that share a position side by side as position_dodge2() does. It has no stat_boxplot() of its own, no geom_jitter() and no cut_width(), maps no lower, middle or upper, and cannot choose a layer's stat. A plot is described with Giggly.Plot and drawn with Giggly.to_svg/2, which returns an SVG document. The setup cell aliases Giggly.Plot, so the cells write Plot.new/2, and defines render, which draws a plot and shows it below the cell with Kino.Image. Most examples use Giggly.Datasets.mpg/0, ggplot2's mpg: fuel economy for 234 cars of 38 popular models from 1999 to 2008; the rest use ggplot2's diamonds, Giggly.Datasets.diamonds/0. The setup cell binds each to ggplot2's name for it.

A box for each class

p <- ggplot(mpg, aes(class, hwy))
p + geom_boxplot()
p = Plot.new(mpg, %{x: :class, y: :hwy})

p
|> Plot.geom_boxplot()
|> render.()

Horizontal boxes

# Orientation follows the discrete axis
ggplot(mpg, aes(hwy, class)) + geom_boxplot()

Giggly's boxes stand along the discrete axis too, here y.

Plot.new(mpg, %{x: :hwy, y: :class})
|> Plot.geom_boxplot()
|> render.()

Notches

p + geom_boxplot(notch = TRUE)

A notch around each median shows a rough 95% confidence interval for comparing medians. Where a notch reaches past its box's hinges, as two do here, R warns that the notch went outside the hinges; Giggly draws the same notches without a warning.

p
|> Plot.geom_boxplot(notch: true)
|> render.()

Widths that follow the group's size

p + geom_boxplot(varwidth = TRUE)

Each box's width is in proportion to the square root of its group's number of rows.

p
|> Plot.geom_boxplot(varwidth: true)
|> render.()

A fixed fill and colour

p + geom_boxplot(fill = "white", colour = "#3366FF")
p
|> Plot.geom_boxplot(fill: "white", colour: "#3366FF")
|> render.()

Outliers of their own colour

# By default, outlier points match the colour of the box. Use
# outlier.colour to override
p + geom_boxplot(outlier.colour = "red", outlier.shape = 1)

Shape 1 is a hollow circle. Giggly writes ggplot2's dotted option names with underscores.

p
|> Plot.geom_boxplot(outlier_colour: "red", outlier_shape: 1)
|> render.()

Boxes over the data's points

# Remove outliers when overlaying boxplot with original data points
p + geom_boxplot(outlier.shape = NA) + geom_jitter(width = 0.2)

Not in Giggly yet: geom_jitter(). Giggly hides the outliers with outlier_shape: :none, as outlier.shape = NA does.

Dodged boxes

# Boxplots are automatically dodged when any aesthetic is a factor
p + geom_boxplot(aes(colour = drv))

Mapping colour to drv splits each class into a box for each drive train, which Giggly sets side by side as position_dodge2() does, its default for boxes too.

p
|> Plot.geom_boxplot(%{colour: :drv})
|> render.()

A continuous x

# You can also use boxplots with continuous x, as long as you supply
# a grouping variable. cut_width is particularly useful
ggplot(diamonds, aes(carat, price)) +
  geom_boxplot()

Without a group, the whole of carat makes one box, nine tenths as wide as its range. R warns that a continuous x wants a group; Giggly draws the same box without a warning.

Plot.new(diamonds, %{x: :carat, y: :price})
|> Plot.geom_boxplot()
|> render.()
ggplot(diamonds, aes(carat, price)) +
  geom_boxplot(aes(group = cut_width(carat, 0.25)))

Not in Giggly yet: cut_width(), and a whole-column function in a mapping, such as cut_width(carat, 0.25).

Transparent outliers

# Adjust the transparency of outliers using outlier.alpha
ggplot(diamonds, aes(carat, price)) +
  geom_boxplot(aes(group = cut_width(carat, 0.25)), outlier.alpha = 0.1)

Not in Giggly yet: cut_width(), and a whole-column function in a mapping, such as cut_width(carat, 0.25). Giggly's outlier_alpha: sets the outliers' transparency.

Numbers computed beforehand

# It's possible to draw a boxplot with your own computations if you
# use stat = "identity":
set.seed(1)
y <- rnorm(100)
df <- data.frame(
  x = 1,
  y0 = min(y),
  y25 = quantile(y, 0.25),
  y50 = median(y),
  y75 = quantile(y, 0.75),
  y100 = max(y)
)
ggplot(df, aes(x)) +
  geom_boxplot(
   aes(ymin = y0, lower = y25, middle = y50, upper = y75, ymax = y100),
   stat = "identity"
 )

R draws 100 random numbers and puts their five numbers in a table of one row, which stat = "identity" draws as they are. Not in Giggly yet: mapping lower, middle and upper, and choosing a layer's stat, here stat = "identity".