Skip to content

Commit

Permalink
Overhaul DimTable (#536)
Browse files Browse the repository at this point in the history
* Add initial implementation of `mergedims`

* Use values instead of layers

* Simplify base method

* Perform all checks in main method

* Simplify mergedims for arrays

* Handle case where different numbers of dims provided

* Add mergedims to docs

* Implemented WideDimTable

* Removed Dead Code

* mergedims now allows selection of specific dimensions

* Dropped MergedDimColumn

* cleaned up code

* More cleanup

* Added comparedims when constructing a DimTable from multiple AbstractDimArrays

* mergedims can now accept dimensions that are not present in the stack/array

* Implemented unmergedims

* Removed unnecessary argument from undergedims

* DimTable now preserves name of AbstractDimArray if present.

* Added parent field to DimTable

* Passed test cases

* Added test cases and updated docs

* Updated docs for DimTable

* Updated docs and test cases

* Removed Any[]

---------

Co-authored-by: Seth Axen <seth@sethaxen.com>
  • Loading branch information
JoshuaBillson and sethaxen authored Sep 19, 2023
1 parent f9e45af commit abfbf2d
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 53 deletions.
2 changes: 2 additions & 0 deletions docs/src/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ rebuild
modify
broadcast_dims
broadcast_dims!
mergedims
unmergedims
reorder
Base.cat
Base.map
Expand Down
2 changes: 1 addition & 1 deletion src/DimensionalData.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export dims, refdims, metadata, name, lookup, bounds
export dimnum, hasdim, hasselection, otherdims

# utils
export set, rebuild, reorder, modify, broadcast_dims, broadcast_dims!
export set, rebuild, reorder, modify, broadcast_dims, broadcast_dims!, mergedims, unmergedims

const DD = DimensionalData

Expand Down
1 change: 1 addition & 0 deletions src/Dimensions/primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ or are at least rotations/transformations of the same type.
"""
@inline dimsmatch(dims, query) = dimsmatch(<:, dims, query)
@inline function dimsmatch(f::Function, dims::Tuple, query::Tuple)
length(dims) == length(query) || return false
all(map((d, l) -> dimsmatch(f, d, l), dims, query))
end
@inline dimsmatch(f::Function, dim, query) = dimsmatch(f, typeof(dim), typeof(query))
Expand Down
117 changes: 117 additions & 0 deletions src/array/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -576,3 +576,120 @@ end
# Thed default constructor is DimArray
dimconstructor(dims::DimTuple) = dimconstructor(tail(dims))
dimconstructor(dims::Tuple{}) = DimArray

"""
mergedims(old_dims => new_dim) => Dimension
Return a dimension `new_dim` whose indices are a [`MergedLookup`](@ref) of the indices of
`old_dims`.
"""
function mergedims((old_dims, new_dim)::Pair)
data = vec(DimPoints(_astuple(old_dims)))
return rebuild(basedims(new_dim), MergedLookup(data, old_dims))
end

"""
mergedims(dims, old_dims => new_dim, others::Pair...) => dims_new
If dimensions `old_dims`, `new_dim`, etc. are found in `dims`, then return new `dims_new`
where all dims in `old_dims` have been combined into a single dim `new_dim`.
The returned dimension will keep only the name of `new_dim`. Its coords will be a
[`MergedLookup`](@ref) of the coords of the dims in `old_dims`. New dimensions are always
placed at the end of `dims_new`. `others` contains other dimension pairs to be merged.
# Example
````jldoctest
julia> ds = (X(0:0.1:0.4), Y(10:10:100), Ti([0, 3, 4]));
julia> mergedims(ds, Ti => :time, (X, Y) => :space)
Dim{:time} MergedLookup{Tuple{Int64}} Tuple{Int64}[(0,), (3,), (4,)] Ti,
Dim{:space} MergedLookup{Tuple{Float64, Int64}} Tuple{Float64, Int64}[(0.0, 10), (0.1, 10), …, (0.3, 100), (0.4, 100)] X, Y
````
"""
function mergedims(all_dims, dim_pairs::Pair...)
# filter out dims completely missing
dim_pairs = map(x -> _filter_dims(all_dims, first(x)) => last(x), dim_pairs)
dim_pairs_complete = filter(dim_pairs) do (old_dims,)
dims_present = dims(all_dims, _astuple(old_dims))
isempty(dims_present) && return false
all(hasdim(dims_present, old_dims)) || throw(ArgumentError(
"Not all dimensions $old_dims found in $(map(basetypeof, all_dims))"
))
return true
end
isempty(dim_pairs_complete) && return all_dims
dim_pairs_concrete = map(dim_pairs_complete) do (old_dims, new_dim)
return dims(all_dims, _astuple(old_dims)) => new_dim
end
# throw error if old dim groups overlap
old_dims_tuples = map(first, dim_pairs_concrete)
if !dimsmatch(_cat_tuples(old_dims_tuples...), combinedims(old_dims_tuples...))
throw(ArgumentError("Dimensions to be merged are not all unique"))
end
return _mergedims(all_dims, dim_pairs_concrete...)
end

"""
mergedims(A::AbstractDimArray, dim_pairs::Pair...) => AbstractDimArray
mergedims(A::AbstractDimStack, dim_pairs::Pair...) => AbstractDimStack
Return a new array or stack whose dimensions are the result of [`mergedims(dims(A), dim_pairs)`](@ref).
"""
function mergedims(A::AbstractDimArray, dim_pairs::Pair...)
isempty(dim_pairs) && return A
all_dims = dims(A)
dims_new = mergedims(all_dims, dim_pairs...)
dimsmatch(all_dims, dims_new) && return A
dims_perm = _unmergedims(dims_new, map(last, dim_pairs))
Aperm = PermutedDimsArray(A, dims_perm)
data_merged = reshape(data(Aperm), map(length, dims_new))
rebuild(A, data_merged, dims_new)
end

"""
unmergedims(merged_dims::Tuple{Vararg{Dimension}}) => Tuple{Vararg{Dimension}}
Return the unmerged dimensions from a tuple of merged dimensions. However, the order of the original dimensions are not necessarily preserved.
"""
function unmergedims(merged_dims::Tuple{Vararg{Dimension}})
reduce(map(dims, merged_dims), init=()) do acc, x
x isa Tuple ? (acc..., x...) : (acc..., x)
end
end

"""
unmergedims(A::AbstractDimArray, original_dims) => AbstractDimArray
unmergedims(A::AbstractDimStack, original_dims) => AbstractDimStack
Return a new array or stack whose dimensions are restored to their original prior to calling [`mergedims(A, dim_pairs)`](@ref).
"""
function unmergedims(A::AbstractDimArray, original_dims)
merged_dims = dims(A)
unmerged_dims = unmergedims(merged_dims)
reshaped = reshape(data(A), size(unmerged_dims))
permuted = permutedims(reshaped, dimnum(unmerged_dims, original_dims))
return DimArray(permuted, original_dims)
end

function _mergedims(all_dims, dim_pair::Pair, dim_pairs::Pair...)
old_dims, new_dim = dim_pair
dims_to_merge = dims(all_dims, _astuple(old_dims))
merged_dim = mergedims(dims_to_merge => new_dim)
all_dims_new = (otherdims(all_dims, dims_to_merge)..., merged_dim)
isempty(dim_pairs) && return all_dims_new
return _mergedims(all_dims_new, dim_pairs...)
end

function _unmergedims(all_dims, merged_dims)
_merged_dims = dims(all_dims, merged_dims)
unmerged_dims = map(all_dims) do d
hasdim(_merged_dims, d) || return _astuple(d)
return dims(lookup(d))
end
return _cat_tuples(unmerged_dims...)
end

_unmergedims(all_dims, dim_pairs::Pair...) = _cat_tuples(replace(all_dims, dim_pairs...))

_cat_tuples(tuples...) = mapreduce(_astuple, (x, y) -> (x..., y...), tuples)

_filter_dims(alldims, dims) = filter(dim -> hasdim(alldims, dim), dims)
11 changes: 11 additions & 0 deletions src/stack/stack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ for func in (:index, :lookup, :metadata, :sampling, :span, :bounds, :locus, :ord
@eval ($func)(s::AbstractDimStack, args...) = ($func)(dims(s), args...)
end

function mergedims(ds::AbstractDimStack, dim_pairs::Pair...)
isempty(dim_pairs) && return ds
vals = map(A -> mergedims(A, dim_pairs...), values(ds))
rebuild_from_arrays(ds, vals)
end

function unmergedims(s::AbstractDimStack, original_dims)
return map(A -> unmergedims(A, original_dims), s)
end


"""
DimStack <: AbstractDimStack
Expand Down
Loading

0 comments on commit abfbf2d

Please sign in to comment.