Skip to content

Commit 69bf442

Browse files
authored
Merge branch 'master' into axes
2 parents ccb9212 + 4988b2c commit 69bf442

23 files changed

+692
-373
lines changed

.github/workflows/CompatHelper.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: CompatHelper
2+
3+
on:
4+
schedule:
5+
- cron: '00 * * * *'
6+
7+
jobs:
8+
build:
9+
runs-on: ${{ matrix.os }}
10+
strategy:
11+
matrix:
12+
julia-version: [1.2.0]
13+
julia-arch: [x86]
14+
os: [ubuntu-latest]
15+
steps:
16+
- uses: julia-actions/setup-julia@latest
17+
with:
18+
version: ${{ matrix.julia-version }}
19+
- name: Install dependencies
20+
run: julia -e 'using Pkg; Pkg.add(Pkg.PackageSpec(name = "CompatHelper", url = "https://github.com/bcbi/CompatHelper.jl.git"))'
21+
- name: CompatHelper.main
22+
env:
23+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
JULIA_DEBUG: CompatHelper
25+
run: julia -e 'using CompatHelper; CompatHelper.main()'

.github/workflows/TagBot.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: TagBot
2+
on:
3+
schedule:
4+
- cron: 0 * * * *
5+
jobs:
6+
TagBot:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: JuliaRegistries/TagBot@v1
10+
with:
11+
token: ${{ secrets.GITHUB_TOKEN }}

Project.toml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
name = "AxisKeys"
22
uuid = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5"
33
license = "MIT"
4-
version = "0.0.1"
4+
version = "0.0.2"
55

66
[deps]
77
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
8-
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
8+
InvertedIndices = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
99
LazyStack = "1fad7336-0346-5a1a-a56f-a06ba010965b"
10+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1011
NamedDims = "356022a1-0364-5f58-8944-0da4b18d706f"
1112
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
1213
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1314
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1415

1516
[compat]
17+
BenchmarkTools = "0.5"
1618
IntervalSets = "0.3, 0.4"
17-
NamedDims = "0.2.18"
18-
LazyStack = "0.0.7"
19-
OffsetArrays = "0.10, 0.11"
19+
InvertedIndices = "1.0"
20+
LazyStack = "0.0.7, 0.0.8"
21+
NamedDims = "0.2.20"
22+
OffsetArrays = "0.10, 0.11, 1.0"
2023
Tables = "0.2, 1"
2124
julia = "1"
2225

2326
[extras]
27+
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
28+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
2429
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2530
UniqueVectors = "2fbcfb34-fd0c-5fbb-b5d7-e826d8f5b0a9"
2631

2732
[targets]
28-
test = ["Test", "UniqueVectors"]
33+
test = ["BenchmarkTools", "Dates", "Test", "UniqueVectors"]

README.md

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@
22

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

5+
<!--<img src="docs/readmefigure.png" alt="block picture" width="400" align="right">-->
6+
57
This package defines a thin wrapper which, alongside any array, stores a vector of "keys"
68
for each dimension. This may be useful to store perhaps actual times of measurements,
79
or some strings labeling columns, etc. These will be propagated through many
810
operations on arrays, including broadcasting, `map`, comprehensions, `sum` etc.
911

10-
It works closely with [NamedDims.jl](https://github.com/invenia/NamedDims.jl),
12+
It works closely with [NamedDims.jl](https://github.com/invenia/NamedDims.jl), another wrapper
1113
which attaches names to dimensions. These names are a tuple of symbols, like those of
1214
a `NamedTuple`. They can be used for specifying which dimensions to sum over, etc.
13-
14-
The function `wrapdims` constructs a nested pair of these wrappers,
15-
for example:
15+
The function `wrapdims` constructs a nested pair of these wrappers, for example:
1616

1717
```julia
1818
using AxisKeys
1919
data = rand(Int8, 2,10,3) .|> abs;
20-
A = wrapdims(data; channel=[:left, :right], time=range(13, step=2.5, length=10), iter=31:33)
20+
A = KeyedArray(data; channel=[:left, :right], time=range(13, step=2.5, length=10), iter=31:33)
2121
```
2222

23-
<center>
24-
<img src="docs/readmeterminal.png" alt="??" width="600" align="center"></img>
25-
</center>
23+
<p align="center">
24+
<img src="docs/readmeterminal.png" alt="terminal pretty printing" width="550" align="center">
25+
</p>
2626

2727
### Selections
2828

@@ -48,14 +48,14 @@ There are also a numer of special selectors, which work like this:
4848
|-----------------|------------------|-------------------------|---------|
4949
| one nearest | `B[time = 3]` | `B(time = Near(17.0))` | vector |
5050
| all in a range | `B[2:5, :]` | `B(Interval(14,25), :)` | matrix |
51-
| all matching | `B[3:end, 3:3]` | `B(>(17), ==(33))` | matrix |
51+
| all matching | `B[3:end, Not(3)]` | `B(>(17), !=(33))` | matrix |
5252
| mixture | `A[1, 2, end]` | `A(:left, Index[2], Index[end])` | scalar |
5353
| non-scalar | `B[iter=[1, 3]]` | `B(iter=[31, 33])` | matrix |
5454

5555
Here `Interval(13,18)` can also be written `13..18`, it's from [IntervalSets.jl](https://github.com/JuliaMath/IntervalSets.jl).
5656
Any functions can be used to select keys, including lambdas: `B(time = t -> 0<t<17)`.
5757
You may give just one `::Base.Fix2` function
58-
(such as `<=(18)` or `!=(20)`) provided its argument type matches the keys of one dimension.
58+
(such as `<=(18)` or `==(20)`) provided its argument type matches the keys of one dimension.
5959
An interval or a function always selects via `findall`,
6060
i.e. it does not drop a dimension, even if there is exactly one match.
6161

@@ -67,24 +67,27 @@ as a trailing colon makes a zero-dimensional view.
6767

6868
(Possibly selectors should be made to work in square brackets too,
6969
allowing mixed indexing `B[1, Key('β')] == B(Index[1], 'β')`
70-
and `setkey!` via `B[Key(13.0), Key('α')] = 0`. But they don't right now.)
70+
and `setkey!` via `B[Key(13.0), Key('α')] = 0`. But they don't right now,
71+
see [PR#5](https://github.com/mcabbott/AxisKeys.jl/pull/5) for an attempt.)
7172

7273
### Construction
7374

7475
```julia
75-
KeyedArray(rand(Int8, 2,10), ([:a, :b], 10:10:100))
76+
KeyedArray(rand(Int8, 2,10), ([:a, :b], 10:10:100)) # AbstractArray, Tuple{AbstractVector, ...}
7677
```
7778

78-
A nested pair with names can be constructed with keywords,
79-
and (apart from a few bugs) this should work the same in either order:
79+
A nested pair with names can be constructed with keywords for names,
80+
and (apart from a few bugs) everything should work the same way in either order:
8081

8182
```julia
8283
KeyedArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100) # KeyedArray(NamedDimsArray(...))
8384
NamedDimsArray(rand(Int8, 2,10), row=[:a, :b], col=10:10:100) # NamedDimsArray(KeyedArray(...))
8485
```
8586

86-
The function `wrapdims` does a bit more checking and fixing.
87-
It will adjust the length of key vectors if it can, and their indexing if needed to match the array:
87+
The function `wrapdims` does a bit more checking and fixing, but is not type-stable.
88+
It will adjust the length of ranges of keys if it can,
89+
and will fix indexing offsets if needed to match the array.
90+
The resulting order of wrappers is controlled by `AxisKeys.nameouter()=false`.
8891

8992
```julia
9093
wrapdims(rand(Int8, 10), alpha='a':'z')
@@ -98,35 +101,41 @@ axiskeys(ans,1) # 10:10:100 with indices 0:9
98101

99102
As usual `axes(A)` returns (a tuple of vectors of) indices,
100103
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.
104+
If the array has names, then `dimnames(A)` returns them.
105+
These functions work like `size(A, d) = size(A, name)` to get just one.
103106

104107
Many functions should work, for example:
105108

106-
* Reductions like `sum(A; dims=:channel)` can use dimension names.
107-
Likewise `prod`, `mean` etc., and `dropdims`.
108-
109109
* Broadcasting `log.(A)` and `map(log, A)`, as well as comprehensions
110110
`[log(x) for x in A]` should all work.
111111

112-
* Transpose etc, `permutedims`.
112+
* Transpose etc, `permutedims`, `mapslices`.
113113

114114
* Concatenation `hcat(B, B .+ 100)` works.
115115
Note that the keys along the glued direction may not be unique afterwards.
116116

117+
* Reductions like `sum(A; dims=:channel)` can use dimension names.
118+
Likewise `prod`, `mean` etc., and `dropdims`.
119+
117120
* Some linear algebra functions like `*` and `\` will work.
118121

119122
* Getproperty returns the key vector, to allow things like
120-
`for (i,t) in enumerate(A.time); fun(A[i], t); ...`.
123+
`for (i,t) in enumerate(A.time); fun(val = A[i,:], time = t); ...`.
121124

122125
* Vectors support `push!(V, val)`, which will try to extend the key vector.
123126
There is also a method `push!(V, key => val)` which pushes in a new key.
124127

125128
To allow for this limited mutability, `V.keys isa Ref` for vectors,
126129
while `A.keys isa Tuple` for matrices & higher. But `axiskeys(A)` always returns a tuple.
127130

128-
* [LazyStack](https://github.com/mcabbott/LazyStack.jl)`.stack` is now hooked up.
129-
Stacks of named tuples like `stack((a=i, b=i^2) for i=1:3)` create axis keys.
131+
* Named tuples can be converted to and from keyed vectors,
132+
with `collect(keys(nt)) == Symbol.(axiskeys(V),1)`
133+
134+
* [LazyStack](https://github.com/mcabbott/LazyStack.jl)`.stack` understands names and keys.
135+
Stacks of named tuples like `stack((a=i, b=i^2) for i=1:5)` create a matrix with `[:a, :b]`.
136+
137+
* [NamedPlus](https://github.com/mcabbott/NamedPlus.jl) has a macro which works on comprehensions:
138+
`@named [n^pow for n=1:10, pow=0:2:4]` has names and keys.
130139

131140
### Absent
132141

@@ -234,4 +243,4 @@ In 🐍-land:
234243
[DataFrames](https://github.com/JuliaData/DataFrames.jl), only one- and two-dimensional.
235244
Writes indexing "by position" as `df.iat[1, 1]` for scalars or `df.iloc[1:3, :]` allowing slices,
236245
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.
237-
246+
See also [Pandas.jl](https://github.com/JuliaPy/Pandas.jl) for a wrapper.

docs/readmefigure.png

-22.8 KB
Loading

docs/readmefigure.wl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ Text["[:l, :r]",{10.5,1.5,4},{-1,-1},{4,-1}]
3131
},
3232
{Darker[Orange],
3333
Arrow[Tube[{{10.5,0.1,0.8}, {10.5,0.1,3.5}}]],
34-
Text["'\[Alpha]':'\[Gamma]'",{10.5,0,2},{0,1},{1,20}]
34+
Text["31:33",{10.5,0,2},{0,1},{1,20}]
3535
}
3636

3737
},
3838
Boxed->False,
3939
Axes->True,
4040
Ticks->{Range[10], Range[2], Range[3]},
41-
AxesLabel->{":time", ":channel", ":z"},
41+
AxesLabel->{":time", ":channel", ":iter"},
4242
AxesStyle->Directive[Darker[Gray], Thickness[0.004]],
4343

4444
(*FormatType\[Rule]StandardForm,*)

docs/readmeterminal.png

16.2 KB
Loading

docs/repl.jl

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

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

55
julia> using AxisKeys, Random
66

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

9+
julia> KeyedArray((a=3, b=5, c=7)) # Using keys of a NamedTuple
10+
1-dimensional KeyedArray(...) with keys:
11+
3-element Vector{Symbol}
12+
And data, 3-element Array{Int64,1}:
13+
(:a) 3
14+
(:b) 5
15+
(:c) 7
16+
917
julia> D = wrapdims(rand(Int8,5), iter = 10:10:50) # Convenience constructor
1018
1-dimensional KeyedArray(NamedDimsArray(...)) with range:
1119
iter 5-element StepRange{Int64,...}
@@ -39,7 +47,7 @@ julia> @view E[col=1] # Fixing one index gives a slice
3947
row -105
4048
3
4149

42-
julia> C = wrapdims(rand(2,10) .+ (0:1), obs=["dog", "cat"], time=range(0, step=0.5, length=10))
50+
julia> C = KeyedArray(rand(2,10) .+ (0:1), obs=["dog", "cat"], time=range(0, step=0.5, length=10))
4351
2-dimensional KeyedArray(NamedDimsArray(...)) with ranges:
4452
obs 2-element Vector{String}
4553
time 10-element StepRangeLen{Float64,...}
@@ -48,7 +56,7 @@ And data, 2×10 Array{Float64,2}:
4856
("dog") 0.160006 0.602298 0.383491 0.745181 0.0823367 0.452418 0.281987
4957
("cat") 1.42296 1.36346 1.59291 1.26281 1.24468 1.76372 1.14364
5058

51-
julia> names(C) # Works like size & axes, i.e. names(C,2) == :time
59+
julia> dimnames(C) # Works like size & axes, i.e. dimnames(C,2) == :time
5260
(:obs, :time)
5361

5462
julia> axiskeys(C) # Likewise, axiskeys(C, :time) == 0:0.5:4.5
@@ -89,10 +97,10 @@ And data, 2×2 Array{Float64,2}:
8997
("cat") 5.85958 21.8234
9098

9199
julia> ans("mouse")
92-
ERROR: key of type String is ambiguous, matches dimensions (1, 2)
100+
ERROR: ArgumentError: key of type String is ambiguous, matches dimensions (1, 2)
93101

94102
julia> C("mouse")
95-
ERROR: could not find key "mouse" in range ["dog", "cat"]
103+
ERROR: ArgumentError: could not find key "mouse" in vector ["dog", "cat"]
96104

97105
julia> for (i,t) in enumerate(C.time)
98106
t > 3 && println("at time $t, value cat = ", C[2,i])
@@ -178,7 +186,19 @@ And data, 3×81×2 Array{Float64,3}:
178186
(:b) 0.123933 0.988803 0.243089 0.701553 0.11737
179187
(:c) 0.850917 0.0495313 0.0470764 0.322251 0.642556
180188

181-
# Ranges are printed with colours based on eltype, btw!
182-
183-
julia> H(:a, -14, "one") # uses UniqueVector's fast lookup
189+
julia> H(:a, -14, "one") # uses the UniqueVector's fast lookup
184190
0.9948971186701887
191+
192+
julia> using LazyStack # A package for concatenating arrays
193+
194+
julia> stack(:pre, n .* D for n in 1:10)
195+
2-dimensional NamedDimsArray(KeyedArray(...)) with keys:
196+
iter 5-element StepRange{Int64,...}
197+
pre 10-element OneTo{Int}
198+
And data, 5×10 Array{Int64,2}:
199+
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10)
200+
(10) 115 230 345 460 575 690 805 920 1035 1150
201+
(20) 99 198 297 396 495 594 693 792 891 990
202+
(30) 0 0 0 0 0 0 0 0 0 0
203+
(40) 57 114 171 228 285 342 399 456 513 570
204+
(50) 88 176 264 352 440 528 616 704 792 880

docs/speed.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ and of other packages of similar or overlapping concerns.
44
Plus generally a place to collect their various syntax, for comparison.
55
=#
66

7-
using AxisKeys, BenchmarkTools # Julia 1.3 + macbook escape
7+
using AxisKeys, BenchmarkTools # Julia 1.4 + macbook escape
88

99
#==============================#
1010
#===== getkey vs getindex =====#
@@ -19,9 +19,9 @@ bothmat2 = wrapdims(mat.data, x=collect(11:13), y=collect(21:24))
1919

2020
@btime $bothmat[3,4] # 1.700 ns
2121
@btime $bothmat[x=3, y=4] # 1.701 ns
22-
@btime $bothmat(13, 24) # 5.874 ns
23-
@btime $bothmat(x=13, y=24) # 43.302 ns (2 allocations: 64 bytes)
24-
@btime $bothmat2(13, 24) # 16.719 ns
22+
@btime $bothmat(13, 24) # 5.874 ns -- fast range lookup
23+
@btime $bothmat(x=13, y=24) # 14.063 ns
24+
@btime $bothmat2(13, 24) # 16.719 ns -- collected vector
2525

2626
ind_collect(A) = [@inbounds(A[ijk...]) for ijk in Iterators.ProductIterator(axes(A))]
2727
key_collect(A) = [@inbounds(A(vals...)) for vals in Iterators.ProductIterator(axiskeys(A))]
@@ -31,14 +31,14 @@ bigmat2 = wrapdims(rand(100,100), collect(1:100), collect(1:100));
3131

3232
@btime ind_collect($(bigmat.data)); # 9.117 μs (4 allocations: 78.25 KiB)
3333
@btime ind_collect($bigmat); # 11.530 μs (4 allocations: 78.25 KiB)
34-
@btime key_collect($bigmat); # 20.671 μs (4 allocations: 78.27 KiB) -- fast range lookup
34+
@btime key_collect($bigmat); # 64.064 μs (4 allocations: 78.27 KiB) -- fast range lookup, was 20μs!
3535
@btime key_collect($bigmat2); # 718.804 μs (5 allocations: 78.27 KiB) -- findfirst(..., vector) lookup
3636

3737
twomat = wrapdims(mat.data, x=[:a, :b, :c], y=21:24)
38-
@btime $twomat(x=:a, y=24) # 57.396 ns (2 allocations: 64 bytes)
38+
@btime $twomat(x=:a, y=24) # 36.734 ns (2 allocations: 64 bytes)
3939

4040
@btime $twomat(24.0) # 26.686 ns (4 allocations: 112 bytes)
41-
@btime $twomat(y=24.0) # 52.372 ns (6 allocations: 144 bytes)
41+
@btime $twomat(y=24.0) # 33.860 ns (4 allocations: 112 bytes)
4242
@btime view($twomat, :,3) # 24.951 ns (4 allocations: 112 bytes)
4343

4444

@@ -50,7 +50,7 @@ using OffsetArrays #===== OffsetArrays =====#
5050

5151
of1 = OffsetArray(rand(3,4), 11:13, 21:24)
5252

53-
@btime $of1[13,24] # 3.652 ns
53+
@btime $of1[13,24] # 1.700 ns
5454

5555
bigoff = OffsetArray(bigmat.data, 1:100, 1:100);
5656
@btime ind_collect($bigoff); # 15.372 μs (5 allocations: 78.30 KiB)

src/AxisKeys.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ export KeyedArray, axiskeys
55

66
include("axes.jl")
77

8+
include("lookup.jl")
9+
810
include("names.jl")
911
export NamedDimsArray, dimnames
1012

1113
include("wrap.jl")
1214
export wrapdims
1315

1416
include("selectors.jl")
15-
export Near, Index, Interval
17+
export Near, Index, Interval, Not
1618

1719
include("functions.jl")
1820

0 commit comments

Comments
 (0)