# Axis keys


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

An array can have string keys on any of its axes. A key names one
position on its axis. The position still has its number, counting from
0. Functions keep their ordinary meanings and carry the keys along.

## Construction

Keys, dyadic `:`, puts the keys on its left onto the leading axis of the
array on its right. Several keys name the major cells. This table is a
vector of two columns, each keyed by its name:

``` bpl
T←"price" "qty":[[1 2 3] [4 5 6]]
T
≢T
```

    ["price":[1 2 3] "qty":[4 5 6]]

    2ₓ

A single key makes the whole right argument the one item of a keyed
vector:

``` bpl
"ab":11 12 13
⍴"ab":11 12 13
```

    ["ab":[11 12 13]]

    [1]ₓ

On a matrix, the keys name the rows:

``` bpl
"price" "qty":2 3⍴⍳6
```

    price 0 1 2
      qty 3 4 5

You can write a keyed vector the way it displays, as a bracket list of
`key:value` items. In `["aa":1 2]`, the `2` has no key. The empty record
is `⍬:⍬`.

``` bpl
T≡["price":1 2 3;"qty":4 5 6]
["kind":"bar"]≡"kind":"bar"
```

    $t

    $t

A record inside a vector needs its own brackets, as in `[15 ["aa":1]]`.

A character or a string is one key. `'a' 'b'` is the single key `"ab"`,
because characters side by side form one string. Write `"a" "b"` for two
keys. Iota along an axis, `⍳⍠0`, lists that axis’s keys:

``` bpl
⍳⍠0'a' 'b':1 2
⍳⍠0"a" "b":1 2
```

    ["ab"]

    "a" "b"

`K:⍠axis A` puts keys on any axis of `A`. Here they name the columns:

``` bpl
m←"Jan" "Feb" "Mar":⍠1 (2 3⍴⍳6)
m
```

    Jan Feb Mar
      0   1   2
      3   4   5

Selecting `∞` on the first axis and `"Feb"` on the second gives the Feb
column:

``` bpl
∞ "Feb"⌷m
```

    1 4

Several key vectors label successive axes. Write them inside one pair of
brackets, with `;` between them, because each one contains spaces:

``` bpl
M←["alice" "bob";"price" "qty"]:[10 2⋄20 4]
M
```

          price qty
    alice    10   2
      bob    20   4

`⍳⍠0 1` lists the keys of both axes:

``` bpl
⍳⍠0 1 M
```

    ("alice" "bob"⋄"price" "qty")

With several axes, `:⍠` takes them in the order given. This builds the
same `M` from its columns’ keys first:

``` bpl
M≡["price" "qty";"alice" "bob"]:⍠1 0 [10 2⋄20 4]
```

    $t

Monadic `:` removes every key:

``` bpl
:M
```

    10 2
    20 4

`:⍠` with an axis removes only that axis’s keys:

``` bpl
:⍠1 M
```

    alice 10 2
      bob 20 4

An axis without keys lists its positions:

``` bpl
⍳⍠0 1 :M
```

    (0 1⋄0 1)ₓ

A dfn can build a record with `:` like any other code:

``` bpl
{"total":+/⍵}1 2 3
{+/:⍵}"aa" "bb":1 2
```

    ["total":6]

    3

Keys on one axis must be unique strings, one for each position:

<table>
<thead>
<tr>
<th>Expression</th>
<th>Error</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>"aa" "aa":1 2</code></td>
<td><code>DOMAIN</code></td>
</tr>
<tr>
<td><code>"aa" "bb":⍠0 [1 2 3]</code></td>
<td><code>LENGTH</code></td>
</tr>
</tbody>
</table>

## Selection and assignment

Index, `⌷`, takes a key or a position for each leading axis. A single
key or position removes its axis. The key `"bob"` and the position `1`
select the same row:

``` bpl
"bob"⌷M
1⌷M
```

    ["price":20 "qty":4]

    ["price":20 "qty":4]

Selecting on both axes gives one item. `∞` takes a whole axis. Keys and
positions can be mixed:

``` bpl
"alice" "qty"⌷M
∞ "qty"⌷M
"bob" 0⌷M
```

    2

    ["alice":2 "bob":4]

    20

A vector selector keeps its axis. A string counts as one key. To select
one row and keep the axis, use a one-item vector of keys:

``` bpl
[,⊂"alice"]⌷M
```

          price qty
    alice    10   2

Pick, `⊃`, selects by key too. One key picks a row as a record:

``` bpl
"bob" "qty"⊃M
"bob"⊃M
```

    4

    ["price":20 "qty":4]

Dot indexing puts the selection after the array, with one bracket item
for each axis. `M.["alice" "qty"]` is `"alice" "qty"⌷M`. A path of names
after a dot, as in `rec.addr.city`, picks by each name in turn through
nested records:

``` bpl
rec←["n":1 "addr":["city":"LA"]]
rec.addr.city
M.["alice" "qty"]
```

    LA

    2

Modified assignment works through a dot path:

``` bpl
rec.n+←1
rec.n
```

    2

Plain assignment through a dot path creates any missing keys and records
along the path:

``` bpl
rec.style.color←"red"
rec.style
```

    ["color":"red"]

On an axis without keys, the new position gets a key and the existing
positions have none:

``` bpl
v←10 20 30
v.x←9
v
```

    [10 20 30 "x":9]

Plain assignment through dot indexing appends a missing row or column,
as assignment through `⌷` does. New cells start as the array’s prototype
until the assignment fills them. Here the first assignment adds a row
for cara, and the second a tax column:

``` bpl
N←M
N.["cara"]←30 5
N.[∞ "tax"]←1 2 3
N
```

          price qty tax
    alice    10   2   1
      bob    20   4   2
     cara    30   5   3

Appending on both axes at once fills the new cross-cells as well:

``` bpl
P←M
P.["cara" "tax"]←3
P
```

          price qty tax
    alice    10   2   0
      bob    20   4   0
     cara     0   0   3

A missing key, or assigning past the end, is an `INDEX` error. Selecting
a keyed position twice is a `DOMAIN` error. For `T←"aa" "bb":1 2`:

<table>
<thead>
<tr>
<th>Expression</th>
<th>Error</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>"cc"⌷T</code></td>
<td><code>INDEX</code></td>
</tr>
<tr>
<td><code>T.["cc"]+←1</code></td>
<td><code>INDEX</code></td>
</tr>
<tr>
<td><code>[[0 0]]⌷T</code></td>
<td><code>DOMAIN</code></td>
</tr>
<tr>
<td><code>T.[2]←9</code></td>
<td><code>INDEX</code></td>
</tr>
</tbody>
</table>

Assigning to a single selection stores the whole right argument.
Assigning to a vector selection aligns the kept axis with the right
argument’s keys. The same keyed `u` gives a different result each way:

``` bpl
u←"bb" "aa":8 9
t←"aa" "bb":1 2
t.["aa"]←u
"aa"⌷t
```

    ["bb":8 "aa":9]

``` bpl
tt←"aa" "bb":1 2
tt.[,⊂"aa"]←u
"aa"⌷tt
```

    9

## Agreement

When both arguments of a pervasive function have keys on an axis, they
align by key. The result lists the left argument’s keys, then the keys
only the right argument has. A missing entry reads as its own array’s
prototype:

``` bpl
["aa":2] + ["bb":3]
["aa":2] × ["bb":3]
["aa":0] = ["bb":0]
```

    ["aa":2 "bb":3]

    ["aa":0 "bb":0]

    ["aa":$t "bb":$t]

Keys in a different order still line up:

``` bpl
["aa":1 "bb":2] + ["bb":10 "aa":20]
```

    ["aa":21 "bb":12]

Each keyed axis aligns on its own. Two one-cell matrices with no keys in
common add up to a 2×2 matrix:

``` bpl
a←[["alice"] ["price"]]:1 1⍴10
b←[["bob"] ["qty"]]:1 1⍴2
a+b
```

          price qty
    alice    10   0
      bob     0   2

When one argument has no keys on an axis, agreement goes by position. A
keyed singleton stretches to match the other argument and loses its key.
Enclosing the other argument keeps the key and puts the sum inside it:

``` bpl
["base":5] + 10 20 30
["base":5] + ⊂10 20 30
["aa":1 "bb":2] + 10 20
```

    15 25 35

    ["base":[15 25 35]]

    ["aa":11 "bb":22]

An axis operand chooses which axes correspond. Lined up with the columns
of `M`, the keyed vector adds to the qty column and creates a tax
column:

``` bpl
M+⍠1"qty" "tax":10 2
```

          price qty tax
    alice    10  12   2
      bob    20  14   2

## Array operations

Transpose moves each axis with its keys:

``` bpl
Mᵀ
```

          alice bob
    price    10  20
      qty     2   4

Reverse, rotate, take, drop and filtering move keys with their
positions:

``` bpl
scores←"aa" "bb" "cc":10 20 5
⌽scores
1⌽scores
¯1↑scores
1↓scores
1 0 1#scores
```

    ["cc":5 "bb":20 "aa":10]

    ["bb":20 "cc":5 "aa":10]

    ["cc":5]

    ["bb":20 "cc":5]

    ["aa":10 "cc":5]

Reduction removes the reduced axis and its keys:

``` bpl
+/M
+⌿M
```

    ["alice":12 "bob":24]

    ["price":30 "qty":6]

Scan and Each keep their argument’s keys:

``` bpl
+\scores
+/¨["price":1 2 3;"qty":4 5 6]
```

    ["aa":10 "bb":30 "cc":35]

    ["price":6 "qty":15]

Reshape keeps the keys only when the shape is unchanged. Ravel drops the
keys of the axes it merges. Table keeps the keys of the axis it leaves
alone:

``` bpl
pair←"aa" "bb":1 2
2⍴pair
4⍴pair
,"aa" "bb":2 2⍴⍳4
⍳⍠0 ⍪"aa" "bb":2 2⍴⍳4
```

    ["aa":1 "bb":2]

    1 2 1 2

    0 1 2 3

    "aa" "bb"

Rank keeps the keys of the frame. An axis inside the result cells keeps
a key only when every cell has that key in the same place. Pairing each
item of `pair` with itself keeps the row keys:

``` bpl
{⍵,⍵}⍤0 pair
```

    aa 1 1
    bb 2 2

Reversing each row of `M` moves the column keys in every row alike,
which keeps them:

``` bpl
⌽⍤1 M
```

          qty price
    alice   2    10
      bob   4    20

Reversing only alice’s row loses the column keys, because the two rows
now have them in different orders:

``` bpl
{10=↑⍵?⌽⍵;⍵}⍤1 M
```

    alice  2 10
      bob 20  4

A rotation with a different count for each row drops the rotated axis’s
keys in the same way:

``` bpl
0 1⌽M
```

    alice 10  2
      bob  4 20

Outer product keeps both arguments’ axes and their keys:

``` bpl
["aa":1 "bb":2] +⊗ ["xx":10 "yy":20]
```

       xx yy
    aa 11 21
    bb 12 22

An array of Power counts supplies the result’s frame, with its keys:

``` bpl
counts←"initial" "once" "twice":0 1 2
⊽⍣counts 3
```

    ["initial":3 "once":6 "twice":12]

Inner product, Decode and matrix solve align the keys of the axes they
contract. When both arguments have keys there, the key sets must be
equal, or the call is a `LENGTH` error. When either argument has none,
the contraction goes by position:

``` bpl
["hi":1 "lo":2] +.× ["lo":10 "hi":20]
["hi":10 "lo":10] ⊥ ["lo":2 "hi":1]
```

    40

    12

Solving against `S`, which has keys on both axes, gives a vector keyed
by its column keys:

``` bpl
S←["r1" "r2";"xx" "yy"]:[2 0⋄0 4]ₓ
["r2":12 "r1":4]ₓ ⌹ S
```

    ["xx":2 "yy":3]ₓ

Inverting `S` swaps its row and column keys:

``` bpl
⌹S
```

        r1  r2
    xx 1ᵣ2  0ₓ
    yy  0ₓ 1ᵣ4

Catenate joins the keys along its axis and aligns the other keyed axes.
A position from an unkeyed part has no key:

``` bpl
["aa":1] , ["bb":2]
["aa":1 "bb":2] , 5
```

    ["aa":1 "bb":2]

    ["aa":1 "bb":2 5]

`B` lists its rows in the other order from `A`:

``` bpl
A←["aa" "bb";"xx" "yy"]:[1 2⋄3 4]
B←["bb" "aa";["zz"]]:[[5]⋄[6]]
A,B
```

       xx yy zz
    aa  1  2  6
    bb  3  4  5

Replicate can repeat the items of an unkeyed axis, and the other axes
keep their keys:

``` bpl
2#⍠¯1 ["aa" "bb"]:[1 2⋄3 4]
```

    aa 1 1 2 2
    bb 3 3 4 4

Take and [expand](glyphs/hash.qmd#expand) add positions without keys, as
fill from an unkeyed part does:

``` bpl
3↑pair
1 0 1#⁻¹pair
```

    ["aa":1 "bb":2 0]

    ["aa":1 0 "bb":2]

Repeating positions on a keyed axis is a `DOMAIN` error, because it
would repeat keys. For `V←"aa" "bb":1 2`:

<table>
<thead>
<tr>
<th>Expression</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>V,V</code></td>
<td>Duplicate joined keys</td>
</tr>
<tr>
<td><code>2#V</code></td>
<td>Repeated keys</td>
</tr>
</tbody>
</table>

### Windows

Full windows add an unkeyed axis of window positions. The result keeps
only the keys that every window shares. With windows of 2, no key is in
every window:

``` bpl
w←"aa" "bb" "cc":1 2 3
2↕w
```

    1 2
    2 3

A single window of 3 covers every key:

``` bpl
3↕w
```

    aa bb cc
     1  2  3

Each padded window is keyed by its centre:

``` bpl
¯3↕w
```

    aa 0 1 2
    bb 1 2 3
    cc 2 3 0

A stencil’s result is keyed by each frame’s centre. Inside the operand,
only an unpadded frame has all of `w`’s keys:

``` bpl
(+/⊢)⌺3 w
{⍳⍠0⍵ ≡ "aa" "bb" "cc"}⌺3 w
```

    ["aa":3 "bb":6 "cc":5]

    ["aa":$f "bb":$t "cc":$f]

## Match and search

Match aligns keys on every keyed axis before comparing. Shapes and
unkeyed axes must match by position. A keyed vector matches its reverse,
because its keys move with its values:

``` bpl
levels←"low" "mid" "high":10 20 30
levels≡⌽levels
```

    $t

Searches compare values or cells and return a found position as its key.
A value that isn’t found gets the length, as usual:

``` bpl
levels⍳20 99
levels⍸5 15 25 40
```

    "mid" 3ₓ

    0ₓ "low" "mid" "high"

Grade down lists the keys from the largest value to the smallest:

``` bpl
⍒levels
[⍒levels]⌷levels
```

    "high" "mid" "low"

    ["high":30 "mid":20 "low":10]

Membership compares values and keeps the left argument’s keys:

``` bpl
["aa":7] ∊ ["bb":7]
```

    ["aa":$t]

A position in a matrix mixes keys and numbers. Bare Iota still counts
from values as usual, whatever keys its argument has:

``` bpl
Q←"price" "qty":⍠1 [10 2⋄20 4]
↑⍸Q=20
⍳"rows" "cols":2 3
```

    1ₓ "price"

    [0 0] [0 1] [0 2]
    [1 0] [1 1] [1 2]

Unique and the set functions compare values and keep the keys of the
positions they select:

``` bpl
p←"aa" "bb":1 2
q←"xx" "yy":2 3
p∩q
p~q
p∪q
∪"aa" "bb" "cc":1 1 2
```

    ["bb":2]

    ["aa":1]

    ["aa":1 "bb":2 "yy":3]

    ["aa":1 "cc":2]

Monadic Key passes the keys of each group’s positions to its operand.
Dyadic Key keeps the keys on the grouped values:

``` bpl
k←"aa" "bb" "cc":1 2 1
{⊂⍵}⌸k
1 2 1{⊂⍵}⌸k
```

    ("aa" "cc"⋄"bb")

    ("aa":1 "cc":1⋄"bb":2)

## Named axes

An axis name identifies a dimension, such as `city`, while its keys
identify positions along it, such as `LA` and `NY`. Names are unique
within an array.

An array’s shape is keyed by its axis names. Reshaping by a keyed shape
names the axes:

``` bpl
C←["city":2 "month":3]⍴⍳6
⍴C
"month"⌷⍴C
```

    ["city":2 "month":3]ₓ

    3ₓ

An axis operand can name an axis instead of numbering it:

``` bpl
+/⍠"month" C
```

    ["city":2]⍴[3 12]

An entry of the shape without a key leaves its axis unnamed. `N:⍴A ⍴ A`
renames the axes and `:⍴A ⍴ A` removes the names. An integer key equal
to its own position stands for no key, as the `1` in `"city" 1` does. A
reshape that keeps the shape also keeps the position keys and shares the
array’s storage:

``` bpl
⍴("city" 1:⍴C)⍴C
⍴(:⍴C)⍴C
```

    ["city":2 3]ₓ

    [2 3]ₓ

A keyed vector to the left of `:` names each axis with a key, and lists
that axis’s positions as the key’s value:

``` bpl
sales←["city":"NY" "LA";"month":"Jan" "Feb" "Mar"]:[10 20 30⋄40 50 60]
sales
```

       Jan Feb Mar
    NY  10  20  30
    LA  40  50  60

Its shape is keyed by axis name, and its positions by key:

``` bpl
⍴sales
"LA" "Feb"⌷sales
```

    ["city":2 "month":3]ₓ

    50

An axis description can be kept in a name and reused, or attached to
chosen axes. The other axes keep their names and keys:

``` bpl
months←"month":"Jan" "Feb" "Mar"
D←months:⍠1 (2 3⍴⍳6)
⍴D
⍴("city":"NY" "LA"):D
```

    [2 "month":3]ₓ

    ["city":2 "month":3]ₓ

A record of several axes attaches to the chosen axes in the order given:

``` bpl
axes←["month":"Jan" "Feb" "Mar";"city":"NY" "LA"]
⍴axes:⍠1 0 (2 3⍴⍳6)
```

    ["city":2 "month":3]ₓ

Agreement first pairs axes with equal names, wherever they sit. Unpaired
axes broadcast. The result has the left argument’s axes first, then the
axes only the right argument has:

``` bpl
monthly←["month":3]⍴10 20 30
C+monthly
```

    10 21 32
    13 24 35

The order of the arguments decides the order of the result’s axes, but
`C+Cᵀ` still pairs each axis with the one of the same name:

``` bpl
⍴monthly+C
C+Cᵀ
```

    ["month":3 "city":2]ₓ

    0 2  4
    6 8 10

The remaining axes pair in leading-axis order. A named axis paired with
an unnamed one keeps its name. Length agreement and key alignment then
apply as usual:

``` bpl
C+10 20
```

    10 11 12
    23 24 25

Axes with different names stay separate, each broadcasting against the
other:

``` bpl
⍴(["city":2 3]⍴1)+["product":4 3]⍴2
```

    ["city":2 3 "product":4]ₓ

A name or a list of names as the `⍠` operand names axes. Index with that
operand selects positions on the named axes:

``` bpl
L←["city":"LA" "NY";"month":"Jan" "Feb" "Mar"]:2 3⍴⍳6
"LA"⌷⍠"city" L
"LA"⌷L ≡ "LA"⌷⍠"city" L
⍴⊂⍠"month" L
+/⍠"city" "month" L
```

    ["month":3]⍴["Jan":0 "Feb":1 "Mar":2]

    $t

    ["city":2]ₓ

    15

Transpose moves names with their axes. Selecting with a single key or
position removes the axis and its name. Replicate and Table keep the
name. Reshape takes its names from its left argument. Ravel drops the
names of the axes it merges. New axes start unnamed:

``` bpl
⍴Cᵀ
⍴0⌷C
⍴2 3⍴C
⍴,C
cities←["city":2]⍴1 2
⍴2#cities
⍴⍪cities
```

    ["month":3 "city":2]ₓ

    ["month":3]ₓ

    [2 3]ₓ

    [6]ₓ

    ["city":4]ₓ

    ["city":2 1]ₓ

When an operation gives two axes the same name, both lose it. Their
position keys remain:

``` bpl
lanes←["city":2]⍴"LA" "NY":1 2
⍴lanes×⊗lanes
⍳⍠0 1 lanes×⊗lanes
```

    [2 2]ₓ

    ("LA" "NY"⋄"LA" "NY")

<table>
<thead>
<tr>
<th>Expression</th>
<th>Error</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>+/⍠"missing" C</code></td>
<td><code>INDEX</code></td>
</tr>
<tr>
<td><code>["city":2 "city":3]⍴0</code></td>
<td><code>DOMAIN</code></td>
</tr>
</tbody>
</table>

## Python and JSON

Python dicts become keyed vectors recursively. `.py` converts them back
to dicts.

``` python
from basedpl import Array
import numpy as np

record = Array({'price': 10, 'address': {'city': 'LA'}})
assert record.py == {'price': 10, 'address': {'city': 'LA'}}
assert record.axis_keys == (('price', 'address'),)
```

Attach position keys with `axis_keys`, and axis names with `axis_names`.
`None` leaves an axis without keys or a name. The properties of the same
names return the keys and names. `.np` copies the values to NumPy.

``` python
rows, cols = ['NY', 'LA'], ['Jan', 'Feb', 'Mar']
data = [[10, 20, 30], [40, 50, 60]]
sales = Array(data, axis_keys=(rows, cols), axis_names=('city', 'month'))
assert sales.axis_keys == (tuple(rows), tuple(cols))
assert sales.axis_names == ('city', 'month')
assert sales.np.tolist() == data
columns_only = Array(data, axis_keys=(None, cols), axis_names=(None, 'month'))
assert columns_only.axis_keys == (None, tuple(cols))
assert columns_only.axis_names == (None, 'month')
```

`.df` converts to a pandas DataFrame. For a matrix, the position keys
become the row and column labels, and the axis names become the names of
the index and the columns. `.py` uses this conversion for keyed arrays
of rank 2 or more. Install pandas with `pip install pandas`. basedpl
imports it only when converting.

``` python
df = sales.df
assert df.loc['LA', 'Feb'] == 50
assert (df.index.name, df.columns.name) == ('city', 'month')
assert sales.py.equals(df)
assert columns_only.df.index.tolist() == [0, 1]
```

Above rank 2, the last axis supplies the columns, and the earlier axes
form a row MultiIndex. Unkeyed axes use labels that count from 0. Units
and vectors become one-column DataFrames.

``` python
counts = np.arange(12).reshape(2, 2, 3)
names = 'year', 'city', 'month'
cube = Array(counts, axis_keys=(None, rows, cols), axis_names=names)
assert cube.df.index.names == ['year', 'city']
assert cube.df.loc[(1, 'LA'), 'Mar'] == 11
assert Array({'aa': 1, 'bb': 2}).df.loc['bb', 0] == 2
assert Array(7).df.loc[0, 0] == 7
```

[`•json`](data.ipynb#json) converts ordinary JSON objects to keyed
vectors. The [process protocol](processes.qmd) carries `axis_keys` and
`axis_names` beside shape, row-major data and prototype. For `sales`
above:

``` json
{
  "shape": [2, 3],
  "data": [10, 20, 30, 40, 50, 60],
  "prototype": 0,
  "axis_keys": [["NY", "LA"], ["Jan", "Feb", "Mar"]],
  "axis_names": ["city", "month"]
}
```

Use `null` for an axis without keys or a name. Omit each metadata field
when none of its axes has that metadata:

``` json
{"shape":[1,2],"data":[10,20],"prototype":0,"axis_keys":[null,["Jan","Feb"]]}
{"shape":[2],"data":[10,20],"prototype":0}
```
