Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export coalesce #72

Merged
merged 2 commits into from
Jan 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 48 additions & 40 deletions src/Missings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ module Missings
using Compat

export allowmissing, disallowmissing, ismissing, missing, missings,
Missing, MissingException, levels, skipmissing
Missing, MissingException, levels, coalesce

if VERSION < v"0.7.0-DEV.2762"
if VERSION < v"0.7.0-DEV.2762"
"""
Missing

Expand Down Expand Up @@ -88,7 +88,7 @@ else
:(Base.log2), :(Base.exponent), :(Base.sqrt), :(Base.gamma), :(Base.lgamma),
:(Base.iseven), :(Base.ispow2), :(Base.isfinite), :(Base.isinf), :(Base.isodd),
:(Base.isinteger), :(Base.isreal), :(Base.isimag), :(Base.isnan), :(Base.isempty),
:(Base.iszero), :(Base.transpose), :(Base.ctranspose), :(Base.float))
:(Base.iszero), :(Base.transpose), :(Base.float))
@eval $(f)(d::Missing) = missing
end

Expand Down Expand Up @@ -206,15 +206,19 @@ else
Base.float(A::AbstractArray{Missing}) = A
end

@static if isdefined(Base, :adjoint) && !applicable(adjoint, missing)
Base.adjoint(::Missing) = missing
end

T(::Type{Union{T1, Missing}}) where {T1} = T1
T(::Type{Missing}) = Union{}
T(::Type{T1}) where {T1} = T1
T(::Type{Any}) = Any

# vector constructors
missings(dims...) = fill(missing, dims)
missings(::Type{T}, dims...) where {T >: Missing} = fill!(Array{T}(dims), missing)
missings(::Type{T}, dims...) where {T} = fill!(Array{Union{T, Missing}}(dims), missing)
missings(::Type{T}, dims...) where {T >: Missing} = fill!(Array{T}(uninitialized, dims), missing)
missings(::Type{T}, dims...) where {T} = fill!(Array{Union{T, Missing}}(uninitialized, dims), missing)

"""
allowmissing(x::AbstractArray)
Expand Down Expand Up @@ -272,10 +276,10 @@ struct EachReplaceMissing{T, U}
x::T
replacement::U
end
Base.iteratorsize(::Type{<:EachReplaceMissing{T}}) where {T} =
Base.iteratorsize(T)
Base.iteratoreltype(::Type{<:EachReplaceMissing{T}}) where {T} =
Base.iteratoreltype(T)
Compat.IteratorSize(::Type{<:EachReplaceMissing{T}}) where {T} =
Compat.IteratorSize(T)
Compat.IteratorEltype(::Type{<:EachReplaceMissing{T}}) where {T} =
Compat.IteratorEltype(T)
Base.length(itr::EachReplaceMissing) = length(itr.x)
Base.size(itr::EachReplaceMissing) = size(itr.x)
Base.start(itr::EachReplaceMissing) = start(itr.x)
Expand All @@ -286,6 +290,8 @@ Base.eltype(itr::EachReplaceMissing) = Missings.T(eltype(itr.x))
(v isa Missing ? itr.replacement : v, s)
end

@static if !isdefined(Base, :skipmissing)
export skipmissing
"""
skipmissing(itr)

Expand Down Expand Up @@ -315,10 +321,10 @@ skipmissing(itr) = EachSkipMissing(itr)
struct EachSkipMissing{T}
x::T
end
Base.iteratorsize(::Type{<:EachSkipMissing}) =
Compat.IteratorSize(::Type{<:EachSkipMissing}) =
Base.SizeUnknown()
Base.iteratoreltype(::Type{EachSkipMissing{T}}) where {T} =
Base.iteratoreltype(T)
Compat.IteratorEltype(::Type{EachSkipMissing{T}}) where {T} =
Compat.IteratorEltype(T)
Base.eltype(itr::EachSkipMissing) = Missings.T(eltype(itr.x))
# Fallback implementation for general iterables: we cannot access a value twice,
# so after finding the next non-missing element in start() or next(), we have to
Expand Down Expand Up @@ -364,6 +370,8 @@ end
(v, _next_nonmissing_ind(itr.x, state))
end

end # isdefined

"""
Missings.fail(itr)

Expand Down Expand Up @@ -391,10 +399,10 @@ fail(itr) = EachFailMissing(itr)
struct EachFailMissing{T}
x::T
end
Base.iteratorsize(::Type{EachFailMissing{T}}) where {T} =
Base.iteratorsize(T)
Base.iteratoreltype(::Type{EachFailMissing{T}}) where {T} =
Base.iteratoreltype(T)
Compat.IteratorSize(::Type{EachFailMissing{T}}) where {T} =
Compat.IteratorSize(T)
Compat.IteratorEltype(::Type{EachFailMissing{T}}) where {T} =
Compat.IteratorEltype(T)
Base.length(itr::EachFailMissing) = length(itr.x)
Base.size(itr::EachFailMissing) = size(itr.x)
Base.start(itr::EachFailMissing) = start(itr.x)
Expand All @@ -407,6 +415,28 @@ Base.eltype(itr::EachFailMissing) = Missings.T(eltype(itr.x))
(v::eltype(itr), s)
end

"""
levels(x)

Return a vector of unique values which occur or could occur in collection `x`,
omitting `missing` even if present. Values are returned in the preferred order
for the collection, with the result of [`sort`](@ref) as a default.

Contrary to [`unique`](@ref), this function may return values which do not
actually occur in the data, and does not preserve their order of appearance in `x`.
"""
function levels(x)
T = Missings.T(eltype(x))
levs = convert(AbstractArray{T}, filter!(!ismissing, unique(x)))
if method_exists(isless, Tuple{T, T})
try; sort!(levs); end
end
levs
end

# Deprecations
@deprecate skip(itr) skipmissing(itr) false

"""
coalesce(x, y...)

Expand Down Expand Up @@ -441,29 +471,7 @@ julia> coalesce.([missing, 1, missing], [0, 10, 5])

```
"""
coalesce(x) = x
coalesce(x, y...) = ifelse(x !== missing, x, coalesce(y...))

"""
levels(x)

Return a vector of unique values which occur or could occur in collection `x`,
omitting `missing` even if present. Values are returned in the preferred order
for the collection, with the result of [`sort`](@ref) as a default.

Contrary to [`unique`](@ref), this function may return values which do not
actually occur in the data, and does not preserve their order of appearance in `x`.
"""
function levels(x)
T = Missings.T(eltype(x))
levs = convert(AbstractArray{T}, filter!(!ismissing, unique(x)))
if method_exists(isless, Tuple{T, T})
try; sort!(levs); end
end
levs
end

# Deprecations
@deprecate skip(itr) skipmissing(itr) false
Compat.coalesce(x::Missing) = missing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be defined before Base includes coalesce, or it's going to overwrite the equivalent Base method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That assumes that Compat already defines coalesce(x::Missing), which it currently doesn't (it's commented out). It really boils down to whether the actual compat definition is in Missings or Compat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but I was speaking about a different issue, which is that recent Julia 0.7 versions define that method, and that Compat.coalesce = Base.coalesce there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so? I don't see any overwrite happening on latest master?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easy enough to just wrap this in is !isdefined(Base, :coalesce) regardless.

Compat.coalesce(x::Missing, y...) = coalesce(y...)

end # module
33 changes: 21 additions & 12 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Base.Test, Missings, Compat
using Compat.Test, Compat.SparseArrays, Missings, Compat

@testset "Missings" begin
# test promote rules
Expand All @@ -17,7 +17,11 @@ using Base.Test, Missings, Compat
@test promote_type(Union{Int, Missing}, Union{Int, Missing}) == Union{Int, Missing}
@test promote_type(Union{Float64, Missing}, Union{String, Missing}) == Any
@test promote_type(Union{Float64, Missing}, Union{Int, Missing}) == Union{Float64, Missing}
@test promote_type(Union{Void, Missing, Int}, Float64) == Any
if VERSION < v"0.7.0-"
@test promote_type(Union{Nothing, Missing, Int}, Float64) == Any
else
@test_broken promote_type(Union{Nothing, Missing, Int}, Float64) == Any
end

bit_operators = [&, |, ⊻]

Expand All @@ -32,7 +36,7 @@ using Base.Test, Missings, Compat
iseven, isodd, ispow2,
isfinite, isinf, isnan, iszero,
isinteger, isreal,
isempty, transpose, ctranspose, float]
isempty, transpose, float]
VERSION < v"0.7.0-DEV" && push!(elementary_functions, isimag)

rounding_functions = [ceil, floor, round, trunc]
Expand All @@ -46,6 +50,11 @@ using Base.Test, Missings, Compat
for f in elementary_functions
@test ismissing(f(missing))
end
@static if isdefined(Base, :adjoint)
@test ismissing(adjoint(missing))
else
@test ismissing(ctranspose(missing))
end

# All rounding functions return missing when evaluating missing as first argument
for f in rounding_functions
Expand Down Expand Up @@ -212,16 +221,16 @@ using Base.Test, Missings, Compat
@test collect(x) == [1, 2, 4]
@test collect(x) isa Vector{Int}

@test Missings.coalesce(missing, 1) === 1
@test Missings.coalesce(1, missing) === 1
@test Missings.coalesce(missing, missing) === missing
@test Missings.coalesce.([missing, 1, missing], 0) == [0, 1, 0]
@test Missings.coalesce.([missing, 1, missing], 0) isa Vector{Int}
@test Missings.coalesce.([missing, 1, missing], [0, 10, 5]) == [0, 1, 5]
@test Missings.coalesce.([missing, 1, missing], [0, 10, 5]) isa Vector{Int}
@test isequal(Missings.coalesce.([missing, 1, missing], [0, missing, missing]), [0, 1, missing])
@test coalesce(missing, 1) === 1
@test coalesce(1, missing) === 1
@test coalesce(missing, missing) === missing
@test coalesce.([missing, 1, missing], 0) == [0, 1, 0]
@test coalesce.([missing, 1, missing], 0) isa Vector{Int}
@test coalesce.([missing, 1, missing], [0, 10, 5]) == [0, 1, 5]
@test coalesce.([missing, 1, missing], [0, 10, 5]) isa Vector{Int}
@test isequal(coalesce.([missing, 1, missing], [0, missing, missing]), [0, 1, missing])
# Fails in Julia 0.6 and 0.7.0-DEV.1556
@test_broken Missings.coalesce.([missing, 1, missing], [0, missing, missing]) isa Vector{Union{Missing, Int}}
@test_broken coalesce.([missing, 1, missing], [0, missing, missing]) isa Vector{Union{Missing, Int}}

@test levels(1:1) == levels([1]) == levels([1, missing]) == levels([missing, 1]) == [1]
@test levels(2:-1:1) == levels([2, 1]) == levels([2, missing, 1]) == [1, 2]
Expand Down