Skip to content

Commit

Permalink
Deprecate passing m as keyword in ordinal pattern stuff (#333)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
Datseris and kahaaga authored Nov 8, 2023
1 parent b3769da commit 5592dfc
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 56 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/deprecations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 3 additions & 5 deletions src/encoding_implementations/ordinal_pattern.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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<A<1``, a combined weighting is used.
"""
struct AmplitudeAwareOrdinalPatterns{M,F} <: PermutationOutcomeSpace{M}
struct AmplitudeAwareOrdinalPatterns{M,F} <: OrdinalOutcomeSpace{M}
encoding::OrdinalPatternEncoding{M,F}
τ::Int
A::Float64
Expand All @@ -187,33 +184,33 @@ end
is_counting_based(o::AmplitudeAwareOrdinalPatterns) = false

# Initializations
function OrdinalPatterns(; τ::Int = 1, m::Int = 3, lt::F=isless_rand) where {F}
function OrdinalPatterns{m}(τ::Int = 1, lt::F=isless_rand) where {m, 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}
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!"
))
Expand All @@ -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
Expand All @@ -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, χ)
Expand All @@ -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
Expand All @@ -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]).
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/outcome_spaces/outcome_spaces.jl
Original file line number Diff line number Diff line change
@@ -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")
Expand Down
44 changes: 26 additions & 18 deletions test/deprecations.jl
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand All @@ -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

0 comments on commit 5592dfc

Please sign in to comment.