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

let Near At and Contains select ranges between two values #717

Merged
merged 6 commits into from
Jul 15, 2024
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
12 changes: 6 additions & 6 deletions src/Dimensions/indexing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ Convert a `Dimension` or `Selector` `I` to indices of `Int`, `AbstractArray` or
@inline dims2indices(dims::DimTuple, I) = dims2indices(dims, (I,))
# Standard array indices are simply returned
@inline dims2indices(dims::DimTuple, I::Tuple{Vararg{StandardIndices}}) = I
@inline dims2indices(dims::DimTuple, I::Tuple{<:Extents.Extent}) = dims2indices(dims, _extent_as_intervals(first(I)))
@inline dims2indices(dims::DimTuple, I::Tuple{<:Touches{<:Extents.Extent}}) = dims2indices(dims, _extent_as_touches(val(first(I))))
@inline dims2indices(dims::DimTuple, I::Tuple{<:Extents.Extent}) = dims2indices(dims, _extent_as(Interval, first(I)))
@inline dims2indices(dims::DimTuple, I::Tuple{<:Touches{<:Extents.Extent}}) = dims2indices(dims, _extent_as(Touches, val(first(I))))
@inline dims2indices(dims::DimTuple, I::Tuple{<:Near{<:Extents.Extent}}) = dims2indices(dims, _extent_as(Near, val(first(I))))

@inline dims2indices(dims::DimTuple, I::Tuple{<:CartesianIndex}) = I
@inline dims2indices(dims::DimTuple, sel::Tuple) =
Expand Down Expand Up @@ -115,14 +116,13 @@ _unwrapdim(x) = x
@inline _dims2indices(dim::Dimension, ::Nothing) = Colon()
@inline _dims2indices(dim::Dimension, x) = Lookups.selectindices(val(dim), x)

function _extent_as_intervals(extent::Extents.Extent{Keys}) where Keys
function _extent_as(::Type{Lookups.Interval}, extent::Extents.Extent{Keys}) where Keys
map(map(name2dim, Keys), values(extent)) do k, v
rebuild(k, Lookups.Interval(v...))
end
end

function _extent_as_touches(extent::Extents.Extent{Keys}) where Keys
function _extent_as(::Type{T}, extent::Extents.Extent{Keys}) where {T,Keys}
map(map(name2dim, Keys), values(extent)) do k, v
rebuild(k, Touches(v))
rebuild(k, T(v))
end
end
61 changes: 47 additions & 14 deletions src/Lookups/selector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Base.showerror(io::IO, ex::SelectorError{<:Categorical}) =
Abstract supertype for all selectors.
Selectors are wrappers that indicate that passed values are not the array indices,
but values to be selected from the dimension index, such as `DateTime` objects for
but values to be selected from the dimension lookup, such as `DateTime` objects for
a `Ti` dimension.
Selectors provided in DimensionalData are:
Expand Down Expand Up @@ -92,18 +92,17 @@ end
"""
At <: IntSelector
At(x, atol, rtol)
At(x; atol=nothing, rtol=nothing)
At(a, b; kw...)
Selector that exactly matches the value on the passed-in dimensions, or throws an error.
For ranges and arrays, every intermediate value must match an existing value -
not just the end points.
`x` can be any value or `Vector` of values.
`x` can be any value to select a single index, or a `Vector` of values to select vector of indices.
If two values `a` and `b` are used, the range between them will be selected.
`atol` and `rtol` are passed to `isapprox`.
For `Number` `rtol` will be set to `Base.rtoldefault`, otherwise `nothing`,
and wont be used.
Keyword `atol` is passed to `isapprox`.
## Example
Expand All @@ -124,7 +123,8 @@ struct At{T,A,R} <: IntSelector{T}
rtol::R
end
At(val; atol=nothing, rtol=nothing) = At(val, atol, rtol)
At() = At(nothing)
At(; kw...) = At(nothing; kw...)
At(a, b; kw...) = At((a, b); kw...)

rebuild(sel::At, val) = At(val, sel.atol, sel.rtol)

Expand All @@ -137,9 +137,22 @@ struct _True end
struct _False end

@inline selectindices(l::Lookup, sel::At; kw...) = at(l, sel; kw...)
@inline selectindices(l::Lookup, sel::At{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...)
@inline selectindices(l::Lookup, sel::At{<:AbstractVector}; kw...) =
_selectvec(l, sel; kw...)
@inline selectindices(l::Lookup, sel::At{<:Tuple{<:Any,<:Any}}; kw...) =
_selecttuple(l, sel; kw...)
# Handle lookups of Tuple
@inline selectindices(l::Lookup{<:Tuple}, sel::At{<:Tuple}; kw...) = at(l, sel; kw...)
@inline selectindices(l::Lookup{<:Tuple}, sel::At{<:Tuple{<:Any,<:Any}}; kw...) =
at(l, sel; kw...)
@inline selectindices(l::Lookup{<:Tuple}, sel::At{<:Tuple{<:Tuple,<:Tuple}}; kw...) =
_selecttuple(l, sel; kw...)

@inline _selectvec(l, sel; kw...) = [selectindices(l, rebuild(sel, v); kw...) for v in val(sel)]
@inline function _selecttuple(l, sel; kw...)
v1, v2 = _maybeflipbounds(l, val(sel))
selectindices(l, rebuild(sel, v1); kw...):selectindices(l, rebuild(sel, v2); kw...)
end

function at(lookup::AbstractCyclic{Cycling}, sel::At; kw...)
cycled_sel = rebuild(sel, cycle_val(lookup, val(sel)))
Expand Down Expand Up @@ -237,19 +250,24 @@ end

_selnotfound_or_nothing(err::_True, lookup, selval) = _selnotfound(lookup, selval)
_selnotfound_or_nothing(err::_False, lookup, selval) = nothing
@noinline _selnotfound(lookup, selval) = throw(ArgumentError("$selval for not found in $lookup"))
@noinline _selnotfound(l, selval) = throw(SelectorError(l, selval))

"""
Near <: IntSelector
Near(x)
Near(a, b)
Selector that selects the nearest index to `x`.
With [`Points`](@ref) this is simply the index values nearest to the `x`,
With [`Points`](@ref) this is simply the lookup values nearest to the `x`,
however with [`Intervals`](@ref) it is the interval _center_ nearest to `x`.
This will be offset from the index value for `Start` and [`End`](@ref) locus.
`x` can be any value to select a single index, or a `Vector` of values to select vector of indices.
If two values `a` and `b` are used, the range between the nearsest value
to each of them will be selected.
## Example
```jldoctest
Expand All @@ -266,9 +284,14 @@ struct Near{T} <: IntSelector{T}
val::T
end
Near() = Near(nothing)
Near(a, b) = Near((a, b))

@inline selectindices(l::Lookup, sel::Near; kw...) = near(l, sel)
@inline selectindices(l::Lookup, sel::Near{<:AbstractVector}; kw...) = _selectvec(l, sel)
@inline selectindices(l::Lookup, sel::Near; kw...) = near(l, sel; kw...)
@inline selectindices(l::Lookup, sel::Near{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...)
@inline selectindices(l::Lookup, sel::Near{<:Tuple}; kw...) = _selecttuple(l, sel; kw...)
# Handle lookups of Tuple
@inline selectindices(l::Lookup{<:Tuple}, sel::Near{<:Tuple}; kw...) = near(l, sel; kw...)
@inline selectindices(l::Lookup{<:Tuple}, sel::Near{<:Tuple{<:Tuple,<:Tuple}}; kw...) = _selecttuple(l, sel; kw...)

Base.show(io::IO, x::Near) = print(io, "Near(", val(x), ")")

Expand Down Expand Up @@ -335,13 +358,18 @@ _adjust_locus(locus::End, v::Dates.Date, lookup) = v + (v + abs(step(lookup)) -
Contains <: IntSelector
Contains(x)
Contains(a, b)
Selector that selects the interval the value is contained by. If the
interval is not present in the index, an error will be thrown.
interval is not present in the lookup, an error will be thrown.
Can only be used for [`Intervals`](@ref) or [`Categorical`](@ref).
For [`Categorical`](@ref) it falls back to using [`At`](@ref).
`Contains` should not be confused with `Base.contains` - use `Where(contains(x))` to check for if values are contain in categorical values like strings.
`Contains` should not be confused with `Base.contains` - use `Where(contains(x))`
to check for if values are contain in categorical values like strings.
`x` can be any value to select a single index, or a `Vector` of values to select vector of indices.
If two values `a` and `b` are used, the range between them will be selected.
## Example
Expand All @@ -360,10 +388,15 @@ struct Contains{T} <: IntSelector{T}
val::T
end
Contains() = Contains(nothing)
Contains(a, b) = Contains((a, b))

# Filter based on sampling and selector -----------------
@inline selectindices(l::Lookup, sel::Contains; kw...) = contains(l, sel; kw...)
@inline selectindices(l::Lookup, sel::Contains{<:AbstractVector}; kw...) = _selectvec(l, sel; kw...)
@inline selectindices(l::Lookup, sel::Contains{<:Tuple}; kw...) = _selecttuple(l, sel; kw...)
# Handle lookups of Tuple
@inline selectindices(l::Lookup{<:Tuple}, sel::Contains{<:Tuple}; kw...) = contains(l, sel; kw...)
@inline selectindices(l::Lookup{<:Tuple}, sel::Contains{<:Tuple{<:Tuple,<:Tuple}}; kw...) = _selecttuple(l, sel; kw...)

Base.show(io::IO, x::Contains) = print(io, "Contains(", val(x), ")")

Expand Down
8 changes: 4 additions & 4 deletions src/array/indexing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ for f in (:getindex, :view, :dotview)
@propagate_inbounds Base.$f(A::AbstractDimArray, i1::SelectorOrStandard, i2::SelectorOrStandard, I::SelectorOrStandard...) =
Base.$f(A, dims2indices(A, (i1, i2, I...))...)

@propagate_inbounds Base.$f(A::AbstractDimVector, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) =
@propagate_inbounds Base.$f(A::AbstractDimVector, extent::Union{Extents.Extent,Near{<:Extents.Extent},Touches{<:Extents.Extent}}) =
Base.$f(A, dims2indices(A, extent)...)
@propagate_inbounds Base.$f(A::AbstractDimArray, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) =
@propagate_inbounds Base.$f(A::AbstractDimArray, extent::Union{Extents.Extent,Near{<:Extents.Extent},Touches{<:Extents.Extent}}) =
Base.$f(A, dims2indices(A, extent)...)
@propagate_inbounds Base.$f(A::AbstractBasicDimVector, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) =
@propagate_inbounds Base.$f(A::AbstractBasicDimVector, extent::Union{Extents.Extent,Near{<:Extents.Extent},Touches{<:Extents.Extent}}) =
Base.$f(A, dims2indices(A, extent)...)
@propagate_inbounds Base.$f(A::AbstractBasicDimArray, extent::Union{Extents.Extent,Touches{<:Extents.Extent}}) =
@propagate_inbounds Base.$f(A::AbstractBasicDimArray, extent::Union{Extents.Extent,Near{<:Extents.Extent},Touches{<:Extents.Extent}}) =
Base.$f(A, dims2indices(A, extent)...)
# All Dimension indexing modes combined
@propagate_inbounds Base.$f(A::AbstractBasicDimArray; kw...) =
Expand Down
9 changes: 5 additions & 4 deletions test/dimindices.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using DimensionalData, Test, Dates
using DimensionalData.Lookups, DimensionalData.Dimensions
using DimensionalData.Lookups: SelectorError

A = zeros(X(4.0:7.0), Y(10.0:12.0))

Expand Down Expand Up @@ -83,7 +84,7 @@ end
@test dsa[4, 3] == (X(At(7.0; atol=0.3)), Y(At(12.0, atol=0.3)))
@test broadcast(ds -> B[ds...] + 2, dsa) == fill(2.0, 4, 3)
@test broadcast(ds -> B[ds...], dsa[X(At(7.0))]) == [0.0 for i in 1:3]
@test_throws ArgumentError broadcast(ds -> B[ds...] + 2, ds) == fill(2.0, 4, 3)
@test_throws SelectorError broadcast(ds -> B[ds...] + 2, ds) == fill(2.0, 4, 3)
@test_throws ArgumentError DimSelectors(zeros(2, 2))
@test_throws ArgumentError DimSelectors(nothing)
end
Expand All @@ -99,13 +100,13 @@ end
@test @inferred broadcast(ds -> C[ds...] + 2, dsa2) == fill(2.0, 4, 3)
@test @inferred broadcast(ds -> C[ds...], dsa2[X(At(7.0))]) == [0.0 for i in 1:3]
# without atol it errors
@test_throws ArgumentError broadcast(ds -> C[ds...] + 2, ds) == fill(2.0, 4, 3)
@test_throws SelectorError broadcast(ds -> C[ds...] + 2, ds) == fill(2.0, 4, 3)
# no dims errors
@test_throws ArgumentError DimSelectors(zeros(2, 2))
@test_throws ArgumentError DimSelectors(nothing)
# Only Y can handle errors > 0.1
D = zeros(X(4.15:7.15), Y(10.15:12.15))
@test_throws ArgumentError broadcast(ds -> D[ds...] + 2, dsa2) == fill(2.0, 4, 3)
@test_throws SelectorError broadcast(ds -> D[ds...] + 2, dsa2) == fill(2.0, 4, 3)
end

@testset "mixed selectors" begin
Expand All @@ -119,7 +120,7 @@ end
@test @inferred broadcast(ds -> C[ds...] + 2, dsa2) == fill(2.0, 4, 3)
@test @inferred broadcast(ds -> C[ds...], dsa2[X(At(7.0))]) == [0.0 for i in 1:3]
# without atol it errors
@test_throws ArgumentError broadcast(ds -> C[ds...] + 2, ds) == fill(2.0, 4, 3)
@test_throws SelectorError broadcast(ds -> C[ds...] + 2, ds) == fill(2.0, 4, 3)
# no dims errors
@test_throws ArgumentError DimSelectors(zeros(2, 2))
@test_throws ArgumentError DimSelectors(nothing)
Expand Down
5 changes: 3 additions & 2 deletions test/plotrecipes.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DimensionalData, Test, Plots, Dates, StatsPlots, Unitful
using DimensionalData, Test, Dates
using Plots, StatsPlots, Unitful
import Distributions

using DimensionalData: Metadata, NoMetadata, ForwardOrdered, ReverseOrdered, Unordered,
Expand Down Expand Up @@ -269,7 +270,7 @@ using ColorTypes
fig, ax, _ = M.heatmap(A2ab; y=:b)
M.heatmap!(ax, A2ab; y=:b)
fig, ax, _ = M.series(A2ab)
# M.series!(ax, A2ab)
M.series!(ax, A2ab)
fig, ax, _ = M.boxplot(A2ab)
M.boxplot!(ax, A2ab)
fig, ax, _ = M.violin(A2ab)
Expand Down
23 changes: 17 additions & 6 deletions test/selector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ A = DimArray([1 2 3; 4 5 6], dims_)
@test at(startrev, At(29.9; atol=0.2)) == 1
@test at(startfwd, At(30.1; atol=0.2)) == 20
@test at(startrev, At(30.1; atol=0.2)) == 1
@test_throws ArgumentError at(startrev, At(0.0; atol=0.2))
@test_throws SelectorError at(startrev, At(0.0; atol=0.2))
@test at(startrev, At(0.0; atol=0.2); err=Lookups._False()) == nothing
end

Expand Down Expand Up @@ -951,7 +951,7 @@ end

@testset "selectors with dim wrappers" begin
@test @inferred da[Y(At([10, 30])), Ti(At([1u"s", 4u"s"]))] == [1 4; 9 12]
@test_throws ArgumentError da[Y(At([9, 30])), Ti(At([1u"s", 4u"s"]))]
@test_throws SelectorError da[Y(At([9, 30])), Ti(At([1u"s", 4u"s"]))]
@test @inferred view(da, Y(At(20)), Ti(At((3:4)u"s"))) == [7, 8]
@test @inferred view(da, Y(Near(17)), Ti(Near([1.5u"s", 3.1u"s"]))) == [6, 7]
@test @inferred view(da, Y(Between(9, 21)), Ti(At((3:4)u"s"))) == [3 4; 7 8]
Expand Down Expand Up @@ -1141,14 +1141,16 @@ end
Ti(Sampled((1:4)u"s"; sampling=Intervals()))))

@testset "Extent indexing" begin
# THese should be the same because da is the maximum size
# These should be the same because da is the maximum size
# we can index with `Touches`
@test da[Touches(Extents.extent(da))] == da[Extents.extent(da)] == da
@test da[Near(Extents.extent(da))] == da[Touches(Extents.extent(da))] == da[Extents.extent(da)] == da
rda = reverse(da; dims=Y)
@test rda[Near(Extents.extent(rda))] == rda[Touches(Extents.extent(rda))] == rda[Extents.extent(rda)] == rda
end

@testset "with dim wrappers" begin
@test @inferred da[Y(At([10, 30])), Ti(At([1u"s", 4u"s"]))] == [1 4; 9 12]
@test_throws ArgumentError da[Y(At([9, 30])), Ti(At([1u"s", 4u"s"]))]
@test_throws SelectorError da[Y(At([9, 30])), Ti(At([1u"s", 4u"s"]))]
@test @inferred view(da, Y(At(20)), Ti(At((3:4)u"s"))) == [7, 8]
@test @inferred view(da, Y(Contains(17)), Ti(Contains([1.9u"s", 3.1u"s"]))) == [5, 7]
@test @inferred view(da, Y(Between(4, 26)), Ti(At((3:4)u"s"))) == [3 4; 7 8]
Expand Down Expand Up @@ -1244,7 +1246,7 @@ end

@testset "with dim wrappers" begin
@test @inferred da[Y(At([10, 30])), Ti(At([1u"s", 4u"s"]))] == [1 4; 9 12]
@test_throws ArgumentError da[Y(At([9, 30])), Ti(At([1u"s", 4u"s"]))]
@test_throws SelectorError da[Y(At([9, 30])), Ti(At([1u"s", 4u"s"]))]
@test @inferred view(da, Y(At(20)), Ti(At((3:4)u"s"))) == [7, 8]
@test @inferred view(da, Y(Contains(17)), Ti(Contains([1.4u"s", 3.1u"s"]))) == [5, 7]
end
Expand Down Expand Up @@ -1427,6 +1429,15 @@ end
@test selectindices(A[X(1)], Contains(10); err=Lookups._False()) == nothing
end

@testset "selectindices with Tuple" begin
@test selectindices(lookup(A, Y), At(6, 7)) == 2:3
@test_throws SelectorError selectindices(lookup(A, Y), At(5.3, 8))
@test selectindices(lookup(A, Y), At(5.1, 7.1; atol=0.1)) == 1:3
@test selectindices(lookup(A, Y), Near(5.3, 8)) == 1:3
@test selectindices(lookup(A, Y), Contains(4.7, 6.1)) == 1:2
@test_throws SelectorError selectindices(lookup(A, Y), Contains(5.3, 8)) == 1:3
end

@testset "hasselection" begin
@test hasselection(A, X(At(20)))
@test hasselection(dims(A, X), X(At(20)))
Expand Down
Loading