Skip to content

Commit

Permalink
Merge pull request #400 from KristofferC/kc/extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
andreasnoack committed Apr 17, 2023
2 parents 197f0a0 + cbf26b0 commit 5251f03
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 60 deletions.
12 changes: 12 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 14 additions & 0 deletions ext/CategoricalArraysJSONExt.jl
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions ext/CategoricalArraysRecipesBaseExt.jl
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions ext/CategoricalArraysSentinelArraysExt.jl
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions ext/CategoricalArraysStructTypesExt.jl
Original file line number Diff line number Diff line change
@@ -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
69 changes: 9 additions & 60 deletions src/CategoricalArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ module CategoricalArrays
using DataAPI
using Missings
using Printf
using Requires: @require

# JuliaLang/julia#36810
if VERSION < v"1.5.2"
Expand All @@ -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

0 comments on commit 5251f03

Please sign in to comment.