Skip to content

Commit

Permalink
Fix similar() to respect requested element type, implement float() an…
Browse files Browse the repository at this point in the history
…d complex() (#133)

similar(::CategoricalArray, T) used to return a CategoricalArray{T}, which is not a correct
implementation of the AbstractArray interface. Change it to return Array{T}, except when
T <: Union{CatValue,Missing}. This also fixes the behavior of AbstractArray constructors and
convert methods, whose fallback definitions use similar().

With these fixes, implementing float() and complex() is trivial.

Stop testing AbstractArray{T}(::CategoricalArray), which is not supported in Base either.
  • Loading branch information
nalimilan committed Apr 5, 2018
1 parent 084da04 commit e2db279
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 14 deletions.
42 changes: 34 additions & 8 deletions src/array.jl
@@ -1,7 +1,7 @@
## Code for CategoricalArray

import Base: Array, convert, collect, copy, getindex, setindex!, similar, size,
unique, vcat, in, summary
unique, vcat, in, summary, float, complex
import Compat: copyto!

# Used for keyword argument default value
Expand Down Expand Up @@ -467,13 +467,26 @@ copyto!(dest::CatArrOrSub, src::CatArrOrSub) =
copyto!(dest::CatArrOrSub, dstart::Integer, src::CatArrOrSub) =
copyto!(dest, dstart, src, 1, length(src))

"""
similar(A::CategoricalArray, element_type=eltype(A), dims=size(A))
For `CategoricalArray`, preserves the ordered property of `A` (see [`isordered`](@ref)).
"""
similar(A::CategoricalArray{S, M, R}, ::Type{T}, dims::NTuple{N, Int}) where {S, T, M, N, R} =
CategoricalArray{T, N, R}(undef, dims; ordered=isordered(A))
similar(A::CategoricalArray{S, M, R}, ::Type{T},
dims::NTuple{N, Int}) where {T, N, S, M, R} =
Array{T, N}(undef, dims)
similar(A::CategoricalArray{S, M, R}, ::Type{Missing},
dims::NTuple{N, Int}) where {N, S, M, R} =
Array{Missing, N}(missing, dims)
similar(A::CategoricalArray{S, M, Q}, ::Type{T},
dims::NTuple{N, Int}) where {R, T<:CatValue{R}, N, S, M, Q} =
CategoricalArray{T, N, R}(undef, dims)
similar(A::CategoricalArray{S, M, R}, ::Type{T},
dims::NTuple{N, Int}) where {S, T<:CatValue, M, N, R} =
CategoricalArray{T, N, R}(undef, dims)
# Union{T, Missing} is repeated even if theoretically redundant because of JuliaLang/julia#26405
# Once that bug is fixed, Union{T, Missing} can be replaced with T and the two definitions above can be removed
similar(A::CategoricalArray{S, M, Q}, ::Type{T},
dims::NTuple{N, Int}) where {R, T<:Union{CatValue{R}, Missing}, N, S, M, Q} =
CategoricalArray{Union{T, Missing}, N, R}(undef, dims)
similar(A::CategoricalArray{S, M, R}, ::Type{T},
dims::NTuple{N, Int}) where {S, T<:Union{CatValue, Missing}, M, N, R} =
CategoricalArray{Union{T, Missing}, N, R}(undef, dims)

"""
compress(A::CategoricalArray)
Expand Down Expand Up @@ -745,6 +758,19 @@ end
Array(A::CategoricalArray{T}) where {T} = Array{T}(A)
collect(A::CategoricalArray{T}) where {T} = Array{T}(A)

function float(A::CategoricalArray{T}) where T
if !isconcretetype(T)
error("`float` not defined on abstractly-typed arrays; please convert to a more specific type")
end
convert(AbstractArray{typeof(float(zero(T)))}, A)
end
function complex(A::CategoricalArray{T}) where T
if !isconcretetype(T)
error("`complex` not defined on abstractly-typed arrays; please convert to a more specific type")
end
convert(AbstractArray{typeof(complex(zero(T)))}, A)
end

# Override AbstractArray method to avoid printing useless type parameters
summary(A::CategoricalArray{T, N, R}) where {T, N, R} =
string(Base.dims2string(size(A)), " $CategoricalArray{$T,$N,$R}")
Expand Down
107 changes: 101 additions & 6 deletions test/13_arraycommon.jl
Expand Up @@ -126,23 +126,59 @@ end
y = similar(x)
@test typeof(x) === typeof(y)
@test size(y) == size(x)
if T === Missing
@test all(ismissing, y)
else
@test !any(isassigned(y, i) for i in 1:length(y))
end

x = CategoricalArray{Union{T, String}}(["Old", "Young", "Middle", "Young"])
y = similar(x, 3)
@test typeof(x) === typeof(y)
@test size(y) == (3,)
if T === Missing
@test all(ismissing, y)
else
@test !any(isassigned(y, i) for i in 1:length(y))
end

x = CategoricalArray{Union{T, String}, 1, UInt8}(["Old", "Young", "Middle", "Young"])
y = similar(x, Int)
@test isa(y, CategoricalArray{Int, 1, UInt8})
@test isa(y, Vector{Int})
@test size(y) == size(x)

x = CategoricalArray{Union{T, String}}(["Old", "Young", "Middle", "Young"])
y = similar(x, Int, 3, 2)
@test isa(y, CategoricalArray{Int, 2, CategoricalArrays.DefaultRefType})
@test isa(y, Matrix{Int})
@test size(y) == (3, 2)
end

x = CategoricalArray{Union{T, String}, 1, UInt8}(["Old", "Young", "Middle", "Young"])
y = similar(x, Union{T, CategoricalString})
@test typeof(x) === typeof(y)
@test size(y) == size(x)
T === Missing && @test all(ismissing, y)

y = similar(x, Union{T, CategoricalString{UInt32}})
@test isa(y, CategoricalVector{Union{T, String}, UInt32})
@test size(y) == size(x)
T === Missing && @test all(ismissing, y)

y = similar(x, Union{Missing, CategoricalString}, 3, 2)
@test isa(y, CategoricalMatrix{Union{String, Missing}, UInt8})
@test size(y) == (3, 2)
@test all(ismissing, y)

y = similar(x, CategoricalString)
@test isa(y, CategoricalVector{String, UInt8})
@test size(y) == size(x)
@test !any(isassigned(y, i) for i in 1:length(y))

y = similar(x, CategoricalString{UInt32})
@test isa(y, CategoricalVector{String, UInt32})
@test size(y) == size(x)
@test !any(isassigned(y, i) for i in 1:length(y))

y = similar(x, CategoricalValue{Int, UInt32})
@test isa(y, CategoricalVector{Int, UInt32})
@test size(y) == size(x)
end

@testset "copy!()" begin
x = CategoricalArray{Union{T, String}}(["Old", "Young", "Middle", "Young"])
Expand Down Expand Up @@ -819,6 +855,52 @@ end
@test z x
end

@testset "Array{T} constructors and convert" begin
x = [1,1,2,2]
y = categorical(x)
z = Array{Int}(y)
@test typeof(x) == typeof(z)
@test z == x
z = convert(Array{Int}, y)
@test typeof(x) == typeof(z)
@test z == x

x = [1,1,2,missing]
y = categorical(x)
z = Array{Union{Int, Missing}}(y)
@test typeof(x) == typeof(z)
@test z x
z = convert(Array{Union{Int, Missing}}, y)
@test typeof(x) == typeof(z)
@test z x
end

@testset "convert(AbstractArray{T}, x)" begin
x = [1,1,2,2]
y = categorical(x)
z = convert(AbstractArray{Int}, y)
@test typeof(x) == typeof(z)
@test z == x

x = [1,1,2,missing]
y = categorical(x)
z = convert(AbstractArray{Union{Int, Missing}}, y)
@test typeof(x) == typeof(z)
@test z x

# Check that convert is a no-op when appropriate
for x in (categorical([1,1,2,2]), categorical([1,1,2,missing]))
y = convert(AbstractArray, x)
@test x === y
y = convert(AbstractVector, x)
@test x === y
y = convert(AbstractArray{eltype(x)}, x)
@test x === y
y = convert(AbstractArray{eltype(x), 1}, x)
@test x === y
end
end

@testset "new levels can't be added through assignment when levels are ordered" begin
x = categorical([1,2,3])
ordered!(x, true)
Expand All @@ -836,4 +918,17 @@ end
@test x == [4,2,3]
end

@testset "float() and complex()" begin
x = categorical([1,2,3])
@test float(x) == x
@test float(x) isa Vector{Float64}

x = categorical([1,2,3])
@test complex(x) == x
@test complex(x) isa Vector{Complex{Int}}

@test_throws ErrorException float(categorical(Union{Int,Missing}[1]))
@test_throws ErrorException complex(categorical(Union{Int,Missing}[1]))
end

end

0 comments on commit e2db279

Please sign in to comment.