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-06-01T00:00:00   6
 2000-07-01T00:00:00   7
 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         323          24         25
   1    0.624685   0.909533  0.43314        0.0541549   0.721101   0.600306
   2    0.546357   0.621601  0.960677       0.129382    0.434374   0.438592
   3    0.407207   0.861652  0.0837074      0.0725917   0.800084   0.749829
   4    0.67539    0.637399  0.416444       0.853879    0.988957   0.44724
   ⋮                                    ⋱                          ⋮
  97    0.474087   0.475786  0.759047       0.704613    0.706165   0.1279
  98    0.844262   0.668594  0.431398       0.746465    0.919534   0.398776
  99    0.0471885  0.788358  0.0145691      0.647328    0.517971   0.910412
 100    0.0442448  0.702787  0.585658   …   0.68326     0.774131   0.403955

A regular broadcast fails:

julia
julia> scaled = data .* month_scalars
ERROR: DimensionMismatch: arrays could not be broadcast to a common size: a has axes DimensionalData.Dimensions.DimUnitRange(Base.OneTo(100), X{Sampled{Int64, UnitRange{Int64}, ForwardOrdered, Regular{Int64}, Points, NoMetadata}}([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100])) and b has axes DimensionalData.Dimensions.DimUnitRange(Base.OneTo(12), Ti{Sampled{DateTime, StepRange{DateTime, Month}, ForwardOrdered, Regular{Month}, Points, NoMetadata}}([DateTime("2000-01-01T00:00:00"), DateTime("2000-02-01T00:00:00"), DateTime("2000-03-01T00:00:00"), DateTime("2000-04-01T00:00:00"), DateTime("2000-05-01T00:00:00"), DateTime("2000-06-01T00:00:00"), DateTime("2000-07-01T00:00:00"), DateTime("2000-08-01T00:00:00"), DateTime("2000-09-01T00:00:00"), DateTime("2000-10-01T00:00:00"), DateTime("2000-11-01T00:00:00"), DateTime("2000-12-01T00:00:00")]))

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         323          24         25
   1    0.624685   0.909533  0.43314        0.0541549   0.721101   0.600306
   2    0.546357   0.621601  0.960677       0.129382    0.434374   0.438592
   3    0.407207   0.861652  0.0837074      0.0725917   0.800084   0.749829
   4    0.67539    0.637399  0.416444       0.853879    0.988957   0.44724
   ⋮                                    ⋱                          ⋮
  97    0.474087   0.475786  0.759047       0.704613    0.706165   0.1279
  98    0.844262   0.668594  0.431398       0.746465    0.919534   0.398776
  99    0.0471885  0.788358  0.0145691      0.647328    0.517971   0.910412
 100    0.0442448  0.702787  0.585658   …   0.68326     0.774131   0.403955

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.50854
 2000-02-01T00:00:00  0.499674
 2000-03-01T00:00:00  0.510863
 2000-04-01T00:00:00  0.500818
 2000-05-01T00:00:00  0.501124
 2000-06-01T00:00:00  0.495215
 2000-07-01T00:00:00  0.489497
 2000-08-01T00:00:00  0.500495
 2000-09-01T00:00:00  0.491479
 2000-10-01T00:00:00  0.499282
 2000-11-01T00:00:00  0.491202
 2000-12-01T00:00:00  0.485718
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.50854
 2000-02-01T00:00:00  0.999348
 2000-03-01T00:00:00  1.53259
 2000-04-01T00:00:00  2.00327
 2000-05-01T00:00:00  2.50562
 2000-06-01T00:00:00  2.97129
 2000-07-01T00:00:00  3.42648
 2000-08-01T00:00:00  4.00396
 2000-09-01T00:00:00  4.42331
 2000-10-01T00:00:00  4.99282
 2000-11-01T00:00:00  5.40322
 2000-12-01T00:00:00  5.82862

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         323          24         25
   1    0.624685   0.909533  0.43314        0.0541549   0.721101   0.600306
   2    0.546357   0.621601  0.960677       0.129382    0.434374   0.438592
   3    0.407207   0.861652  0.0837074      0.0725917   0.800084   0.749829
   4    0.67539    0.637399  0.416444       0.853879    0.988957   0.44724
   ⋮                                    ⋱                          ⋮
  97    0.474087   0.475786  0.759047       0.704613    0.706165   0.1279
  98    0.844262   0.668594  0.431398       0.746465    0.919534   0.398776
  99    0.0471885  0.788358  0.0145691      0.647328    0.517971   0.910412
 100    0.0442448  0.702787  0.585658   …   0.68326     0.774131   0.403955

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]
                   198         99          100
  2000-01-01T00:00:00  0.624685      0.844262   0.0471885    0.0442448
  2000-02-01T00:00:00  0.547848      1.79785    0.187495     0.441762
  2000-03-01T00:00:00  2.49232       1.15489    2.82356      0.921936
  2000-04-01T00:00:00  0.439142      2.61341    1.59542      2.24247
 ⋮                               ⋱                           ⋮
  2000-09-01T00:00:00  3.27042       6.61858    3.10246      5.93972
  2000-10-01T00:00:00  0.12404   …   2.53632    7.83904      6.55713
  2000-11-01T00:00:00  1.79388       2.5891     2.14203      5.52389
  2000-12-01T00:00:00  8.84219       6.15004    5.04169      6.10415

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]
                   198         99          100
  2000-01-01T00:00:00  0.624685      0.844262   0.0471885    0.0442448
  2000-02-01T00:00:00  0.547848      1.79785    0.187495     0.441762
  2000-03-01T00:00:00  2.49232       1.15489    2.82356      0.921936
  2000-04-01T00:00:00  0.439142      2.61341    1.59542      2.24247
 ⋮                               ⋱                           ⋮
  2000-09-01T00:00:00  3.27042       6.61858    3.10246      5.93972
  2000-10-01T00:00:00  0.12404   …   2.53632    7.83904      6.55713
  2000-11-01T00:00:00  1.79388       2.5891     2.14203      5.52389
  2000-12-01T00:00:00  8.84219       6.15004    5.04169      6.10415