{"id":16705110,"url":"https://github.com/mcabbott/axiskeys.jl","last_synced_at":"2026-01-27T23:46:58.756Z","repository":{"id":38230141,"uuid":"217616146","full_name":"mcabbott/AxisKeys.jl","owner":"mcabbott","description":"🎹","archived":false,"fork":false,"pushed_at":"2024-10-30T16:26:27.000Z","size":2163,"stargazers_count":148,"open_issues_count":55,"forks_count":28,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-10T20:55:10.195Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mcabbott.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-25T21:18:52.000Z","updated_at":"2025-03-10T10:18:34.000Z","dependencies_parsed_at":"2024-03-02T21:25:15.392Z","dependency_job_id":"f11716c2-2c7a-4e63-a13b-0c8f848d3594","html_url":"https://github.com/mcabbott/AxisKeys.jl","commit_stats":{"total_commits":343,"total_committers":26,"mean_commits":"13.192307692307692","dds":"0.49562682215743437","last_synced_commit":"550924a92f49f56492459cc7268b91b93f8ff3ec"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcabbott%2FAxisKeys.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcabbott%2FAxisKeys.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcabbott%2FAxisKeys.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mcabbott%2FAxisKeys.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mcabbott","download_url":"https://codeload.github.com/mcabbott/AxisKeys.jl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243561249,"owners_count":20311064,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-12T19:28:42.615Z","updated_at":"2026-01-27T23:46:58.748Z","avatar_url":"https://github.com/mcabbott.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AxisKeys.jl\n\n[![Docstrings](https://img.shields.io/badge/j_hub-docstrings-blue)](https://juliahub.com/docs/AxisKeys/)\n[![Github CI](https://github.com/mcabbott/AxisKeys.jl/workflows/CI/badge.svg)](https://github.com/mcabbott/AxisKeys.jl/actions?query=workflow%3ACI+branch%3Amaster)\n\n\u003c!--\u003cimg src=\"docs/readmefigure.png\" alt=\"block picture\" width=\"400\" align=\"right\"\u003e--\u003e\n\nThis package defines a thin wrapper which, alongside any array, stores a vector of \"keys\" \nfor each dimension. This may be useful to store perhaps actual times of measurements, \nor some strings labeling columns, etc. These will be propagated through many \noperations on arrays (including broadcasting, `map`, comprehensions, `sum` etc.)\nand altered by a few (sorting, `fft`, `push!`).\n\nIt works closely with [NamedDims.jl](https://github.com/invenia/NamedDims.jl), another wrapper \nwhich attaches names to dimensions. These names are a tuple of symbols, like those of \na `NamedTuple`, and can be used for specifying which dimensions to sum over, etc.\nA nested pair of these wrappers can be made as follows:\n\n```julia\nusing AxisKeys\ndata = rand(Int8, 2,10,3) .|\u003e abs;\nA = KeyedArray(data; channel=[:left, :right], time=range(13, step=2.5, length=10), iter=31:33)\n```\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"docs/readmeterminal.png\" alt=\"terminal pretty printing\" width=\"550\" align=\"center\"\u003e\n\u003c/p\u003e\n\nThe package aims not to be opinionated about what you store in these \"key vectors\":\nthey can be arbitrary `AbstractVectors`, and need not be sorted nor have unique elements.\nInteger \"keys\" are allowed, and should have no surprising interactions with indices.\nWhile it is further from zero-cost than [NamedDims.jl](https://github.com/invenia/NamedDims.jl), it aims to be light-weight,\nleaving as much functionality as possible to other packages.\n\nSee \u003ca href=\"#elsewhere\"\u003e§ elsewhere\u003c/a\u003e below for other packages doing similar things.\n\n### Selections\n\nIndexing still works directly on the underlying array, \nand keyword indexing (of a nested pair) works exactly as for a `NamedDimsArray`.\nBut in addition, it is possible to pick out elements based on the keys,\nwhich for clarity I will call lookup. This is written with round brackets:\n\n| Dimension `d` | Indexing: `i ∈ axes(A,d)` | Lookup: `key ∈ axiskeys(A,d)` |\n|--------------------|---------------------|---------------------|\n| by position        | `A[1,2,:]`          | `A(:left, 15.5, :)` |\n| by name            | `A[iter=1]`         | `A(iter=31)`        |\n| by type            |  --                 | `B = A(:left)`      |\n\nWhen using dimension names, fixing only some of them will return a slice, \nsuch as `B = A[channel=1]`.\nYou may also give just one key, provided its type matches those of just one dimension,\nsuch as `B = A(:left)` where the key is a Symbol.\n\nNote that indexing is the primary way to access the data. Lookup calls for example\n`i = findfirst(axiskeys(A,1), :left)` to convert keys to indices, thus will always be slower.\nIf you want this to be the primary mode of access, then you may want a dictionary,\npossibly [Dictionaries.jl](https://github.com/andyferris/Dictionaries.jl).\n\nThere are also a numer of special selectors, which work like this:\n\n|                 | Indexing         | Lookup                  |         |\n|-----------------|------------------|-------------------------|---------|\n| one nearest     | `B[time = 3]`    | `B(time = Near(17.0))`  | vector  |\n| all in a range  | `B[2:5, :]`      | `B(Interval(14,25), :)` | matrix  |\n| all matching    | `B[3:end, Not(3)]` | `B(\u003e(17), !=(33))`    | matrix  |\n| mixture         | `B[1, Key(33)]`  | `B(Index[1], 33)`       | scalar  |\n| non-scalar      | `B[iter=[1, 3]]` | `B(iter=[31, 33])`      | matrix  |\n\nHere `Interval(13,18)` can also be written `13..18`, it's from [IntervalSets.jl](https://github.com/JuliaMath/IntervalSets.jl). \nAny functions can be used to select keys, including lambdas: `B(time = t -\u003e 0\u003ct\u003c17)`. \nYou may give just one `::Base.Fix2` function \n(such as `\u003c=(18)` or `==(20)`) provided its argument type matches the keys of one dimension.\nAn interval or a function always selects via `findall`, \ni.e. it does not drop a dimension, even if there is exactly one match. \n\nWhile this table shows lookup selectors inside `B(...)`, they can in fact all be \nused inside `B[...]`, not just `Key(k)` as shown. They still refer to keys not indices!\n(This will not select dimension based on type, i.e. `A[Key(:left)]` is an error.)\nYou may also write `Index[end]` but not `Index[end-1]`.\n\nBy default lookup returns a view, while indexing returns a copy unless you add `@views`. \nThis means that you can write into the array with `B(time = \u003c=(18)) .= 0`.\nFor scalar output, you cannot of course write `B(13.0, 33) = 0` \nas this parsed as a function definition, but you can write `B[Key(13.0), Key(33)] = 0`,\nor else `B(13.0, 33, :) .= 0` as a trailing colon makes a zero-dimensional view.\n\n### Construction\n\n```julia\nKeyedArray(rand(Int8, 2,10), ([:a, :b], 10:10:100)) # AbstractArray, Tuple{AbstractVector, ...}\n```\n\nA nested pair of wrappers can be constructed with keywords for names,\nand everything should work the same way in either order:\n\n```julia\nKeyedArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100)     # KeyedArray(NamedDimsArray(...))\nNamedDimsArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100) # NamedDimsArray(KeyedArray(...))\n```\n\nCalling `AxisKeys.keyless(A)` removes the `KeyedArray` wrapper, if any, \nand `NamedDims.unname(A)` similarly removes the names (regardless of which is outermost).\n\nThere is another more \"casual\" constructor, via the function `wrapdims`.\nThis does a bit more checking of inputs, and will adjust the length of ranges of keys if it can, \nand will fix indexing offsets if needed to match the array.\nThe resulting order of wrappers is controlled by `AxisKeys.nameouter()=false`.\n\n```julia\nwrapdims(rand(Int8, 10), alpha='a':'z') \n# Warning: range 'a':1:'z' replaced by 'a':1:'j', to match size(A, 1) == 10\n\nwrapdims(OffsetArray(rand(Int8, 10),-1), iter=10:10:100)\naxiskeys(ans,1) # 10:10:100 with indices 0:9\n```\n\nFinally, `wrapdims` will also convert `AxisArray`s, `NamedArray`s, as well as `NamedTuple`s. \n\n### Functions\n\nThe function `axes(A)` returns (a tuple of vectors of) indices as usual, \nand `axiskeys(A)` similarly returns (a tuple of vectors of) keys.\nIf the array has names, then `dimnames(A)` returns them.\nThese functions work like `size(A, d) = size(A, name)` to get just one.\n\nThe following things should work:\n\n* Broadcasting `log.(A)` and `map(log, A)`, as well as comprehensions \n  `[log(x) for x in A]` should all work. \n\n* Transpose etc, `permutedims`, `mapslices`.\n\n* Concatenation `hcat(B, B .+ 100)` works. \n  Note that the keys along the glued direction may not be unique afterwards.\n\n* Reductions like `sum(A; dims=:channel)` can use dimension names. \n  Likewise `prod`, `mean` etc., and `dropdims`.\n\n* Sorting: `sort` and `sortslices` permute keys \u0026 data by the array, \n  while a new function `sortkeys` goes by the keys.\n  `reverse` similarly re-orders keys to match data.\n\n* Some linear algebra functions like `*` and `\\` will work. \n\n* Getproperty returns the key vector, to allow things like\n  `for (i,t) in enumerate(A.time); fun(val = A[i,:], time = t); ...`.\n\n* Vectors support `push!(V, val)`, which will try to extend the key vector. \n  There is also a method `push!(V, key =\u003e val)` which pushes in a new key. \n\nTo allow for this limited mutability, `V.keys isa Ref` for vectors, \nwhile `A.keys isa Tuple` for matrices \u0026 higher. But `axiskeys(A)` always returns a tuple.\n\n* Named tuples can be converted to and from keyed vectors,\n  with `collect(keys(nt)) == Symbol.(axiskeys(V),1)`\n\n* The [Tables.jl](https://github.com/JuliaData/Tables.jl) interface is supported,\n  with `wrapdims(df, :val, :x, :y)` creating a matrix from 3 columns.\n\n* Some [StatsBase.jl](https://github.com/JuliaStats/StatsBase.jl) and \n  [CovarianceEstimation.jl](https://github.com/mateuszbaran/CovarianceEstimation.jl) functions \n  are supported. ([PR#28](https://github.com/mcabbott/AxisKeys.jl/pull/28).)\n\n* [FFTW](https://github.com/JuliaMath/FFTW.jl)`.fft` transforms the keys; \n  if these are times such as [Unitful](https://github.com/PainterQubits/Unitful.jl)`.s` \n  then the results are fequency labels. ([PR#15](https://github.com/mcabbott/AxisKeys.jl/pull/15).)\n\n* [Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl) functions work with keyed arrays.\n  `interpolate(A, mode)` and `linear_interpolation(A)` both create interpolators\n  that accept keys as arguments, either positional or named: `itp(time=13.7)`.\n\n* [LazyStack](https://github.com/mcabbott/LazyStack.jl)`.stack` understands names and keys.\n  Stacks of named tuples like `stack((a=i, b=i^2) for i=1:5)` create a matrix with `[:a, :b]`.\n\n* [NamedPlus](https://github.com/mcabbott/NamedPlus.jl) has a macro which works on comprehensions:\n  `@named [n^pow for n=1:10, pow=0:2:4]` has names and keys.\n\n### Absent\n\n* There is no automatic alignment of dimensions by name. \n  Thus `A .+ A[iter=3]` is fine as both names and keys line up, \n  but `A .+ B` is an error, as `B`'s first name is `:time` not `:channel`.\n  (See [NamedPlus](https://github.com/mcabbott/NamedPlus.jl)`.@named` for something like this.)\n\nAs for [NamedDims.jl](https://github.com/invenia/NamedDims.jl), the guiding idea \nis that every operation which could be done on ordinary arrays \nshould still produce the same data, but propagate the extra information (names/keys), \nand error if it conflicts. \n\nBoth packages allow for wildcards, which never conflict. \nIn NamedDims.jl this is the name `:_`, here it is a `Base.OneTo(n)`, \nlike the `axes` of an `Array`. These can be constructed as \n`M = wrapdims(rand(2,2); _=[:a, :b], cols=nothing)`, \nand for instance `M .+ M'` is not an error. \n\n* There are no special types provided for key vectors, they can be any `AbstractVector`s.\n  Lookup happens by calling `i = findfirst(isequal(20.0), axiskeys(A,2))`, \n  or `is = findall(\u003c(18), axiskeys(A,2))`.\n\nIf you need lookup to be very fast, then you will want to use a package like \n[UniqueVectors.jl](https://github.com/garrison/UniqueVectors.jl)\nor [AcceleratedArrays.jl](https://github.com/andyferris/AcceleratedArrays.jl) \nor [CategoricalArrays.jl](https://github.com/JuliaData/CategoricalArrays.jl).\nTo apply such a type to all dimensions, you may write \n`D = wrapdims(rand(1000), UniqueVector, rand(Int, 1000))`.\nThen `D(n)` here will use the fast lookup from UniqueVectors.jl (about 60x faster).\n\nWhen a key vector is a Julia `AbstractRange`, then this package provides some faster \noverloads for things like `findall(\u003c=(42), 10:10:100)`. \n\n* There is also no automatic alignment by keys, like time. \n  But this could be done elsewhere?\n\n### Elsewhere\n\nThis is more or less an attempt to replace [AxisArrays](https://github.com/JuliaArrays/AxisArrays.jl) \nwith several smaller packages. The complaints are:\n(1) It's confusing  to guess whether to perform indexing or lookup \n  based on whether it is given an integer (index) or not (key). \n(2) Each \"axis\" was its own type `Axis{:name}` which allowed zero-overhead lookup\n  before Julia 1.0. But this is now possible with a simpler design. \n  (They were called axes before `Base.axes()` was added, hence (3) the confusing terminology.)\n(4) Broadcasting is not supported, as this changed dramatically in Julia 1.0.\n(5) There are lots of assorted functions, special categorical vector types, etc. \n  which aren't part of the core, and are poorly documented.\n\nOther older packages (pre-Julia-1.0):\n\n* [NamedArrays](https://github.com/davidavdav/NamedArrays.jl) also provides names \u0026 keys, \nwhich are always `OrderedDict`s. Named lookup looks like `NA[:x =\u003e 13.0]` \ninstead of `A(x=13.0)` here; this is not very fast. \nDimension names \u0026 keys can be set after creation. Has nice pretty-printing routines. \nReturned by [FreqTables](https://github.com/nalimilan/FreqTables.jl).\n\n* [LabelledArrays](https://github.com/JuliaDiffEq/LabelledArrays.jl) adds names for individual elements, more like a NamedTuple. \nOnly for small sizes: the storage inside is a Tuple, not an Array.\n\n* [AxisArrayPlots](https://github.com/jw3126/AxisArrayPlots.jl) has some plot recipes. \n\n* [OffsetArrays](https://github.com/JuliaArrays/OffsetArrays.jl) actually changes the indices\nof an Array, allowing any continuous integer range, like `0:9` or `-10:10`.\nThis package is happy to wrap such arrays, \nand if needed will adjust indices of the given key vectors:\n`O = wrapdims(OffsetArray([\"left\", \"mid\", \"right\"], -1:1), 'A':'C')`, \nthen `O[-1:0]` works.\n\nOther new packages (post-1.0):\n\n* [Dictionaries](https://github.com/andyferris/Dictionaries.jl) does very fast lookup only\n(in this terminology), with no indexing. Not `\u003c: AbstractArray`, not a wrapped around an Array.\nAnd presently only one-dimensional. \n\n* [NamedPlus](https://github.com/mcabbott/NamedPlus.jl) is some experiments using NamedDims. \nFunction `align` permutes dimensions automatically, \nand macro `@named` can introduce this into broadcasting expressions. \n\n* [AxisSets](https://github.com/invenia/AxisSets.jl) builds on this package to handle groups of arrays as a `KeyedDataset`.\n\n* [DimensionalData](https://github.com/rafaqz/DimensionalData.jl) is another replacement \nfor AxisArrays. It again uses types like `Dim{:name}` to store both name \u0026 keys, although you\ncan use `Symbol` keys that are converted to types internally.\nThere are also some special ones like `X, Y` of the same abstract type (which must be in scope).\nNamed lookup can use these types `DA[X(At(:a))]`, or use the corresponding symbols `DA[X=At(:a)]`, for what this package would write `A(x=:a)` or `A[x=Key(:a)]`.\n\n* [AxisIndices](https://github.com/Tokazama/AxisIndices.jl) differs mainly by storing \nthe keys with the axes in its own `Axis` type. This is returned by `Base.axes(A)` \n(instead of `Base.OneTo` etc.) like [PR#6](https://github.com/mcabbott/AxisKeys.jl/pull/6).\n\nSee also [docs/speed.jl](docs/speed.jl) for some checks on this package, \nand comparisons to other ones.\nAnd see [docs/repl.jl](docs/repl.jl) for some usage examples, showing pretty printing. \n\nIn 🐍-land:\n\n* [xarray](http://xarray.pydata.org/) does indexing `x[:, 0]` \n  and lookup by \"coordinate label\" as `x.loc[:, 'IA']`;\n  with names these become `x.isel(space=0)` and `da.sel(space='IA')`. \n\n* [pandas](https://pandas.pydata.org) is really more like \n  [DataFrames](https://github.com/JuliaData/DataFrames.jl), only one- and two-dimensional.\n  Writes indexing \"by position\" as `df.iat[1, 1]` for scalars or `df.iloc[1:3, :]` allowing slices,\n  and lookup \"by label\" as `df.at[dates[0], 'A']` for scalars or `df.loc['20130102':'20130104', ['A', 'B']]` for slices, \"both endpoints are *included*\" in this.\n  See also [Pandas.jl](https://github.com/JuliaPy/Pandas.jl) for a wrapper.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcabbott%2Faxiskeys.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmcabbott%2Faxiskeys.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmcabbott%2Faxiskeys.jl/lists"}