Powered by AppSignal & Oban Pro

Transformed coordinates: coord_transform

notebooks/coord_transform.livemd

Transformed coordinates: coord_transform

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

alias Giggly.Plot

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 coord_transform(), which covers coord_trans() 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.

coord_transform() transforms the coordinate system, such as putting an axis on a log scale. Unlike transforming a scale, it happens after the statistics are computed, and it changes the shapes geoms are drawn in, so a straight line need not stay straight. It transforms continuous positions only. coord_trans() is its old name, now deprecated. Giggly has neither yet, but transforms a scale, as Giggly.Plot.scale_x_log10/2 does, and the data, with an expression in the mapping. 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.diamonds/0, ggplot2's diamonds, to diamonds, ggplot2's name for it.

Transforming the data

# See ?geom_boxplot for other examples

# Three ways of doing transformation in ggplot:
#  * by transforming the data
ggplot(diamonds, aes(log10(carat), log10(price))) +
  geom_point()

Giggly names the base-10 logarithm as R does, {:log10, [:carat]}, and titles the axes as R does, log10(carat) and log10(price).

diamonds
|> Plot.new(%{x: {:log10, [:carat]}, y: {:log10, [:price]}})
|> Plot.geom_point()
|> render.()

Transforming the scales

#  * by transforming the scales
ggplot(diamonds, aes(carat, price)) +
  geom_point() +
  scale_x_log10() +
  scale_y_log10()

Each axis is placed on a log scale and labelled in the data's own units: 0.3, 1 and 3 carats, $300 to $10,000.

diamonds
|> Plot.new(%{x: :carat, y: :price})
|> Plot.geom_point()
|> Plot.scale_x_log10()
|> Plot.scale_y_log10()
|> render.()

Transforming the coordinate system

#  * by transforming the coordinate system:
ggplot(diamonds, aes(carat, price)) +
  geom_point() +
  coord_transform(x = "log10", y = "log10")

Not in Giggly yet: coord_transform().

A fitted line on transformed scales

R's subset() keeps the diamonds over half a carat.

# The difference between transforming the scales and
# transforming the coordinate system is that scale
# transformation occurs BEFORE statistics, and coordinate
# transformation afterwards.  Coordinate transformation also
# changes the shape of geoms:

d <- subset(diamonds, carat > 0.5)

ggplot(d, aes(carat, price)) +
  geom_point() +
  geom_smooth(method = "lm") +
  scale_x_log10() +
  scale_y_log10()

The cell keeps the rows subset() keeps, each column's values where carat is over 0.5. The scales transform the data before the stat, so the line is fitted to the logarithms and is straight on these axes.

kept = Enum.map(Keyword.fetch!(diamonds, :carat), fn carat -> carat > 0.5 end)

d =
  Enum.map(diamonds, fn {name, values} ->
    rows =
      values
      |> Enum.zip(kept)
      |> Enum.filter(fn {_value, keep} -> keep end)
      |> Enum.map(fn {value, _keep} -> value end)

    {name, rows}
  end)

d
|> Plot.new(%{x: :carat, y: :price})
|> Plot.geom_point()
|> Plot.geom_smooth(method: :lm)
|> Plot.scale_x_log10()
|> Plot.scale_y_log10()
|> render.()

A fitted line in transformed coordinates

ggplot(d, aes(carat, price)) +
  geom_point() +
  geom_smooth(method = "lm") +
  coord_transform(x = "log10", y = "log10")

# Here I used a subset of diamonds so that the smoothed line didn't
# drop below zero, which obviously causes problems on the log-transformed
# scale

Not in Giggly yet: coord_transform().

Transforming back

# With a combination of scale and coordinate transformation, it's
# possible to do back-transformations:
ggplot(diamonds, aes(carat, price)) +
  geom_point() +
  geom_smooth(method = "lm") +
  scale_x_log10() +
  scale_y_log10() +
  coord_transform(x = scales::transform_exp(10), y = scales::transform_exp(10))

Not in Giggly yet: coord_transform() with transformations from the scales package.

No transformation

# cf.
ggplot(diamonds, aes(carat, price)) +
  geom_point() +
  geom_smooth(method = "lm")
diamonds
|> Plot.new(%{x: :carat, y: :price})
|> Plot.geom_point()
|> Plot.geom_smooth(method: :lm)
|> render.()

A discrete axis

R's letters is the 26 lowercase letters, and abs(rnorm(26)) gives each one a random positive number, so a is continuous and letters discrete. Only the continuous x axis is transformed.

# Also works with discrete scales
set.seed(1)
df <- data.frame(a = abs(rnorm(26)),letters)
plot <- ggplot(df,aes(a,letters)) + geom_point()

plot + coord_transform(x = "log10")
plot + coord_transform(x = "sqrt")

Not in Giggly yet: coord_transform().