diff --git a/Project.toml b/Project.toml index 1b02788b..a02a994b 100644 --- a/Project.toml +++ b/Project.toml @@ -5,13 +5,10 @@ version = "0.10.0" [deps] DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" Future = "9fa8497b-333b-5362-9e8d-4d0656e87820" -JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" Missings = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" -RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +Requires = "ae029012-a4dd-5104-9daa-d747884805df" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [compat] @@ -20,16 +17,22 @@ JSON = "0.15, 0.16, 0.17, 0.18, 0.19, 0.20, 0.21" JSON3 = "1.1.2" Missings = "0.4.3, 1" RecipesBase = "1.1" +Requires = "1" SentinelArrays = "1" StructTypes = "1" julia = "1" [extras] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Dates", "JSON3", "Plots", "PooledArrays", "Test"] +test = ["Dates", "JSON", "JSON3", "Plots", "PooledArrays", + "RecipesBase", "SentinelArrays", "StructTypes", "Test"] diff --git a/src/CategoricalArrays.jl b/src/CategoricalArrays.jl index a6c5b0ea..967cc368 100644 --- a/src/CategoricalArrays.jl +++ b/src/CategoricalArrays.jl @@ -11,13 +11,10 @@ module CategoricalArrays import DataAPI: unwrap export unwrap - using JSON using DataAPI using Missings using Printf - import RecipesBase - import SentinelArrays - import StructTypes + using Requires: @require # JuliaLang/julia#36810 if VERSION < v"1.5.2" @@ -37,4 +34,67 @@ module CategoricalArrays include("recode.jl") include("deprecated.jl") + + function __init__() + @require JSON="682c06a0-de6a-54ab-a142-c8b1cf79cde6" begin + # JSON of CategoricalValue is JSON of the value it refers to + JSON.lower(x::CategoricalValue) = JSON.lower(unwrap(x)) + end + + @require RecipesBase="3cdcf5f2-1ef4-517c-9805-6587b60abb01" @eval begin + RecipesBase.@recipe function f(::Type{T}, v::T) where T <: CategoricalValue + level_strings = [map(string, levels(v)); missing] + ticks --> eachindex(level_strings) + v -> ismissing(v) ? length(level_strings) : Int(refcode(v)), + i -> level_strings[Int(i)] + end + end + + @require SentinelArrays="91c51154-3ec4-41a3-a24f-3f23e20d615c" begin + copyto!(dest::CatArrOrSub{<:Any, 1}, src::SentinelArrays.ChainedVector) = + copyto!(dest, 1, src, 1, length(src)) + copyto!(dest::CatArrOrSub{<:Any, 1}, dstart::Union{Signed, Unsigned}, + src::SentinelArrays.ChainedVector, sstart::Union{Signed, Unsigned}, + n::Union{Signed, Unsigned}) = + invoke(copyto!, Tuple{AbstractArray, Union{Signed, Unsigned}, + SentinelArrays.ChainedVector, + Union{Signed, Unsigned}, Union{Signed, Unsigned}}, + dest, dstart, src, sstart, n) + end + + @require StructTypes="856f2bd8-1eba-4b0a-8007-ebc267875bd4" begin + # define appropriate handlers for JSON3 interface + StructTypes.StructType(x::CategoricalValue) = StructTypes.StructType(unwrap(x)) + StructTypes.StructType(::Type{<:CategoricalValue{T}}) where {T} = StructTypes.StructType(T) + StructTypes.numbertype(::Type{<:CategoricalValue{T}}) where {T <: Number} = T + StructTypes.construct(::Type{T}, x::CategoricalValue{T}) where {T} = T(unwrap(x)) + + # JSON3 writing/reading + StructTypes.StructType(::Type{<:CategoricalVector}) = StructTypes.ArrayType() + + StructTypes.construct(::Type{<:CategoricalArray}, A::AbstractVector) = + constructgeneral(A) + StructTypes.construct(::Type{<:CategoricalArray}, A::Vector) = + constructgeneral(A) + + function constructgeneral(A) + if eltype(A) === Any + # unlike `replace`, broadcast narrows the type, which allows us to return small + # union eltypes (e.g. Union{String,Missing}) + categorical(ifelse.(A .=== nothing, missing, A)) + elseif eltype(A) >: Nothing + categorical(replace(A, nothing=>missing)) + else + categorical(A) + end + end + + StructTypes.construct(::Type{<:CategoricalArray{Union{Missing, T}}}, + A::AbstractVector) where {T} = + CategoricalArray{Union{Missing, T}}(replace(A, nothing=>missing)) + StructTypes.construct(::Type{<:CategoricalArray{Union{Missing, T}}}, + A::Vector) where {T} = + CategoricalArray{Union{Missing, T}}(replace(A, nothing=>missing)) + end + end end diff --git a/src/array.jl b/src/array.jl index 8ffc3428..ddb6af6c 100644 --- a/src/array.jl +++ b/src/array.jl @@ -630,16 +630,6 @@ copy!(dest::CatArrOrSub{<:Any, 1}, src::AbstractArray{<:Any, 1}) = copy!(dest::CatArrOrSub{T, 1}, src::AbstractArray{T, 1}) where {T} = copyto!(dest, 1, src, 1, length(src)) -copyto!(dest::CatArrOrSub{<:Any, 1}, src::SentinelArrays.ChainedVector) = - copyto!(dest, 1, src, 1, length(src)) -copyto!(dest::CatArrOrSub{<:Any, 1}, dstart::Union{Signed, Unsigned}, - src::SentinelArrays.ChainedVector, sstart::Union{Signed, Unsigned}, - n::Union{Signed, Unsigned}) = - invoke(copyto!, Tuple{AbstractArray, Union{Signed, Unsigned}, - SentinelArrays.ChainedVector, - Union{Signed, Unsigned}, Union{Signed, Unsigned}}, - dest, dstart, src, sstart, n) - similar(A::CategoricalArray{S, M, R}, ::Type{T}, dims::NTuple{N, Int}) where {T, N, S, M, R} = Array{T, N}(undef, dims) @@ -1109,33 +1099,6 @@ Base.repeat(a::CatArrOrSub{T, N}; inner = nothing, outer = nothing) where {T, N} = CategoricalArray{T, N}(repeat(refs(a), inner=inner, outer=outer), copy(pool(a))) -# JSON3 writing/reading -StructTypes.StructType(::Type{<:CategoricalVector}) = StructTypes.ArrayType() - -StructTypes.construct(::Type{<:CategoricalArray}, A::AbstractVector) = - constructgeneral(A) -StructTypes.construct(::Type{<:CategoricalArray}, A::Vector) = - constructgeneral(A) - -function constructgeneral(A) - if eltype(A) === Any - # unlike `replace`, broadcast narrows the type, which allows us to return small - # union eltypes (e.g. Union{String,Missing}) - categorical(ifelse.(A .=== nothing, missing, A)) - elseif eltype(A) >: Nothing - categorical(replace(A, nothing=>missing)) - else - categorical(A) - end -end - -StructTypes.construct(::Type{<:CategoricalArray{Union{Missing, T}}}, - A::AbstractVector) where {T} = - CategoricalArray{Union{Missing, T}}(replace(A, nothing=>missing)) -StructTypes.construct(::Type{<:CategoricalArray{Union{Missing, T}}}, - A::Vector) where {T} = - CategoricalArray{Union{Missing, T}}(replace(A, nothing=>missing)) - # DataAPI refarray/refvalue/refpool support struct CategoricalRefPool{T, P} <: AbstractVector{T} pool::P diff --git a/src/value.jl b/src/value.jl index 4ec789ee..e2fe2eb5 100644 --- a/src/value.jl +++ b/src/value.jl @@ -179,22 +179,7 @@ Base.:<(x::CategoricalValue, y::SupportedTypes) = "or `CategoricalValue(v, catarray)` first")) Base.:<(y::SupportedTypes, x::CategoricalValue) = x < y -# JSON of CategoricalValue is JSON of the value it refers to -JSON.lower(x::CategoricalValue) = JSON.lower(unwrap(x)) DataAPI.defaultarray(::Type{CategoricalValue{T, R}}, N) where {T, R} = - CategoricalArray{T, N, R} + CategoricalArray{T, N, R} DataAPI.defaultarray(::Type{Union{CategoricalValue{T, R}, Missing}}, N) where {T, R} = - CategoricalArray{Union{T, Missing}, N, R} - -# define appropriate handlers for JSON3 interface -StructTypes.StructType(x::CategoricalValue) = StructTypes.StructType(unwrap(x)) -StructTypes.StructType(::Type{<:CategoricalValue{T}}) where {T} = StructTypes.StructType(T) -StructTypes.numbertype(::Type{<:CategoricalValue{T}}) where {T <: Number} = T -StructTypes.construct(::Type{T}, x::CategoricalValue{T}) where {T} = T(unwrap(x)) - -RecipesBase.@recipe function f(::Type{T}, v::T) where T <: CategoricalValue - level_strings = [map(string, levels(v)); missing] - ticks --> eachindex(level_strings) - v -> ismissing(v) ? length(level_strings) : Int(refcode(v)), - i -> level_strings[Int(i)] -end + CategoricalArray{Union{T, Missing}, N, R} \ No newline at end of file