Skip to content

Commit

Permalink
Supporting indexing EachReplaceMissing and EachFailMissing (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
nalimilan authored Jan 25, 2021
1 parent f11436a commit 927f794
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Missings"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "0.4.4"
version = "0.4.5"

[deps]
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
Expand Down
33 changes: 29 additions & 4 deletions src/Missings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,15 @@ disallowmissing(x::AbstractArray{T}) where {T} = convert(AbstractArray{nonmissin
"""
Missings.replace(itr, replacement)
Return an iterator wrapping iterable `itr` which replaces [`missing`](@ref) values with
`replacement`. When applicable, the size of `itr` is preserved.
Return an iterator over the elements in `itr` which replaces [`missing`](@ref) values with
`replacement`.
When applicable, the size of `itr` is preserved.
The returned object can be indexed using indices of `itr` if the latter is indexable,
and `eachindex` and `keys` return the indices of `itr`.
If the type of `replacement` differs from the element type of `itr`,
it will be converted.
it will be converted to it.
See also: [`skipmissing`](@ref), [`Missings.fail`](@ref)
Expand Down Expand Up @@ -86,7 +91,10 @@ Base.IteratorEltype(::Type{<:EachReplaceMissing{T}}) where {T} =
Base.IteratorEltype(T)
Base.length(itr::EachReplaceMissing) = length(itr.x)
Base.size(itr::EachReplaceMissing) = size(itr.x)
Base.axes(itr::EachReplaceMissing) = axes(itr.x)
Base.eltype(itr::EachReplaceMissing) = nonmissingtype(eltype(itr.x))
Base.eachindex(itr::EachReplaceMissing) = eachindex(itr.x)
Base.keys(itr::EachReplaceMissing) = keys(itr.x)

@inline function Base.iterate(itr::EachReplaceMissing)
st = iterate(itr.x)
Expand All @@ -102,11 +110,19 @@ end
return (v isa Missing ? itr.replacement : v, s)
end

Base.@propagate_inbounds function Base.getindex(itr::EachReplaceMissing, I...)
v = itr.x[I...]
return v === missing ? itr.replacement : v
end

"""
Missings.fail(itr)
Return an iterator wrapping iterable `itr` which will throw a [`MissingException`](@ref)
Return an iterator over the elements in `itr` which will throw a [`MissingException`](@ref)
if a [`missing`](@ref) value is found.
The returned object can be indexed using indices of `itr` if the latter is indexable.
Indices corresponding to missing values are not valid: even though they are not skipped
by `keys` and eachindex, a `MissingException` is thrown when trying to use them.
Use [`collect`](@ref) to obtain an `Array` containing the resulting values.
If `itr` is an array, the resulting array will have the same dimensions.
Expand Down Expand Up @@ -135,7 +151,10 @@ Base.IteratorEltype(::Type{EachFailMissing{T}}) where {T} =
Base.IteratorEltype(T)
Base.length(itr::EachFailMissing) = length(itr.x)
Base.size(itr::EachFailMissing) = size(itr.x)
Base.axes(itr::EachFailMissing) = axes(itr.x)
Base.eltype(itr::EachFailMissing) = nonmissingtype(eltype(itr.x))
Base.eachindex(itr::EachFailMissing) = eachindex(itr.x)
Base.keys(itr::EachFailMissing) = keys(itr.x)

@inline function Base.iterate(itr::EachFailMissing)
st = iterate(itr.x)
Expand All @@ -153,6 +172,12 @@ end
return (v::eltype(itr), s)
end

Base.@propagate_inbounds function Base.getindex(itr::EachFailMissing, I...)
v = itr.x[I...]
v === missing && throw(MissingException("the value at index $I is missing"))
return v
end

const levels = DataAPI.levels

struct PassMissing{F} <: Function
Expand Down
37 changes: 32 additions & 5 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,37 @@ struct CubeRooter end
@test eltype(x) === Int
@test length(x) == 4
@test size(x) == (4,)
@test collect(x) == collect(1:4)
@test axes(x) == (1:4,)
@test keys(x) == eachindex(x) == 1:4
@test collect(x) == [x[i] for i in keys(x)] == 1:4
@test collect(x) isa Vector{Int}
x = Missings.replace([1, 2, missing, 4], 3.0)
@test eltype(x) === Int
@test length(x) == 4
@test size(x) == (4,)
@test collect(x) == collect(1:4)
@test axes(x) == (1:4,)
@test keys(x) == eachindex(x) == 1:4
@test collect(x) == [x[i] for i in keys(x)] == 1:4
@test collect(x) isa Vector{Int}
x = Missings.replace([1 2; missing 4], 3)
@test eltype(x) === Int
@test length(x) == 4
@test size(x) == (2, 2)
@test collect(x) == [1 2; 3 4]
@test axes(x) == (1:2, 1:2)
@test keys(x) == CartesianIndices((1:2, 1:2))
@test eachindex(x) == 1:4
@test collect(x) == [x[i] for i in keys(x)] == [1 2; 3 4]
@test collect(x) isa Matrix{Int}
x = Missings.replace((v for v in [missing, 1, missing, 2, 4]), 0)
@test length(x) == 5
@test size(x) == (5,)
if VERSION >= v"1.7.0-DEV"
@test eachindex(x) == keys(x) == 1:5
else
@test_throws MethodError keys(x)
@test_throws MethodError eachindex(x)
end
@test_throws MethodError x[1]
@test eltype(x) === Any
@test collect(x) == [0, 1, 0, 2, 4]
@test collect(x) isa Vector{Int}
Expand All @@ -34,19 +48,32 @@ struct CubeRooter end
@test eltype(x) === Int
@test length(x) == 4
@test size(x) == (4,)
@test collect(x) == [1, 2, 3, 4]
@test axes(x) == (1:4,)
@test keys(x) == eachindex(x) == 1:4
@test collect(x) == [x[i] for i in keys(x)] == [1, 2, 3, 4]
@test collect(x) isa Vector{Int}
x = Missings.fail([1 2; 3 4])
@test eltype(x) === Int
@test length(x) == 4
@test size(x) == (2, 2)
@test collect(x) == [1 2; 3 4]
@test axes(x) == (1:2, 1:2)
@test keys(x) == CartesianIndices((1:2, 1:2))
@test eachindex(x) == 1:4
@test collect(x) == [x[i] for i in keys(x)] == [1 2; 3 4]
@test collect(x) isa Matrix{Int}
@test_throws MissingException collect(Missings.fail([1, 2, missing, 4]))
x = Missings.fail(v for v in [1, 2, 4])
@test eltype(x) === Any
@test length(x) == 3
@test size(x) == (3,)
@test axes(x) == (1:3,)
if VERSION >= v"1.7.0-DEV"
@test eachindex(x) == keys(x) == 1:3
else
@test_throws MethodError keys(x)
@test_throws MethodError eachindex(x)
end
@test_throws MethodError x[1]
@test collect(x) == [1, 2, 4]
@test collect(x) isa Vector{Int}

Expand Down

2 comments on commit 927f794

@nalimilan
Copy link
Member Author

Choose a reason for hiding this comment

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

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/28600

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.4.5 -m "<description of version>" 927f794890d0111fa45fd3e2e7a225dc33a62702
git push origin v0.4.5

Please sign in to comment.