DimArrays
DimArray
s are wrappers for other kinds of AbstractArray
that add named dimension lookups.
Here we define a Matrix
of Float64
, and give it X
and Y
dimensions
julia> using DimensionalData
julia> A = rand(5, 10)
5×10 Matrix{Float64}:
0.827655 0.0102884 0.688175 0.254555 … 0.628275 0.517329 0.886959
0.223602 0.737979 0.996807 0.124594 0.0959042 0.628507 0.893267
0.0392779 0.792885 0.249616 0.519235 0.0315486 0.113704 0.997572
0.451879 0.846175 0.373575 0.891743 0.700389 0.958811 0.68136
0.689712 0.0927459 0.765773 0.273573 0.688928 0.761347 0.596077
julia> da = DimArray(A, (X, Y))
╭───────────────────────────╮
│ 5×10 DimArray{Float64, 2} │
├───────────────────────────┴────────────────────────────── dims ┐
↓ X, → Y
└────────────────────────────────────────────────────────────────┘
0.827655 0.0102884 0.688175 0.254555 … 0.628275 0.517329 0.886959
0.223602 0.737979 0.996807 0.124594 0.0959042 0.628507 0.893267
0.0392779 0.792885 0.249616 0.519235 0.0315486 0.113704 0.997572
0.451879 0.846175 0.373575 0.891743 0.700389 0.958811 0.68136
0.689712 0.0927459 0.765773 0.273573 0.688928 0.761347 0.596077
We can access a value with the same dimension wrappers:
julia> da[Y(1), X(2)]
0.2236016853688918
There are shortcuts for creating DimArray
:
julia> A = rand(5, 10)
5×10 Matrix{Float64}:
0.670688 0.534915 0.111072 0.194465 … 0.121525 0.797168 0.0239356
0.741593 0.707692 0.879899 0.813705 0.601833 0.826098 0.493417
0.119505 0.0999314 0.403111 0.755958 0.289335 0.542251 0.0622255
0.775377 0.217733 0.865298 0.876395 0.108514 0.306932 0.627107
0.576903 0.950338 0.854076 0.511978 0.866334 0.905021 0.0238569
julia> DimArray(A, (X, Y))
╭───────────────────────────╮
│ 5×10 DimArray{Float64, 2} │
├───────────────────────────┴────────────────────────────── dims ┐
↓ X, → Y
└────────────────────────────────────────────────────────────────┘
0.670688 0.534915 0.111072 0.194465 … 0.121525 0.797168 0.0239356
0.741593 0.707692 0.879899 0.813705 0.601833 0.826098 0.493417
0.119505 0.0999314 0.403111 0.755958 0.289335 0.542251 0.0622255
0.775377 0.217733 0.865298 0.876395 0.108514 0.306932 0.627107
0.576903 0.950338 0.854076 0.511978 0.866334 0.905021 0.0238569
julia> DimArray(A, (X, Y); name=:DimArray, metadata=Dict())
╭────────────────────────────────────╮
│ 5×10 DimArray{Float64, 2} DimArray │
├────────────────────────────────────┴───────────────────── dims ┐
↓ X, → Y
├────────────────────────────────────────────────────── metadata ┤
Dict{Any, Any}()
└────────────────────────────────────────────────────────────────┘
0.670688 0.534915 0.111072 0.194465 … 0.121525 0.797168 0.0239356
0.741593 0.707692 0.879899 0.813705 0.601833 0.826098 0.493417
0.119505 0.0999314 0.403111 0.755958 0.289335 0.542251 0.0622255
0.775377 0.217733 0.865298 0.876395 0.108514 0.306932 0.627107
0.576903 0.950338 0.854076 0.511978 0.866334 0.905021 0.0238569
Constructing DimArray with arbitrary dimension names
For arbitrary names, we can use the Dim{:name}
dims by using Symbol
s, and indexing with keywords:
julia> da1 = DimArray(rand(5, 5), (:a, :b))
╭──────────────────────────╮
│ 5×5 DimArray{Float64, 2} │
├──────────────────────────┴─────────────────────────────── dims ┐
↓ a, → b
└────────────────────────────────────────────────────────────────┘
0.601474 0.337576 0.770316 0.21149 0.121375
0.113873 0.511078 0.201362 0.716257 0.253984
0.910981 0.503823 0.488029 0.0130048 0.121186
0.436293 0.789198 0.32806 0.361921 0.830187
0.426888 0.46788 0.724709 0.15163 0.996398
and get a value, here another smaller DimArray
:
julia> da1[a=3, b=1:3]
╭────────────────────────────────╮
│ 3-element DimArray{Float64, 1} │
├────────────────────────────────┴ dims ┐
↓ b
└─────────────────────────────────┘
0.910981
0.503823
0.488029
Dimensional Indexing
When used for indexing, dimension wrappers free us from knowing the order of our objects axes. These are the same:
julia> da[X(2), Y(1)] == da[Y(1), X(2)]
true
We also can use Tuples
of dimensions, like CartesianIndex
, but they don't have to be in order of consecutive axes.
julia> da2 = rand(X(10), Y(7), Z(5))
╭─────────────────────────────╮
│ 10×7×5 DimArray{Float64, 3} │
├─────────────────────────────┴────────────────────────────────────────── dims ┐
↓ X, → Y, ↗ Z
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
0.41173 0.0789906 0.655936 0.762722 0.56273 0.0160205 0.640693
0.228294 0.687236 0.345046 0.749573 0.949769 0.783195 0.591606
0.258576 0.89609 0.00337686 0.99118 0.169845 0.159817 0.784693
0.295346 0.768639 0.754468 0.0738813 0.675126 0.968484 0.243035
0.254627 0.411529 0.629352 0.285205 0.18989 0.891663 0.597808
0.435719 0.0450386 0.0531003 0.385452 0.322612 0.667691 0.906601
0.88841 0.571658 0.991676 0.677848 0.697976 0.971252 0.776593
0.284399 0.196524 0.119937 0.493258 0.342919 0.866772 0.0990347
0.192286 0.765808 0.502499 0.404773 0.0623616 0.627002 0.184813
0.762199 0.159233 0.769884 0.74428 0.606279 0.351096 0.268379
julia> da2[(X(3), Z(5))]
╭────────────────────────────────╮
│ 7-element DimArray{Float64, 1} │
├────────────────────────────────┴ dims ┐
↓ Y
└─────────────────────────────────┘
0.644748
0.493072
0.316833
0.372311
0.313185
0.494267
0.705582
We can index with Vector
of Tuple{Vararg(Dimension}}
like vectors of CartesianIndex
. This will merge the dimensions in the tuples:
julia> inds = [(X(3), Z(5)), (X(7), Z(4)), (X(8), Z(2))]
3-element Vector{Tuple{X{Int64}, Z{Int64}}}:
(↓ X 3, → Z 5)
(↓ X 7, → Z 4)
(↓ X 8, → Z 2)
julia> da2[inds]
╭──────────────────────────╮
│ 7×3 DimArray{Float64, 2} │
├──────────────────────────┴───────────────────────────────────────────── dims ┐
↓ Y ,
→ XZ MergedLookup{Tuple{Int64, Int64}} [(3, 5), (7, 4), (8, 2)] ↓ X, → Z
└──────────────────────────────────────────────────────────────────────────────┘
(3, 5) (7, 4) (8, 2)
0.644748 0.510196 0.528138
0.493072 0.925624 0.142957
0.316833 0.485321 0.708081
0.372311 0.676945 0.406221
0.313185 0.0379776 0.6778
0.494267 0.287739 0.155826
0.705582 0.996358 0.638336
DimIndices
can be used like CartesianIndices
but again, without the constraint of consecutive dimensions or known order.
julia> da2[DimIndices(dims(da2, (X, Z))), Y(3)]
╭───────────────────────────╮
│ 10×5 DimArray{Float64, 2} │
├───────────────────────────┴────────────────────────────── dims ┐
↓ X, → Z
└────────────────────────────────────────────────────────────────┘
0.655936 0.853835 0.870583 0.0578313 0.971794
0.345046 0.17597 0.638072 0.136127 0.962808
0.00337686 0.378395 0.0314382 0.310753 0.316833
0.754468 0.188864 0.614012 0.883048 0.191049
0.629352 0.190726 0.338669 0.105539 0.925844
0.0531003 0.876115 0.945147 0.873096 0.342887
0.991676 0.702956 0.281077 0.485321 0.798621
0.119937 0.708081 0.0363983 0.247755 0.527261
0.502499 0.641023 0.0104608 0.10233 0.635425
0.769884 0.37821 0.881533 0.535933 0.454033
The Dimension
indexing layer sits on top of regular indexing and can not be combined with it! Regular indexing specifies order, so doesn't mix well with our dimensions.
Mixing them will throw an error:
julia> da1[X(3), 4]
ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64}
Begin End indexing
julia> da1[X=Begin+1, Y=End]
┌ Warning: (X, Y) dims were not found in object.
└ @ DimensionalData.Dimensions ~/work/DimensionalData.jl/DimensionalData.jl/src/Dimensions/primitives.jl:846
╭──────────────────────────╮
│ 5×5 DimArray{Float64, 2} │
├──────────────────────────┴─────────────────────────────── dims ┐
↓ a, → b
└────────────────────────────────────────────────────────────────┘
0.601474 0.337576 0.770316 0.21149 0.121375
0.113873 0.511078 0.201362 0.716257 0.253984
0.910981 0.503823 0.488029 0.0130048 0.121186
0.436293 0.789198 0.32806 0.361921 0.830187
0.426888 0.46788 0.724709 0.15163 0.996398
It also works in ranges, even with basic math:
julia> da1[X=Begin:Begin+1, Y=Begin+1:End-1]
┌ Warning: (X, Y) dims were not found in object.
└ @ DimensionalData.Dimensions ~/work/DimensionalData.jl/DimensionalData.jl/src/Dimensions/primitives.jl:846
╭──────────────────────────╮
│ 5×5 DimArray{Float64, 2} │
├──────────────────────────┴─────────────────────────────── dims ┐
↓ a, → b
└────────────────────────────────────────────────────────────────┘
0.601474 0.337576 0.770316 0.21149 0.121375
0.113873 0.511078 0.201362 0.716257 0.253984
0.910981 0.503823 0.488029 0.0130048 0.121186
0.436293 0.789198 0.32806 0.361921 0.830187
0.426888 0.46788 0.724709 0.15163 0.996398
In base julia the keywords begin
and end
can be used to index the first or last element of an array. But this doesn't work when named indexing is used. Instead you can use the types Begin
and End
.
Indexing
Indexing AbstractDimArray
s works with getindex
, setindex!
and view
. The result is still an AbstracDimArray
, unless using all single Int
or Selector
s that resolve to Int
inside Dimension
.
dims
keywords
In many Julia functions like, size
or sum
, you can specify the dimension along which to perform the operation as an Int
. It is also possible to do this using Dimension
types with AbstractDimArray
:
julia> da5 = rand(X(3), Y(4), Ti(5))
╭────────────────────────────╮
│ 3×4×5 DimArray{Float64, 3} │
├────────────────────────────┴─────────────────────────────────────────── dims ┐
↓ X, → Y, ↗ Ti
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
0.573147 0.206406 0.916702 0.481184
0.0760848 0.94412 0.48817 0.758865
0.0992684 0.917457 0.811947 0.0641884
julia> sum(da5; dims=Ti)
╭────────────────────────────╮
│ 3×4×1 DimArray{Float64, 3} │
├────────────────────────────┴─────────────────────────────────────────── dims ┐
↓ X, → Y, ↗ Ti
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
2.89248 1.86986 2.12435 1.86194
1.29325 2.76668 2.64923 2.70488
1.11475 3.10914 3.02098 2.35189
Dims keywords
Methods where dims, dim types, or Symbol
s can be used to indicate the array dimension:
size
,axes
,firstindex
,lastindex
cat
,reverse
,dropdims
reduce
,mapreduce
sum
,prod
,maximum
,minimum
mean
,median
,extrema
,std
,var
,cor
,cov
permutedims
,adjoint
,transpose
,Transpose
mapslices
,eachslice
Performance
Indexing with Dimension
s has no runtime cost. Let's benchmark it:
julia> using BenchmarkTools
julia> da4 = ones(X(3), Y(3))
╭──────────────────────────╮
│ 3×3 DimArray{Float64, 2} │
├──────────────────────────┴─────────────────────────────── dims ┐
↓ X, → Y
└────────────────────────────────────────────────────────────────┘
1.0 1.0 1.0
1.0 1.0 1.0
1.0 1.0 1.0
julia> @benchmark $da4[X(1), Y(2)]
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
Range (min … max): 3.095 ns … 17.453 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 3.106 ns ┊ GC (median): 0.00%
Time (mean ± σ): 3.113 ns ± 0.317 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▂ █
▄▁▁▁▁█▁▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▁▁▁▁█ ▂
3.1 ns Histogram: frequency by time 3.11 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
the same as accessing the parent array directly:
julia> @benchmark parent($da4)[1, 2]
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
Range (min … max): 3.095 ns … 22.803 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 3.106 ns ┊ GC (median): 0.00%
Time (mean ± σ): 3.118 ns ± 0.349 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
▂ █
▄▁█▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂▁▂ ▂
3.1 ns Histogram: frequency by time 3.12 ns <
Memory estimate: 0 bytes, allocs estimate: 0.