rafaqz / DimensionalData.jl

Named dimensions and indexing for julia arrays and other data
https://rafaqz.github.io/DimensionalData.jl/stable/
MIT License
262 stars 38 forks source link

Support `replace!` for `DimStack`s #751

Open haakon-e opened 1 week ago

haakon-e commented 1 week ago

This should be easy to do. Currently you can

julia> A = rand(0.:1., (x,y))
╭─────────────────────────╮
│ 3×4 DimArray{Float64,2} │
├─────────────────────────┴──────────────────────── dims ┐
  ↓ X Sampled{Int64} 1:3 ForwardOrdered Regular Points,
  → Y Sampled{Int64} 10:13 ForwardOrdered Regular Points
└────────────────────────────────────────────────────────┘
 ↓ →  10    11    12    13
 1     0.0   0.0   1.0   1.0
 2     0.0   1.0   0.0   1.0
 3     0.0   0.0   0.0   1.0

julia> replace!(A, 0 => NaN)
╭─────────────────────────╮
│ 3×4 DimArray{Float64,2} │
├─────────────────────────┴──────────────────────── dims ┐
  ↓ X Sampled{Int64} 1:3 ForwardOrdered Regular Points,
  → Y Sampled{Int64} 10:13 ForwardOrdered Regular Points
└────────────────────────────────────────────────────────┘
 ↓ →   10   11     12    13
 1    NaN  NaN      1.0   1.0
 2    NaN    1.0  NaN     1.0
 3    NaN  NaN    NaN     1.0

However, this doesn't work over DimStacks unless you know what you're doing

julia> stack = DimStack(rand(0.:1., (x,y)), rand(0.:1., (x,y)) )
╭──────────────╮
│ 3×4 DimStack │
├──────────────┴─────────────────────────────────── dims ┐
  ↓ X Sampled{Int64} 1:3 ForwardOrdered Regular Points,
  → Y Sampled{Int64} 10:13 ForwardOrdered Regular Points
├──────────────────────────────────────────────── layers ┤
  :layer1 eltype: Float64 dims: X, Y size: 3×4
  :layer2 eltype: Float64 dims: X, Y size: 3×4
└────────────────────────────────────────────────────────┘

julia> replace!(stack, 0 => NaN)
ERROR: MethodError: no method matching _replace!(::Base.var"#new#395"{…}, ::DimStack{…}, ::DimStack{…}, ::Int64)

# broadcasting gives incorrect hint
julia> replace!.(stack, 0 => NaN)
ERROR: Use iterate(layers(s)) rather than `iterate(s)`

julia> replace!.(iterate(layers(stack)), 0 => NaN)
ERROR: MethodError: no method matching _replace!(::Base.var"#new#395"{Tuple{Pair{Int64, Float64}}}, ::Int64, ::Int64, ::Int64)

# this works
julia> replace!.(values(layers(stack)), 0 => NaN)

maybe I'm just again stumbling over julian-vs-non-julian ways of doing this with respect to broadcasting, and what's more sensible is to extend the error hint to be:

ERROR: Use iterate(layers(s)) rather than `iterate(s)`. If broadcasting over layers, values(layers(s)), may also be useful.

ref array-of-arrays.

julia> arrs = [rand(0.:1., (2,3)), rand(0.:1., (2,3)) ]
2-element Vector{Matrix{Float64}}:
 [0.0 0.0 1.0; 1.0 0.0 0.0]
 [1.0 0.0 0.0; 0.0 1.0 0.0]

julia> replace!(arrs, 0 => NaN)
2-element Vector{Matrix{Float64}}:
 [0.0 0.0 1.0; 1.0 0.0 0.0]
 [1.0 0.0 0.0; 0.0 1.0 0.0]

julia> replace!.(arrs, 0 => NaN)
2-element Vector{Matrix{Float64}}:
 [NaN NaN 1.0; 1.0 NaN NaN]
 [1.0 NaN NaN; NaN 1.0 NaN]
rafaqz commented 1 week ago

DimStack is a totally custom creation (it's like a NamedTuple/Array Franken thing) so we can do what we want. It makes sense to add a replace! method.

You may also want to pass a NamedTuple of values for each layer? That should also be allowed.