Powered by AppSignal & Oban Pro

Aesthetic evaluation: aes_eval

notebooks/aes_eval.livemd

Aesthetic evaluation: aes_eval

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 after_stat(), which covers after_scale(), from_theme() and stage() 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.

ggplot2 evaluates a mapping at one of three stages: at the start, from the layer's data; after the stat, from the variables the stat computes, with after_stat(); or after the scales, from the aesthetics already mapped, with after_scale(). stage() maps one aesthetic at more than one of them, and from_theme() takes a colour from the theme's settings for geoms. Giggly maps at the start and after the stat: a mapping value is a column, an expression or a constant evaluated from the layer's data, or {:after_stat, [value]}, a value evaluated from what its stat computes: counting (Giggly.Plot.geom_bar/3), summing (Giggly.Plot.geom_count/3), binning (Giggly.Plot.geom_histogram/3), a box plot's summary (Giggly.Plot.geom_boxplot/3), a density (Giggly.Plot.geom_density/3) or a smooth's fit (Giggly.Plot.geom_smooth/3). It has no after_scale(), stage() or from_theme(), and no whole-column function in a mapping, such as max(count). 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. It binds Giggly.Datasets.mpg/0, ggplot2's mpg, to mpg, ggplot2's name for it.

The default histogram

# Default histogram display
ggplot(mpg, aes(displ)) +
  geom_histogram(aes(y = after_stat(count)))

after_stat(count) is what geom_histogram() maps y to anyway. Giggly writes it {:after_stat, [:count]}.

Plot.new(mpg, %{x: :displ})
|> Plot.geom_histogram(%{y: {:after_stat, [:count]}})
|> render.()

The tallest bin scaled to 1

# Scale tallest bin to 1
ggplot(mpg, aes(displ)) +
  geom_histogram(aes(y = after_stat(count / max(count))))

Not in Giggly yet: a whole-column function in a mapping, here max(count) inside after_stat().

A fill made from the colour

# Use a transparent version of colour for fill
ggplot(mpg, aes(class, hwy)) +
  geom_boxplot(aes(colour = class, fill = after_scale(alpha(colour, 0.4))))

after_scale() takes each box's colour once the scale has mapped class to it, and alpha() makes it translucent.

Not in Giggly yet: after_scale().

Changing a scaled fill

# Use stage to modify the scaled fill
ggplot(mpg, aes(class, hwy)) +
  geom_boxplot(aes(fill = stage(class, after_scale = alpha(fill, 0.4))))

stage() maps fill from class at the start, and then makes the colour the scale gives it translucent.

Not in Giggly yet: stage().

A proportional stacked density

# Making a proportional stacked density plot
ggplot(mpg, aes(cty)) +
  geom_density(
    aes(
      colour = factor(cyl),
      fill = after_scale(alpha(colour, 0.3)),
      y = after_stat(count / sum(n[!duplicated(group)]))
    ),
    position = "stack", bw = 1
  ) +
  geom_density(bw = 1)

Each group's density is scaled by its share of all the cars, so the stacked densities add up to the density of all of them, which the second layer draws.

Not in Giggly yet: after_scale(), and a whole-column function in a mapping, here sum(n[!duplicated(group)]) inside after_stat().

A ridgeline

# Imitating a ridgeline plot
ggplot(mpg, aes(cty, colour = factor(cyl))) +
  geom_ribbon(
    stat = "density", outline.type = "upper",
    aes(
      fill = after_scale(alpha(colour, 0.3)),
      ymin = after_stat(group),
      ymax = after_stat(group + ndensity)
    )
  )

Each group's density, scaled to a peak of 1, rises from a baseline at its group's number, so the curves sit one above another.

Not in Giggly yet: stat_density() given to a geom as stat = "density", after_scale(), and after_stat() reading group.

Labelling bars with their counts

# Labelling a bar plot
ggplot(mpg, aes(class)) +
  geom_bar() +
  geom_text(
    aes(
      y = after_stat(count + 2),
      label = after_stat(count)
    ),
    stat = "count"
  )

The text layer counts too, with stat = "count", and writes each count just above its bar. The bars alone are the counted bars Giggly.Plot.geom_bar/3 draws.

Not in Giggly yet: stat_count() given to a geom as stat = "count".

Labelling a box plot

# Labelling the upper hinge of a boxplot,
# inspired by June Choe
ggplot(mpg, aes(displ, class)) +
  geom_boxplot(outlier.shape = NA) +
  geom_text(
    aes(
      label = after_stat(xmax),
      x = stage(displ, after_stat = xmax)
    ),
    stat = "boxplot", hjust = -0.5
  )

The text layer computes the box plot's statistics too, with stat = "boxplot": stage() gives it displ to compute them from, and then places each label at the computed xmax.

Not in Giggly yet: stat_boxplot() given to a geom as stat = "boxplot", and stage(). Giggly's box plot hides its outliers with outlier_shape: :none, as outlier.shape = NA does.