Skip to content

DimArrays

DimArrays 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
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
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
julia> da[Y(1), X(2)]
0.2236016853688918

There are shortcuts for creating DimArray:

julia
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
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
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 Symbols, and indexing with keywords:

julia
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
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
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
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
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
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
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
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
julia> da1[X(3), 4]
ERROR: ArgumentError: invalid index: X{Int64}(3) of type X{Int64}

Begin End indexing

julia
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
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 AbstractDimArrays works with getindex, setindex! and view. The result is still an AbstracDimArray, unless using all single Int or Selectors 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
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
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 Symbols 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 Dimensions has no runtime cost. Let's benchmark it:

julia
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
julia> @benchmark $da4[X(1), Y(2)]
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (minmax):  3.095 ns13.095 ns GC (min … max): 0.00% … 0.00%
 Time  (median):     3.106 ns               GC (median):    0.00%
 Time  (mean ± σ):   3.116 ns ±  0.248 ns GC (mean ± σ):  0.00% ± 0.00%



  3.1 ns       Histogram: log(frequency) by time     3.12 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

the same as accessing the parent array directly:

julia
julia> @benchmark parent($da4)[1, 2]
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (minmax):  3.095 ns26.340 ns GC (min … max): 0.00% … 0.00%
 Time  (median):     3.105 ns               GC (median):    0.00%
 Time  (mean ± σ):   3.120 ns ±  0.389 ns GC (mean ± σ):  0.00% ± 0.00%

 

  3.1 ns         Histogram: frequency by time        3.11 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.