From 5592dfc24b95ed5390711315a7ce51f648f4ccd4 Mon Sep 17 00:00:00 2001 From: George Datseris Date: Wed, 8 Nov 2023 13:37:49 +0000 Subject: [PATCH] Deprecate passing `m` as keyword in ordinal pattern stuff (#333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deprecate m as argument in OrdinalPatternEncoding * rename file: symbolic_permutation -> ordinal_pattterns * rename abstract type to OrdinalOutcomeSpace * change outcome space definition for ordinal patterns * add deprecaton notice * Correct file name * add deprecation test for Ordinal keuwrods --------- Co-authored-by: Kristian Agasøster Haaga --- CHANGELOG.md | 4 ++ src/deprecations.jl | 19 ++++++ .../ordinal_pattern.jl | 8 +-- ...lic_permutation.jl => ordinal_patterns.jl} | 61 +++++++++---------- src/outcome_spaces/outcome_spaces.jl | 2 +- test/deprecations.jl | 44 +++++++------ 6 files changed, 82 insertions(+), 56 deletions(-) rename src/outcome_spaces/{symbolic_permutation.jl => ordinal_patterns.jl} (85%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5294896d4..5f01b5f05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ Further additions to the library in v3: - `SymbolicAmplitudeAwarePermutation` is now `AmplitudeAwareOrdinalPatterns`. - `SpatialSymbolicPermutation` is now `SpatialOrdinalPatterns`. +### Deprecations + +- Passing `m` as a positional or keyword argument to ordinal pattern outcome space or encoding is deprecated. It is given as a type parameter now, e.g., `OrdinalPatterns{m}(...)` instead of `OrdinalPatterns(m = ..., ...)`. + ## 2.7.1 - Fix bug in calculation of statistical complexity diff --git a/src/deprecations.jl b/src/deprecations.jl index a9bd911b1..2016e414f 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -76,3 +76,22 @@ end @deprecate SymbolicPermutation OrdinalPatterns @deprecate SymbolicWeightedPermutation WeightedOrdinalPatterns @deprecate SymbolicAmplitudeAwarePermutation AmplitudeAwareOrdinalPatterns + +function OrdinalPatternEncoding(m::Int, lt::F = isless_rand) where {F} + @warn "Passing `m` as an argument to `OrdinalPattern...(m = ...)` is deprecated. "* + "Pass it as a type parameter instead: `OrdinalPattern...{m}`." + return OrdinalPatternEncoding{m, F}(zero(MVector{m, Int}), lt) +end +# Initializations +function OrdinalPatterns(; τ::Int = 1, m::Int = 3, lt::F=isless_rand) where {F} + m >= 2 || throw(ArgumentError("Need order m ≥ 2.")) + return OrdinalPatterns{m, F}(OrdinalPatternEncoding{m}(lt), τ) +end +function WeightedOrdinalPatterns(; τ::Int = 1, m::Int = 3, lt::F=isless_rand) where {F} + m >= 2 || throw(ArgumentError("Need order m ≥ 2.")) + return WeightedOrdinalPatterns{m, F}(OrdinalPatternEncoding{m}(lt), τ) +end +function AmplitudeAwareOrdinalPatterns(; A = 0.5, τ::Int = 1, m::Int = 3, lt::F=isless_rand) where {F} + m >= 2 || throw(ArgumentError("Need order m ≥ 2.")) + return AmplitudeAwareOrdinalPatterns{m, F}(OrdinalPatternEncoding{m}(lt), τ, A) +end \ No newline at end of file diff --git a/src/encoding_implementations/ordinal_pattern.jl b/src/encoding_implementations/ordinal_pattern.jl index c1e5ee32d..a2328427b 100644 --- a/src/encoding_implementations/ordinal_pattern.jl +++ b/src/encoding_implementations/ordinal_pattern.jl @@ -5,7 +5,7 @@ export OrdinalPatternEncoding """ OrdinalPatternEncoding <: Encoding - OrdinalPatternEncoding(m::Int, lt = ComplexityMeasures.isless_rand) + OrdinalPatternEncoding{m}(lt = ComplexityMeasures.isless_rand) An encoding scheme that [`encode`](@ref)s length-`m` vectors into their permutation/ordinal patterns and then into the integers based on the Lehmer @@ -49,12 +49,10 @@ struct OrdinalPatternEncoding{M, F} <: Encoding perm::MVector{M, Int} lt::F end -function OrdinalPatternEncoding{m}(lt::F) where {m,F} +function OrdinalPatternEncoding{m}(lt::F = isless_rand) where {m,F} OrdinalPatternEncoding{m, F}(zero(MVector{m, Int}), lt) end -function OrdinalPatternEncoding(m = 3, lt::F = isless_rand) where {F} - return OrdinalPatternEncoding{m, F}(zero(MVector{m, Int}), lt) -end +OrdinalPatternEncoding() = OrdinalPatternEncoding{3}(isless_rand) # So that SymbolicPerm stuff fallback here total_outcomes(::OrdinalPatternEncoding{m}) where {m} = factorial(m) diff --git a/src/outcome_spaces/symbolic_permutation.jl b/src/outcome_spaces/ordinal_patterns.jl similarity index 85% rename from src/outcome_spaces/symbolic_permutation.jl rename to src/outcome_spaces/ordinal_patterns.jl index c0a92be28..eab04af4e 100644 --- a/src/outcome_spaces/symbolic_permutation.jl +++ b/src/outcome_spaces/ordinal_patterns.jl @@ -12,18 +12,19 @@ Subtypes must implement fields: - `lt::Function`: A function determining how ties are to be broken when constructing permutation patterns from embedding vectors. """ -abstract type PermutationOutcomeSpace{m} <: CountBasedOutcomeSpace end -const PermProbEst = PermutationOutcomeSpace +abstract type OrdinalOutcomeSpace{m} <: CountBasedOutcomeSpace end ########################################################################################### # Types and docstrings ########################################################################################### """ OrdinalPatterns <: OutcomeSpace - OrdinalPatterns(; m = 3, τ = 1, lt::Function = ComplexityMeasures.isless_rand) + OrdinalPatterns{m}(τ = 1, lt::Function = ComplexityMeasures.isless_rand) -An [`OutcomeSpace`](@ref) based on ordinal permutation patterns, originally introduced in -[BandtPompe2002](@citet)'s paper on permutation entropy. +An [`OutcomeSpace`](@ref) based on lengh-`m` ordinal permutation patterns, originally +introduced in [BandtPompe2002](@citet)'s paper on permutation entropy. +Note that `m` is given as a type parameter, so that when it is a literal integer +there are performance accelerations. When passed to [`probabilities`](@ref) the output depends on the input data type: @@ -83,17 +84,13 @@ For example ```julia using ComplexityMeasures m, N = 2, 100 -est = OrdinalPatterns(; m, τ) +est = OrdinalPatterns{m}(τ) x = StateSpaceSet(rand(N, m)) # some input dataset πs_ts = zeros(Int, N) # length must match length of `x` p = probabilities!(πs_ts, est, x) ``` - -## Implements - -- [`symbolize`](@ref). Used for encoding time series. """ -struct OrdinalPatterns{M,F} <: PermutationOutcomeSpace{M} +struct OrdinalPatterns{M,F} <: OrdinalOutcomeSpace{M} encoding::OrdinalPatternEncoding{M,F} τ::Int end @@ -120,11 +117,11 @@ end """ WeightedOrdinalPatterns <: OutcomeSpace - WeightedOrdinalPatterns(; τ = 1, m = 3, lt::Function = ComplexityMeasures.isless_rand) + WeightedOrdinalPatterns{m}(τ = 1, lt::Function = ComplexityMeasures.isless_rand) A variant of [`OrdinalPatterns`](@ref) that also incorporates amplitude information, based on the weighted permutation entropy [Fadlallah2013](@cite). The outcome space and -keywords are the same as in [`OrdinalPatterns`](@ref). +arguments are the same as in [`OrdinalPatterns`](@ref). ## Description @@ -147,7 +144,7 @@ of the same pattern. correctly for each vector of the input dataset (which may be a delay-embedded timeseries). """ -struct WeightedOrdinalPatterns{M,F} <: PermutationOutcomeSpace{M} +struct WeightedOrdinalPatterns{M,F} <: OrdinalOutcomeSpace{M} encoding::OrdinalPatternEncoding{M,F} τ::Int end @@ -156,11 +153,11 @@ is_counting_based(o::WeightedOrdinalPatterns) = false """ AmplitudeAwareOrdinalPatterns <: OutcomeSpace - AmplitudeAwareOrdinalPatterns(; τ = 1, m = 3, A = 0.5, lt = ComplexityMeasures.isless_rand) + AmplitudeAwareOrdinalPatterns{m}(τ = 1, A = 0.5, lt = ComplexityMeasures.isless_rand) A variant of [`OrdinalPatterns`](@ref) that also incorporates amplitude information, based on the amplitude-aware permutation entropy [Azami2016](@cite). The outcome space and -keywords are the same as in [`OrdinalPatterns`](@ref). +arguments are the same as in [`OrdinalPatterns`](@ref). ## Description @@ -178,7 +175,7 @@ elements of ``\\mathbf{x}_i`` are weighted. Only mean amplitude of the state vector elements are weighted when ``A=1``. With, ``0= 2 || throw(ArgumentError("Need order m ≥ 2.")) return OrdinalPatterns{m, F}(OrdinalPatternEncoding{m}(lt), τ) end -function WeightedOrdinalPatterns(; τ::Int = 1, m::Int = 3, lt::F=isless_rand) where {F} +function WeightedOrdinalPatterns{m}(τ::Int = 1, lt::F=isless_rand) where {m, F} m >= 2 || throw(ArgumentError("Need order m ≥ 2.")) return WeightedOrdinalPatterns{m, F}(OrdinalPatternEncoding{m}(lt), τ) end -function AmplitudeAwareOrdinalPatterns(; A = 0.5, τ::Int = 1, m::Int = 3, lt::F=isless_rand) where {F} +function AmplitudeAwareOrdinalPatterns{m}(τ::Int = 1, A = 0.5, lt::F=isless_rand) where {m, F} m >= 2 || throw(ArgumentError("Need order m ≥ 2.")) return AmplitudeAwareOrdinalPatterns{m, F}(OrdinalPatternEncoding{m}(lt), τ, A) end ########################################################################################### -# Implementation of the whole `probabilities` API on abstract `PermProbEst` +# Implementation of the whole `probabilities` API on abstract `OrdinalOutcomeSpace` ########################################################################################### # Probabilities etc. simply initialize the datasets and containers of the encodings # and just map everythihng using `encode`. The only difference between the three # types is whether they compute some additional weights that are affecting # how the probabilities are counted. -function probabilities(est::PermProbEst{m}, x::AbstractVector{T}) where {m, T<:Real} +function probabilities(est::OrdinalOutcomeSpace{m}, x::AbstractVector{T}) where {m, T<:Real} dataset::StateSpaceSet{m,T} = embed(x, m, est.τ) return probabilities(est, dataset) end -function probabilities(est::PermProbEst{m}, x::AbstractStateSpaceSet{D}) where {m, D} +function probabilities(est::OrdinalOutcomeSpace{m}, x::AbstractStateSpaceSet{D}) where {m, D} m != D && throw(ArgumentError( "Order of ordinal patterns and dimension of `StateSpaceSet` must match!" )) @@ -223,7 +220,7 @@ function probabilities(est::PermProbEst{m}, x::AbstractStateSpaceSet{D}) where { return probs end -function probabilities!(::Vector{Int}, ::PermProbEst, ::AbstractVector) +function probabilities!(::Vector{Int}, ::OrdinalOutcomeSpace, ::AbstractVector) error(""" In-place `probabilities!` for `OrdinalPatterns` can only be used by StateSpaceSet input, not timeseries. First embed the timeseries or use the @@ -232,7 +229,7 @@ function probabilities!(::Vector{Int}, ::PermProbEst, ::AbstractVector) end -function fasthist!(πs::Vector{Int}, est::PermProbEst{m}, x::AbstractStateSpaceSet{m}) where {m} +function fasthist!(πs::Vector{Int}, est::OrdinalOutcomeSpace{m}, x::AbstractStateSpaceSet{m}) where {m} # TODO: The following loop can probably be parallelized! @inbounds for (i, χ) in enumerate(x) πs[i] = encode(est.encoding, χ) @@ -244,7 +241,7 @@ function fasthist!(πs::Vector{Int}, est::PermProbEst{m}, x::AbstractStateSpaceS return cts end -function codify(est::PermProbEst{m}, x) where m +function codify(est::OrdinalOutcomeSpace{m}, x) where m if x isa AbstractVector dataset = embed(x, m, est.τ) else @@ -270,7 +267,7 @@ function probabilities!(πs::Vector{Int}, est::OrdinalPatterns{m}, x::AbstractSt end # The scaled versions -function probabilities!(πs::Vector{Int}, est::PermProbEst{m}, x::AbstractStateSpaceSet{m}) where {m} +function probabilities!(πs::Vector{Int}, est::OrdinalOutcomeSpace{m}, x::AbstractStateSpaceSet{m}) where {m} # This sorts πs in-place. For `WeightedOrdinalPatterns` and # `AmplitudeAwareOrdinalPatterns`, this returned normalized counts (i.e. numbers on # [0, 1]). @@ -283,7 +280,7 @@ end # `OrdinalPatterns` is counting-compatible (i.e. returns a true histogram `Vector{Int}`), # while `WeightedOrdinalPatterns` and `AmplitudeAwareOrdinalPatterns` returns # a normalized histogram, and are thus *not* counting compatible. -function weighted_counts_and_outcomes(est::PermProbEst{m}, x::Vector_or_SSSet) where {m} +function weighted_counts_and_outcomes(est::OrdinalOutcomeSpace{m}, x::Vector_or_SSSet) where {m} # A bit of code duplication here, because we actually need the processed # `πs` to invert it with `decode`. This can surely be optimized with some additional # function that both maps to integers with `decode` but also keeps track of @@ -304,18 +301,18 @@ function weighted_counts_and_outcomes(est::PermProbEst{m}, x::Vector_or_SSSet) w return freqs, outcomes end -function probabilities_and_outcomes(est::PermProbEst{m}, x::Vector_or_SSSet) where {m} +function probabilities_and_outcomes(est::OrdinalOutcomeSpace{m}, x::Vector_or_SSSet) where {m} freqs, outcomes = weighted_counts_and_outcomes(est, x) return Probabilities(freqs), outcomes end # fallback -total_outcomes(est::PermutationOutcomeSpace) = total_outcomes(est.encoding) -outcome_space(est::PermutationOutcomeSpace) = outcome_space(est.encoding) +total_outcomes(est::OrdinalOutcomeSpace) = total_outcomes(est.encoding) +outcome_space(est::OrdinalOutcomeSpace) = outcome_space(est.encoding) # If `x` is state space set, delay embedding is ignored and all elements # are mapped to outcomes. Otherwise, delay embedding is done. -function encoded_space_cardinality(o::PermProbEst{m}, x::AbstractArray) where {m} +function encoded_space_cardinality(o::OrdinalOutcomeSpace{m}, x::AbstractArray) where {m} N = length(x) return N - (m - 1)*o.τ end diff --git a/src/outcome_spaces/outcome_spaces.jl b/src/outcome_spaces/outcome_spaces.jl index c47663dd7..080be6310 100644 --- a/src/outcome_spaces/outcome_spaces.jl +++ b/src/outcome_spaces/outcome_spaces.jl @@ -1,6 +1,6 @@ include("unique_elements.jl") include("value_binning.jl") -include("symbolic_permutation.jl") +include("ordinal_patterns.jl") include("kernel_density.jl") include("power_spectrum.jl") include("wavelet_overlap.jl") diff --git a/test/deprecations.jl b/test/deprecations.jl index 319e2529e..17c42e331 100644 --- a/test/deprecations.jl +++ b/test/deprecations.jl @@ -1,27 +1,28 @@ using ComplexityMeasures, Test -x = randn(1000) +@testset "2.0 deprecations" begin + x = randn(1000) -# Convenience -@test permentropy(x) == entropy_permutation(x; base=MathConstants.e) -msg ="`permentropy(x; τ, m, base)` is deprecated.\nUse instead: `entropy_permutation(x; τ, m, base)`, or even better, use the\ndirect syntax discussed in the docstring of `entropy_permutation`.\n" -@test_logs (:warn, msg) permentropy(x) + # Convenience + @test permentropy(x) == entropy_permutation(x; base=MathConstants.e) + msg ="`permentropy(x; τ, m, base)` is deprecated.\nUse instead: `entropy_permutation(x; τ, m, base)`, or even better, use the\ndirect syntax discussed in the docstring of `entropy_permutation`.\n" + @test_logs (:warn, msg) permentropy(x) -# Generalized entropies -@test genentropy(x, 0.1) == information(Shannon(MathConstants.e), ValueBinning(0.1), x) -msg = "`genentropy(probs::Probabilities; q, base)` deprecated.\nUse instead: `information(Renyi(q, base), probs)`.\n" -@test_logs (:warn, msg) genentropy(Probabilities(rand(3))) + # Generalized entropies + @test genentropy(x, 0.1) == information(Shannon(MathConstants.e), ValueBinning(0.1), x) + msg = "`genentropy(probs::Probabilities; q, base)` deprecated.\nUse instead: `information(Renyi(q, base), probs)`.\n" + @test_logs (:warn, msg) genentropy(Probabilities(rand(3))) -msg = "`genentropy(x::Array_or_SSSet, est::ProbabilitiesEstimator; q, base)` is deprecated.\nUse instead: `information(Renyi(q, base), est, x)`.\n" -@test_logs (:warn, msg) genentropy(x, ValueBinning(0.1)) + msg = "`genentropy(x::Array_or_SSSet, est::ProbabilitiesEstimator; q, base)` is deprecated.\nUse instead: `information(Renyi(q, base), est, x)`.\n" + @test_logs (:warn, msg) genentropy(x, ValueBinning(0.1)) -@test probabilities(x, 0.1) == probabilities(ValueBinning(0.1), x) -msg = "`probabilities(x, est::OutcomeSpace)`\nis deprecated, use `probabilities(est::OutcomeSpace, x) instead`.\n" -@test_logs (:warn, msg) probabilities(x, ValueBinning(0.1)) - -x = StateSpaceSet(rand(100, 3)) -@test genentropy(x, 4) == information(Shannon(MathConstants.e), ValueBinning(4), x) + @test probabilities(x, 0.1) == probabilities(ValueBinning(0.1), x) + msg = "`probabilities(x, est::OutcomeSpace)`\nis deprecated, use `probabilities(est::OutcomeSpace, x) instead`.\n" + @test_logs (:warn, msg) probabilities(x, ValueBinning(0.1)) + x = StateSpaceSet(rand(100, 3)) + @test genentropy(x, 4) == information(Shannon(MathConstants.e), ValueBinning(4), x) +end @testset "deprecations: binning" begin @test FixedRectangularBinning((0, 1), (0, 1), 2) isa FixedRectangularBinning @@ -31,7 +32,7 @@ end @testset "2.9 deprecations" begin - # For + x = StateSpaceSet(rand(100, 3)) @test entropy_maximum(Shannon(MathConstants.e), ValueBinning(4), x) == information_maximum(Shannon(MathConstants.e), ValueBinning(4), x) @@ -42,4 +43,11 @@ end @test SymbolicPermutation() isa OrdinalPatterns @test SymbolicWeightedPermutation() isa WeightedOrdinalPatterns @test SymbolicAmplitudeAwarePermutation() isa AmplitudeAwareOrdinalPatterns + + for f in (OrdinalPatterns, WeightedOrdinalPatterns, AmplitudeAwareOrdinalPatterns) + a = f(; m = 3, τ = 2) + b = f{3}(2) + @test entropy(a, x) == entropy(b, x) + end + end