Is there a NamedArray or DimensionalData type package that can combine orthogonal dimensions?

Vectorised operations on regular arrays produce a cross product across dimensions. e.g.

x = [1 2 3]

a = reshape( x, (3,1,1))
b = reshape( x, (1,3,1))
c = reshape( x, (1,1,3))

X = @. a + b + c

X[3,3,3]  #  =9

Similarly, is there a package that allows something like:

@dimensions begin
    Currency => [USD, GBP, EUR]
    Date     => [ Date(2022,1,1),  Date(2022,1,2), Date(2022,1,3) ]
    Scenario => [1, 2, 3]
end

a = NammedArray( x, Currency )
b = NammedArray( x, Date     )
c = NammedArray( x, Scenario )

X = @. a + b + c             #NammedArray across Currency, Date and Scenario dimensions

X[EUR,  Date(2022,1,3),  3]  #   =9

It’s not so hard to do, by fiddling with broadcasting… making this happen automatically means these things are less like arrays.

At some point I made a package which asked you to specify the names (and the order!) before inserting the necessary permutations:

julia> using NamedPlus, AxisKeys, Dates

julia> begin
         x = [1,2,3]
         
         currency = [:USD, :GBP, :EUR]
         date = [ Date(2022,1,1),  Date(2022,1,2), Date(2022,1,3) ]
         scenario = ["one", "two", "three"]
         
         a = NamedDimsArray(x; currency)
         b = NamedDimsArray(x; date)
         c = NamedDimsArray(x; scenario)
       end
1-dimensional NamedDimsArray(KeyedArray(...)) with keys:
↓   scenario ∈ 3-element Vector{String}
And data, 3-element Vector{Int64}:
 ("one")    1
 ("two")    2
 ("three")  3

julia> @named out{date, currency} = a .+ b
3×3 NamedDimsArray(::Matrix{Int64}, (:date, :currency)):
        → currency
↓ date  2  3  4
        3  4  5
        4  5  6

julia> @named out{date, currency, scenario} = a .+ b ./ c
3×3×3 NamedDimsArray(::Array{Float64, 3}, (:date, :currency, :scenario)):
[:, :, scenario=1]        → currency
↓ date  2.0  3.0  4.0
        3.0  4.0  5.0
        4.0  5.0  6.0

[:, :, scenario=2]        → currency
↓ date  1.5  2.5  3.5
        2.0  3.0  4.0
        2.5  3.5  4.5

[:, :, scenario=3]        → currency
↓ date  1.33333  2.33333  3.33333
        1.66667  2.66667  3.66667
        2.0      3.0      4.0

julia> let names = (:date, :currency, :scenario)
         align(a, names)
       end
2-dimensional NamedDimsArray(KeyedArray(...)) with keys:
↓   _ ∈ 1-element OneTo{Int}
→   currency ∈ 3-element Vector{Symbol}
And data, 1×3 transpose(::Vector{Int64}) with eltype Int64:
      (:USD)  (:GBP)  (:EUR)
 (1)   1       2       3

julia> let names = (:date, :currency, :scenario)
         align(a, names) .+ align(b, names) ./ align(c, names)
       end
3-dimensional NamedDimsArray(KeyedArray(...)) with keys:
↓   date ∈ 3-element Vector{Date}
→   currency ∈ 3-element Vector{Symbol}
◪   scenario ∈ 3-element OneTo{Int}
And data, 3×3×3 Array{Float64, 3}:
[:, :, 1] ~ (:, :, 1):
                        (:USD)  (:GBP)  (:EUR)
   Date("2022-01-01")    2.0     3.0     4.0
   Date("2022-01-02")    3.0     4.0     5.0
   Date("2022-01-03")    4.0     5.0     6.0

[:, :, 2] ~ (:, :, 2):
                        (:USD)  (:GBP)  (:EUR)
   Date("2022-01-01")    1.5     2.5     3.5
   Date("2022-01-02")    2.0     3.0     4.0
   Date("2022-01-03")    2.5     3.5     4.5

[:, :, 3] ~ (:, :, 3):
                        (:USD)    (:GBP)    (:EUR)
   Date("2022-01-01")    1.33333   2.33333   3.33333
   Date("2022-01-02")    1.66667   2.66667   3.66667
   Date("2022-01-03")    2.0       3.0       4.0

I’m not sure why the KeyedArray wrapper has gone missing in the macro version. NamedPlus is a bit experimental!

2 Likes

@named out{date, currency} = a .+ b

that’s really nice syntax.
Unfortunately NamedPlus wouldn’t install on my machine.
I’ll try again later.

broadcast_dims in DimensionalData.jl will probably do what you want. It permutes and reshapes so that the output has the dimensions of all the input arrays.

I guess someone could make it a macro so it feels more like regular broadcast, but I don’t want to mix it with normal syntax.

2 Likes

Thanks Raf. I’ll check it out