Skip to content

Commit 3bb3514

Browse files
authored
Multiscale analysis (#132)
* Multiscale analysis * Complexity API, and rework reverse dispersion * Remember to use Base.@kwdef * Fix normalization * Fix docs * Typo * Multiscale complexity * Update docs * remove unnecessary spaces * Docs * Fix tests * Add generic error messages * Multivariate downsampling * Multiscale normalized * Test downsampling explicitly * Always return float-vectors * Update API Multiscale API * Correct analytical tests * Cleaner docs * Switch order * Shorten docs. * Fix cross-references * Auto stash before merge of "multiscale_entropy" and "main" * Re-introduce regular.jl file that was lost during merge * Correct argument order * Update multiscale and tests * Update gitignore
1 parent 94d6e27 commit 3bb3514

File tree

12 files changed

+463
-0
lines changed

12 files changed

+463
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ docs/build/*
22
Manifest.toml
33
*.scss
44
*.css
5+
.DS_Store

docs/make.jl

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ ENTROPIES_PAGES = [
3636
"index.md",
3737
"probabilities.md",
3838
"entropies.md",
39+
"multiscale.md",
3940
"examples.md",
4041
"devdocs.md",
4142
]

docs/src/multiscale.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Multiscale
2+
3+
## Multiscale API
4+
5+
The multiscale API is defined by the functions
6+
7+
- [`multiscale`](@ref)
8+
- [`multiscale_normalized`](@ref)
9+
- [`downsample`](@ref)
10+
11+
which dispatch any of the [`MultiScaleAlgorithm`](@ref)s listed below.
12+
13+
```@docs
14+
MultiScaleAlgorithm
15+
Regular
16+
Composite
17+
```
18+
19+
## Multiscale entropy
20+
21+
```@docs
22+
multiscale
23+
multiscale_normalized
24+
downsample
25+
```
26+
27+
## Available literature methods
28+
29+
A non-exhaustive list of literature methods, and the syntax to compute them, are listed
30+
below. Please open an issue or make a pull-request to
31+
[Entropies.jl](https://github.com/JuliaDynamics/Entropies.jl) if you find a literature
32+
method missing from this list, or if you publish a paper based on some new multiscale
33+
combination.
34+
35+
| Method | Syntax | Reference |
36+
| ----------------------------------------------- | ------------------------------------------------------------------ | ------------------------------- |
37+
| Refined composite multiscale dispersion entropy | `multiscale(Composite(), Dispersion(), est, x, normalized = true)` | Azami et al. (2017)[^Azami2017] |
38+
39+
[^Azami2017]:
40+
Azami, H., Rostaghi, M., Abásolo, D., & Escudero, J. (2017). Refined
41+
composite multiscale dispersion entropy and its application to biomedical signals.
42+
IEEE Transactions on Biomedical Engineering, 64(12), 2872-2879.

src/Entropies.jl

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const Vector_or_Dataset = Union{<:AbstractVector{<:Real}, <:AbstractDataset}
2020
include("probabilities.jl")
2121
include("entropy.jl")
2222
include("encodings.jl")
23+
include("multiscale.jl")
24+
2325
# Library implementations (files include other files)
2426
include("probabilities_estimators/probabilities_estimators.jl")
2527
include("entropies/entropies.jl")

src/multiscale.jl

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# This file contains an API for multiscale (coarse-grained/downsampled) computations.
2+
3+
using Statistics
4+
export multiscale
5+
export multiscale_normalized
6+
export downsample
7+
export MultiScaleAlgorithm
8+
9+
"""
10+
MultiScaleAlgorithm
11+
12+
The supertype for all multiscale algorithms.
13+
"""
14+
abstract type MultiScaleAlgorithm end
15+
16+
"""
17+
downsample(algorithm::MultiScaleAlgorithm, s::Int, x)
18+
19+
Downsample and coarse-grain `x` to scale `s` according to the given multiscale `algorithm`.
20+
21+
Positional arguments `args` and keyword arguments `kwargs` are propagated to relevant
22+
functions in `algorithm`, if applicable.
23+
24+
The return type depends on `algorithm`. For example:
25+
26+
- [`Regular`](@ref) yields a single `Vector` per scale.
27+
- [`Composite`](@ref) yields a `Vector{Vector}` per scale.
28+
"""
29+
downsample(method::MultiScaleAlgorithm, s::Int, x)
30+
31+
downsample(alg::MultiScaleAlgorithm, s::Int, x::AbstractDataset) =
32+
Dataset(map(t -> downsample(alg, s, t)), columns(x)...)
33+
34+
35+
function multiscale end
36+
function multiscale_normalized end
37+
38+
"""
39+
multiscale(alg::MultiScaleAlgorithm, e::Entropy, est::EntropyEstimator, x; kwargs...)
40+
multiscale(alg::MultiScaleAlgorithm, e::Entropy, est::ProbabilitiesEstimator, x; kwargs...)
41+
42+
Compute the multi-scale entropy `e` with estimator `est` for timeseries `x`.
43+
44+
The first signature estimates differential/continuous multiscale entropy. The second
45+
signature estimates discrete multiscale entropy.
46+
47+
This function generalizes *all* multi-scale entropy estimators, as long as a relevant
48+
[`MultiScaleAlgorithm`](@ref), [`downsample`](@ref) method and estimator is defined.
49+
Multi-scale complexity ("entropy-like") measures, such as "sample entropy", are found in
50+
the Complexity.jl package.
51+
52+
## Description
53+
54+
Utilizes [`downsample`](@ref) to compute the entropy/complexity of coarse-grained,
55+
downsampled versions of `x` for scale factors `1:maxscale`. If `N = length(x)`, then the
56+
length of the most severely downsampled version of `x` is `N ÷ maxscale`, while for scale
57+
factor `1`, the original time series is considered.
58+
59+
## Arguments
60+
61+
- `e::Entropy`. A valid [entropy type](@ref entropies), i.e. `Shannon()` or `Renyi()`.
62+
- `alg::MultiScaleAlgorithm`. A valid [multiscale algorithm](@ref multiscale_algorithms),
63+
i.e. `Regular()` or `Composite()`, which determines how down-sampling/coarse-graining
64+
is performed.
65+
- `x`. The input data. Usually a timeseries.
66+
- `est`. For discrete entropy, `est` is a [`ProbabilitiesEstimator`](@ref), which determines
67+
how probabilities are estimated for each sampled time series. Alternatively,for
68+
continuous/differential entropy, `est` can be a [`EntropyEstimator`](@ref),
69+
which dictates the entropy estimation method for each downsampled time series.
70+
71+
## Keyword Arguments
72+
73+
- `maxscale::Int`. The maximum number of scales (i.e. levels of downsampling). The actual
74+
maximum scale level is `length(x) ÷ 2`, but the default is `length(x) ÷ 5`, to avoid
75+
computing the entropies for time series that are extremely short.
76+
77+
[^Costa2002]: Costa, M., Goldberger, A. L., & Peng, C. K. (2002). Multiscale entropy
78+
analysis of complex physiologic time series. Physical review letters, 89(6), 068102.
79+
"""
80+
function multiscale(alg::MultiScaleAlgorithm, e::Entropy,
81+
est::Union{ProbabilitiesEstimator, EntropyEstimator}, x)
82+
msg = "`multiscale` entropy not implemented for $e $est on data type $(typeof(x))"
83+
throw(ArgumentError(msg))
84+
end
85+
86+
"""
87+
multiscale_normalized(alg::MultiScaleAlgorithm, e::Entropy,
88+
est::ProbabilitiesEstimator, x)
89+
90+
The same as [`multiscale`](@ref), but normalizes the entropy if [`entropy_maximum`](@ref)
91+
is implemented for `e`.
92+
93+
Note: this doesn't work if `est` is an [`EntropyEstimator`](@ref).
94+
"""
95+
function multiscale_normalized(alg::MultiScaleAlgorithm, e::Entropy, est, x)
96+
msg = "`multiscale_normalized` not implemented for $e $(typeof(est)) on data type $(typeof(x))"
97+
throw(ArgumentError(msg))
98+
end
99+
100+
multiscale_normalized(alg::MultiScaleAlgorithm, e::Entropy, est::EntropyEstimator, x) =
101+
error("multiscale_normalized not defined for $(typeof(est))")
102+
103+
max_scale_level(method::MultiScaleAlgorithm, x) = length(x) ÷ 2
104+
function verify_scale_level(method, s::Int, x)
105+
err = DomainError(
106+
"Maximum scale for length-$(length(x)) timeseries is "*
107+
"`s = $(max_scale_level(method, x))`. Got s = $s"
108+
)
109+
length(x) ÷ s >= 2 || throw(err)
110+
end
111+
112+
113+
include("multiscale/regular.jl")
114+
include("multiscale/composite.jl")

src/multiscale/composite.jl

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
export Composite
2+
3+
"""
4+
Composite <: MultiScaleAlgorithm
5+
Composite(; f::Function = Statistics.mean)
6+
7+
Composite multi-scale algorithm for multiscale entropy analysis (Wu et al.,
8+
2013)[^Wu2013], used, with [`multiscale`](@ref) to compute, for example, composite
9+
multiscale entropy (CMSE).
10+
11+
## Description
12+
13+
Given a scalar-valued input time series `x`, the composite multiscale algorithm,
14+
like [`Regular`](@ref), downsamples and coarse-grains `x` by splitting it into
15+
non-overlapping windows of length `s`, and then constructing downsampled time series by
16+
applying the function `f` to each of the resulting length-`s` windows.
17+
18+
However, Wu et al. (2013)[^Wu2013] realized that for each scale `s`, there are actually `s`
19+
different ways of selecting windows, depending on where indexing starts/ends.
20+
These `s` different downsampled time series `D_t(s, f)` at each scale `s` are
21+
constructed as follows:
22+
23+
```math
24+
\\{ D_{k}(s) \\} = \\{ D_{t, k}(s) \\}_{t = 1}^{L}, = \\{ f \\left( \\bf x_{t, k} \\right) \\} =
25+
\\left\\{
26+
{f\\left( (x_i)_{i = (t - 1)s + k}^{ts + k - 1} \\right)}
27+
\\right\\}_{t = 1}^{L},
28+
```
29+
30+
where `L = floor((N - s + 1) / s)` and `1 ≤ k ≤ s`, such that ``D_{i, k}(s)`` is the `i`-th
31+
element of the `k`-th downsampled time series at scale `s`.
32+
33+
Finally, compute ``\\dfrac{1}{s} \\sum_{k = 1}^s g(D_{k}(s))``, where `g` is some summary
34+
function, for example [`entropy`](@ref) or [`complexity`](@ref).
35+
36+
!!! note "Relation to Regular"
37+
The downsampled time series ``D_{t, 1}(s)`` constructed using the composite
38+
multiscale method is equivalent to the downsampled time series ``D_{t}(s)`` constructed
39+
using the [`Regular`](@ref) method, for which `k == 1` is fixed, such that only
40+
a single time series is returned.
41+
42+
See also: [`Regular`](@ref).
43+
44+
[^Wu2013]: Wu, S. D., Wu, C. W., Lin, S. G., Wang, C. C., & Lee, K. Y. (2013). Time series
45+
analysis using composite multiscale entropy. Entropy, 15(3), 1069-1084.
46+
"""
47+
Base.@kwdef struct Composite <: MultiScaleAlgorithm
48+
f::Function = Statistics.mean
49+
end
50+
51+
function downsample(method::Composite, s::Int, x::AbstractVector{T}, args...;
52+
kwargs...) where T
53+
verify_scale_level(method, s, x)
54+
55+
f = method.f
56+
ET = eltype(one(1.0)) # always return floats, even if input is e.g. integer-valued
57+
58+
if s == 1
59+
return [ET.(x)]
60+
else
61+
N = length(x)
62+
# Because input time series are finite, there is always a minimum number of windows
63+
# that we can construct at a given scale. We restrict the number of windows
64+
# considered at each scale to this minimum to ensure windows are well-defined,
65+
# i.e. we're not trying to summarize data at indices outside the input data,
66+
# which would give out-of-bounds errors.
67+
#
68+
# For example, if the input is [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], then we sample
69+
# the following subvectors at different scales:
70+
# Scale 3:
71+
# start index 1: [1, 2, 3], [4, 5, 6], [7, 8, 9]
72+
# start index 2: [2, 3, 4], [5, 6, 7], [8, 9, 10]
73+
# start index 3: [3, 4, 5], [6, 7, 8]
74+
# Only two windows are possible for start index 3, so `min_possible_windows = 2`
75+
# Scale 4:
76+
# start index 1: [1, 2, 3, 4], [5, 6, 7, 8]
77+
# start index 2: [2, 3, 4, 5], [6, 7, 8, 9]
78+
# start index 3: [3, 4, 5, 6], [7, 8, 9, 10]
79+
# start index 4: [4, 5, 6, 7]
80+
# Only one window is possible for start index 4, so `min_possible_windows = 1`
81+
min_possible_windows = floor(Int, (N - s + 1) / s)
82+
83+
ys = [zeros(ET, min_possible_windows) for i = 1:s]
84+
for k = 1:s
85+
for t = 1:min_possible_windows
86+
inds = ((t - 1)*s + k):(t * s + k - 1)
87+
ys[k][t] = @views f(x[inds], args...; kwargs...)
88+
end
89+
end
90+
return ys
91+
end
92+
end
93+
94+
function multiscale(alg::Composite, e::Entropy,
95+
est::Union{ProbabilitiesEstimator, EntropyEstimator},
96+
x::AbstractVector;
97+
maxscale::Int = 8)
98+
99+
downscaled_timeseries = [downsample(alg, s, x) for s in 1:maxscale]
100+
hs = zeros(Float64, maxscale)
101+
for s in 1:maxscale
102+
hs[s] = mean(entropy.(Ref(e), Ref(est), downscaled_timeseries[s]))
103+
end
104+
105+
return hs
106+
end
107+
108+
function multiscale_normalized(alg::Composite, e::Entropy, est::ProbabilitiesEstimator,
109+
x::AbstractVector;
110+
maxscale::Int = 8)
111+
112+
downscaled_timeseries = [downsample(alg, s, x) for s in 1:maxscale]
113+
hs = zeros(Float64, maxscale)
114+
for s in 1:maxscale
115+
hs[s] = mean(entropy_normalized.(Ref(e), Ref(est), downscaled_timeseries[s]))
116+
end
117+
118+
return hs
119+
end

src/multiscale/regular.jl

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
export Regular
2+
3+
"""
4+
Regular <: MultiScaleAlgorithm
5+
Regular(; f::Function = Statistics.mean)
6+
7+
The original multi-scale algorithm for multiscale entropy analysis (Costa et al.,
8+
2022)[^Costa2002], which yields a single downsampled time series per scale `s`.
9+
10+
## Description
11+
12+
Given a scalar-valued input time series `x`, the `Regular` multiscale algorithm downsamples
13+
and coarse-grains `x` by splitting it into non-overlapping windows of length `s`, and
14+
then constructing a new downsampled time series ``D_t(s, f)`` by applying the function `f`
15+
to each of the resulting length-`s` windows.
16+
17+
The downsampled time series `D_t(s)` with `t ∈ [1, 2, …, L]`, where `L = floor(N / s)`,
18+
is given by:
19+
20+
```math
21+
\\{ D_t(s, f) \\}_{t = 1}^{L} = \\left\\{ f \\left( \\bf x_t \\right) \\right\\}_{t = 1}^{L} =
22+
\\left\\{
23+
{f\\left( (x_i)_{i = (t - 1)s + 1}^{ts} \\right)}
24+
\\right\\}_{t = 1}^{L}
25+
```
26+
27+
where `f` is some summary statistic applied to the length-`ts-((t - 1)s + 1)` tuples `xₖ`.
28+
Different choices of `f` have yield different multiscale methods appearing in the
29+
literature. For example:
30+
31+
- `f == Statistics.mean` yields the original first-moment multiscale sample entropy (Costa
32+
et al., 2002)[^Costa2002].
33+
- `f == Statistics.var` yields the generalized multiscale sample entropy (Costa &
34+
Goldberger, 2015)[^Costa2015], which uses the second-moment (variance) instead of the
35+
mean.
36+
37+
See also: [`Composite`](@ref).
38+
39+
[^Costa2002]: Costa, M., Goldberger, A. L., & Peng, C. K. (2002). Multiscale entropy
40+
analysis of complex physiologic time series. Physical review letters, 89(6), 068102.
41+
[^Costa2015]: Costa, M. D., & Goldberger, A. L. (2015). Generalized multiscale entropy
42+
analysis: Application to quantifying the complex volatility of human heartbeat time
43+
series. Entropy, 17(3), 1197-1203.
44+
"""
45+
Base.@kwdef struct Regular <: MultiScaleAlgorithm
46+
f::Function = Statistics.mean
47+
end
48+
49+
function downsample(method::Regular, s::Int, x::AbstractVector{T}, args...; kwargs...) where T
50+
f = method.f
51+
verify_scale_level(method, s, x)
52+
53+
ET = eltype(one(1.0)) # consistently return floats, even if input is e.g. integer-valued
54+
if s == 1
55+
return x
56+
else
57+
N = length(x)
58+
L = floor(Int, N / s)
59+
ys = zeros(ET, L)
60+
61+
for t = 1:L
62+
inds = ((t - 1)*s + 1):(t * s)
63+
ys[t] = @views f(x[inds], args...; kwargs...)
64+
end
65+
return ys
66+
end
67+
end
68+
69+
function multiscale(alg::Regular, e::Entropy,
70+
est::Union{ProbabilitiesEstimator, EntropyEstimator},
71+
x::AbstractVector;
72+
maxscale::Int = 8)
73+
74+
downscaled_timeseries = [downsample(alg, s, x) for s in 1:maxscale]
75+
return entropy.(Ref(e), Ref(est), downscaled_timeseries)
76+
end
77+
78+
function multiscale_normalized(alg::Regular, e::Entropy,
79+
est::ProbabilitiesEstimator, x::AbstractVector,;
80+
maxscale::Int = 8)
81+
82+
downscaled_timeseries = [downsample(alg, s, x) for s in 1:maxscale]
83+
return entropy_normalized.(Ref(e), Ref(est), downscaled_timeseries)
84+
end

0 commit comments

Comments
 (0)