1
module Missings
2

3
export allowmissing, disallowmissing, ismissing, missing, missings,
4
       Missing, MissingException, levels, coalesce, passmissing
5

6
using Base: ismissing, missing, Missing, MissingException
7

8 2
T(::Type{S}) where {S} = Core.Compiler.typesubtract(S, Missing)
9

10
# vector constructors
11 2
missings(dims::Dims) = fill(missing, dims)
12 2
missings(::Type{T}, dims::Dims) where {T >: Missing} = fill!(Array{T}(undef, dims), missing)
13 2
missings(::Type{T}, dims::Dims) where {T} = fill!(Array{Union{T, Missing}}(undef, dims), missing)
14 2
missings(dims::Integer...) = missings(dims)
15 2
missings(::Type{T}, dims::Integer...) where {T} = missings(T, dims)
16

17
"""
18
    allowmissing(x::AbstractArray)
19

20
Return an array equal to `x` allowing for [`missing`](@ref) values,
21
i.e. with an element type equal to `Union{eltype(x), Missing}`.
22

23
When possible, the result will share memory with `x` (as with [`convert`](@ref)).
24

25
See also: [`disallowmissing`](@ref)
26
"""
27 2
allowmissing(x::AbstractArray{T}) where {T} = convert(AbstractArray{Union{T, Missing}}, x)
28

29
"""
30
    disallowmissing(x::AbstractArray)
31

32
Return an array equal to `x` not allowing for [`missing`](@ref) values,
33
i.e. with an element type equal to `Missings.T(eltype(x))`.
34

35
When possible, the result will share memory with `x` (as with [`convert`](@ref)).
36
If `x` contains missing values, a `MethodError` is thrown.
37

38
See also: [`allowmissing`](@ref)
39
"""
40 2
disallowmissing(x::AbstractArray{T}) where {T} = convert(AbstractArray{Missings.T(T)}, x)
41

42
# Iterators
43
"""
44
    Missings.replace(itr, replacement)
45

46
Return an iterator wrapping iterable `itr` which replaces [`missing`](@ref) values with
47
`replacement`. When applicable, the size of `itr` is preserved.
48
If the type of `replacement` differs from the element type of `itr`,
49
it will be converted.
50

51
See also: [`skipmissing`](@ref), [`Missings.fail`](@ref)
52

53
# Examples
54
```jldoctest
55
julia> collect(Missings.replace([1, missing, 2], 0))
56
3-element Array{Int64,1}:
57
 1
58
 0
59
 2
60

61
julia> collect(Missings.replace([1 missing; 2 missing], 0))
62
2×2 Array{Int64,2}:
63
 1  0
64
 2  0
65

66
```
67
"""
68 2
replace(itr, replacement) = EachReplaceMissing(itr, convert(eltype(itr), replacement))
69
struct EachReplaceMissing{T, U}
70 2
    x::T
71
    replacement::U
72
end
73 0
Base.IteratorSize(::Type{<:EachReplaceMissing{T}}) where {T} =
74
    Base.IteratorSize(T)
75 0
Base.IteratorEltype(::Type{<:EachReplaceMissing{T}}) where {T} =
76
    Base.IteratorEltype(T)
77 2
Base.length(itr::EachReplaceMissing) = length(itr.x)
78 2
Base.size(itr::EachReplaceMissing) = size(itr.x)
79 2
Base.eltype(itr::EachReplaceMissing) = Missings.T(eltype(itr.x))
80

81
@inline function Base.iterate(itr::EachReplaceMissing)
82 2
    st = iterate(itr.x)
83 2
    st === nothing && return nothing
84
    v, s = st
85 2
    return (v isa Missing ? itr.replacement : v, s)
86
end
87

88
@inline function Base.iterate(itr::EachReplaceMissing, state)
89 2
    st = iterate(itr.x, state)
90 2
    st === nothing && return nothing
91
    v, s = st
92 2
    return (v isa Missing ? itr.replacement : v, s)
93
end
94

95
"""
96
    Missings.fail(itr)
97

98
Return an iterator wrapping iterable `itr` which will throw a [`MissingException`](@ref)
99
if a [`missing`](@ref) value is found.
100

101
Use [`collect`](@ref) to obtain an `Array` containing the resulting values.
102
If `itr` is an array, the resulting array will have the same dimensions.
103

104
See also: [`skipmissing`](@ref), [`Missings.replace`](@ref)
105

106
# Examples
107
```jldoctest
108
julia> collect(Missings.fail([1 2; 3 4]))
109
2×2 Array{Int64,2}:
110
 1  2
111
 3  4
112

113
julia> collect(Missings.fail([1, missing, 2]))
114
ERROR: MissingException: missing value encountered by Missings.fail
115
[...]
116
```
117
"""
118 2
fail(itr) = EachFailMissing(itr)
119
struct EachFailMissing{T}
120 2
    x::T
121
end
122 0
Base.IteratorSize(::Type{EachFailMissing{T}}) where {T} =
123
    Base.IteratorSize(T)
124 0
Base.IteratorEltype(::Type{EachFailMissing{T}}) where {T} =
125
    Base.IteratorEltype(T)
126 2
Base.length(itr::EachFailMissing) = length(itr.x)
127 2
Base.size(itr::EachFailMissing) = size(itr.x)
128 2
Base.eltype(itr::EachFailMissing) = Missings.T(eltype(itr.x))
129

130
@inline function Base.iterate(itr::EachFailMissing)
131 2
    st = iterate(itr.x)
132 2
    st === nothing && return nothing
133
    v, s = st
134 2
    ismissing(v) && throw(MissingException("missing value encountered by Missings.fail"))
135 2
    return (v::eltype(itr), s)
136
end
137

138
@inline function Base.iterate(itr::EachFailMissing, state)
139 2
    st = iterate(itr.x, state)
140 2
    st === nothing && return nothing
141
    v, s = st
142 2
    ismissing(v) && throw(MissingException("missing value encountered by Missings.fail"))
143 2
    return (v::eltype(itr), s)
144
end
145

146
"""
147
    levels(x)
148

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

153
Contrary to [`unique`](@ref), this function may return values which do not
154
actually occur in the data, and does not preserve their order of appearance in `x`.
155
"""
156
function levels(x)
157 2
    T = Missings.T(eltype(x))
158 2
    levs = convert(AbstractArray{T}, filter!(!ismissing, unique(x)))
159 2
    if hasmethod(isless, Tuple{T, T})
160 2
        try
161 2
            sort!(levs)
162
        catch
163
        end
164
    end
165 2
    levs
166
end
167

168
struct PassMissing{F} <: Function
169 2
    f::F
170
end
171

172
function (f::PassMissing{F})(x) where {F}
173 2
    if @generated
174 2
        return x === Missing ? missing : :(f.f(x))
175
    else
176
        return x === missing ? missing : f.f(x)
177
    end
178
end
179

180
function (f::PassMissing{F})(xs...) where {F}
181 2
    if @generated
182 2
        for T in xs
183 2
            T === Missing && return missing
184
        end
185 2
        return :(f.f(xs...))
186
    else
187
        return any(ismissing, xs) ? missing : f.f(xs...)
188
    end
189
end
190

191
"""
192
    passmissing(f)
193

194
Return a function that returns `missing` if any of its positional arguments
195
are `missing` (even if their number or type is not consistent with any of the
196
methods defined for `f`) and otherwise applies `f` to these arguments.
197

198
`passmissing` does not support passing keyword arguments to the `f` function.
199

200
# Examples
201
```jldoctest
202
julia> passmissing(sqrt)(4)
203
2.0
204

205
julia> passmissing(sqrt)(missing)
206
missing
207

208
julia> passmissing(sqrt).([missing, 4])
209
2-element Array{Union{Missing, Float64},1}:
210
  missing
211
   2.0
212

213
julia> passmissing((x,y)->"\$x \$y")(1, 2)
214
"1 2"
215

216
julia> passmissing((x,y)->"\$x \$y")(missing)
217
missing
218
"""
219 2
passmissing(f::Base.Callable) = PassMissing{typeof(f)}(f)
220

221
end # module

Read our documentation on viewing source code .

Loading