Powered by AppSignal & Oban Pro

Points: geom_point

notebooks/geom_point.livemd

Points: geom_point

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

alias Giggly.Plot

mtcars = Giggly.Datasets.mtcars()
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_point(), 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.

Points draw a scatter plot: one point per row of the data, at its x and y. 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 most examples is Giggly.Datasets.mtcars/0, R's mtcars: 32 cars from the 1974 Motor Trend US magazine, which the setup cell binds to mtcars, R's name for it; the large dataset is Giggly.Datasets.diamonds/0, ggplot2's diamonds: 53,940 diamonds, bound to diamonds.

A scatter plot

p <- ggplot(mtcars, aes(wt, mpg))
p + geom_point()
p = Plot.new(mtcars, %{x: :wt, y: :mpg})

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

Mapping colour, shape and size

# Add aesthetic mappings
p + geom_point(aes(colour = factor(cyl)))
p + geom_point(aes(shape = factor(cyl)))
# A "bubblechart":
p + geom_point(aes(size = qsec))

Giggly writes factor(cyl) as {:factor, [:cyl]}.

p
|> Plot.geom_point(%{colour: {:factor, [:cyl]}})
|> render.()
p
|> Plot.geom_point(%{shape: {:factor, [:cyl]}})
|> render.()
p
|> Plot.geom_point(%{size: :qsec})
|> render.()

Setting an aesthetic to a fixed value

# Set aesthetics to fixed value
ggplot(mtcars, aes(wt, mpg)) + geom_point(colour = "red", size = 3)
mtcars
|> Plot.new(%{x: :wt, y: :mpg})
|> Plot.geom_point(colour: "red", size: 3)
|> render.()

Transparency for large datasets

# Varying alpha is useful for large datasets
d <- ggplot(diamonds, aes(carat, price))
d + geom_point(alpha = 1/10)
d + geom_point(alpha = 1/20)
d + geom_point(alpha = 1/100)

Each plot draws all 53,940 diamonds, so the fainter the points, the more only the dense regions show.

plot = Plot.new(diamonds, %{x: :carat, y: :price})

plot
|> Plot.geom_point(alpha: 1 / 10)
|> render.()
plot
|> Plot.geom_point(alpha: 1 / 20)
|> render.()
plot
|> Plot.geom_point(alpha: 1 / 100)
|> render.()

A point's fill and border

# For shapes that have a border (like 21), you can colour the inside and
# outside separately. Use the stroke aesthetic to modify the width of the
# border
ggplot(mtcars, aes(wt, mpg)) +
  geom_point(shape = 21, colour = "black", fill = "white", size = 5, stroke = 5)
mtcars
|> Plot.new(%{x: :wt, y: :mpg})
|> Plot.geom_point(shape: 21, colour: "black", fill: "white", size: 5, stroke: 5)
|> render.()

A legend that shows the fill

# The default shape in legends is not filled, but you can override the shape
# in the guide to reflect the fill in the legend
ggplot(mtcars, aes(wt, mpg, fill = factor(carb), shape = factor(cyl))) +
  geom_point(size = 5, stroke = 1) +
  scale_shape_manual(values = 21:25) +
  scale_fill_ordinal(guide = guide_legend(override.aes = list(shape = 21)))

Not in Giggly yet: scale_fill_ordinal() and guide_legend()'s override.aes option.

Layering points of different sizes

# You can create interesting shapes by layering multiple points of
# different sizes
p <- ggplot(mtcars, aes(mpg, wt, shape = factor(cyl)))
p +
  geom_point(aes(colour = factor(cyl)), size = 4) +
  geom_point(colour = "grey90", size = 1.5)
p +
  geom_point(colour = "black", size = 4.5) +
  geom_point(colour = "pink", size = 4) +
  geom_point(aes(shape = factor(cyl)))

R's "grey90" is a name CSS lacks, so the cell writes its hex, "#E5E5E5".

p = Plot.new(mtcars, %{x: :mpg, y: :wt, shape: {:factor, [:cyl]}})

p
|> Plot.geom_point(%{colour: {:factor, [:cyl]}}, size: 4)
|> Plot.geom_point(colour: "#E5E5E5", size: 1.5)
|> render.()
p
|> Plot.geom_point(colour: "black", size: 4.5)
|> Plot.geom_point(colour: "pink", size: 4)
|> Plot.geom_point(%{shape: {:factor, [:cyl]}})
|> render.()

Missing values

ggplot2's example blanks the mpg of about a fifth of the cars at random, with R's set.seed(1), and draws the rest: a row without a y has no point.

# geom_point warns when missing values have been dropped from the data set
# and not plotted, you can turn this off by setting na.rm = TRUE
set.seed(1)
mtcars2 <- transform(mtcars, mpg = ifelse(runif(32) < 0.2, NA, mpg))
ggplot(mtcars2, aes(wt, mpg)) +
  geom_point()

R blanks four cars there; Giggly's version blanks the same four by name. ggplot2 also warns that it removed them. Giggly drops them without a warning, since drawing a plot has no side effects.

blanked = ["Merc 280", "Merc 450SE", "Camaro Z28", "Porsche 914-2"]

mpg =
  Enum.zip_with(Keyword.fetch!(mtcars, :model), Keyword.fetch!(mtcars, :mpg), fn model, value ->
    if model in blanked, do: nil, else: value
  end)

mtcars2 = List.keyreplace(mtcars, :mpg, 0, {:mpg, mpg})

mtcars2
|> Plot.new(%{x: :wt, y: :mpg})
|> Plot.geom_point()
|> render.()
ggplot(mtcars2, aes(wt, mpg)) +
  geom_point(na.rm = TRUE)

Not in Giggly yet: the na.rm option. Giggly never warns about a dropped row, so the drawing above is already what it would draw.