diff --git a/Project.toml b/Project.toml index 2086dec3..cc643f52 100644 --- a/Project.toml +++ b/Project.toml @@ -11,6 +11,18 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +[weakdeps] +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" + +[extensions] +CategoricalArraysJSONExt = "JSON" +CategoricalArraysRecipesBaseExt = "RecipesBase" +CategoricalArraysSentinelArraysExt = "SentinelArrays" +CategoricalArraysStructTypesExt = "StructTypes" + [compat] DataAPI = "1.6" JSON = "0.15, 0.16, 0.17, 0.18, 0.19, 0.20, 0.21" diff --git a/ext/CategoricalArraysJSONExt.jl b/ext/CategoricalArraysJSONExt.jl new file mode 100644 index 00000000..86c0e67f --- /dev/null +++ b/ext/CategoricalArraysJSONExt.jl @@ -0,0 +1,14 @@ +module CategoricalArraysJSONExt + +if isdefined(Base, :get_extension) + using CategoricalArrays + using JSON +else + using ..CategoricalArrays + using ..JSON +end + +# JSON of CategoricalValue is JSON of the value it refers to +JSON.lower(x::CategoricalValue) = JSON.lower(unwrap(x)) + +end diff --git a/ext/CategoricalArraysRecipesBaseExt.jl b/ext/CategoricalArraysRecipesBaseExt.jl new file mode 100644 index 00000000..2642f838 --- /dev/null +++ b/ext/CategoricalArraysRecipesBaseExt.jl @@ -0,0 +1,18 @@ +module CategoricalArraysRecipesBaseExt + +if isdefined(Base, :get_extension) + using CategoricalArrays + using RecipesBase +else + using ..CategoricalArrays + using ..RecipesBase +end + +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(CategoricalArrays.refcode(v)), + i -> level_strings[Int(i)] +end + +end diff --git a/ext/CategoricalArraysSentinelArraysExt.jl b/ext/CategoricalArraysSentinelArraysExt.jl new file mode 100644 index 00000000..fd439bbd --- /dev/null +++ b/ext/CategoricalArraysSentinelArraysExt.jl @@ -0,0 +1,21 @@ +module CategoricalArraysSentinelArraysExt + +if isdefined(Base, :get_extension) + using CategoricalArrays + using SentinelArrays +else + using ..CategoricalArrays + using ..SentinelArrays +end + +Base.copyto!(dest::CategoricalArrays.CatArrOrSub{<:Any, 1}, src::SentinelArrays.ChainedVector) = + copyto!(dest, 1, src, 1, length(src)) +Base.copyto!(dest::CategoricalArrays.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 diff --git a/ext/CategoricalArraysStructTypesExt.jl b/ext/CategoricalArraysStructTypesExt.jl new file mode 100644 index 00000000..723ba8cf --- /dev/null +++ b/ext/CategoricalArraysStructTypesExt.jl @@ -0,0 +1,44 @@ +module CategoricalArraysStructTypesExt + +if isdefined(Base, :get_extension) + using CategoricalArrays + using StructTypes +else + using ..CategoricalArrays + using ..StructTypes +end + +# 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 diff --git a/src/CategoricalArrays.jl b/src/CategoricalArrays.jl index 967cc368..214a5d17 100644 --- a/src/CategoricalArrays.jl +++ b/src/CategoricalArrays.jl @@ -14,7 +14,6 @@ module CategoricalArrays using DataAPI using Missings using Printf - using Requires: @require # JuliaLang/julia#36810 if VERSION < v"1.5.2" @@ -35,66 +34,16 @@ module CategoricalArrays 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 + if !isdefined(Base, :get_extension) + using Requires: @require + 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)) + @static if !isdefined(Base, :get_extension) + function __init__() + @require JSON="682c06a0-de6a-54ab-a142-c8b1cf79cde6" include("../ext/CategoricalArraysJSONExt.jl") + @require RecipesBase="3cdcf5f2-1ef4-517c-9805-6587b60abb01" include("../ext/CategoricalArraysRecipesBaseExt.jl") + @require SentinelArrays="91c51154-3ec4-41a3-a24f-3f23e20d615c" include("../ext/CategoricalArraysSentinelArraysExt.jl") + @require StructTypes="856f2bd8-1eba-4b0a-8007-ebc267875bd4" include("../ext/CategoricalArraysStructTypesExt.jl") end end end