Powered by AppSignal & Oban Pro

Facets: facet_wrap

notebooks/facet_wrap.livemd

Facets: facet_wrap

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

alias Giggly.Plot

mpg = Giggly.Datasets.mpg()

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 facet_wrap(), 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.

Giggly.Plot.facet_wrap/3 splits a plot into panels, one for each value of a column, or each combination of values of several, that occurs in the data, and wraps them into rows: a one-dimensional sequence of panels laid out in two dimensions. Each panel draws the rows with its values, and a strip above it says which those are. 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. The data in these examples is Giggly.Datasets.mpg/0, ggplot2's mpg: fuel economy for 234 cars of 38 popular models from 1999 to 2008, which the setup cell binds to mpg, ggplot2's name for it.

Facets by one column

ggplot2 names the columns to facet by with vars(); Giggly names a column as a mapping does, by an atom or a string.

p <- ggplot(mpg, aes(displ, hwy)) + geom_point()
# Use vars() to supply faceting variables:
p + facet_wrap(vars(class))
plot =
  mpg
  |> Plot.new(%{x: :displ, y: :hwy})
  |> Plot.geom_point()

plot
|> Plot.facet_wrap(:class)
|> render.()

The number of rows and columns

# Control the number of rows and columns with nrow and ncol
p + facet_wrap(vars(class), nrow = 4)
plot
|> Plot.facet_wrap(:class, nrow: 4)
|> render.()

Facets by several columns

A panel for each combination of the columns' values that occurs, with a strip for each column.

# You can facet by multiple variables
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(cyl, drv))
plot
|> Plot.facet_wrap([:cyl, :drv])
|> render.()

How strips are labelled

# Use the `labeller` option to control how labels are printed:
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(cyl, drv), labeller = "label_both")

Not in Giggly yet: labeller, which here writes each strip as cyl: 4 rather than 4.

The order of the panels

# To change the order in which the panels appear, change the levels
# of the underlying factor.
mpg$class2 <- reorder(mpg$class, mpg$displ)
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(class2))

reorder() gives class2 the values of class, its levels ordered by each class's mean displ, smallest first, and the panels follow the levels. Giggly reorders a mapping's values with {:reorder, [:class, :displ]}, but faceting by an expression waits, so the cell writes out the levels R's reorder() gives and adds the column to the table, as R does, with Giggly.Factor.new/2.

class2 =
  Giggly.Factor.new(Keyword.fetch!(mpg, :class),
    levels: ["compact", "subcompact", "midsize", "minivan", "pickup", "suv", "2seater"]
  )

mpg = mpg ++ [class2: class2]

mpg
|> Plot.new(%{x: :displ, y: :hwy})
|> Plot.geom_point()
|> Plot.facet_wrap(:class2)
|> render.()

Free scales

# By default, the same scales are used for all panels. You can allow
# scales to vary across the panels with the `scales` argument.
# Free scales make it easier to see patterns within each panel, but
# harder to compare across panels.
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(class), scales = "free")
plot
|> Plot.facet_wrap(:class, scales: :free)
|> render.()

Axes on every panel

# When scales are constant, duplicated axes can be shown with
# or without labels
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(class), axes = "all", axis.labels = "all_y")

Not in Giggly yet: axes and axis.labels, which draw an axis on every panel when the scales are fixed.

The same data in every panel

# To repeat the same data in every panel, simply construct a data frame
# that does not contain the faceting variable.
ggplot(mpg, aes(displ, hwy)) +
  geom_point(data = transform(mpg, class = NULL), colour = "grey85") +
  geom_point() +
  facet_wrap(vars(class))

A layer given a table of its own by data: draws that table; here it is mpg without its class column, so the facet repeats every car in every panel, in grey behind the panel's own. R's "grey85" is a name CSS lacks, so the cell writes its hex, "#D9D9D9".

mpg
|> Plot.new(%{x: :displ, y: :hwy})
|> Plot.geom_point(data: Keyword.delete(mpg, :class), colour: "#D9D9D9")
|> Plot.geom_point()
|> Plot.facet_wrap(:class)
|> render.()

Where the strips go

# Use `strip.position` to display the facet labels at the side of your
# choice. Setting it to `bottom` makes it act as a subtitle for the axis.
# This is typically used with free scales and a theme without boxes around
# strip labels.
ggplot(economics_long, aes(date, value)) +
  geom_line() +
  facet_wrap(vars(variable), scales = "free_y", nrow = 2, strip.position = "top") +
  theme(strip.background = element_blank(), strip.placement = "outside")

Not in Giggly yet: strip.position and the theme's strip.placement setting.

The direction the panels fill

# The two letters determine the starting position, so 'tr' starts
# in the top-right.
# The first letter determines direction, so 'tr' fills top-to-bottom.
# `dir = "tr"` is equivalent to `dir = "v", as.table = FALSE`
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(class), dir = "tr")

Not in Giggly yet: dir and as.table. Giggly's panels fill each row left to right, from the top.