T←"price" "qty":[[1 2 3] [4 5 6]]
T
≢T["price":[1 2 3] "qty":[4 5 6]]
2ₓ
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.
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:
A single key makes the whole right argument the one item of a keyed vector:
On a matrix, the keys name the rows:
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 ⍬:⍬.
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:
K:⍠axis A puts keys on any axis of A. Here they name the columns:
Selecting ∞ on the first axis and "Feb" on the second gives the Feb column:
Several key vectors label successive axes. Write them inside one pair of brackets, with ; between them, because each one contains spaces:
⍳⍠0 1 lists the keys of both axes:
With several axes, :⍠ takes them in the order given. This builds the same M from its columns’ keys first:
Monadic : removes every key:
:⍠ with an axis removes only that axis’s keys:
An axis without keys lists its positions:
A dfn can build a record with : like any other code:
Keys on one axis must be unique strings, one for each position:
| Expression | Error |
|---|---|
"aa" "aa":1 2 |
DOMAIN |
"aa" "bb":⍠0 [1 2 3] |
LENGTH |
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:
Selecting on both axes gives one item. ∞ takes a whole axis. Keys and positions can be mixed:
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:
Pick, ⊃, selects by key too. One key picks a row as a record:
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:
Modified assignment works through a dot path:
Plain assignment through a dot path creates any missing keys and records along the path:
On an axis without keys, the new position gets a key and the existing positions have none:
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:
Appending on both axes at once fills the new cross-cells as well:
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:
| Expression | Error |
|---|---|
"cc"⌷T |
INDEX |
T.["cc"]+←1 |
INDEX |
[[0 0]]⌷T |
DOMAIN |
T.[2]←9 |
INDEX |
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:
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:
["aa":2 "bb":3]
["aa":0 "bb":0]
["aa":$t "bb":$t]
Keys in a different order still line up:
Each keyed axis aligns on its own. Two one-cell matrices with no keys in common add up to a 2×2 matrix:
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:
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:
Transpose moves each axis with its keys:
Reverse, rotate, take, drop and filtering move keys with their positions:
["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:
Scan and Each keep their argument’s keys:
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:
["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:
Reversing each row of M moves the column keys in every row alike, which keeps them:
Reversing only alice’s row loses the column keys, because the two rows now have them in different orders:
A rotation with a different count for each row drops the rotated axis’s keys in the same way:
Outer product keeps both arguments’ axes and their keys:
An array of Power counts supplies the result’s frame, with its keys:
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:
Solving against S, which has keys on both axes, gives a vector keyed by its column keys:
Inverting S swaps its row and column keys:
Catenate joins the keys along its axis and aligns the other keyed axes. A position from an unkeyed part has no key:
B lists its rows in the other order from A:
Replicate can repeat the items of an unkeyed axis, and the other axes keep their keys:
Take and expand add positions without keys, as fill from an unkeyed part does:
Repeating positions on a keyed axis is a DOMAIN error, because it would repeat keys. For V←"aa" "bb":1 2:
| Expression | Reason |
|---|---|
V,V |
Duplicate joined keys |
2#V |
Repeated keys |
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:
A single window of 3 covers every key:
Each padded window is keyed by its centre:
A stencil’s result is keyed by each frame’s centre. Inside the operand, only an unpadded frame has all of w’s keys:
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:
Searches compare values or cells and return a found position as its key. A value that isn’t found gets the length, as usual:
Grade down lists the keys from the largest value to the smallest:
Membership compares values and keeps the left argument’s keys:
A position in a matrix mixes keys and numbers. Bare Iota still counts from values as usual, whatever keys its argument has:
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:
["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:
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:
An axis operand can name an axis instead of numbering it:
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:
A keyed vector to the left of : names each axis with a key, and lists that axis’s positions as the key’s value:
Jan Feb Mar
NY 10 20 30
LA 40 50 60
Its shape is keyed by axis name, and its positions by key:
An axis description can be kept in a name and reused, or attached to chosen axes. The other axes keep their names and keys:
[2 "month":3]ₓ
["city":2 "month":3]ₓ
A record of several axes attaches to the chosen axes in the order given:
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:
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:
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:
Axes with different names stay separate, each broadcasting against the other:
A name or a list of names as the ⍠ operand names axes. Index with that operand selects positions on the named axes:
["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:
["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:
| Expression | Error |
|---|---|
+/⍠"missing" C |
INDEX |
["city":2 "city":3]⍴0 |
DOMAIN |
Python dicts become keyed vectors recursively. .py converts them back to dicts.
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.
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.
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.
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 converts ordinary JSON objects to keyed vectors. The process protocol carries axis_keys and axis_names beside shape, row-major data and prototype. For sales above:
Use null for an axis without keys or a name. Omit each metadata field when none of its axes has that metadata: