DimStacks
An AbstractDimStack
represents a collection of AbstractDimArray
layers that share some or all dimensions. For any two layers, a dimension of the same name must have the identical lookup - in fact, only one is stored for all layers to enforce this consistency.
julia> using DimensionalData
julia> x, y = X(1.0:10.0), Y(5.0:10.0)
(↓ X 1.0:1.0:10.0,
→ Y 5.0:1.0:10.0)
julia> st = DimStack((a=rand(x, y), b=rand(x, y), c=rand(y), d=rand(x)))
╭───────────────╮
│ 10×6 DimStack │
├───────────────┴──────────────────────────────────────────────────────── dims ┐
↓ X Sampled{Float64} 1.0:1.0:10.0 ForwardOrdered Regular Points,
→ Y Sampled{Float64} 5.0:1.0:10.0 ForwardOrdered Regular Points
├────────────────────────────────────────────────────────────────────── layers ┤
:a eltype: Float64 dims: X, Y size: 10×6
:b eltype: Float64 dims: X, Y size: 10×6
:c eltype: Float64 dims: Y size: 6
:d eltype: Float64 dims: X size: 10
└──────────────────────────────────────────────────────────────────────────────┘
The behavior of a DimStack
is at times like a NamedTuple
of DimArray
and, at other times, an AbstractArray
of NamedTuple
.
NamedTuple-like indexing
Layers can be accessed with .name
or [:name]
julia> st.a
╭─────────────────────────────╮
│ 10×6 DimArray{Float64, 2} a │
├─────────────────────────────┴────────────────────────────────────────── dims ┐
↓ X Sampled{Float64} 1.0:1.0:10.0 ForwardOrdered Regular Points,
→ Y Sampled{Float64} 5.0:1.0:10.0 ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
↓ → 5.0 6.0 7.0 8.0 9.0 10.0
1.0 0.341785 0.25853 0.788056 0.498846 0.277507 0.048092
2.0 0.275784 0.130942 0.666313 0.483033 0.257433 0.0878344
3.0 0.659836 0.220699 0.487872 0.0571503 0.192702 0.620657
4.0 0.168617 0.36022 0.822368 0.544803 0.970824 0.989812
5.0 0.395053 0.268922 0.19689 0.569194 0.317199 0.275147
6.0 0.144422 0.0838917 0.389598 0.879411 0.0561384 0.778766
7.0 0.938771 0.106544 0.8698 0.722335 0.511141 0.562491
8.0 0.609537 0.935937 0.956044 0.223596 0.211265 0.369204
9.0 0.31628 0.745734 0.912254 0.447013 0.287284 0.727536
10.0 0.0979352 0.497456 0.333095 0.781583 0.477439 0.662413
julia> st[:c]
╭──────────────────────────────────╮
│ 6-element DimArray{Float64, 1} c │
├──────────────────────────────────┴───────────────────────────────────── dims ┐
↓ Y Sampled{Float64} 5.0:1.0:10.0 ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
5.0 0.498429
6.0 0.887397
7.0 0.576224
8.0 0.982172
9.0 0.113665
10.0 0.994955
Array-like indexing
Indexing with a scalar returns a NamedTuple
of values, one for each layer:
julia> st[X=1, Y=4]
(a = 0.4988459421184759, b = 0.2571287355813575, c = 0.9821724302512657, d = 0.2868262581079416)
Reducing functions
Base functions like mean
, maximum
, reverse
are applied to all layers of the stack.
using Statistics
julia> maximum(st)
(a = 0.9898115471237202, b = 0.9698812177371097, c = 0.9949547126815458, d = 0.7802406914680406)
julia> maximum(st; dims=Y)
╭───────────────╮
│ 10×1 DimStack │
├───────────────┴──────────────────────────────────────────────────────── dims ┐
↓ X Sampled{Float64} 1.0:1.0:10.0 ForwardOrdered Regular Points,
→ Y Sampled{Float64} 7.5:6.0:7.5 ForwardOrdered Regular Points
├────────────────────────────────────────────────────────────────────── layers ┤
:a eltype: Float64 dims: X, Y size: 10×1
:b eltype: Float64 dims: X, Y size: 10×1
:c eltype: Float64 dims: Y size: 1
:d eltype: Float64 dims: X size: 10
└──────────────────────────────────────────────────────────────────────────────┘
broadcast_dims
broadcasts functions over any mix of AbstractDimStack
and AbstractDimArray
returning a new AbstractDimStack
with layers the size of the largest layer in the broadcast. This will work even if dimension permutation does not match in the objects.
Only matrix layers can be rotated
julia> rotl90(st[(:a, :b)])
╭───────────────╮
│ 6×10 DimStack │
├───────────────┴──────────────────────────────────────────────────────── dims ┐
↓ Y Sampled{Float64} 10.0:-1.0:5.0 ReverseOrdered Regular Points,
→ X Sampled{Float64} 1.0:1.0:10.0 ForwardOrdered Regular Points
├────────────────────────────────────────────────────────────────────── layers ┤
:a eltype: Float64 dims: Y, X size: 6×10
:b eltype: Float64 dims: Y, X size: 6×10
└──────────────────────────────────────────────────────────────────────────────┘
julia> rotl90(st[(:a, :b)], 2)
╭───────────────╮
│ 10×6 DimStack │
├───────────────┴──────────────────────────────────────────────────────── dims ┐
↓ X Sampled{Float64} 10.0:-1.0:1.0 ReverseOrdered Regular Points,
→ Y Sampled{Float64} 10.0:-1.0:5.0 ReverseOrdered Regular Points
├────────────────────────────────────────────────────────────────────── layers ┤
:a eltype: Float64 dims: X, Y size: 10×6
:b eltype: Float64 dims: X, Y size: 10×6
└──────────────────────────────────────────────────────────────────────────────┘
Performance
Indexing a stack is fast - indexing a single value and returning a NamedTuple
from all layers is usually measured in nanoseconds, and no slower than manually indexing into each parent array directly.
There are some compilation overheads to this though, and stacks with very many layers can take a long time to compile.
julia> using BenchmarkTools
julia> @btime $st[X=1, Y=4]
4.058 ns (0 allocations: 0 bytes)
(a = 0.4988459421184759, b = 0.2571287355813575, c = 0.9821724302512657, d = 0.2868262581079416)
julia> @btime $st[1, 4]
4.038 ns (0 allocations: 0 bytes)
(a = 0.4988459421184759, b = 0.2571287355813575, c = 0.9821724302512657, d = 0.2868262581079416)