Overlapping points: geom_count
Mix.install([
{:giggly, path: Path.join(__DIR__, "..")},
{:kino, "~> 0.19"}
])
alias Giggly.Plot
mpg = Giggly.Datasets.mpg()
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_count(), which covers
stat_sum() 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.
geom_count() is a variant of geom_point() for discrete data whose points overlap: stat_sum()
counts the rows at each position, and the count is mapped to the point's area. Giggly has both,
Giggly.Plot.geom_count/3 and Giggly.Plot.stat_sum/3, the count n and its proportion of the
group, prop, both read with {:after_stat, [...]}, and the size scales and legends that read them
back. 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 first examples use 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. The rest use ggplot2's diamonds, Giggly.Datasets.diamonds/0,
which the setup cell binds to diamonds, and whose cut and clarity are ordered factors, as R's
are, so each axis follows its levels.
Points that overlap
ggplot(mpg, aes(cty, hwy)) +
geom_point()
cty and hwy are whole numbers of miles per gallon, so many cars share a point, and one point
drawn over another looks like one.
mpg
|> Plot.new(%{x: :cty, y: :hwy})
|> Plot.geom_point()
|> render.()
Counting the points
ggplot(mpg, aes(cty, hwy)) +
geom_count()
Each point's area grows with the number of cars at its place, and the legend reads the counts back.
p =
mpg
|> Plot.new(%{x: :cty, y: :hwy})
|> Plot.geom_count()
render.(p)
Sizes by area
# Best used in conjunction with scale_size_area which ensures that
# counts of zero would be given size 0. Doesn't make much different
# here because the smallest count is already close to 0.
ggplot(mpg, aes(cty, hwy)) +
geom_count() +
scale_size_area()
p
|> Plot.scale_size_area()
|> render.()
Proportions of the whole panel
# Display proportions instead of counts -------------------------------------
# By default, all categorical variables in the plot form the groups.
# Specifying geom_count without a group identifier leads to a plot which is
# not useful:
d <- ggplot(diamonds, aes(x = cut, y = clarity))
d + geom_count(aes(size = after_stat(prop)))
# To correct this problem and achieve a more desirable plot, we need
# to specify which group the proportion is to be calculated over.
d + geom_count(aes(size = after_stat(prop), group = 1)) +
scale_size_area(max_size = 10)
With each combination of cut and clarity a group of its own, every proportion is 1, so every
point is the same size; group = 1 makes the whole panel one group, a constant in the mapping.
plot = Plot.new(diamonds, %{x: :cut, y: :clarity})
plot
|> Plot.geom_count(%{size: {:after_stat, [:prop]}})
|> render.()
plot
|> Plot.geom_count(%{size: {:after_stat, [:prop]}, group: 1})
|> Plot.scale_size_area(max_size: 10)
|> render.()
Proportions of each row or column
# Or group by x/y variables to have rows/columns sum to 1.
d + geom_count(aes(size = after_stat(prop), group = cut)) +
scale_size_area(max_size = 10)
d + geom_count(aes(size = after_stat(prop), group = clarity)) +
scale_size_area(max_size = 10)
Grouping by a column is a mapping of group.
plot
|> Plot.geom_count(%{size: {:after_stat, [:prop]}, group: :cut})
|> Plot.scale_size_area(max_size: 10)
|> render.()
plot
|> Plot.geom_count(%{size: {:after_stat, [:prop]}, group: :clarity})
|> Plot.scale_size_area(max_size: 10)
|> render.()