Skip to content

Commit

Permalink
add lift function for working with missing values
Browse files Browse the repository at this point in the history
  • Loading branch information
bkamins committed Mar 30, 2018
1 parent 8a5f747 commit 93f775f
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 0 deletions.
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ export
ismissing,
missing,
skipmissing,
lift,

# time
sleep,
Expand Down
44 changes: 44 additions & 0 deletions base/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,47 @@ end
@inbounds v = itr.x[i]::eltype(itr)
(v, _next_nonmissing_ind(itr.x, state))
end

"""
lift(f)
lift(f, x...; kw...)
Lift function `f` so that it returns `missing` when any of its positional arguments
is `missing`. Otherwise `f` is applied to its arguments.
The form `lift(f)` returns an anonymous function that has lifted behavior.
The form `lift(f, x...; kw...)` returns `missing` if any of `x` is `missing`
and otherwise returns `f(x...; kw...)`.
# Examples
```jldoctest
julia> g = lift(uppercase);
julia> g("a")
"A"
julia> g(missing)
missing
julia> lift(parse, Int, "aa", base=16)
170
julia> lift(parse, missing, "aa", base=16)
missing
julia> lift(parse).(Int, ["1", "2", missing])
3-element Array{Union{Missing, Int64},1}:
1
2
missing
julia> lift.(parse, Int, ["a", "b", missing], base=16)
3-element Array{Union{Missing, Int64},1}:
10
11
missing
```
"""
lift(f::Function) = (x...; kw...) -> any(ismissing, x) ? missing : f(x...; kw...)
lift(f::Function, x; kw...) = ismissing(x) ? missing : f(x; kw...)
lift(f::Function, x...; kw...) = any(ismissing, x) ? missing : f(x...; kw...)
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ Base.missing
Base.coalesce
Base.ismissing
Base.skipmissing
Base.lift
```

## System
Expand Down
52 changes: 52 additions & 0 deletions test/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,55 @@ end
@test collect(x) == [1, 2, 4]
@test collect(x) isa Vector{Int}
end

@testset "lift" begin
x = ["a", missing, "b", missing]
for v in x
if ismissing(v)
@test ismissing(lift(uppercase)(v))
@test ismissing(lift(uppercase, v))
else
@test lift(uppercase)(v) == uppercase(v)
@test lift(uppercase, v) == uppercase(v)
end
end
ref = [ismissing(v) ? missing : uppercase(v) for v in x]
@test isequal(lift(uppercase).(x), ref)
@test isequal(lift.(uppercase, x), ref)

x = ["12345", missing, "1234567", missing]
for v in x
if ismissing(v)
@test ismissing(lift(chop)(v, head=2, tail=2))
@test ismissing(lift(chop, v, head=2, tail=2))
else
@test lift(chop)(v, head=2, tail=2) == chop(v, head=2, tail=2)
@test lift(chop, v, head=2, tail=2) == chop(v, head=2, tail=2)
end
end
ref = [ismissing(v) ? missing : chop(v, head=2, tail=2) for v in x]
@test isequal(lift(chop).(x, head=2, tail=2), ref)
@test isequal(lift.(chop, x, head=2, tail=2), ref)

s = ["12345", missing, "1234567", missing]
t = [Int, Int, missing, missing]
for (v, w) in zip(s, t)
if ismissing(v) || ismissing(w)
@test ismissing(lift(parse)(w, v))
@test ismissing(lift(parse, w, v))
@test ismissing(lift(parse)(w, v, base=16))
@test ismissing(lift(parse, w, v, base=16))
else
@test lift(parse)(w, v) == parse(w, v)
@test lift(parse, w, v) == parse(w, v)
@test lift(parse)(w, v, base=16) == parse(w, v, base=16)
@test lift(parse, w, v, base=16) == parse(w, v, base=16)
end
end
ref10 = [any(ismissing, (v,w)) ? missing : parse(w, v) for (v, w) in zip(s, t)]
ref16 = [any(ismissing, (v,w)) ? missing : parse(w, v, base=16) for (v, w) in zip(s, t)]
@test isequal(lift(parse).(t, s), ref10)
@test isequal(lift(parse).(t, s, base=16), ref16)
@test isequal(lift.(parse, t, s), ref10)
@test isequal(lift.(parse, t, s, base=16), ref16)
end

0 comments on commit 93f775f

Please sign in to comment.