|
1 | 1 | # AxisArrays
|
2 | 2 |
|
3 |
| -[](https://travis-ci.org/JuliaArrays/AxisArrays.jl) [](https://coveralls.io/github/JuliaArrays/AxisArrays.jl?branch=master) |
| 3 | +[](http://pkg.julialang.org/?pkg=AxisArrays) |
| 4 | + |
| 5 | +[](https://travis-ci.org/JuliaArrays/AxisArrays.jl) |
| 6 | +[](https://coveralls.io/github/JuliaArrays/AxisArrays.jl?branch=master) |
| 7 | +[![Stable Documentation][docs-stable-img]][docs-stable-url] |
| 8 | +[![Latest Documentation][docs-latest-img]][docs-latest-url] |
4 | 9 |
|
5 | 10 | This package for the Julia language provides an array type (the `AxisArray`) that knows about its dimension names and axis values.
|
6 | 11 | This allows for indexing with the axis name without incurring any runtime overhead.
|
7 | 12 | AxisArrays can also be indexed by the values of their axes, allowing column names or interval selections.
|
8 | 13 | This permits one to implement algorithms that are oblivious to the storage order of the underlying arrays.
|
9 |
| -In contrast to similar approaches in [Images.jl](https://github.com/timholy/Images.jl) and [NamedArrays.jl](https://github.com/davidavdav/NamedArrays), this allows for type-stable selection of dimensions and compile-time axis lookup. It is also better suited for regularly sampled axes, like samples over time. |
| 14 | +In contrast to similar approaches in [NamedArrays.jl](https://github.com/davidavdav/NamedArrays) and old versions of [Images.jl](https://github.com/timholy/Images.jl), this allows for type-stable selection of dimensions and compile-time axis lookup. It is also better suited for regularly sampled axes, like samples over time. |
10 | 15 |
|
11 | 16 | Collaboration is welcome! This is still a work-in-progress. See [the roadmap](https://github.com/JuliaArrays/AxisArrays.jl/issues/7) for the project's current direction.
|
12 | 17 |
|
13 |
| -## Example of currently-implemented behavior: |
14 |
| - |
15 |
| -```julia |
16 |
| -julia> Pkg.add("AxisArrays") |
17 |
| -julia> using AxisArrays, Unitful |
18 |
| -julia> import Unitful: s, ms, µs |
19 |
| - |
20 |
| -julia> rng = MersenneTwister(123) # Seed a random number generator for repeatable examples |
21 |
| -julia> fs = 40000 # Generate a 40kHz noisy signal, with spike-like stuff added for testing |
22 |
| -julia> y = randn(rng, 60*fs+1)*3 |
23 |
| -julia> for spk = (sin.(0.8:0.2:8.6) .* [0:0.01:.1; .15:.1:.95; 1:-.05:.05] .* 50, |
24 |
| - sin.(0.8:0.4:8.6) .* [0:0.02:.1; .15:.1:1; 1:-.2:.1] .* 50) |
25 |
| - i = rand(rng, round(Int,.001fs):1fs) |
26 |
| - while i+length(spk)-1 < length(y) |
27 |
| - y[i:i+length(spk)-1] += spk |
28 |
| - i += rand(rng, round(Int,.001fs):1fs) |
29 |
| - end |
30 |
| - end |
31 |
| - |
32 |
| -julia> A = AxisArray([y 2y], Axis{:time}(0s:1s/fs:60s), Axis{:chan}([:c1, :c2])) |
33 |
| -2-dimensional AxisArray{Float64,2,...} with axes: |
34 |
| - :time, 0.0 s:2.5e-5 s:60.0 s |
35 |
| - :chan, Symbol[:c1, :c2] |
36 |
| -And data, a 2400001×2 Array{Float64,2}: |
37 |
| - 3.5708 7.14161 |
38 |
| - 6.14454 12.2891 |
39 |
| - 3.42795 6.85591 |
40 |
| - 1.37825 2.75649 |
41 |
| - -1.19004 -2.38007 |
42 |
| - -1.99414 -3.98828 |
43 |
| - 2.9429 5.88581 |
44 |
| - -0.226449 -0.452898 |
45 |
| - 0.821446 1.64289 |
46 |
| - -0.582687 -1.16537 |
47 |
| - ⋮ |
48 |
| - -3.50593 -7.01187 |
49 |
| - 2.26783 4.53565 |
50 |
| - -0.16902 -0.33804 |
51 |
| - -3.84852 -7.69703 |
52 |
| - 0.226457 0.452914 |
53 |
| - 0.560809 1.12162 |
54 |
| - 4.67663 9.35326 |
55 |
| - -2.41005 -4.8201 |
56 |
| - -3.71612 -7.43224 |
57 |
| -``` |
58 |
| - |
59 |
| -AxisArrays behave like regular arrays, but they additionally use the axis |
60 |
| -information to enable all sorts of fancy behaviors. For example, we can specify |
61 |
| -indices in *any* order, just so long as we annotate them with the axis name: |
62 |
| - |
63 |
| -```julia |
64 |
| -julia> A[Axis{:time}(4)] |
65 |
| -1-dimensional AxisArray{Float64,1,...} with axes: |
66 |
| - :chan, Symbol[:c1, :c2] |
67 |
| -And data, a 2-element Array{Float64,1}: |
68 |
| - 1.37825 |
69 |
| - 2.75649 |
70 |
| - |
71 |
| -julia> A[Axis{:chan}(:c2), Axis{:time}(1:5)] |
72 |
| -1-dimensional AxisArray{Float64,1,...} with axes: |
73 |
| - :time, 0.0 s:2.5e-5 s:0.0001 s |
74 |
| -And data, a 5-element Array{Float64,1}: |
75 |
| - 7.14161 |
76 |
| - 12.2891 |
77 |
| - 6.85591 |
78 |
| - 2.75649 |
79 |
| - -2.38007 |
80 |
| -``` |
81 |
| - |
82 |
| -We can also index by the *values* of each axis using an `Interval` type that |
83 |
| -selects all values between two endpoints `a .. b` or the axis values directly. |
84 |
| -Notice that the returned AxisArray still has axis information itself... and it |
85 |
| -still has the correct time information for those datapoints! |
86 |
| - |
87 |
| -```julia |
88 |
| -julia> A[40µs .. 220µs, :c1] |
89 |
| -1-dimensional AxisArray{Float64,1,...} with axes: |
90 |
| - :time, 5.0e-5 s:2.5e-5 s:0.0002 s |
91 |
| -And data, a 7-element Array{Float64,1}: |
92 |
| - 3.42795 |
93 |
| - 1.37825 |
94 |
| - -1.19004 |
95 |
| - -1.99414 |
96 |
| - 2.9429 |
97 |
| - -0.226449 |
98 |
| - 0.821446 |
99 |
| - |
100 |
| -julia> axes(ans, 1) |
101 |
| -AxisArrays.Axis{:time,StepRangeLen{Quantity{Float64, Dimensions:{𝐓}, Units:{s}},Base.TwicePrecision{Quantity{Float64, Dimensions:{𝐓}, Units:{s}}},Base.TwicePrecision{Quantity{Float64, Dimensions:{𝐓}, Units:{s}}}}}(5.0e-5 s:2.5e-5 s:0.0002 s) |
102 |
| -``` |
103 |
| - |
104 |
| -You can also index by a single value on an axis using `atvalue`. This will drop |
105 |
| -a dimension. Indexing with an `Interval` type retains dimensions, even |
106 |
| -when the ends of the interval are equal: |
107 |
| - |
108 |
| -```julia |
109 |
| -julia> A[atvalue(2.5e-5s), :c1] |
110 |
| -6.14453912336772 |
111 |
| - |
112 |
| -julia> A[2.5e-5s..2.5e-5s, :c1] |
113 |
| -1-dimensional AxisArray{Float64,1,...} with axes: |
114 |
| - :time, 2.5e-5 s:2.5e-5 s:2.5e-5 s |
115 |
| -And data, a 1-element Array{Float64,1}: |
116 |
| - 6.14454 |
117 |
| -``` |
118 |
| - |
119 |
| -You can even index by multiple values by broadcasting `atvalue` over an array: |
120 |
| - |
121 |
| -```julia |
122 |
| -julia> A[atvalue.([2.5e-5s, 75.0µs])] |
123 |
| -2-dimensional AxisArray{Float64,2,...} with axes: |
124 |
| - :time, Quantity{Float64, Dimensions:{𝐓}, Units:{s}}[2.5e-5 s, 7.5e-5 s] |
125 |
| - :chan, Symbol[:c1, :c2] |
126 |
| -And data, a 2×2 Array{Float64,2}: |
127 |
| - 6.14454 12.2891 |
128 |
| - 1.37825 2.75649 |
129 |
| -``` |
130 |
| - |
131 |
| -Sometimes, though, what we're really interested in is a window of time about a |
132 |
| -specific index. One of the operations above (looking for values in the window from 40µs |
133 |
| -to 220µs) might be more clearly expressed as a symmetrical window about a |
134 |
| -specific index where we know something interesting happened. To represent this, |
135 |
| -we use the `atindex` function: |
136 |
| - |
137 |
| -```julia |
138 |
| -julia> A[atindex(-90µs .. 90µs, 5), :c2] |
139 |
| -1-dimensional AxisArray{Float64,1,...} with axes: |
140 |
| - :time_sub, -7.5e-5 s:2.5e-5 s:7.500000000000002e-5 s |
141 |
| -And data, a 7-element Array{Float64,1}: |
142 |
| - 6.85591 |
143 |
| - 2.75649 |
144 |
| - -2.38007 |
145 |
| - -3.98828 |
146 |
| - 5.88581 |
147 |
| - -0.452898 |
148 |
| - 1.64289 |
149 |
| -``` |
150 |
| - |
151 |
| -Note that the returned AxisArray has its time axis shifted to represent the |
152 |
| -interval about the given index! This simple concept can be extended to some |
153 |
| -very powerful behaviors. For example, let's threshold our data and find windows |
154 |
| -about those threshold crossings. |
155 |
| - |
156 |
| -```julia |
157 |
| -julia> idxs = find(diff(A[:,:c1] .< -15) .> 0); |
158 |
| - |
159 |
| -julia> spks = A[atindex(-200µs .. 800µs, idxs), :c1] |
160 |
| -2-dimensional AxisArray{Float64,2,...} with axes: |
161 |
| - :time_sub, -0.0002 s:2.5e-5 s:0.0008 s |
162 |
| - :time_rep, Quantity{Float64, Dimensions:{𝐓}, Units:{s}}[0.162 s, 0.20045 s, 0.28495 s, 0.530325 s, 0.821725 s, 1.0453 s, 1.11967 s, 1.1523 s, 1.22085 s, 1.6253 s … 57.0094 s, 57.5818 s, 57.8716 s, 57.8806 s, 58.4353 s, 58.7041 s, 59.1015 s, 59.1783 s, 59.425 s, 59.5657 s] |
163 |
| -And data, a 41×247 Array{Float64,2}: |
164 |
| - 0.672063 7.25649 0.633375 … 1.54583 5.81194 -4.706 |
165 |
| - -1.65182 2.57487 0.477408 3.09505 3.52478 4.13037 |
166 |
| - 4.46035 2.11313 4.78372 1.23385 7.2525 3.57485 |
167 |
| - 5.25651 -2.19785 3.05933 0.965021 6.78414 5.94854 |
168 |
| - 7.8537 0.345008 0.960533 0.812989 0.336715 0.303909 |
169 |
| - 0.466816 0.643649 -3.67087 … 3.92978 -3.1242 0.789722 |
170 |
| - -6.0445 -13.2441 -4.60716 0.265144 -4.50987 -8.84897 |
171 |
| - -9.21703 -13.2254 -14.4409 -8.6664 -13.3457 -11.6213 |
172 |
| - -16.1809 -22.7037 -25.023 -15.9376 -28.0817 -16.996 |
173 |
| - -23.2671 -31.2021 -25.3787 -24.4914 -32.2599 -26.1118 |
174 |
| - ⋮ ⋱ ⋮ |
175 |
| - -0.301629 0.0683982 -4.36574 1.92362 -5.12333 -3.4431 |
176 |
| - 4.7182 1.18615 4.40717 -4.51757 -8.64314 0.0800021 |
177 |
| - -2.43775 -0.151882 -1.40817 -3.38555 -2.23418 0.728549 |
178 |
| - 3.2482 -0.60967 0.471288 … 2.53395 0.468817 -3.65905 |
179 |
| - -4.26967 2.24747 -3.13758 1.74967 4.5052 -0.145357 |
180 |
| - -0.752487 1.69446 -1.20491 1.71429 1.81936 0.290158 |
181 |
| - 4.64348 -3.94187 -1.59213 7.15428 -0.539748 4.82309 |
182 |
| - 1.09652 -2.66999 0.521931 -3.80528 1.70421 3.40583 |
183 |
| - -0.94341 2.60785 -3.34291 … 1.10584 4.31118 3.6404 |
184 |
| -``` |
185 |
| - |
186 |
| -By indexing with a repeated interval, we have *added* a dimension to the |
187 |
| -output! The returned AxisArray's columns specify each repetition of the |
188 |
| -interval, and each datapoint in the column represents a timepoint within that |
189 |
| -interval, adjusted by the time of the theshold crossing. The best part here |
190 |
| -is that the returned matrix knows precisely where its data came from, and has |
191 |
| -labeled its dimensions appropriately. Not only is there the proper time |
192 |
| -base for each waveform, but we also have recorded the event times as the axis |
193 |
| -across the columns. |
194 |
| - |
195 |
| -## Indexing |
196 |
| - |
197 |
| -### Indexing axes |
198 |
| - |
199 |
| -Two main types of Axes supported by default include: |
200 |
| - |
201 |
| -* Categorical axis -- These are vectors of labels, normally symbols or |
202 |
| - strings. Elements or slices can be selected by elements or vectors |
203 |
| - of elements. |
204 |
| - |
205 |
| -* Dimensional axis -- These are sorted vectors or iterators that can |
206 |
| - be selected by `Intervals`. These are commonly used for sequences of |
207 |
| - times or date-times. For regular sample rates, ranges can be used. |
208 |
| - |
209 |
| -Here is an example with a Dimensional axis representing a time |
210 |
| -sequence along rows and a Categorical axis of symbols for column |
211 |
| -headers. |
212 |
| - |
213 |
| -```julia |
214 |
| -B = AxisArray(reshape(1:15, 5, 3), .1:.1:0.5, [:a, :b, :c]) |
215 |
| -B[Axis{:row}(Interval(.2,.4))] # restrict the AxisArray along the time axis |
216 |
| -B[Interval(0.,.3), [:a, :c]] # select an interval and two of the columns |
217 |
| -``` |
| 18 | +**Installation**: at the Julia REPL, `Pkg.add("AxisArrays")` |
218 | 19 |
|
219 |
| -User-defined axis types can be added along with custom indexing |
220 |
| -behaviors. |
| 20 | +**Documentation**: [![Stable Documentation][docs-stable-img]][docs-stable-url] [![Latest Documentation][docs-latest-img]][docs-latest-url] |
221 | 21 |
|
222 |
| -### Example: compute the intensity-weighted mean along the z axis |
223 |
| -```julia |
224 |
| -B = AxisArray(randn(100,100,100), :x, :y, :z) |
225 |
| -Itotal = sumz = 0.0 |
226 |
| -for iter in eachindex(B) # traverses in storage order for cache efficiency |
227 |
| - I = B[iter] # intensity in a single voxel |
228 |
| - Itotal += I |
229 |
| - sumz += I * iter[axisdim(B, Axis{:z})] # axisdim "looks up" the z dimension |
230 |
| -end |
231 |
| -meanz = sumz/Itotal |
232 |
| -``` |
| 22 | +[docs-latest-img]: https://img.shields.io/badge/docs-latest-blue.svg |
| 23 | +[docs-latest-url]: http://juliaarrays.github.io/AxisArrays.jl/latest/ |
233 | 24 |
|
234 |
| -The intention is that all of these operations are just as efficient as they would be if you used traditional position-based indexing with all the inherent assumptions about the storage order of `B`. |
| 25 | +[docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg |
| 26 | +[docs-stable-url]: http://juliaarrays.github.io/AxisArrays.jl/stable/ |
0 commit comments