From 43614cca7b2751899d347d7b66a0ab9857945b22 Mon Sep 17 00:00:00 2001 From: Rafael Schouten Date: Fri, 3 Apr 2020 04:26:54 +1100 Subject: [PATCH] add metadata field to DimensionalArray, and test --- src/array.jl | 86 ++++++++++++++++++++++++++++++++++----------------- test/array.jl | 51 ++++++++++++++++++++---------- 2 files changed, 92 insertions(+), 45 deletions(-) diff --git a/src/array.jl b/src/array.jl index 4507a5bab..32cb15ac8 100644 --- a/src/array.jl +++ b/src/array.jl @@ -10,15 +10,38 @@ const StandardIndices = Union{AbstractArray,Colon,Integer} # Interface methods ############################################################ -dims(A::AbDimArray) = A.dims -@inline rebuild(A::AbstractArray, data, dims::Tuple=dims(A), refdims=refdims(A)) = - rebuild(A, data, dims, refdims, name(A)) -# Rebuild for name-updating methods, to avoid having to add dims and refdims -@inline rebuild(A::AbstractArray, data, name::String) = - rebuild(A, data, dims(A), refdims(A), name) +""" + bounds(A::AbstractArray) +Returns a tuple of bounds for each array axis. +""" bounds(A::AbstractArray) = bounds(dims(A)) -bounds(A::AbstractArray, lookup::DimOrDimType) = bounds(dims(A), lookup) +""" + bounds(A::AbstractArray, dims) + +Returns the bounds for the specified dimension(s). +`dims` can be a `Dimension`, a dimension type, or a tuple of either. +""" +bounds(A::AbstractArray, dims::DimOrDimType) = + bounds(DimensionalData.dims(A), dims) + +""" + metadata(A::AbstractArray, dims) + +Returns the bounds for the specified dimension(s). +`dims` can be a `Dimension`, a dimension type, or a tuple of either. +""" +metadata(A::AbstractDimensionalArray, dim) = metadata(dims(A, dim)) +metadata(A::AbstractDimensionalArray, dims::Tuple) = + map(metadata, DimensionalData.dims(A, dims)) + +dims(A::AbDimArray) = A.dims +@inline rebuild(A::AbstractArray, data, dims::Tuple=dims(A), refdims=refdims(A), + name=name(A), metadata=metadata(A)) = + rebuild(A, data, dims, refdims, name, metadata) +# Rebuild for name-updating methods, to avoid having to add dims and refdims +@inline rebuild(A::AbstractArray, data, name::AbstractString) = + rebuild(A, data, dims(A), refdims(A), name, metadata) # Array interface methods ###################################################### @@ -56,8 +79,7 @@ Base.copy!(dst::AbstractArray, src::AbDimArray) = copy!(dst, data(src)) Base.Array(A::AbDimArray) = data(A) # Need to cover a few type signatures to avoid ambiguity with base -# Don't remove these even though they look redundant -Base.similar(A::AbDimArray) = rebuild(A, similar(data(A)), "") +# Don't remove these even though they look redundant Base.similar(A::AbDimArray) = rebuild(A, similar(data(A)), "") Base.similar(A::AbDimArray, ::Type{T}) where T = rebuild(A, similar(data(A), T), "") # If the shape changes, use the wrapped array: Base.similar(A::AbDimArray, ::Type{T}, I::Tuple{Int,Vararg{Int}}) where T = similar(data(A), T, I) @@ -73,12 +95,14 @@ The main subtype of `AbstractDimensionalArray`. Maintains and updates its dimensions through transformations and moves dimensions to `refdims` after reducing operations (like e.g. `mean`). """ -struct DimensionalArray{T,N,D<:Tuple,R<:Tuple,A<:AbstractArray{T,N}} <: AbstractDimensionalArray{T,N,D,A} +struct DimensionalArray{T,N,D<:Tuple,R<:Tuple,A<:AbstractArray{T,N},Na<:AbstractString,Me} <: AbstractDimensionalArray{T,N,D,A} data::A dims::D refdims::R - name::String - function DimensionalArray(data::A, dims::D, refdims::R, name::String) where A <:AbstractArray{T,N} where D where R where T where N + name::Na + metadata::Me + function DimensionalArray(data::A, dims::D, refdims::R, name::Na, metadata::Me + ) where {D,R,A<:AbstractArray{T,N},Na,Me} where {T,N} map(dims, size(data)) do d, s if !(val(d) isa Colon) && length(d) != s throw(DimensionMismatch( @@ -86,25 +110,29 @@ struct DimensionalArray{T,N,D<:Tuple,R<:Tuple,A<:AbstractArray{T,N}} <: Abstract )) end end - new{T,N,D,R,A}(data, dims, refdims, name) + new{T,N,D,R,A,Na,Me}(data, dims, refdims, name, metadata) end end """ - DimensionalArray(data, dims::Tuple [, name::String]; refdims=()) -Constructor with optional `name` and `refdims`. -The `name` is propagated across most sensible operations, even reducing ones. + DimensionalArray(data, dims::Tuple [, name::String]; refdims=(), metadata=nothing) + +Constructor with optional `name`, and keyword `refdims` and `metadata`. Example: ```julia using Dates, DimensionalData -timespan = DateTime(2001):Month(1):DateTime(2001,12) -A = DimensionalArray(rand(12,10), (Ti(timespan), X(10:10:100))) -A[X<|Near([12, 35]), Ti<|At(DateTime(2001,5))] -A[Near(DateTime(2001, 5, 4)), Between(20, 50)] + +ti = (Ti(DateTime(2001):Month(1):DateTime(2001,12)), +x = X(10:10:100)) +A = DimensionalArray(rand(12,10), (ti, x), "example") + +julia> A[X(Near([12, 35])), Ti(At(DateTime(2001,5)))]; + +julia> A[Near(DateTime(2001, 5, 4)), Between(20, 50)]; ``` """ -DimensionalArray(A::AbstractArray, dims, name::String = ""; refdims=()) = - DimensionalArray(A, formatdims(A, _to_tuple(dims)), refdims, name) +DimensionalArray(A::AbstractArray, dims, name::String=""; refdims=(), metadata=nothing) = + DimensionalArray(A, formatdims(A, _to_tuple(dims)), refdims, name, metadata) _to_tuple(t::T where T <: Tuple) = t _to_tuple(t) = tuple(t) @@ -112,25 +140,25 @@ _to_tuple(t) = tuple(t) refdims(A::DimensionalArray) = A.refdims data(A::DimensionalArray) = A.data name(A::DimensionalArray) = A.name +metadata(A::DimensionalArray) = A.metadata label(A::DimensionalArray) = name(A) -# DimensionalArray interface +# AbstractDimensionalArray interface @inline rebuild(A::DimensionalArray, data::AbstractArray, dims::Tuple, - refdims::Tuple, name::String) = - DimensionalArray(data, dims, refdims, name) + refdims::Tuple, name::AbstractString, metadata) = + DimensionalArray(data, dims, refdims, name, metadata) # Array interface (AbstractDimensionalArray takes care of everything else) Base.@propagate_inbounds Base.setindex!(A::DimensionalArray, x, I::Vararg{StandardIndices}) = setindex!(data(A), x, I...) -# Applying function across dimension should give an Array """ DimensionalArray(f::Function, dim::Dimension [, name]) Apply function `f` across the values of the dimension `dim` -(using broadcasting), and return the result as a dimensional array with +(using `broadcast`), and return the result as a dimensional array with the given dimension. Optionally provide a name for the result. """ -DimensionalArray(f::Function, dim::Dimension, name=string(nameof(f))*"("*name(dim)*")") = -DimensionalArray(f.(val(dim)), (dim,), name) +DimensionalArray(f::Function, dim::Dimension, name=string(nameof(f), "(", name(dim), ")")) = + DimensionalArray(f.(val(dim)), (dim,), name) diff --git a/test/array.jl b/test/array.jl index f8ca26fd8..0d0119f2d 100644 --- a/test/array.jl +++ b/test/array.jl @@ -2,8 +2,9 @@ using DimensionalData, Test, Unitful, OffsetArrays, SparseArrays using DimensionalData: Start a = [1 2; 3 4] -dimz = (X((143.0, 145.0)), Y((-38.0, -36.0))) -da = DimensionalArray(a, dimz) +dimz = (X((143.0, 145.0); metadata=Dict(:meta => "X")), + Y((-38.0, -36.0); metadata=Dict(:meta => "Y"))) +da = DimensionalArray(a, dimz, "test"; metadata=Dict(:meta => "da")) @testset "getindex for single integers returns values" begin @test da[X(1), Y(2)] == 2 @@ -14,8 +15,12 @@ end a = da[X(1:2), Y(1)] @test a == [1, 3] @test typeof(a) <: DimensionalArray{Int,1} - @test dims(a) == (X(LinRange(143.0, 145.0, 2); mode=Sampled(span=Regular(2.0))),) - @test refdims(a) == (Y(-38.0; mode=Sampled(span=Regular(2.0))),) + @test dims(a) == (X(LinRange(143.0, 145.0, 2); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "X")),) + @test refdims(a) == (Y(-38.0; mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "Y")),) + @test name(a) == "test" + @test metadata(a) == Dict(:meta => "da") + @test metadata(a, X) == Dict(:meta => "X") @test bounds(a) == ((143.0, 145.0),) @test bounds(a, X) == (143.0, 145.0) # @test locus(mode(dims(da, X))) == Start() @@ -24,8 +29,11 @@ end @test a == [1, 2] @test typeof(a) <: DimensionalArray{Int,1} @test typeof(data(a)) <: Array{Int,1} - @test dims(a) == (Y(LinRange(-38.0, -36.0, 2); mode=Sampled(span=Regular(2.0))),) - @test refdims(a) == (X(143.0; mode=Sampled(span=Regular(2.0))),) + @test dims(a) == (Y(LinRange(-38.0, -36.0, 2); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "Y")),) + @test refdims(a) == (X(143.0; mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "X")),) + @test name(a) == "test" + @test metadata(a) == Dict(:meta => "da") @test bounds(a) == ((-38.0, -36.0),) @test bounds(a, Y()) == (-38.0, -36.0) @@ -34,9 +42,12 @@ end @test typeof(a) <: DimensionalArray{Int,2} @test typeof(data(a)) <: Array{Int,2} @test typeof(dims(a)) <: Tuple{<:X,<:Y} - @test dims(a) == (X(LinRange(143.0, 145.0, 2); mode=Sampled(span=Regular(2.0))), - Y(LinRange(-38.0, -36.0, 2); mode=Sampled(span=Regular(2.0)))) + @test dims(a) == (X(LinRange(143.0, 145.0, 2); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "X")), + Y(LinRange(-38.0, -36.0, 2); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "Y"))) @test refdims(a) == () + @test name(a) == "test" @test bounds(a) == ((143.0, 145.0), (-38.0, -36.0)) @test bounds(a, X) == (143.0, 145.0) end @@ -48,8 +59,10 @@ end @test typeof(data(v)) <:SubArray{Int,0} @test typeof(dims(v)) == Tuple{} @test dims(v) == () - @test refdims(v) == (X(143.0; mode=Sampled(span=Regular(2.0))), - Y(-38.0; mode=Sampled(span=Regular(2.0)))) + @test refdims(v) == (X(143.0; mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "X")), + Y(-38.0; mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "Y"))) + @test name(v) == "test" + @test metadata(v) == Dict(:meta => "da") @test bounds(v) == () v = view(da, Y(1), X(1:2)) @@ -57,8 +70,11 @@ end @test typeof(v) <: DimensionalArray{Int,1} @test typeof(data(v)) <: SubArray{Int,1} @test typeof(dims(v)) <: Tuple{<:X} - @test dims(v) == (X(LinRange(143.0, 145.0, 2); mode=Sampled(span=Regular(2.0))),) - @test refdims(v) == (Y(-38.0; mode=Sampled(span=Regular(2.0))),) + @test dims(v) == (X(LinRange(143.0, 145.0, 2); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "X")),) + @test refdims(v) == (Y(-38.0; mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "Y")),) + @test name(v) == "test" + @test metadata(v) == Dict(:meta => "da") @test bounds(v) == ((143.0, 145.0),) v = view(da, Y(1:2), X(1:1)) @@ -66,16 +82,19 @@ end @test typeof(v) <: DimensionalArray{Int,2} @test typeof(data(v)) <: SubArray{Int,2} @test typeof(dims(v)) <: Tuple{<:X,<:Y} - @test dims(v) == (X(LinRange(143.0, 143.0, 1); mode=Sampled(span=Regular(2.0))), - Y(LinRange(-38, -36, 2); mode=Sampled(span=Regular(2.0)))) + @test dims(v) == (X(LinRange(143.0, 143.0, 1); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "X")), + Y(LinRange(-38, -36, 2); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "Y"))) @test bounds(v) == ((143.0, 143.0), (-38.0, -36.0)) v = view(da, Y(Base.OneTo(2)), X(1)) @test v == [1, 2] @test typeof(data(v)) <: SubArray{Int,1} @test typeof(dims(v)) <: Tuple{<:Y} - @test dims(v) == (Y(LinRange(-38.0, -36.0, 2); mode=Sampled(span=Regular(2.0))),) - @test refdims(v) == (X(143.0; mode=Sampled(span=Regular(2.0))),) + @test dims(v) == (Y(LinRange(-38.0, -36.0, 2); + mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "Y")),) + @test refdims(v) == (X(143.0; mode=Sampled(span=Regular(2.0)), metadata=Dict(:meta => "X")),) @test bounds(v) == ((-38.0, -36.0),) end