diff --git a/Project.toml b/Project.toml index 18c0d8b..a0fe3b3 100644 --- a/Project.toml +++ b/Project.toml @@ -17,6 +17,26 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +[weakdeps] +AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c" +ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +CovarianceEstimation = "587fd27a-f159-11e8-2dae-1979310e6154" +InvertedIndices = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +LazyStack = "1fad7336-0346-5a1a-a56f-a06ba010965b" +OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" + +[extensions] +AbstractFFTsExt = "AbstractFFTs" +ChainRulesCoreExt = "ChainRulesCore" +CovarianceEstimationExt = "CovarianceEstimation" +InvertedIndicesExt = "InvertedIndices" +LazyStackExt = "LazyStack" +OffsetArraysExt = "OffsetArrays" +StatisticsExt = "Statistics" +StatsBaseExt = "StatsBase" + [compat] AbstractFFTs = "0.5, 1.0" BenchmarkTools = "0.5, 1.0" @@ -36,15 +56,22 @@ julia = "1.6" [extras] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" +CovarianceEstimation = "587fd27a-f159-11e8-2dae-1979310e6154" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" +InvertedIndices = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +LazyStack = "1fad7336-0346-5a1a-a56f-a06ba010965b" NamedArrays = "86f7a689-2022-50b4-a561-43c23ac3c673" +OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" UniqueVectors = "2fbcfb34-fd0c-5fbb-b5d7-e826d8f5b0a9" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["BenchmarkTools", "ChainRulesTestUtils", "DataFrames", "Dates", "FiniteDifferences", "FFTW", "NamedArrays", "Test", "UniqueVectors", "Unitful"] +test = ["BenchmarkTools", "CovarianceEstimation", "ChainRulesCore", "ChainRulesTestUtils", "DataFrames", "Dates", "FiniteDifferences", "FFTW", "InvertedIndices", "LazyStack", "NamedArrays", "OffsetArrays", "Test", "Statistics", "StatsBase", "UniqueVectors", "Unitful"] diff --git a/src/fft.jl b/ext/AbstractFFTsExt.jl similarity index 96% rename from src/fft.jl rename to ext/AbstractFFTsExt.jl index 1be5ce4..02f0dfe 100644 --- a/src/fft.jl +++ b/ext/AbstractFFTsExt.jl @@ -1,3 +1,7 @@ +module AbstractFFTsExt + +using AxisKeys: KeyedArray, NdaKa, axiskeys, keyless, NamedDims +using AbstractFFTs #= Simple support for FFTs using: @@ -7,8 +11,6 @@ Does not (yet) cover plan_fft & friends, because extracting the dimensions from those is tricky =# -using AbstractFFTs - for fun in [:fft, :ifft, :bfft, :rfft] @eval function AbstractFFTs.$fun(A::Union{KeyedArray,NdaKa}, dims = ntuple(+,ndims(A))) numerical_dims = NamedDims.dim(A, dims) @@ -80,3 +82,5 @@ function irfft_un_freq(x, len) s = inv(step(x) * len) range(zero(s), step = s, length = len) end + +end diff --git a/src/chainrules.jl b/ext/ChainRulesCoreExt.jl similarity index 86% rename from src/chainrules.jl rename to ext/ChainRulesCoreExt.jl index 8a2d872..68e7fe4 100644 --- a/src/chainrules.jl +++ b/ext/ChainRulesCoreExt.jl @@ -1,3 +1,6 @@ +module ChainRulesCoreExt + +using AxisKeys: KeyedArray, KaNda, NdaKa, keyless, keyless_unname, axiskeys, named_axiskeys, wrapdims using ChainRulesCore function ChainRulesCore.ProjectTo(x::Union{KaNda, NdaKa}) @@ -19,3 +22,5 @@ function ChainRulesCore.rrule(::typeof(keyless_unname), x) pb(y) = _KeyedArray_pullback(y, ProjectTo(x)) return keyless_unname(x), pb end + +end diff --git a/ext/CovarianceEstimationExt.jl b/ext/CovarianceEstimationExt.jl new file mode 100644 index 0000000..d00ec77 --- /dev/null +++ b/ext/CovarianceEstimationExt.jl @@ -0,0 +1,33 @@ +module CovarianceEstimationExt + +using AxisKeys: KeyedArray, KeyedMatrix, NamedDims, NamedDimsArray, axiskeys, dimnames, keyless_unname, hasnames +using CovarianceEstimation +using CovarianceEstimation: AbstractWeights +using CovarianceEstimation.Statistics + +# Since we get ambiguity errors with specific implementations we need to wrap each supported method +# A better approach might be to add `NamedDims` support to CovarianceEstimators.jl in the future. + +estimators = [ + :SimpleCovariance, + :LinearShrinkage, + :DiagonalUnitVariance, + :DiagonalCommonVariance, + :DiagonalUnequalVariance, + :CommonCovariance, + :PerfectPositiveCorrelation, + :ConstantCorrelation, + :AnalyticalNonlinearShrinkage, +] +for estimator in estimators + @eval function Statistics.cov(ce::$estimator, A::KeyedMatrix, wv::Vararg{AbstractWeights}; dims=1, kwargs...) + d = NamedDims.dim(A, dims) + data = cov(ce, keyless_unname(A), wv...; dims=d, kwargs...) + L1 = dimnames(A, 3 - d) + data2 = hasnames(A) ? NamedDimsArray(data, (L1, L1)) : data + K1 = axiskeys(A, 3 - d) + KeyedArray(data2, (copy(K1), copy(K1))) + end +end + +end diff --git a/ext/InvertedIndicesExt.jl b/ext/InvertedIndicesExt.jl new file mode 100644 index 0000000..9d30e2d --- /dev/null +++ b/ext/InvertedIndicesExt.jl @@ -0,0 +1,10 @@ +module InvertedIndicesExt + +using AxisKeys +using InvertedIndices + +# needs only Base.to_indices in struct.jl to work, +# plus this to work when used in round brackets: +AxisKeys.findindex(not::InvertedIndex, r::AbstractVector) = Base.unalias(r, not) + +end diff --git a/src/stack.jl b/ext/LazyStackExt.jl similarity index 95% rename from src/stack.jl rename to ext/LazyStackExt.jl index 1849591..4c202dc 100644 --- a/src/stack.jl +++ b/ext/LazyStackExt.jl @@ -1,4 +1,6 @@ +module LazyStackExt +using AxisKeys: KeyedArray, NamedDims, NamedDimsArray, axiskeys, hasnames, dimnames, keys_or_axes import LazyStack # for stack_iter @@ -57,3 +59,5 @@ function LazyStack.getnames(xs::AbstractArray{<:KeyedArray{T,N,IT}}) where {T,N, out_names = hasnames(xs) ? dimnames(xs) : NamedDims.dimnames(xs) (NamedDims.dimnames(IT)..., out_names...) end + +end diff --git a/ext/OffsetArraysExt.jl b/ext/OffsetArraysExt.jl new file mode 100644 index 0000000..56eec09 --- /dev/null +++ b/ext/OffsetArraysExt.jl @@ -0,0 +1,9 @@ +module OffsetArraysExt + +using AxisKeys +using OffsetArrays + +AxisKeys.no_offset(x::OffsetArray) = parent(x) +AxisKeys.shorttype(r::OffsetArray) = "OffsetArray(::" * shorttype(parent(r)) * ",...)" + +end diff --git a/ext/StatisticsExt.jl b/ext/StatisticsExt.jl new file mode 100644 index 0000000..c85e15f --- /dev/null +++ b/ext/StatisticsExt.jl @@ -0,0 +1,37 @@ +module StatisticsExt + +using AxisKeys: KeyedArray, KeyedMatrix, NamedDims, axiskeys +using Statistics + +for fun in [:mean, :std, :var] # These don't use mapreduce, but could perhaps be handled better? + @eval function Statistics.$fun(A::KeyedArray; dims=:, kwargs...) + dims === Colon() && return $fun(parent(A); kwargs...) + numerical_dims = NamedDims.dim(A, dims) + data = $fun(parent(A); dims=numerical_dims, kwargs...) + new_keys = ntuple(d -> d in numerical_dims ? Base.OneTo(1) : axiskeys(A,d), ndims(A)) + return KeyedArray(data, map(copy, new_keys))#, copy(A.meta)) + end +end + +# Handle function interface for `mean` only +if VERSION >= v"1.3" + @eval function Statistics.mean(f, A::KeyedArray; dims=:, kwargs...) + dims === Colon() && return mean(f, parent(A); kwargs...) + numerical_dims = NamedDims.dim(A, dims) + data = mean(f, parent(A); dims=numerical_dims, kwargs...) + new_keys = ntuple(d -> d in numerical_dims ? Base.OneTo(1) : axiskeys(A,d), ndims(A)) + return KeyedArray(data, map(copy, new_keys))#, copy(A.meta)) + end +end + +for fun in [:cov, :cor] # Returned the axes work are different for cov and cor + @eval function Statistics.$fun(A::KeyedMatrix; dims=1, kwargs...) + numerical_dim = NamedDims.dim(A, dims) + data = $fun(parent(A); dims=numerical_dim, kwargs...) + # Use same remaining axis for both dimensions of data + rem_key = axiskeys(A, 3-numerical_dim) + KeyedArray(data, (copy(rem_key), copy(rem_key))) + end +end + +end diff --git a/src/statsbase.jl b/ext/StatsBaseExt.jl similarity index 73% rename from src/statsbase.jl rename to ext/StatsBaseExt.jl index be2bac8..25a6d9e 100644 --- a/src/statsbase.jl +++ b/ext/StatsBaseExt.jl @@ -1,5 +1,8 @@ +module StatsBaseExt +using AxisKeys: KeyedArray, KeyedMatrix, NamedDims, NamedDimsArray, axiskeys, dimnames, keyless_unname, hasnames using StatsBase +using StatsBase.Statistics # Support some of the weighted statistics function in StatsBase # NOTES: @@ -55,28 +58,4 @@ for fun in (:std, :var, :cov) ) end -# Since we get ambiguity errors with specific implementations we need to wrap each supported method -# A better approach might be to add `NamedDims` support to CovarianceEstimators.jl in the future. -using CovarianceEstimation - -estimators = [ - :SimpleCovariance, - :LinearShrinkage, - :DiagonalUnitVariance, - :DiagonalCommonVariance, - :DiagonalUnequalVariance, - :CommonCovariance, - :PerfectPositiveCorrelation, - :ConstantCorrelation, - :AnalyticalNonlinearShrinkage, -] -for estimator in estimators - @eval function Statistics.cov(ce::$estimator, A::KeyedMatrix, wv::Vararg{AbstractWeights}; dims=1, kwargs...) - d = NamedDims.dim(A, dims) - data = cov(ce, keyless_unname(A), wv...; dims=d, kwargs...) - L1 = dimnames(A, 3 - d) - data2 = hasnames(A) ? NamedDimsArray(data, (L1, L1)) : data - K1 = axiskeys(A, 3 - d) - KeyedArray(data2, (copy(K1), copy(K1))) - end end diff --git a/src/AxisKeys.jl b/src/AxisKeys.jl index adeabcb..1914775 100644 --- a/src/AxisKeys.jl +++ b/src/AxisKeys.jl @@ -26,11 +26,15 @@ include("show.jl") include("tables.jl") # Tables.jl -include("stack.jl") # LazyStack.jl - -include("fft.jl") # AbstractFFTs.jl - -include("statsbase.jl") # StatsBase.jl +if !isdefined(Base, :get_extension) + include("../ext/AbstractFFTsExt.jl") + include("../ext/ChainRulesCoreExt.jl") + include("../ext/CovarianceEstimationExt.jl") + include("../ext/InvertedIndicesExt.jl") + include("../ext/LazyStackExt.jl") + include("../ext/OffsetArraysExt.jl") + include("../ext/StatisticsExt.jl") + include("../ext/StatsBaseExt.jl") +end -include("chainrules.jl") end diff --git a/src/functions.jl b/src/functions.jl index f1758f8..a4b10f5 100644 --- a/src/functions.jl +++ b/src/functions.jl @@ -43,38 +43,6 @@ function Base.mapreduce(f, op, A::KeyedArray; dims=:, kwargs...) # sum, prod, et return KeyedArray(data, map(copy, new_keys))#, copy(A.meta)) end -using Statistics -for fun in [:mean, :std, :var] # These don't use mapreduce, but could perhaps be handled better? - @eval function Statistics.$fun(A::KeyedArray; dims=:, kwargs...) - dims === Colon() && return $fun(parent(A); kwargs...) - numerical_dims = NamedDims.dim(A, dims) - data = $fun(parent(A); dims=numerical_dims, kwargs...) - new_keys = ntuple(d -> d in numerical_dims ? Base.OneTo(1) : axiskeys(A,d), ndims(A)) - return KeyedArray(data, map(copy, new_keys))#, copy(A.meta)) - end -end - -# Handle function interface for `mean` only -if VERSION >= v"1.3" - @eval function Statistics.mean(f, A::KeyedArray; dims=:, kwargs...) - dims === Colon() && return mean(f, parent(A); kwargs...) - numerical_dims = NamedDims.dim(A, dims) - data = mean(f, parent(A); dims=numerical_dims, kwargs...) - new_keys = ntuple(d -> d in numerical_dims ? Base.OneTo(1) : axiskeys(A,d), ndims(A)) - return KeyedArray(data, map(copy, new_keys))#, copy(A.meta)) - end -end - -for fun in [:cov, :cor] # Returned the axes work are different for cov and cor - @eval function Statistics.$fun(A::KeyedMatrix; dims=1, kwargs...) - numerical_dim = NamedDims.dim(A, dims) - data = $fun(parent(A); dims=numerical_dim, kwargs...) - # Use same remaining axis for both dimensions of data - rem_key = axiskeys(A, 3-numerical_dim) - KeyedArray(data, (copy(rem_key), copy(rem_key))) - end -end - function Base.dropdims(A::KeyedArray; dims) numerical_dims = NamedDims.dim(A, dims) data = dropdims(parent(A); dims=dims) diff --git a/src/selectors.jl b/src/selectors.jl index 62f5917..bd51f12 100644 --- a/src/selectors.jl +++ b/src/selectors.jl @@ -1,10 +1,3 @@ - -using InvertedIndices -# needs only Base.to_indices in struct.jl to work, -# plus this to work when used in round brackets: - -findindex(not::InvertedIndex, r::AbstractVector) = Base.unalias(r, not) - using IntervalSets findindex(int::Interval, r::AbstractVector) = diff --git a/src/show.jl b/src/show.jl index 1821039..6aca95a 100644 --- a/src/show.jl +++ b/src/show.jl @@ -26,7 +26,6 @@ end shorttype(r::Vector{T}) where {T} = "Vector{$T}" shorttype(r::OneTo) = "OneTo{Int}" shorttype(r::SubArray) = "view(::" * shorttype(parent(r)) * ",...)" -shorttype(r::OffsetArray) = "OffsetArray(::" * shorttype(parent(r)) * ",...)" function shorttype(r) bits = split(string(typeof(r)),',') length(bits) == 1 && return bits[1] @@ -122,7 +121,6 @@ function keyed_print_matrix(io::IO, A, reduce_size::Bool=false) end no_offset(x) = x -no_offset(x::OffsetArray) = parent(x) full(x::DenseArray) = x full(x::AbstractArray) = collect(x) # deal with sparse diff --git a/src/wrap.jl b/src/wrap.jl index 58fe258..6a2d4ea 100644 --- a/src/wrap.jl +++ b/src/wrap.jl @@ -53,8 +53,6 @@ for fast lookup. wrapdims(A::AbstractArray, T::Type, r::Union{AbstractVector,Nothing}, keys::Union{AbstractVector,Nothing}...) = KeyedArray(A, map(T, check_keys(A, (r, keys...)))) -using OffsetArrays - function check_keys(A, keys) ndims(A) == length(keys) || throw(ArgumentError( "wrong number of key vectors, got $(length(keys)) with ndims(A) == $(ndims(A))")) @@ -65,7 +63,7 @@ function check_keys(A, keys) elseif axes(r,1) == axes(A,d) r elseif length(r) == size(A,d) - OffsetArray(r, axes(A,d)) + reshape(r, Base.IdentityUnitRange(axes(A,d))) elseif r isa AbstractRange l = size(A,d) r′ = extend_range(r, l) diff --git a/test/_packages.jl b/test/_packages.jl index 35bfa08..e38de82 100644 --- a/test/_packages.jl +++ b/test/_packages.jl @@ -32,16 +32,16 @@ end end @testset "namedarrays" begin - using NamedArrays - - # NamedArrays lets us have strings as well as symbols for dimension names. Make sure - # we can handle both. - for dimension_name in (:aa, "aa") - x = NamedArray([1]) - setdimnames!(x, dimension_name, 1) - k = wrapdims(x) - @test dimnames(k) == (:aa,) - end + # using NamedArrays + + # # NamedArrays lets us have strings as well as symbols for dimension names. Make sure + # # we can handle both. + # for dimension_name in (:aa, "aa") + # x = NamedArray([1]) + # setdimnames!(x, dimension_name, 1) + # k = wrapdims(x) + # @test dimnames(k) == (:aa,) + # end end @testset "DataFrames" begin using DataFrames: DataFrame