Powered by AppSignal & Oban Pro

Facet grids: facet_grid

notebooks/facet_grid.livemd

Facet grids: facet_grid

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

alias Giggly.Plot

mpg = Giggly.Datasets.mpg()
mtcars = Giggly.Datasets.mtcars()

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

facet_grid() lays a plot's panels out as a matrix: a row of panels for each value of one set of columns and a column of panels for each value of another. It suits two discrete columns whose every combination occurs in the data; for one column with many values, ggplot2 suggests facet_wrap(). Its scales can be shared by every panel or free to vary along the rows or the columns, panels can take space in proportion to their scales, and margins add panels that hold the data for every value of a column. Giggly has Giggly.Plot.facet_grid/2, which takes the rows' columns as rows:, the columns' as cols:, and scales:, shared or free along either. It takes no space, margins, axes, axis.labels, labeller, switch, as.table, drop or shrink options. 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 is Giggly.Datasets.mpg/0, ggplot2's mpg: fuel economy for 234 cars of 38 popular models from 1999 to 2008; and Giggly.Datasets.mtcars/0, R's mtcars: 32 cars from the 1974 Motor Trend US magazine. The setup cell binds them to mpg and mtcars, R's names for them.

Rows, columns, or both

p <- ggplot(mpg, aes(displ, cty)) + geom_point()

# Use vars() to supply variables from the dataset:
p + facet_grid(rows = vars(drv))
p + facet_grid(cols = vars(cyl))
p + facet_grid(vars(drv), vars(cyl))

ggplot2 names the columns with vars(), and the last call passes the rows' and the columns' by position, rows first; Giggly names each column as a mapping does, by an atom or a string, given to rows: or cols:.

plot =
  mpg
  |> Plot.new(%{x: :displ, y: :cty})
  |> Plot.geom_point()

plot
|> Plot.facet_grid(rows: :drv)
|> render.()
plot
|> Plot.facet_grid(cols: :cyl)
|> render.()
plot
|> Plot.facet_grid(rows: :drv, cols: :cyl)
|> render.()

The order of the panels

# To change plot order of facet grid,
# change the order of variable levels with factor()

This is advice rather than an example: the panels follow the order of a column's levels, which R sets with factor(). Giggly.Plot.facet_grid/2's rows and columns of panels follow a column's levels too, which Giggly.Factor.new/2 sets in the table.

Data repeated across panels

df is a single row, the mean displ and cty of all the cars, drawn as a red point in every panel.

# If you combine a facetted dataset with a dataset that lacks those
# faceting variables, the data will be repeated across the missing
# combinations:
df <- data.frame(displ = mean(mpg$displ), cty = mean(mpg$cty))
p +
  facet_grid(cols = vars(cyl)) +
  geom_point(data = df, colour = "red", size = 2)

The means are base R's mean(), so the cell writes out the two values R gives. A layer given a table of its own by data: draws that table, and one lacking the facet's columns is drawn in every panel.

df = %{"displ" => [3.471794871794872], "cty" => [16.858974358974358]}

plot
|> Plot.facet_grid(cols: :cyl)
|> Plot.geom_point(data: df, colour: "red", size: 2)
|> render.()

Axes on every panel

year ~ drv is facet_grid()'s formula form: the rows' columns left of the ~, the columns' right of it, and . for none.

# When scales are constant, duplicated axes can be shown with
# or without labels
ggplot(mpg, aes(cty, hwy)) +
  geom_point() +
  facet_grid(year ~ drv, axes = "all", axis.labels = "all_x")

Not in Giggly yet: axes and axis.labels, which here draw both axes on every panel but label only the x axes.

Free scales

# Free scales -------------------------------------------------------
# You can also choose whether the scales should be constant
# across all panels (the default), or whether they should be allowed
# to vary
mt <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) +
  geom_point()

mt + facet_grid(vars(cyl), scales = "free")

{:factor, [:cyl]} is R's factor(cyl), which makes the colour discrete. With scales: :free, each column of panels has an x scale of its own and each row a y scale.

mt =
  mtcars
  |> Plot.new(%{x: :mpg, y: :wt, colour: {:factor, [:cyl]}})
  |> Plot.geom_point()

mt
|> Plot.facet_grid(rows: :cyl, scales: :free)
|> render.()

Free space

# If scales and space are free, then the mapping between position
# and values in the data will be the same across all panels. This
# is particularly useful for categorical axes
ggplot(mpg, aes(drv, model)) +
  geom_point() +
  facet_grid(manufacturer ~ ., scales = "free", space = "free") +
  theme(strip.text.y = element_text(angle = 0))

Not in Giggly yet: space, which sizes each panel in proportion to its scales.

Margins

A margin is an extra row or column of panels, labelled (all), that holds the data for every value of a column.

# Margins ----------------------------------------------------------
# Margins can be specified logically (all yes or all no) or for specific
# variables as (character) variable names
mg <- ggplot(mtcars, aes(x = mpg, y = wt)) + geom_point()
mg + facet_grid(vs + am ~ gear, margins = TRUE)
mg + facet_grid(vs + am ~ gear, margins = "am")
# when margins are made over "vs", since the facets for "am" vary
# within the values of "vs", the marginal facet for "vs" is also
# a margin over "am".
mg + facet_grid(vs + am ~ gear, margins = "vs")

Not in Giggly yet: margins.