|
| 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 |
0 commit comments