# `Macaw.Data.Table`
[🔗](https://github.com/dcoai/macaw_data/blob/v0.5.3/lib/macaw/data/table.ex#L1)

Columnar typed data: the currency of deliveries, derived sources and components
(spec/01 §1.1).

A table is named columns of declared types, every column the same length:

| Type | Holds | Stored as |
|---|---|---|
| `:number` | integers and floats | one packed binary: signed 64-bit integers when every value is an integer that fits, else 64-bit floats |
| `:time` | `DateTime`s | one packed binary of signed 64-bit microseconds since the Unix epoch, UTC |
| `:category`, `:text`, `:color`, `:point`, `:term` | any value | a list |

**A missing value is `nil`,** in every type. A packed column keeps a validity bitmap
beside its binary, so a gap stays a gap: absence is never zero (spec/01 §3).

**The packed columns live off the process heap.** A binary over 64 bytes is
reference-counted, so a garbage collection never copies it: a table of a million numbers
costs its process a constant number of words, whatever its length.

**Row lists are accepted at the edge.** `from/2` reads every shape data arrives in (a
list of row maps, a map or keyword list of columns, an `Nx.Tensor`, anything implementing
`Table.Reader`) into a table, and `rows/1` is the row view for code that wants rows. The
row helpers (`get/2`, `key/2`, `accessor/1`, `temporal?/1`, `time_columns/1`,
`column_types/1`) read any source, as visualize's `Visualize.Data.Table` does; this
module's row readers are its port.

The `table` package and Nx are optional: without them lists and column maps still work,
and a struct source or a tensor raises `UndefinedFunctionError` where it is read.

# `column`

```elixir
@type column() :: {:s64 | :f64 | :us, binary(), bitstring()} | {:list, [term()]}
```

One column: its type, its storage and, when packed, its validity bitmap.

# `name`

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

A column name, as the source spells it.

# `row`

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

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

# `source`

```elixir
@type source() ::
  [row()] | %{required(name()) =&gt; [term()]} | [{name(), [term()]}] | struct()
```

Anything `from/2` and `rows/1` accept: 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`.

# `t`

```elixir
@type t() :: %Macaw.Data.Table{
  columns: %{required(name()) =&gt; {type(), column()}},
  names: [name()],
  size: non_neg_integer()
}
```

A table.

# `type`

```elixir
@type type() :: :number | :time | :category | :text | :color | :point | :term
```

A column's declared type.

# `accessor`

```elixir
@spec accessor(name() | (row() -&gt; term())) :: (row() -&gt; term())
```

The accessor `fn row -> get(row, name) end`; a function is returned unchanged.

# `column`

```elixir
@spec column(t(), name()) :: [term()] | nil
```

The values of column `name`, in order, `nil` where a value is missing; `nil` for a name
the table does not have. A number column of integers reads back integers; of floats, or
of both, floats. A time column reads back `DateTime`s in UTC at microsecond precision.

# `column_type`

```elixir
@spec column_type(t(), name()) :: type() | nil
```

Each column's type, by name.

# `column_types`

```elixir
@spec column_types(source() | t()) :: %{
  required(name()) =&gt; :time | :number | :category | :text
}
```

Every named column's type, read whole: 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 a reader relies on;
a table's own declared types are `column_type/2`'s.

    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> Macaw.Data.Table.column_types(rows)
    %{t: :time, v: :number, k: :category, s: :text}

# `from`

```elixir
@spec from(source() | t(), keyword()) ::
  {:ok, t()} | {:error, [{list(), term()}, ...]}
```

A table of any source `rows/1` reads.

Columns are named by every key of every row map, in term order, a row without the key
missing there. Each column's type is `types:`'s (a map from name to type) when given,
else the one `column_types/1` infers, else `:term`. A time column's values become
`DateTime`s here, at the edge: a `Date` at midnight UTC, a `NaiveDateTime` as UTC, an
ISO 8601 string parsed. A source whose rows are tuples has
no names; `named/2` gives it some first. `{:ok, table}` or `new/1`'s faults.

# `get`

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

The value of column `name` in `row`, by either spelling of the name: the key itself, else
an atom name as its string and a string name as the atom key spelled the same. `nil`
when neither is a key. No atom is ever made.

# `key`

```elixir
@spec key(map(), name()) :: name() | nil
```

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. The atom key a string finds is remembered for the
calling process, so reading one field from every row scans one row's keys. No atom is
ever made or looked up by name: the remembered key is one a row held.

    iex> Macaw.Data.Table.key(%{cpu: 1}, "cpu")
    :cpu
    iex> Macaw.Data.Table.key(%{"cpu" => 1}, :cpu)
    "cpu"
    iex> Macaw.Data.Table.key(%{cpu: 1}, "mem")
    nil

# `named`

```elixir
@spec named(source(), [name()]) ::
  {:ok, source()}
  | {:error,
     {:width, non_neg_integer(), non_neg_integer()} | {:rank, non_neg_integer()}}
```

A tensor's columns, or tuple rows' elements, under `names`, in order.

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}}`. Tuple rows are named row by row, element
`i` under `Enum.at(names, i)`; a tuple of another size is `{:error, {:width, n, size}}`.
Any other source is `{:ok, source}`, unchanged.

    iex> Macaw.Data.Table.named([{1, 4.0}, {2, 5.0}], ["x", "y"])
    {:ok, [%{"x" => 1, "y" => 4.0}, %{"x" => 2, "y" => 5.0}]}
    iex> Macaw.Data.Table.named([{1, 4.0, 7}], ["x", "y"])
    {:error, {:width, 2, 3}}

# `names`

```elixir
@spec names(t()) :: [name()]
```

The column names, in order.

# `new`

```elixir
@spec new([{name(), type(), [term()]}]) ::
  {:ok, t()} | {:error, [{list(), term()}, ...]}
```

A table of `columns`, each `{name, type, values}`, in order.

`{:ok, table}`, or every fault: `{[name], {:enum, types}}` for an unknown type,
`{[name, i], {:type, type}}` for a value its type does not hold, `{[name], {:length, n}}`
for a column whose length is not the first column's `n`, `{[name], :duplicate}` for a
name given twice. A number column holds numbers; a time column `DateTime`s; a point
column `{longitude, latitude}` pairs of numbers; the others any value. `nil` is missing
in every type.

# `new!`

```elixir
@spec new!([{name(), type(), [term()]}]) :: t()
```

As `new/1`, raising `ArgumentError` naming every fault.

# `row`

```elixir
@spec row(t(), non_neg_integer()) :: map() | nil
```

Row `i` (from 0) as a map, or `nil` past the end.

# `rows`

```elixir
@spec rows(source() | t()) :: [row()]
```

The rows, one map per row keyed by every column name, `nil` where missing.

# `size`

```elixir
@spec size(t()) :: non_neg_integer()
```

The number of rows.

# `slice`

```elixir
@spec slice(t(), non_neg_integer(), non_neg_integer()) :: t()
```

The rows `start` to `start + count - 1`, as a table of the same columns: a packed
column's binary is sliced, not copied.

# `temporal?`

```elixir
@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.

# `time_columns`

```elixir
@spec time_columns(source() | t()) :: [name()]
```

The names of the columns whose values are all temporal: at least one value not `nil`,
and every value `nil` or `temporal?/1`. The whole column is read.

# `types`

```elixir
@spec types() :: [type(), ...]
```

The column types, in the order spec/01 §1.1 lists them.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
