Visualize.Data.Table (Visualize v0.2.35)

Copy Markdown View Source

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, 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 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

name()

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

A column name, as the source spells it.

row()

@type row() :: map() | tuple()

A row: a map keyed by column name, or a tuple in column order.

source()

@type source() ::
  [row()] | %{required(name()) => [term()]} | [{name(), [term()]}] | struct()

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, or a struct implementing Table.Reader.

Functions

accessor(fun)

@spec accessor(name() | (row() -> term())) :: (row() -> term())

The accessor fn row -> get(row, name) end, for a shape's x/y setters.

A function is returned unchanged, so a generator can normalise any accessor form through this one call.

column_types(source)

@spec column_types(source()) :: %{
  required(name()) => :time | :number | :category | :text
}

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([])
%{}

get(row, name)

@spec get(map(), name()) :: term()

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.

rows(tensor)

@spec rows(source()) :: [row()]

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 lists is a list of columns; the rows are maps keyed by the keys. (table reads a list the same way, columns before rows.)
  • A map whose values are equal-length lists is a map of columns, rows keyed likewise. Any other plain map raises ArgumentError.
  • An Nx.Tensor of rank 2 yields one tuple per row, in column order — the shape the default Visualize.Shape.Line accessors read; rank 1 yields its values. Any other rank raises ArgumentError.
  • 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 no Table.Reader impl raises Protocol.UndefinedError; a reader answering :none raises ArgumentError.

The empty list, an empty map and an empty keyword list all yield [].

temporal?(string)

@spec temporal?(term()) :: boolean()

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).

time_columns(source)

@spec time_columns(source()) :: [name()]

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 [].