Skip to content

Commit dd345f8

Browse files
Additional partial-randomization algorithms (#149)
* Update partial randomization to have a relative method * Add spectrum-based randomization * Fix surrogate plot but * Add tests for partial randomization * Tweak legend * Tweaks to docstrings * Switch to types for relative randomization * Update docs for relative partial randomization * Update docs * Fix typos in docs for partial randomization * Fix more typos * Temporary commit * Temporary commit * Update plotting merge for 1.9 * Add partial randomization comparison figure * Fix error * Update for consistency * More consistency * Update tests for ArgumentErrors * Update docs figure * Make partial randomization defaults consistent * Make partial randomization defaults consistent * Update based on review * Update docs with lorenz simulation * Fix docs * Update docstring for surrocompare * Update for consistent style * Fix unhidden doc example due to extra whitespace * Update changelog and version number * Fix version number in changelog to match most recent entry * Consistent changelog versions * Remove surrocompare * No need to standardize for plots --------- Co-authored-by: George Datseris <datseris.george@gmail.com>
1 parent aa33e32 commit dd345f8

13 files changed

+368
-54
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ Manifest.toml
77
*.scss
88
*.css
99
.vscode
10-
*style.jl
10+
*style.jl
11+
*.code-workspace

CHANGELOG.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
*Changelog is kept with respect to version 1.0. This software follows SymVer2.0*
22

3-
# 2.5.0
3+
# 2.6
4+
- Added surrogate methods: `RelativePartialRandomization`, `SpectralPartialRandomization`, `RelativePartialRandomizationAAFT`, and `SpectralPartialRandomizationAAFT`.
45

6+
# 2.5
57
- Moved to Julia extensions (requiring julia v1.9).
68

79
# 2.4
@@ -11,7 +13,8 @@
1113
- `pvalue` is now correctly overloaded from StatsAPI.jl.
1214

1315
# 2.2
14-
- Implemented API for automating surrogate hypothesis tests using the new exported names `SurrogateTest` and `pvalue`.
16+
- Implemented API for automating surrogate hypothesis tests using the new exported names
17+
`SurrogateTest` and `pvalue`.
1518
- New documentation section with an educative example of surrogate testing.
1619

1720
# 2.1
@@ -29,17 +32,22 @@
2932
## New features
3033
- New surrogate methods: `PartialRandomization`, `PartialRandomizationAAFT`, `TFTDAAFT`,
3134
`TFTDIAAFT`, and `RandomCascade`.
32-
- Using the `f` keyword, it is now possible to select whether circular shifting at
33-
each wavelet coefficient level should be performed for `WLS` surrogates.
35+
- Using the `f` keyword, it is now possible to select whether circular shifting at each
36+
wavelet coefficient level should be performed for `WLS` surrogates.
3437

3538
# 1.3
3639

3740
## New features
38-
- The API now allows random number generator to be specified for all methods, enabling reproducibility
39-
- New surrogate methods: `PseudoPeriodicTwin`, `IrregularLombScargle` and `TFTDRandomFourier`.
41+
- The API now allows random number generator to be specified for all methods, enabling
42+
reproducibility
43+
- New surrogate methods: `PseudoPeriodicTwin`, `IrregularLombScargle` and
44+
`TFTDRandomFourier`.
4045

4146
## Bug fixes
42-
- Fixed error in `RandomFourier(true)` and `AAFT` surrogates, where phases were not correctly computed, leading to surrogates whose power spectra didn't match the power spectrum of the original signal as well as they should. No other Fourier-based surrogate were affected by this bug.
47+
- Fixed error in `RandomFourier(true)` and `AAFT` surrogates, where phases were not
48+
correctly computed, leading to surrogates whose power spectra didn't match the power
49+
spectrum of the original signal as well as they should. No other Fourier-based surrogate
50+
were affected by this bug.
4351

4452
# 1.2
4553
- New surrogate methods: `AutoRegressive`

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "TimeseriesSurrogates"
22
uuid = "c804724b-8c18-5caa-8579-6025a0767c70"
33
authors = ["Kristian Agasøster Haaga <kahaaga@gmail.com>", "George Datseris"]
44
repo = "https://github.com/JuliaDynamics/TimeseriesSurrogates.jl.git"
5-
version = "2.5.1"
5+
version = "2.6.0"
66

77
[deps]
88
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"

docs/src/index.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ RandomFourier
6262
TFTDRandomFourier
6363
PartialRandomization
6464
PartialRandomizationAAFT
65+
RelativePartialRandomization
66+
RelativePartialRandomizationAAFT
67+
SpectralPartialRandomization
68+
SpectralPartialRandomizationAAFT
6569
AAFT
6670
TAAFT
6771
IAAFT
@@ -141,4 +145,4 @@ BiBTeX:
141145
title = {TimeseriesSurrogates.jl: a Julia package for generating surrogate data},
142146
journal = {Journal of Open Source Software}
143147
}
144-
```
148+
```

docs/src/methods/fourier_surrogates.md

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ surroplot(ts, s)
2626
```
2727

2828

29-
## Partial randomization
29+
## Partial randomization
3030

31-
### Without rescaling
31+
### Without rescaling
3232

33-
[`PartialRandomization`](@ref) surrogates are similar to random phase surrogates,
34-
but allows for tuning the "degree" of phase randomization.
33+
[`PartialRandomization`](@ref) surrogates are similar to random phase surrogates, but allow for tuning the "degree" of phase randomization.
34+
[`PartialRandomization`](@ref) use an algorithm introduced by [^Ortega1998], which draws random phases as:
35+
36+
$$\phi \to \alpha \xi , \quad \xi \sim \mathcal{U}(0, 2\pi),$$
37+
38+
where $\phi$ is a Fourier phase and $\mathcal{U}(0, 2\pi)$ is a uniform distribution.
39+
Tuning the randomization parameter, $\alpha$, produces a set of time series with varying degrees of randomness in their Fourier phases.
3540

3641
```@example MAIN
3742
using TimeseriesSurrogates, CairoMakie
@@ -43,10 +48,65 @@ s = surrogate(ts, PartialRandomization(0.5))
4348
surroplot(ts, s)
4449
```
4550

51+
In addition to [`PartialRandomization`](@ref), we provide two other algorithms for producing partially randomized surrogates, outlined below.
52+
53+
### Relative partial randomization
54+
55+
The [`PartialRandomization`](@ref) algorithm corresponds to assigning entirely new phases to the Fourier spectrum with some degree of randomness, regardless of any deterministic structure in the original phases. As such, even for $\alpha = 0$ the surrogate time series can differ drastically from the original time series.
56+
57+
By contrast, the [`RelativePartialRandomization`](@ref) procedure draws phases as:
58+
59+
$$\phi \to \phi + \alpha \xi, \quad \xi \sim \mathcal{U}(0, 2\pi).$$
60+
61+
With this algorithm, phases are progressively corrupted by higher values of $\alpha$: surrogates are identical to the original time series for $\alpha = 0$, equivalent to random noise for $\alpha = 1$, and retain some of the structure of the original time series when $0 < \alpha < 1$. This procedure is particularly useful for controlling the degree of chaoticity and non-linearity in surrogates of chaotic systems.
62+
63+
### Spectral partial randomization
64+
65+
Both of the algorithms above randomize phases at all frequency components to the same degree.
66+
To assess the contribution of different frequency components to the structure of a time series, the [`SpectralPartialRandomization`](@ref) algorithm only randomizes phases above a frequency threshold.
67+
The threshold is chosen as the lowest frequency at which the power spectrum of the original time series drops below a fraction $1-\alpha$ of its maximum value (such that the power contained above the frequency threshold is a proportion $\alpha$ of the total power, excluding the zero frequency).
68+
69+
See the figure below for a comparison of the three partial randomization algorithms:
70+
```@example MAIN
71+
using DynamicalSystemsBase # hide
72+
function surrocompare(x, surr_types, params; color = ("#7143E0", 0.9), N=1000, linewidth=3, # hide
73+
transient=0, kwargs...) # hide
74+
fig = Makie.Figure(resolution = (1080, 480), fontsize=22, kwargs...) # hide
75+
for (j, a) in enumerate(surr_types) # hide
76+
for (i, p) in enumerate(params) # hide
77+
ax = Makie.Axis(fig[i,j]) # hide
78+
hidedecorations!(ax) # hide
79+
ax.ylabelvisible = true # hide
80+
lines!(ax, surrogate(x, a(p...))[transient+1:transient+N]; color, linewidth) # hide
81+
j == 1 && (ax.ylabel = "α = $(p)"; ax.ylabelfont = :bold) # hide
82+
i == 1 && (ax.title = string(a)) # hide
83+
end # hide
84+
end # hide
85+
colgap!(fig.layout, 30) # hide
86+
rowgap!(fig.layout, 30) # hide
87+
return fig # hide
88+
end # hide
89+
@inbounds function lorenz_rule!(du, u, p, t) # hide
90+
σ = p[1]; ρ = p[2]; β = p[3] # hide
91+
du[1] = σ*(u[2]-u[1]) # hide
92+
du[2] = u[1]*(ρ-u[3]) - u[2] # hide
93+
du[3] = u[1]*u[2] - β*u[3] # hide
94+
return nothing # hide
95+
end # hide
96+
u0 = [0, 10.0, 0] # hide
97+
p0 = [10, 28, 8/3] # hide
98+
diffeq = (; abstol = 1e-9, reltol = 1e-9) # hide
99+
lorenz = CoupledODEs(lorenz_rule!, u0, p0; diffeq) # hide
100+
x = trajectory(lorenz, 1000; Ttr=500, Δt=0.025)[1][:, 1] # hide
101+
surr_types = [PartialRandomization, RelativePartialRandomization, SpectralPartialRandomization] # hide
102+
params = [0.0, 0.1, 0.25] # hide
103+
surrocompare(x, surr_types, params; transient=1000) # hide
104+
```
105+
106+
46107
### With rescaling
47108

48-
[`PartialRandomizationAAFT`](@ref) adds a rescaling step to the [`PartialRandomization`](@ref) surrogates to obtain surrogates that contain the same values as the original time
49-
series.
109+
[`PartialRandomizationAAFT`](@ref) adds a rescaling step to the [`PartialRandomization`](@ref) surrogates to obtain surrogates that contain the same values as the original time series. AAFT versions of [`RelativePartialRandomization`](@ref) and [`SpectralPartialRandomization`](@ref) are also available.
50110

51111
```@example MAIN
52112
using TimeseriesSurrogates, CairoMakie
@@ -57,7 +117,6 @@ s = surrogate(ts, PartialRandomizationAAFT(0.7))
57117
58118
surroplot(ts, s)
59119
```
60-
61120
## Amplitude adjusted Fourier transform (AAFT)
62121

63122

ext/TimeseriesSurrogatesUncertainData.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ TimeseriesSurrogates.surrogate(x::UncertainDataset, method::Surrogate) = surroga
77
TimeseriesSurrogates.surrogate(x::UncertainValueDataset, method::Surrogate) = surrogate(resample(x), method)
88
TimeseriesSurrogates.surrogate(x::UncertainIndexDataset, method::Surrogate) = surrogate(resample(x), method)
99

10-
end
10+
end

ext/TimeseriesSurrogatesVisualizations.jl

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,27 @@ module TimeseriesSurrogatesVisualizations
33
using TimeseriesSurrogates, Makie
44

55
function TimeseriesSurrogates.surroplot!(fig, x, a;
6-
cx = "#191E44", cs = ("#7143E0", 0.9), nbins = 50, kwargs...
7-
)
6+
cx="#191E44", cs=("#7143E0", 0.9), nbins=50, kwargs...
7+
)
88

99
t = 1:length(x)
1010
# make surrogate timeseries
1111
s = a isa Surrogate ? surrogate(x, method) : a
1212

1313
# Time series
14-
ax1, _ = Makie.lines(fig[1,1], t, x; color = cx, linewidth = 2)
15-
Makie.lines!(ax1, t, s; color = cs, linewidth = 2)
14+
ax1, _ = Makie.lines(fig[1, 1], t, x; color=cx, linewidth=2)
15+
Makie.lines!(ax1, t, s; color=cs, linewidth=2)
1616

1717
# Binned multitaper periodograms
1818
p, psurr = TimeseriesSurrogates.DSP.mt_pgram(x), TimeseriesSurrogates.DSP.mt_pgram(s)
19-
ax3 = Makie.Axis(fig[2,1]; yscale = log10)
20-
Makie.lines!(ax3, p.freq, p.power; color = cx, linewidth = 3)
21-
Makie.lines!(ax3, psurr.freq, psurr.power; color = cs, linewidth = 3)
19+
ax3 = Makie.Axis(fig[2, 1]; yscale=log10)
20+
Makie.lines!(ax3, p.freq, p.power; color=cx, linewidth=3)
21+
Makie.lines!(ax3, psurr.freq, psurr.power; color=cs, linewidth=3)
2222

2323
# Histograms
24-
ax4 = Makie.Axis(fig[3,1])
25-
Makie.hist!(ax4, x; label = "original", bins = nbins, color = cx)
26-
Makie.hist!(ax4, s; label = "surrogate", bins = nbins, color = cs)
24+
ax4 = Makie.Axis(fig[3, 1])
25+
Makie.hist!(ax4, x; label="original", bins=nbins, color=cx)
26+
Makie.hist!(ax4, s; label="surrogate", bins=nbins, color=cs)
2727
Makie.axislegend(ax4)
2828

2929
ax1.xlabel = "time step"
@@ -36,9 +36,9 @@ function TimeseriesSurrogates.surroplot!(fig, x, a;
3636
end
3737

3838
function TimeseriesSurrogates.surroplot(x, s;
39-
cx = "#191E44", cs = ("#7143E0", 0.9), nbins = 50, kwargs...)
40-
fig = Makie.Figure(resolution = (500,500), kwargs...)
39+
cx="#191E44", cs=("#7143E0", 0.9), nbins=50, kwargs...)
40+
fig = Makie.Figure(resolution=(500, 500), kwargs...)
4141
surroplot!(fig, x, s; cx, cs, nbins)
4242
end
4343

44-
end
44+
end

src/TimeseriesSurrogates.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ include("methods/aaft.jl")
3131
include("methods/iaaft.jl")
3232
include("methods/truncated_fourier.jl")
3333
include("methods/partial_randomization.jl")
34+
include("methods/relative_partial_randomization.jl")
35+
include("methods/spectral_partial_randomization.jl")
3436
include("methods/wavelet_based.jl")
3537
include("methods/pseudoperiodic.jl")
3638
include("methods/pseudoperiodic_twin.jl")

src/methods/partial_randomization.jl

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ export PartialRandomization, PartialRandomizationAAFT
22

33
"""
44
PartialRandomization(α = 0.5)
5-
`PartialRandomization` surrogates[^Ortega1998] are similar to [`RandomFourier`](@ref) phase
6-
surrogates, but during the phase randomization step, instead of drawing phases from `[0, 2π]`,
7-
phases are drawn from `[0, 2π]*α`, where `α ∈ [0, 1]`. The authors refers to `α` as the
8-
"degree" of phase randomization, where `α = 0` means `0 %` randomization and
9-
`α = 1` means `100 %` randomization.
5+
`PartialRandomization` surrogates[^Ortega1998] are similar to [`RandomFourier`](@ref)
6+
phase surrogates, but during the phase randomization step, instead of drawing phases
7+
from `[0, 2π]`, phases are drawn from `[0, 2π]*α`, where `α ∈ [0, 1]`. The authors refers to `α` as the "degree" of phase randomization, where `α = 0` means `0 %` randomization
8+
and `α = 1` means `100 %` randomization.
9+
10+
See [`RelativePartialRandomization`](@ref) and [`SpectralPartialRandomization`](@ref) for
11+
alternative partial-randomization algorithms
1012
1113
[^Ortega1998]: Ortega, Guillermo J.; Louis, Enrique (1998). Smoothness Implies Determinism in Time Series: A Measure Based Approach. Physical Review Letters, 81(20), 4345–4348. doi:10.1103/PhysRevLett.81.4345
1214
"""
1315
struct PartialRandomization{T} <: Surrogate
1416
α::T
1517

16-
function PartialRandomization::T) where T <: Real
17-
@assert 0 <= α <= 1
18+
function PartialRandomization::T=0.5) where T <: Real
19+
0 <= α <= 1 || throw(ArgumentError("α must be between 0 and 1"))
1820
return new{T}(α)
1921
end
2022
end
@@ -30,15 +32,15 @@ function surrogenerator(x::AbstractVector, rf::PartialRandomization, rng = Rando
3032
r = abs.(𝓕)
3133
ϕ = angle.(𝓕)
3234
coeffs = zero(r)
33-
34-
init = (inverse = inverse, m = m, coeffs = coeffs, n = n, r = r,
35+
36+
init = (inverse = inverse, m = m, coeffs = coeffs, n = n, r = r,
3537
ϕ = ϕ, shuffled𝓕 = shuffled𝓕)
3638
return SurrogateGenerator(rf, x, s, init, rng)
3739
end
3840

3941
function (sg::SurrogateGenerator{<:PartialRandomization})()
40-
inverse, m, coeffs, n, r, ϕ, shuffled𝓕 =
41-
getfield.(Ref(sg.init),
42+
inverse, m, coeffs, n, r, ϕ, shuffled𝓕 =
43+
getfield.(Ref(sg.init),
4244
(:inverse, :m, :coeffs, :n, :r, , :shuffled𝓕))
4345
s, rng = sg.s, sg.rng
4446
α = sg.method.α
@@ -52,20 +54,20 @@ end
5254
"""
5355
PartialRandomizationAAFT(α = 0.5)
5456
55-
`PartialRandomizationAAFF` surrogates are similar to [`PartialRandomization`](@ref)
56-
surrogates[^Ortega1998], but adds a rescaling step, so that the surrogate has
57+
`PartialRandomizationAAFF` surrogates are similar to [`PartialRandomization`](@ref)
58+
surrogates[^Ortega1998], but adds a rescaling step, so that the surrogate has
5759
the same values as the original time series (analogous to the rescaling done for
5860
[`AAFT`](@ref) surrogates).
59-
Partial randomization surrogates have, to the package authors' knowledge, not been
61+
Partial randomization surrogates have, to the package authors' knowledge, not been
6062
published in scientific literature.
6163
6264
[^Ortega1998]: Ortega, Guillermo J.; Louis, Enrique (1998). Smoothness Implies Determinism in Time Series: A Measure Based Approach. Physical Review Letters, 81(20), 4345–4348. doi:10.1103/PhysRevLett.81.4345
6365
"""
6466
struct PartialRandomizationAAFT{T} <: Surrogate
6567
α::T
6668

67-
function PartialRandomizationAAFT::T) where T <: Real
68-
@assert 0 <= α <= 1
69+
function PartialRandomizationAAFT::T=0.5) where T <: Real
70+
0 <= α <= 1 || throw(ArgumentError("α must be between 0 and 1"))
6971
return new{T}(α)
7072
end
7173
end
@@ -92,4 +94,4 @@ function (sg::SurrogateGenerator{<:PartialRandomizationAAFT})()
9294
s[ix] .= x_sorted
9395

9496
return s
95-
end
97+
end

0 commit comments

Comments
 (0)