Tabular data as the row list the generators consume.
Every generator takes a list of rows. rows/1 turns the other shapes data arrives in —
a column-oriented map, a keyword list of columns, an Nx.Tensor, an Explorer DataFrame
or anything else implementing the Table.Reader protocol — into that list, and passes a
list of rows through untouched (spec/08 §6).
Visualize.Data.Table.rows(%{t: [1, 2, 3], v: [10, 20, 30]})
# => [%{t: 1, v: 10}, %{t: 2, v: 20}, %{t: 3, v: 30}]Sources that name their columns with strings (Explorer does) yield rows keyed by
strings; accessor/1 reads a column by either spelling, so :v reaches "v" and the
other way round. time_columns/1 names the columns whose every value is temporal, for a
time scale to be inferred from.
The table package is optional, like Nx: without it lists, column maps and tensors still
work, and a struct source raises UndefinedFunctionError at Table.to_rows/1 (D-52).
Summary
Types
A column name, as the source spells it.
A row: a map keyed by column name, or a tuple in column order.
Anything rows/1 accepts: a list of rows, a keyword list or map of equal-length columns,
an Nx.Tensor of rank 1 or 2, a Visualize.Data.Delivery, or a struct implementing
Table.Reader.
Functions
The accessor fn row -> get(row, name) end, for a shape's x/y setters.
Every named column's type, read whole (spec/08 §6.3, spec/14 §2.3, #369): all non-nil
values temporal is :time, all numbers :number, all strings :text, all atoms or
booleans :category; a mixed or empty column is left out. A suggestion for a tool, never
something the layer reads at render.
The value of column name in row, by either spelling of the name.
The key of row that spells name: name itself when it is a key, else the key of the
other spelling, nil when neither is.
A tensor's columns under names, in order: the column map rows/1 reads (spec/08 §6.4,
D-214).
The rows of a tabular source, as a list.
Whether a value is a point in time: a DateTime, Date or NaiveDateTime, or a string
in ISO 8601 form that one of those parses (DateTime.from_iso8601/1 first, then
NaiveDateTime.from_iso8601/1, then Date.from_iso8601/1).
The names of the columns whose values are all temporal.
Types
A column name, as the source spells it.
A row: a map keyed by column name, or a tuple in column order.
Anything rows/1 accepts: a list of rows, a keyword list or map of equal-length columns,
an Nx.Tensor of rank 1 or 2, a Visualize.Data.Delivery, or a struct implementing
Table.Reader.
Functions
The accessor fn row -> get(row, name) end, for a shape's x/y setters.
A function is returned unchanged, so a generator can normalize any accessor form through this one call.
Every named column's type, read whole (spec/08 §6.3, spec/14 §2.3, #369): all non-nil
values temporal is :time, all numbers :number, all strings :text, all atoms or
booleans :category; a mixed or empty column is left out. A suggestion for a tool, never
something the layer reads at render.
iex> rows = [%{t: ~U[2024-01-01 00:00:00Z], v: 1, k: :a, s: "x", m: 1},
...> %{t: ~U[2024-01-02 00:00:00Z], v: 2.5, k: :b, s: "y", m: "two"}]
iex> Visualize.Data.Table.column_types(rows)
%{t: :time, v: :number, k: :category, s: :text}
iex> Visualize.Data.Table.column_types([])
%{}
The value of column name in row, by either spelling of the name.
Map.get(row, name) when the key is present; otherwise an atom name is tried as its
string and a string name as the atom key spelled the same. nil when neither is a key.
No atom is ever created. A tuple row has no names and raises BadMapError.
The key of row that spells name: name itself when it is a key, else the key of the
other spelling, nil when neither is.
A design names a field by its string (spec/14 §1.4, D-195) and a host's rows are often keyed by atoms: the atom key a string finds is remembered for the calling process, so a frame that reads the same field from every row scans one row's keys, not all of them. No atom is ever created or looked up by name — the remembered key is one a row held.
iex> Visualize.Data.Table.key(%{cpu: 1}, "cpu")
:cpu
iex> Visualize.Data.Table.key(%{"cpu" => 1}, :cpu)
"cpu"
iex> Visualize.Data.Table.key(%{cpu: 1}, "mem")
nil
@spec named(source(), [name()]) :: {:ok, source()} | {:error, {:width, non_neg_integer(), non_neg_integer()} | {:rank, non_neg_integer()}}
A tensor's columns under names, in order: the column map rows/1 reads (spec/08 §6.4,
D-214).
Column i of a rank-2 tensor is Enum.at(names, i), and a rank-1 tensor is one column.
A tensor whose width is not length(names) is {:error, {:width, length(names), width}},
one of another rank {:error, {:rank, rank}}; nothing raises. Any other source is
{:ok, source}, the same term, so every source can pass through.
iex> Visualize.Data.Table.named([%{x: 1}], ["x"])
{:ok, [%{x: 1}]}
The rows of a tabular source, as a list.
- A list of rows is returned as-is: no copy, no traversal.
- A keyword list whose values are equal-length columns is a list of columns; the rows are
maps keyed by the keys. (
tablereads a list the same way, columns before rows.) A column is a list or a rank-1Nx.Tensor(D-214). - A map whose values are equal-length columns is a map of columns, rows keyed likewise.
Any other plain map raises
ArgumentError. - An
Nx.Tensorof rank 2 yields one tuple per row, in column order — the shape the defaultVisualize.Shape.Lineaccessors read; rank 1 yields its values. Any other rank raisesArgumentError. A tuple has no names:named/2gives a tensor's columns some. - A
Visualize.Data.Deliveryis the rows of the source it carries (spec/08 §10.4). - Any other struct is read through
Table.to_rows/1: rows keyed by the reader's column names as it spells them. A struct with noTable.Readerimpl raisesProtocol.UndefinedError; a reader answering:noneraisesArgumentError.
The empty list, an empty map and an empty keyword list all yield [].
Whether a value is a point in time: a DateTime, Date or NaiveDateTime, or a string
in ISO 8601 form that one of those parses (DateTime.from_iso8601/1 first, then
NaiveDateTime.from_iso8601/1, then Date.from_iso8601/1).
The names of the columns whose values are all temporal.
The candidate names are every key of every map row, sorted by term order (a map's own key
order is unspecified); a row without the key contributes nil. A column is temporal
when it has at least one value that is not nil and every value is nil or satisfies
temporal?/1. Detection reads the whole column, not a first row: every row of every
column is visited and every string is parsed, so the cost is the size of the table. A
source whose rows are tuples has no names and yields [].