AxisArray broadcast container type
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
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
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
A lot will change with https://github.com/JuliaLang/julia/pull/23939
That PR looks great. For those of us with Julia 0.6 does the proposed fix make sense?
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?
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
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 Automatic broadcasting deserves its own issue.