AxisArrays.jl icon indicating copy to clipboard operation
AxisArrays.jl copied to clipboard

AxisArray broadcast container type

Open omus opened this issue 8 years ago • 8 comments

Currently AxisArrays do not use the container type information from the parent when working with broadcast. Typically this is fine but in some cases this can raise an exception:

julia> using DataArrays, AxisArrays

julia> a = AxisArray(@data([1, NA, 3]), 'a':'c')
1-dimensional AxisArray{Int64,1,...} with axes:
    :row, 'a':1:'c'
And data, a 3-element DataArrays.DataArray{Int64,1}:
 1
  NA
 3

julia> parent(a) .== 1
3-element DataArrays.DataArray{Bool,1}:
  true
      NA
 false

julia> a .== 1
ERROR: MethodError: Cannot `convert` an object of type DataArrays.NAtype to an object of type Bool
This may have arisen from a call to the constructor Bool(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] setindex!(::Array{Bool,1}, ::DataArrays.NAtype, ::Int64) at ./array.jl:549
 [2] macro expansion at ./broadcast.jl:180 [inlined]
 [3] macro expansion at ./simdloop.jl:73 [inlined]
 [4] macro expansion at ./broadcast.jl:174 [inlined]
 [5] _broadcast!(::##1#2, ::BitArray{1}, ::Tuple{Tuple{Bool}}, ::Tuple{Tuple{Int64}}, ::AxisArrays.AxisArray{Int64,1,DataArrays.DataArray{Int64,1},Tuple{AxisArrays.Axis{:row,StepRange{Char,Int64}}}}, ::Tuple{}, ::Type{Val{0}}, ::CartesianRange{CartesianIndex{1}}) at ./broadcast.jl:162
 [6] broadcast_t(::Function, ::Type{Bool}, ::Tuple{Base.OneTo{Int64}}, ::CartesianRange{CartesianIndex{1}}, ::AxisArrays.AxisArray{Int64,1,DataArrays.DataArray{Int64,1},Tuple{AxisArrays.Axis{:row,StepRange{Char,Int64}}}}) at ./broadcast.jl:279
 [7] broadcast_c at ./broadcast.jl:314 [inlined]
 [8] broadcast(::Function, ::AxisArrays.AxisArray{Int64,1,DataArrays.DataArray{Int64,1},Tuple{AxisArrays.Axis{:row,StepRange{Char,Int64}}}}) at ./broadcast.jl:434

omus avatar Oct 25 '17 21:10 omus

I believe the solution is to defer to using the containertype of the AxisArray parent:

julia> Base.Broadcast.containertype(x::AxisArray) = Base.Broadcast.containertype(parent(x))

julia> a .== 1
3-element DataArrays.DataArray{Bool,1}:
  true
      NA
 false

omus avatar Oct 25 '17 21:10 omus

Additionally we may want some broadcast operations to return an AxisArray:

julia> b = AxisArray([1, 2, 3], 'a':'c')
1-dimensional AxisArray{Int64,1,...} with axes:
    :row, 'a':1:'c'
And data, a 3-element Array{Int64,1}:
 1
 2
 3

julia> b .* 3  # Produce an AxisArray with the same axes as 'b'?
3-element Array{Int64,1}:
 3
 6
 9

omus avatar Oct 25 '17 21:10 omus

A lot will change with https://github.com/JuliaLang/julia/pull/23939

timholy avatar Oct 26 '17 00:10 timholy

That PR looks great. For those of us with Julia 0.6 does the proposed fix make sense?

omus avatar Oct 26 '17 23:10 omus

I'm not quite sure what the proposed fix is---"deferring to the parent" and "want to return an AxisArray" seem to be opposing goals?

timholy avatar Oct 27 '17 11:10 timholy

Currently the behaviour is:

broadcast(*, AxisArray, Number) -> Array
broadcast(==, AxisArray{T,N,C}, Number) -> Array{Bool}

What I'm proposing is that we take a half-step and implement deferring to the parent:

broadcast(*, AxisArray, Number) -> Array
broadcast(==, AxisArray{T,N,C}, Number) -> C{Bool}  # Deferring to the parent

I think the ideal final solution would be to have:

broadcast(*, AxisArray, Number) -> AxisArray
broadcast(==, AxisArray{T,N,C}, Number) -> AxisArray{Bool,N,C{Bool}}

Disclaimer: the type logic above is overly simplified and is most likely only correct for when C == DataArray

omus avatar Oct 27 '17 16:10 omus

It would be really nice to handle boardcasting with axis arrays corectly.

  • AxisArray .+ AxisArray should either return an AxisArray with same axis and its inputs or arror if those do not have identical axes.
  • AxisArray .+ normal array or number should result an AxisArray

oxinabox avatar Feb 04 '19 12:02 oxinabox

@oxinabox Automatic broadcasting deserves its own issue.

lamorton avatar Apr 03 '19 23:04 lamorton