Skip to content

Dimensional broadcasts with @d and broadcast_dims ​

Broadcasting over AbstractDimArray works as usual with Base Julia broadcasts, except that dimensions are checked for compatibility with each other, and that values match. Strict checks can be turned off globally with strict_broadcast!(false). To avoid even dimension name checks, broadcast over parent(dimarray).

The @d macro is a dimension-aware extension to regular dot broadcasting. broadcast_dims is analogous to Base Julia's broadcast.

Because we know the names of the dimensions, there is no ambiguity in which ones we mean to broadcast together. This means we can permute and reshape dims so that broadcasts that would fail with a regular Array just work with a DimArray.

As an added bonus, broadcast_dims even works on DimStacks. Currently, @d does not work on DimStack.

Example: scaling along the time dimension ​

Define some dimensions:

julia
using DimensionalData
using Dates
using Statistics
julia
julia> x, y, t = X(1:100), Y(1:25), Ti(DateTime(2000):Month(1):DateTime(2000, 12))
(↓ X 1:100,
→ Y 1:25,
↗ Ti DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00"))

A DimArray from 1:12 to scale with:

julia
julia> month_scalars = DimArray(month, t)
┌ 12-element DimArray{Int64, 1} month(Ti) ┐
├─────────────────────────────────────────┴────────────────────────────── dims ┐
  ↓ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
 2000-01-01T00:00:00   1
 2000-02-01T00:00:00   2
 2000-03-01T00:00:00   3
 2000-04-01T00:00:00   4
 2000-05-01T00:00:00   5
 ⋮
 2000-08-01T00:00:00   8
 2000-09-01T00:00:00   9
 2000-10-01T00:00:00  10
 2000-11-01T00:00:00  11
 2000-12-01T00:00:00  12

And a larger DimArray for example data:

julia
julia> data = rand(x, y, t)
┌ 100×25×12 DimArray{Float64, 3} ┐
├────────────────────────────────┴─────────────────────────────────────── dims ┐
  ↓ X Sampled{Int64} 1:100 ForwardOrdered Regular Points,
  → Y Sampled{Int64} 1:25 ForwardOrdered Regular Points,
  ↗ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
   ↓ →  1          2          …  23         24            25
   1    0.677207   0.659962       0.524401   0.629328      0.583736
   2    0.768325   0.959452       0.264272   0.14236       0.751662
   3    0.514177   0.63844        0.334894   0.707174      0.433344
   ⋮                          ⋱                            ⋮
  97    0.386618   0.0346624      0.409846   0.000661856   0.945526
  98    0.863602   0.466458       0.668845   0.598736      0.110906
  99    0.707586   0.179463       0.264945   0.559977      0.649172
 100    0.0838604  0.0779536  …   0.611008   0.512089      0.280317

A regular broadcast fails:

julia
julia> scaled = data .* month_scalars
ERROR: DimensionMismatch: arrays could not be broadcast to a common size: a has axes Base.OneTo(100) and b has axes Base.OneTo(12)

But @d knows to broadcast over the Ti dimension:

julia
julia> scaled = @d data .* month_scalars
┌ 100×25×12 DimArray{Float64, 3} ┐
├────────────────────────────────┴─────────────────────────────────────── dims ┐
  ↓ X Sampled{Int64} 1:100 ForwardOrdered Regular Points,
  → Y Sampled{Int64} 1:25 ForwardOrdered Regular Points,
  ↗ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
   ↓ →  1          2          …  23         24            25
   1    0.677207   0.659962       0.524401   0.629328      0.583736
   2    0.768325   0.959452       0.264272   0.14236       0.751662
   3    0.514177   0.63844        0.334894   0.707174      0.433344
   ⋮                          ⋱                            ⋮
  97    0.386618   0.0346624      0.409846   0.000661856   0.945526
  98    0.863602   0.466458       0.668845   0.598736      0.110906
  99    0.707586   0.179463       0.264945   0.559977      0.649172
 100    0.0838604  0.0779536  …   0.611008   0.512089      0.280317

We can see the means of each month are scaled by the broadcast :

julia
julia> mean(eachslice(data; dims=(X, Y)))
┌ 12-element DimArray{Float64, 1} ┐
├─────────────────────────────────┴────────────────────────────────────── dims ┐
  ↓ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
 2000-01-01T00:00:00  0.501337
 2000-02-01T00:00:00  0.504833
 2000-03-01T00:00:00  0.508966
 2000-04-01T00:00:00  0.500979
 2000-05-01T00:00:00  0.499617
 ⋮
 2000-08-01T00:00:00  0.496128
 2000-09-01T00:00:00  0.503223
 2000-10-01T00:00:00  0.506277
 2000-11-01T00:00:00  0.502244
 2000-12-01T00:00:00  0.486316
julia
julia> mean(eachslice(scaled; dims=(X, Y)))
┌ 12-element DimArray{Float64, 1} ┐
├─────────────────────────────────┴────────────────────────────────────── dims ┐
  ↓ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
 2000-01-01T00:00:00  0.501337
 2000-02-01T00:00:00  1.00967
 2000-03-01T00:00:00  1.5269
 2000-04-01T00:00:00  2.00392
 2000-05-01T00:00:00  2.49809
 ⋮
 2000-08-01T00:00:00  3.96902
 2000-09-01T00:00:00  4.52901
 2000-10-01T00:00:00  5.06277
 2000-11-01T00:00:00  5.52468
 2000-12-01T00:00:00  5.83579

You can also use broadcast_dims the same way:

julia
julia> broadcast_dims(*, data, month_scalars)
┌ 100×25×12 DimArray{Float64, 3} ┐
├────────────────────────────────┴─────────────────────────────────────── dims ┐
  ↓ X Sampled{Int64} 1:100 ForwardOrdered Regular Points,
  → Y Sampled{Int64} 1:25 ForwardOrdered Regular Points,
  ↗ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
   ↓ →  1          2          …  23         24            25
   1    0.677207   0.659962       0.524401   0.629328      0.583736
   2    0.768325   0.959452       0.264272   0.14236       0.751662
   3    0.514177   0.63844        0.334894   0.707174      0.433344
   ⋮                          ⋱                            ⋮
  97    0.386618   0.0346624      0.409846   0.000661856   0.945526
  98    0.863602   0.466458       0.668845   0.598736      0.110906
  99    0.707586   0.179463       0.264945   0.559977      0.649172
 100    0.0838604  0.0779536  …   0.611008   0.512089      0.280317

And with the @d macro you can set the dimension order and other properties of the output array, by passing a single assignment or a NamedTuple argument to @d after the broadcast:

julia
julia> @d data .* month_scalars dims=(Ti, X, Y)
┌ 12×100×25 DimArray{Float64, 3} ┐
├────────────────────────────────┴─────────────────────────────────────── dims ┐
  ↓ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points,
  → X Sampled{Int64} 1:100 ForwardOrdered Regular Points,
  ↗ Y Sampled{Int64} 1:25 ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
 ↓ →                    1         …  98         99         100
  2000-01-01T00:00:00   0.677207      0.863602   0.707586    0.0838604
  2000-02-01T00:00:00   1.56224       1.23787    1.75635     1.53717
  2000-03-01T00:00:00   0.151451      0.879216   2.34168     0.586736
 ⋮                                ⋱                          ⋮
  2000-09-01T00:00:00   7.05059       8.43329    0.937591    5.79746
  2000-10-01T00:00:00   5.36825   …   8.79053    8.49961     2.17778
  2000-11-01T00:00:00   6.67138       7.78064    4.61895     6.89244
  2000-12-01T00:00:00  11.4122        4.34702    5.08239    10.7014

Or

julia
julia> @d data .* month_scalars (dims=(Ti, X, Y), name=:scaled)
┌ 12×100×25 DimArray{Float64, 3} scaled ┐
├───────────────────────────────────────┴──────────────────────────────── dims ┐
  ↓ Ti Sampled{DateTime} DateTime("2000-01-01T00:00:00"):Month(1):DateTime("2000-12-01T00:00:00") ForwardOrdered Regular Points,
  → X Sampled{Int64} 1:100 ForwardOrdered Regular Points,
  ↗ Y Sampled{Int64} 1:25 ForwardOrdered Regular Points
└──────────────────────────────────────────────────────────────────────────────┘
[:, :, 1]
 ↓ →                    1         …  98         99         100
  2000-01-01T00:00:00   0.677207      0.863602   0.707586    0.0838604
  2000-02-01T00:00:00   1.56224       1.23787    1.75635     1.53717
  2000-03-01T00:00:00   0.151451      0.879216   2.34168     0.586736
 ⋮                                ⋱                          ⋮
  2000-09-01T00:00:00   7.05059       8.43329    0.937591    5.79746
  2000-10-01T00:00:00   5.36825   …   8.79053    8.49961     2.17778
  2000-11-01T00:00:00   6.67138       7.78064    4.61895     6.89244
  2000-12-01T00:00:00  11.4122        4.34702    5.08239    10.7014