Skip to content

Commit ee3f326

Browse files
author
Michael Abbott
committed
change name to AxisKeys, take 2
1 parent bfe8fbf commit ee3f326

20 files changed

+612
-657
lines changed

β€ŽProject.tomlβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
name = "AxisRanges"
2-
uuid = "7d985058-612f-5500-9f06-de9955ae0899"
1+
name = "AxisKeys"
2+
uuid = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5"
33
license = "MIT"
44
version = "0.0.1"
55

β€ŽREADME.mdβ€Ž

Lines changed: 37 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# AxisRanges.jl
1+
# AxisKeys.jl
22

3-
[![Build Status](https://travis-ci.org/mcabbott/AxisRanges.jl.svg?branch=master)](https://travis-ci.org/mcabbott/AxisRanges.jl)
3+
[![Build Status](https://travis-ci.org/mcabbott/AxisKeys.jl.svg?branch=master)](https://travis-ci.org/mcabbott/AxisKeys.jl)
44

5-
This package defines a thin wrapper which, alongside any array, stores an extra "range"
5+
This package defines a thin wrapper which, alongside any array, stores a vector of "keys"
66
for each dimension. This may be useful to store perhaps actual times of measurements,
77
or some strings labeling columns, etc. These will be propagated through many
88
operations on arrays, including broadcasting, `map`, comprehensions, `sum` etc.
@@ -15,7 +15,7 @@ The function `wrapdims` constructs a nested pair of these wrappers,
1515
for example:
1616

1717
```julia
18-
using AxisRanges
18+
using AxisKeys
1919
data = rand(Int8, 2,10,3) .|> abs;
2020
A = wrapdims(data; channel=[:left, :right], time=range(13, step=2.5, length=10), iter=31:33)
2121
```
@@ -28,11 +28,10 @@ A = wrapdims(data; channel=[:left, :right], time=range(13, step=2.5, length=10),
2828

2929
Indexing still works directly on the underlying array,
3030
and keyword indexing works exactly as for a `NamedDimsArray`.
31-
But in addition, it is possible to pick out elements based on the new ranges,
32-
which for clarity we will call "lookup" based on a "key".
33-
This is written with round brackets:
31+
But in addition, it is possible to pick out elements based on the keys,
32+
which for clarity we will call lookup. This is written with round brackets:
3433

35-
| Dimension `d` | Indexing: `i ∈ axes(A,d)` | Lookup: `key ∈ ranges(A,d)` |
34+
| Dimension `d` | Indexing: `i ∈ axes(A,d)` | Lookup: `key ∈ axiskeys(A,d)` |
3635
|--------------------|---------------------|---------------------|
3736
| by position | `A[1,2,:]` | `A(:left, 15.5, :)` |
3837
| by name | `A[iter=1]` | `A(iter=31)` |
@@ -73,33 +72,34 @@ and `setkey!` via `B[Key(13.0), Key('Ξ±')] = 0`. But they don't right now.)
7372
### Construction
7473

7574
```julia
76-
RangeArray(rand(Int8, 2,10), ([:a, :b], 10:10:100))
75+
KeyedArray(rand(Int8, 2,10), ([:a, :b], 10:10:100))
7776
```
7877

7978
A nested pair with names can be constructed with keywords,
8079
and (apart from a few bugs) this should work the same in either order:
8180

8281
```julia
83-
RangeArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100) # RangeArray(NamedDimsArray(...))
84-
NamedDimsArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100) # NamedDimsArray(RangeArray(...))
82+
KeyedArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100) # KeyedArray(NamedDimsArray(...))
83+
NamedDimsArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100) # NamedDimsArray(KeyedArray(...))
8584
```
8685

8786
The function `wrapdims` does a bit more checking and fixing.
88-
It will adjust the length of ranges if it can, and their indexing if needed to match the array:
87+
It will adjust the length of key vectors if it can, and their indexing if needed to match the array:
8988

9089
```julia
9190
wrapdims(rand(Int8, 10), alpha='a':'z')
9291
# Warning: range 'a':1:'z' replaced by 'a':1:'j', to match size(A, 1) == 10
9392

9493
wrapdims(OffsetArray(rand(Int8, 10),-1), iter=10:10:100)
95-
ranges(ans,1) # 10:10:100 with indices 0:9
94+
axiskeys(ans,1) # 10:10:100 with indices 0:9
9695
```
9796

9897
### Functions
9998

100-
As usual `axes(A)` returns (a tuple of vectors of) indices, and `ranges(A)` returns keys.
101-
If the array has names, then `dimnames(A)` returns them, and functions like `axes(A, name)`
102-
give just one.
99+
As usual `axes(A)` returns (a tuple of vectors of) indices,
100+
and `axiskeys(A)` returns (a tuple of vectors of) keys.
101+
If the array has names, then `dimnames(A)` returns them,
102+
and functions like `axes(A, name)` give just one.
103103

104104
Many functions should work, for example:
105105

@@ -116,49 +116,49 @@ Many functions should work, for example:
116116

117117
* Some linear algebra functions like `*` and `\` will work.
118118

119-
* Getproperty returns the range, to allow things like
119+
* Getproperty returns the key vector, to allow things like
120120
`for (i,t) in enumerate(A.time); fun(A[i], t); ...`.
121121

122-
* Vectors support `push!(V, val)`, which will try to extend the range.
123-
There is also a method `push!(V, key => val)` which pushes the key into the range.
122+
* Vectors support `push!(V, val)`, which will try to extend the key vector.
123+
There is also a method `push!(V, key => val)` which pushes in a new key.
124124

125-
To allow for this limited mutability, `V.ranges isa Ref` for vectors,
126-
while `A.ranges isa Tuple` for matrices & higher. But `ranges(A)` always returns a tuple.
125+
To allow for this limited mutability, `V.keys isa Ref` for vectors,
126+
while `A.keys isa Tuple` for matrices & higher. But `axiskeys(A)` always returns a tuple.
127127

128128
* [LazyStack](https://github.com/mcabbott/LazyStack.jl)`.stack` is now hooked up.
129129
Stacks of named tuples like `stack((a=i, b=i^2) for i=1:3)` create axis keys.
130130

131131
### Absent
132132

133133
* There is no automatic alignment of dimensions by name.
134-
Thus `A .+ A[iter=3]` is fine as both names and ranges line up,
134+
Thus `A .+ A[iter=3]` is fine as both names and keys line up,
135135
but `A .+ B` is an error, as `B`'s first name is `:time` not `:channel`.
136136
(See [NamedPlus](https://github.com/mcabbott/NamedPlus.jl)`.@named` for something like this.)
137137

138138
As for [NamedDims.jl](https://github.com/invenia/NamedDims.jl), the guiding idea
139139
is that every operation which could be done on ordinary arrays
140-
should still produce the same data, but propagate the extra information (names/ranges),
140+
should still produce the same data, but propagate the extra information (names/keys),
141141
and error if it conflicts.
142142

143143
Both packages allow for wildcards, which never conflict.
144-
In NamedDims.jl this is the name `:_`, here it is a range `Base.OneTo(n)`,
144+
In NamedDims.jl this is the name `:_`, here it is a `Base.OneTo(n)`,
145145
like the `axes` of an `Array`. These can be constructed as
146146
`M = wrapdims(rand(2,2); _=[:a, :b], cols=nothing)`,
147147
and for instance `M .+ M'` is not an error.
148148

149-
* There are no special types provided for ranges, they can be any `AbstractVector`s.
150-
Lookup happens by calling `i = findfirst(isequal(20.0), ranges(A,2))`,
151-
or `is = findall(<(18), ranges(A,2))`.
149+
* There are no special types provided for key vectors, they can be any `AbstractVector`s.
150+
Lookup happens by calling `i = findfirst(isequal(20.0), axiskeys(A,2))`,
151+
or `is = findall(<(18), axiskeys(A,2))`.
152152

153153
If you need lookup to be very fast, then you will want to use a package like
154154
[UniqueVectors.jl](https://github.com/garrison/UniqueVectors.jl)
155155
or [AcceleratedArrays.jl](https://github.com/andyferris/AcceleratedArrays.jl)
156156
or [CategoricalArrays.jl](https://github.com/JuliaData/CategoricalArrays.jl).
157-
To apply such a type to all ranges, you may write
157+
To apply such a type to all dimensions, you may write
158158
`D = wrapdims(rand(1000), UniqueVector, rand(Int, 1000))`.
159159
Then `D(n)` here will use the fast lookup from UniqueVectors.jl (about 60x faster).
160160

161-
When a dimension’s range is a Julia `AbstractRange`, then this package provides some faster
161+
When a key vector is a Julia `AbstractRange`, then this package provides some faster
162162
overloads for things like `findall(<=(42), 10:10:100)`.
163163

164164
* There is also no automatic alignment by keys, like time.
@@ -171,7 +171,7 @@ This is more or less an attempt to replace [AxisArrays](https://github.com/Julia
171171
with several smaller packages. The complaints are:
172172
(1) It's confusing to guess whether to perform indexing or lookup
173173
based on whether it is given an integer (index) or not (key).
174-
(2) Each "range" was its own type `Axis{:name}` which allowed zero-overhead lookup
174+
(2) Each "axis" was its own type `Axis{:name}` which allowed zero-overhead lookup
175175
before Julia 1.0. But this is now possible with a simpler design.
176176
(They were called axes before `Base.axes()` was added, hence (3) the confusing terminology.)
177177
(4) Broadcasting is not supported, as this changed dramatically in Julia 1.0.
@@ -180,10 +180,10 @@ with several smaller packages. The complaints are:
180180

181181
Other older packages (pre-Julia-1.0):
182182

183-
* [NamedArrays](https://github.com/davidavdav/NamedArrays.jl) also provides names & ranges,
184-
ranges are always OrderedDict. Named lookup looks like `NA[:x => 13.0]`
183+
* [NamedArrays](https://github.com/davidavdav/NamedArrays.jl) also provides names & keys,
184+
which are always `OrderedDict`s. Named lookup looks like `NA[:x => 13.0]`
185185
instead of `A(x=13.0)` here; this is not very fast.
186-
Dimension names & ranges can be set after creation. Has nice pretty-printing routines.
186+
Dimension names & keys can be set after creation. Has nice pretty-printing routines.
187187

188188
* [LabelledArrays](https://github.com/JuliaDiffEq/LabelledArrays.jl) adds names for individual elements, more like a NamedTuple.
189189
Only for small sizes: the storage inside is a Tuple, not an Array.
@@ -193,24 +193,24 @@ Only for small sizes: the storage inside is a Tuple, not an Array.
193193
* [OffsetArrays](https://github.com/JuliaArrays/OffsetArrays.jl) actually changes the indices
194194
of an Array, allowing any continuous integer range, like `0:9` or `-10:10`.
195195
This package is happy to wrap such arrays,
196-
and if needed will adjust indices of the given ranges:
196+
and if needed will adjust indices of the given key vectors:
197197
`O = wrapdims(OffsetArray(["left", "mid", "right"], -1:1), 'A':'C')`,
198198
then `O[-1:0]` works.
199199

200200
Other new packages (post-1.0):
201201

202202
* [DimensionalData](https://github.com/rafaqz/DimensionalData.jl) is another replacement
203-
for AxisArrays. It again uses types like `Dim{:name}` to store both name & range,
203+
for AxisArrays. It again uses types like `Dim{:name}` to store both name & keys,
204204
plus some special ones like `X, Y` of the same abstract type.
205205
Named lookup then looks like `DA[X <| At(13.0)]`, roughly like `A(x=13.0)` here.
206206

207207
* [NamedPlus](https://github.com/mcabbott/NamedPlus.jl) is some experiments using NamedDims.
208208
Function `align` permutes dimensions automatically,
209209
and macro `@named` can introduce this into broadcasting expressions.
210210

211-
* [IndexedDims](https://github.com/invenia/IndexedDims.jl) like this package adds ranges
211+
* [IndexedDims](https://github.com/invenia/IndexedDims.jl) like this package adds keys
212212
on top of the names from NamedDims.
213-
These ranges must always be [AcceleratedArrays](https://github.com/andyferris/AcceleratedArrays.jl).
213+
These key vectors must always be [AcceleratedArrays](https://github.com/andyferris/AcceleratedArrays.jl).
214214
Like AxisArrays, it tries to guess whether to do indexing or lookup based on type.
215215

216216
* [Dictionaries](https://github.com/andyferris/Dictionaries.jl) does very fast lookup only
@@ -235,53 +235,3 @@ In 🐍-land:
235235
Writes indexing "by position" as `df.iat[1, 1]` for scalars or `df.iloc[1:3, :]` allowing slices,
236236
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.
237237

238-
<!--
239-
### Words
240-
241-
This package isn't registered yet, partly because what to call things is unclear.
242-
243-
1. Julia has adopted `index::Int ∈ axes(A,1)`. So I think these other structures need other names.
244-
245-
2. From NamedDims.jl, each `name::Symbol` belongs to a dimension, `d ∈ 1:ndims(A)`.
246-
247-
3. LabelledArrays.jl attaches a `label::Symbol` to every element of the array,
248-
so that perhaps `A.label == A[2,2]`.
249-
250-
4. For now I use `key` for the thing you lookup, but it could also be `label`?
251-
I think a `value` is what `A[1,2]` returns.
252-
253-
5. For now I use `ranges(A,1)` for the vector of possible keys.
254-
But these do not have to be like `Base.range`'s `AbstractRange`s.
255-
Perhaps `scales(A,1)`? A scale of keys? `domains(A,1)`?
256-
257-
6. The package could be `AxisScales` (hard to say) or `LabelledDims` (how many `l`s again?)
258-
or `AxiKeys` (short!) or `ScaledDims` or what?
259-
260-
7. The constructor function is `wrapdims` because it doesn't only make `RangeArray`s,
261-
but perhaps should match the package name better.
262-
263-
264-
### About
265-
266-
Michael Abbott, 2019.
267-
-->
268-
269-
<!--
270-
Terminology
271-
272-
273-
Julia | axes | index | <i></i> | <i></i> | <i></i> | <i></i>
274-
--------------|------|-------|-----------|-------|-------|------
275-
this package | axes | index | names | range | key | meta
276-
NamedDims | axes | index | dimnames | - | - | -
277-
AxisArrays | | | axisnames | axis | value | -
278-
LabelledArrays| axes | ? | ? | ? | name | -
279-
--------------|------|-------|-----------|-------|-------|------
280-
Python | _ | _ | _ | _ | _ |
281-
--------------|------|---------------|---------|-------|-------------------|------
282-
xarrays | ? | integer label | dims | coords | coordinate label | attrs
283-
pytorch | ? | ? | names | - | - | -
284-
285-
286-
287-
-->

β€Ždocs/readmefigure.wlβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Lighting->{
5353
},
5454

5555
Epilog->{
56-
Inset["RangeArray{T,3,...}",{0.3,1},{0,Top}],
56+
Inset["KeyedArray{T,3,...}",{0.3,1},{0,Top}],
5757
(*Inset[Framed["Array{T,3}",Background\[Rule]Opacity[0.5,White]],{0.4,0.4},{0,0}],*)
5858
Inset["NamedDimsArray{L,T,3,...}",{0.7,Bottom},{0,Bottom}]
5959
}

β€Ždocs/repl.jlβ€Ž

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
julia> #===== Examples of how to use the AxisArrays package. =====#
22

3-
(v1.3) pkg> add https://github.com/mcabbott/AxisRanges.jl # Not a registered package
3+
(v1.3) pkg> add https://github.com/mcabbott/AxisKeys.jl # Not a registered package
44

5-
julia> using AxisRanges, Random
5+
julia> using AxisKeys, Random
66

77
julia> Random.seed!(42);
88

99
julia> D = wrapdims(rand(Int8,5), iter = 10:10:50) # Convenience constructor
10-
1-dimensional RangeArray(NamedDimsArray(...)) with range:
10+
1-dimensional KeyedArray(NamedDimsArray(...)) with range:
1111
↓ iter ∈ 5-element StepRange{Int64,...}
1212
And data, 5-element Array{Int8,1}:
1313
(10) 115
@@ -40,7 +40,7 @@ julia> @view E[col=1] # Fixing one index gives a slice
4040
3
4141

4242
julia> C = wrapdims(rand(2,10) .+ (0:1), obs=["dog", "cat"], time=range(0, step=0.5, length=10))
43-
2-dimensional RangeArray(NamedDimsArray(...)) with ranges:
43+
2-dimensional KeyedArray(NamedDimsArray(...)) with ranges:
4444
↓ obs ∈ 2-element Vector{String}
4545
β†’ time ∈ 10-element StepRangeLen{Float64,...}
4646
And data, 2Γ—10 Array{Float64,2}:
@@ -51,7 +51,7 @@ And data, 2Γ—10 Array{Float64,2}:
5151
julia> names(C) # Works like size & axes, i.e. names(C,2) == :time
5252
(:obs, :time)
5353

54-
julia> ranges(C) # Likewise, ranges(C, :time) == 0:0.5:4.5
54+
julia> axiskeys(C) # Likewise, axiskeys(C, :time) == 0:0.5:4.5
5555
(["dog", "cat"], 0.0:0.5:4.5)
5656

5757
julia> axes(C) # Base.axes is untouched
@@ -67,20 +67,20 @@ julia> C(time=Near(1.1), obs="dog") # Selector Near(val) finds one closest index
6767
0.3834911947029529
6868

6969
julia> C(!=("cat"), Index[end]) # Functions allowed as selectors, and Index[end] works
70-
1-dimensional RangeArray(NamedDimsArray(...)) with range:
70+
1-dimensional KeyedArray(NamedDimsArray(...)) with range:
7171
↓ obs ∈ 1-element view(::Vector{String},...)
7272
And data, 1-element view(::Array{Float64,2}, [1], 10) with eltype Float64:
7373
("dog") 0.28198708251379423
7474

7575
julia> C(0.5) # Here 0.5 is unambiguous as types of ranges are distinct
76-
1-dimensional RangeArray(NamedDimsArray(...)) with range:
76+
1-dimensional KeyedArray(NamedDimsArray(...)) with range:
7777
↓ obs ∈ 2-element Vector{String}
7878
And data, 2-element view(::Array{Float64,2}, :, 2) with eltype Float64:
7979
("dog") 0.602297580266383
8080
("cat") 1.3634584219520556
8181

8282
julia> C * C' # Functions like adjoint and * work through wrappers
83-
2-dimensional RangeArray(NamedDimsArray(...)) with ranges:
83+
2-dimensional KeyedArray(NamedDimsArray(...)) with ranges:
8484
↓ obs ∈ 2-element Vector{String}
8585
β†’ obs ∈ 2-element Vector{String}
8686
And data, 2Γ—2 Array{Float64,2}:
@@ -104,7 +104,7 @@ at time 4.5, value cat = 1.1436376769992096
104104
julia> using Statistics
105105

106106
julia> mean(C, dims=:time) # Reduction functions should accept dimension names
107-
2-dimensional RangeArray(NamedDimsArray(...)) with ranges:
107+
2-dimensional KeyedArray(NamedDimsArray(...)) with ranges:
108108
↓ obs ∈ 2-element Vector{String}
109109
β†’ time ∈ 1-element OneTo{Int}
110110
And data, 2Γ—1 Array{Float64,2}:
@@ -113,7 +113,7 @@ And data, 2Γ—1 Array{Float64,2}:
113113
("cat") 1.4542825908906043
114114

115115
julia> map(sqrt, D) .* sqrt.(D) # map, broadcasting, and generators should work
116-
1-dimensional RangeArray(NamedDimsArray(...)) with range:
116+
1-dimensional KeyedArray(NamedDimsArray(...)) with range:
117117
↓ iter ∈ 5-element StepRange{Int64,...}
118118
And data, 5-element Array{Float64,1}:
119119
(10) 114.99999999999999
@@ -123,7 +123,7 @@ And data, 5-element Array{Float64,1}:
123123
(50) 88.0
124124

125125
julia> vcat(D', zero(D'), similar(D'))
126-
2-dimensional RangeArray(NamedDimsArray(...)) with ranges:
126+
2-dimensional KeyedArray(NamedDimsArray(...)) with ranges:
127127
↓ _ ∈ 3-element OneTo{Int}
128128
β†’ iter ∈ 5-element StepRange{Int64,...}
129129
And data, 3Γ—5 Array{Int8,2}:
@@ -134,8 +134,8 @@ And data, 3Γ—5 Array{Int8,2}:
134134

135135
julia> F = wrapdims(rand(1:100, 5), πŸ”€ = 'a':'z') # ranges are adjusted if possible
136136
β”Œ Warning: range 'a':1:'z' replaced by 'a':1:'e', to match size(A, 1) == 5
137-
β”” @ AxisRanges ~/.julia/dev/AxisRanges/src/wrap.jl:46
138-
1-dimensional RangeArray(NamedDimsArray(...)) with range:
137+
β”” @ AxisKeys ~/.julia/dev/AxisKeys/src/wrap.jl:46
138+
1-dimensional KeyedArray(NamedDimsArray(...)) with range:
139139
↓ πŸ”€ ∈ 5-element StepRange{Char,...}
140140
And data, 5-element Array{Int64,1}:
141141
('a') 16
@@ -145,7 +145,7 @@ And data, 5-element Array{Int64,1}:
145145
('e') 44
146146

147147
julia> push!(F, 10^6) # push! also knows to extend 'a':'e' by one
148-
1-dimensional RangeArray(NamedDimsArray(...)) with range:
148+
1-dimensional KeyedArray(NamedDimsArray(...)) with range:
149149
↓ πŸ”€ ∈ 6-element StepRange{Char,...}
150150
And data, 6-element Array{Int64,1}:
151151
('a') 16
@@ -161,7 +161,7 @@ julia> u = unique(rand(Int8, 100));
161161

162162
julia> H = wrapdims(rand(3,length(u),2), UniqueVector; # apply this type to all ranges
163163
row=[:a, :b, :c], col=u, page=["one", "two"])
164-
3-dimensional RangeArray(NamedDimsArray(...)) with ranges:
164+
3-dimensional KeyedArray(NamedDimsArray(...)) with ranges:
165165
↓ row ∈ 3-element UniqueVector{Symbol}
166166
β†’ col ∈ 81-element UniqueVector{Int8}
167167
β–‘ page ∈ 2-element UniqueVector{String}

0 commit comments

Comments
Β (0)