Giggly.Mapping (giggly v0.1.0)

Copy Markdown View Source

What a mapping maps each aesthetic to: a column, an expression computed from columns in each row, or a constant. A mapping is a map from aesthetic to value:

%{x: :displ, y: {&Kernel.//2, [:hwy, :cty]}, colour: {:constant, "smooth"}}

Values

  • A column is named by an atom or a string, compared with the table's column names as a string, so :hwy and "hwy" name the same column. true, false and nil name none.
  • An expression, {function, [value, ...]}, is the function applied in each row to what its arguments are in that row, so {&Kernel.//2, [:hwy, :cty]} divides each row's hwy by its cty. Each argument is a value, so it may be a column, a constant or another expression, and the function takes as many arguments as it is given.
  • A function by name, {:log10, [value]}, is a common function Elixir lacks, computed by the engine holding the table: log10 is the base-10 logarithm, minus infinity for 0, NaN for a negative number and missing for a missing value. Its value is a column, a constant or another function by name, never a capture or a closure, which the engine cannot run, so the logarithm of what a capture computes is a capture too, {&:math.log10/1, [value]}. Any other function is written as a capture.
  • A constant, {:constant, term}, is the term in every row. A number, a boolean, a date or a time is a constant written alone; a string or an atom would name a column, so a constant one is written {:constant, "smooth"}.
  • A factor, {:factor, [value]}, is the value read as discrete, so a column of numbers maps as levels: a discrete scale whatever the values, its levels in order of value (dates by date) with a missing one last, each written as text (a whole number without a decimal point, 4; a boolean as TRUE or FALSE), and the rows grouped by them. {:factor, [value], levels: [level, ...]} gives the levels and their order, each named as the value holds it ([8, 4, 6] for numbers), a value not among them missing, so the axis, the legend, the groups and stacking follow that order; ordered: true marks the factor's levels as ordered. A factor reads a whole column, so it is the whole of a mapping value, a factor's own or an after_stat's, never an expression's argument.
  • A reordering, {:reorder, [value, by]}, is value read as discrete, as a factor reads it, its levels ordered by the mean of by over each one's rows, the lowest first: levels whose means tie keep the order the factor of value gives them (its own levels where the table's column is a factor), and a level whose mean is missing, since its rows hold a missing or NaN by or there are none, goes last. The mean is R's mean(), worked out as R works it out, digit for digit. Each of value and by is a column or a function by name, which the engine holding the table computes, as it computes the means, and by is numbers; the levels are ordered, as ordered: true marks a factor's, where value is an ordered factor. A reordering reads whole columns, as a factor does, so it goes where a factor goes.
  • An after_stat, {:after_stat, [value]}, is the value computed from what the layer's stat computes: its names are the stat's computed variables (counting's are count and prop), and it is evaluated once the stat has run, so it takes no part in grouping or in what the stat computes. Its value is put through its aesthetic's scale. It is the whole of a mapping value, never inside another, so the share as a percentage is written {:after_stat, [{&Kernel.*/2, [:prop, 100]}]}.

A constant is a value a column can hold: nil, a list, and a value with no column type of its own (a map, a tuple, a struct other than a date or a time) are refused, and so are a function alone, a tuple that is neither an expression nor a constant, and a function given a number of arguments it does not take. A function by name is refused where the table's engine cannot run it, and log10 where its value is a column or a constant that is not numbers, a factor's column among them. A factor's levels are refused unless they are a list of one or more values a column can hold, of one type, none of them nil and none given twice (4 and 4.0 are one); levels of another type than the value's are refused when the plot is run. A reordering is refused where either of its values is a constant, a capture or a closure, a factor or another reordering, or where by is a column that is not numbers. A layer whose stat computes nothing, and so documents no variables, refuses every after_stat.

iex> plot =
...>   %{"hwy" => [29, 24], "cty" => [18, 16]}
...>   |> Giggly.Plot.new(%{x: :cty, y: {&Kernel.//2, [:hwy, :cty]}})
...>   |> Giggly.Plot.geom_point(%{colour: {:constant, "smooth"}})
iex> frame = Giggly.layer_data(plot)
iex> Giggly.DataFrame.pull(frame, :y)
[1.6111111111111112, 1.5]
iex> Giggly.DataFrame.pull(frame, :color)
["#F8766D", "#F8766D"]

Each class's share of all the rows, counted as one group:

iex> plot =
...>   %{"class" => ["suv", "compact", "suv", "midsize"]}
...>   |> Giggly.Plot.new(%{x: :class, y: {:after_stat, [:prop]}, group: 1})
...>   |> Giggly.Plot.geom_bar()
iex> frame = Giggly.layer_data(plot)
iex> Giggly.DataFrame.pull(frame, :y)
[0.25, 0.25, 0.5]

The cylinders in the order given, so the axis reads 8, 4, 6:

iex> plot =
...>   %{"cyl" => [4, 6, 8, 4], "mpg" => [26.0, 19.7, 15.0, 30.4]}
...>   |> Giggly.Plot.new(%{x: {:factor, [:cyl], levels: [8, 4, 6]}, y: :mpg})
...>   |> Giggly.Plot.geom_point()
iex> Giggly.panel_scales(plot, 1).x.levels
[8, 4, 6]

The classes in order of their mean highway mileage, 19 for the SUVs, 27 and 28:

iex> plot =
...>   %{"class" => ["suv", "compact", "suv", "midsize"], "hwy" => [18, 28, 20, 27]}
...>   |> Giggly.Plot.new(%{x: {:reorder, [:class, :hwy]}, y: :hwy})
...>   |> Giggly.Plot.geom_point()
iex> Giggly.panel_scales(plot, 1).x.levels
["suv", "midsize", "compact"]

Titles

A guide is titled by what its aesthetic is mapped to:

  • a column by its name, cty;
  • an expression by its text, as Elixir writes the call: a capture of one of Kernel's operators as the operator, hwy / cty, and any other capture as a call to the function it names, :math.log10(carat), as inspect/1 names it;
  • a function by name as a call to it, log10(carat);
  • an expression whose function is a closure, such as fn hwy -> hwy * 2 end, has no text, so its guide has no title until Giggly.Plot.labs/2 gives it one;
  • a factor by its value's text inside factor(), factor(cyl), and its options as R writes them, factor(cyl, levels = c(8, 4, 6), ordered = TRUE);
  • a reordering as a call to R's reorder(), reorder(class, hwy);
  • an after_stat by its value's text, prop;
  • a constant by its aesthetic's name, color, unless another layer maps the aesthetic to a column or an expression, whose title wins.

So a capture is the form to write where the title should say what was computed, and a closure the form to write inline, with a title from labs/2.

iex> svg =
...>   %{"carat" => [0.23, 1.5], "price" => [326, 18823]}
...>   |> Giggly.Plot.new(%{x: {:log10, [:carat]}, y: {:log10, [:price]}})
...>   |> Giggly.Plot.geom_point()
...>   |> Giggly.to_svg()
...>   |> IO.iodata_to_binary()
iex> svg =~ ">log10(carat)</text>" and svg =~ ">log10(price)</text>"
true

iex> svg =
...>   %{"hwy" => [29, 24], "cty" => [18, 16]}
...>   |> Giggly.Plot.new(%{x: :cty, y: {&Kernel.//2, [:hwy, :cty]}})
...>   |> Giggly.Plot.geom_point()
...>   |> Giggly.to_svg()
...>   |> IO.iodata_to_binary()
iex> svg =~ ">hwy / cty</text>"
true

Summary

Types

An after_stat: a value computed from the stat's computed variables.

A column, named by an atom or a string.

A constant: {:constant, term}, or a number, a boolean, a date or a time written alone.

An expression: a function and the values it is applied to in each row, or a function by name and the value it is computed from.

A factor: a value read as discrete, with the levels it holds and their order.

A reordering: a value read as discrete, its levels ordered by another's means.

A value as a plot holds it: a column by its name as a string, a constant as {:constant, term}, and an expression as its function and its arguments, each held so.

t()

A mapping as a plot holds it: aesthetic to resolved value.

A value as written: a column, an expression, a constant, a factor, a reordering or an after_stat.

Types

after_stat()

@type after_stat() :: {:after_stat, [value()]}

An after_stat: a value computed from the stat's computed variables.

column()

@type column() :: atom() | String.t()

A column, named by an atom or a string.

constant()

@type constant() ::
  {:constant, term()}
  | number()
  | boolean()
  | Date.t()
  | Time.t()
  | NaiveDateTime.t()
  | DateTime.t()

A constant: {:constant, term}, or a number, a boolean, a date or a time written alone.

expression()

@type expression() :: {function(), [value()]} | {:log10, [value()]}

An expression: a function and the values it is applied to in each row, or a function by name and the value it is computed from.

factor()

@type factor() ::
  {:factor, [value()]}
  | {:factor, [value()], levels: [term()], ordered: boolean()}

A factor: a value read as discrete, with the levels it holds and their order.

reordering()

@type reordering() :: {:reorder, [column() | expression()]}

A reordering: a value read as discrete, its levels ordered by another's means.

resolved()

@type resolved() ::
  String.t()
  | {:constant, term()}
  | {:factor, [resolved()]}
  | {:factor, [resolved()], levels: [term()], ordered: boolean()}
  | {:reorder, [resolved()]}
  | {:after_stat, [resolved()]}
  | {function(), [resolved()]}
  | {:log10, [resolved()]}

A value as a plot holds it: a column by its name as a string, a constant as {:constant, term}, and an expression as its function and its arguments, each held so.

t()

@type t() :: %{optional(atom()) => resolved()}

A mapping as a plot holds it: aesthetic to resolved value.

value()

@type value() ::
  column() | expression() | constant() | factor() | reordering() | after_stat()

A value as written: a column, an expression, a constant, a factor, a reordering or an after_stat.