Skip to content

Commit

Permalink
Fix logistic example; rename estimate_transitions to `estimate_indi…
Browse files Browse the repository at this point in the history
…cator_changes` (#45)

* clearer file organization

* fix bug in docsring

* correct example

* logistic example fully works

* bound documenter

* rename `estimate_transitions` to `estimate_indicator_changes`
  • Loading branch information
Datseris authored Sep 25, 2023
1 parent 6ad89af commit cf27bb1
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 63 deletions.
5 changes: 4 additions & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8"
DynamicalSystemsBase = "6e36e845-645a-534a-86f2-f5d4aa5a06b4"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"

[compat]
Documenter = "0.27"
2 changes: 1 addition & 1 deletion docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```@docs
WindowedIndicatorConfig
estimate_transitions
estimate_indicator_changes
WindowedIndicatorResults
```

Expand Down
52 changes: 27 additions & 25 deletions docs/src/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ indistrings = ("var", "ar1", "pe")
# instead, there is a sharp transition between periodic and chaotic motion.
# Hence, we shouldn't be using any trend-based change metrics.
# Instead, we will use the most basic change metric, [`difference_of_means`](@ref).
# With this metric it also makes most sense to use as `stride_cha` half the `width_cha`.
# With this metric it also makes most sense to use as stride half the window width
metric = difference_of_means

width_ind = N÷100
Expand All @@ -71,19 +71,23 @@ config = WindowedIndicatorConfig(indicators, metric;
width_ind, width_cha, stride_cha,
)

results = estimate_transitions(config, x, rs)
results = estimate_indicator_changes(config, x, rs)

# Let's now plot the change metrics of the indicators

function plot_change_metrics()
fig, ax = lines(rs, x; axis = (ylabel = "input",))
fig, ax = lines(rs, x; axis = (ylabel = "input",), figure = (resolution = (600, 600),))
hidexdecorations!(ax; grid = false)
## plot all change metrics
for (i, c) in enumerate(eachcol(results.x_change))
ax, = scatterlines(fig[i+1, 1], results.t_change, c;
axis = (ylabel = indistrings[i],), label = "input"
)
i < 4 && hidexdecorations!(ax; grid = false)
if i < 3
hidexdecorations!(ax; grid = false)
else
ax.xlabel = "r (time)"
end
end
return fig
end
Expand All @@ -99,11 +103,9 @@ fig = plot_change_metrics()
# start of the timeseries, so we can safely ignore the spike at r ≈ 3.83.

# ### Significance via random Fourier surrogates
# %% #src

# One way to test for significance would be via the standard way as in the [Tutorial](@ref),
# utilizing surrogate timeseries and [`SurrogatesSignificance`](@ref).
# There are

# Let's do it here for an example, but, we have to be **careful**.
# It is crucial that for permutation entropy we use `:right` as the `tail`,
Expand All @@ -118,33 +120,31 @@ surromethod = RandomFourier()
function overplot_surrogate_significance!(fig, surromethod, color = "black")

signif = SurrogatesSignificance(;
n = 1000, tail = [:both, :both, :right], surrogate = surromethod
n = 1000, tail = [:both, :both, :right], surromethod
)
pvalues = significant_transitions(results, signif)
flags = pvalues .< 0.05

# To make things visually clear, we will plot an example surrogate for each indicator
# and also plot the flags with same color
flags = significant_transitions(results, signif)

## and also plot the flags with same color
for (i, indicator) in enumerate(indicators)
## generate surrogate change metric timeseries
## To make things visually clear, we will also plot some example surrogate
## timeseries for each indicator and change metric pair
for _ in 1:10
s = TimeseriesSurrogates.surrogate(x, surrogate)
s = TimeseriesSurrogates.surrogate(x, surromethod)
p = windowmap(indicator, s; width = width_ind)
q = windowmap(metric, p; width = width_cha, stride = stride_cha)
lines!(fig[i+1, 1], results.t_change, q; color = (color, 0.2))
lines!(fig[i+1, 1], results.t_change, q; color = (color, 0.2), linewidth = 1)
end
## Plot the flags with flags
## Plot the flags as vertical dashed lines
vlines!(fig[i+1, 1], results.t_change[flags[:, i]];
label = string(nameof(typeof(surromethod))), color = color, linestyle = :dash, linewidth = 3
color = color, linestyle = :dash, linewidth = 3
)
axislegend(content(fig[i+1, 1]); unique = true, position = :lt)
end

end # function
## add a title to the figure with how we estimate significance
content(fig[1, 1]).title = "surrogates: "*string(nameof(typeof(surromethod)))
end

surromethod = RandomFourier()
overplot_significance!(fig, surromethod)
overplot_surrogate_significance!(fig, surromethod)

fig

Expand All @@ -163,8 +163,10 @@ fig = plot_change_metrics()
overplot_surrogate_significance!(fig, surromethod, "red")
fig

# Here the results do not change because the permutation entropy is an exceptionally well
# suited indicator for this application scenario; But in other cases where things
# The results are better for the variance and AR1 indicators.
# For the permutation entropy the results do not change because it already is an
# exceptionally well
# suited indicator for this application scenario. But in other cases where things
# are not as clear, or data are contaminated with noise, or we have shorter data,
# choosing a more suitable
# surrogate generator may make the difference between a false positive or not.
Expand All @@ -183,8 +185,8 @@ flags = significant_transitions(results, QuantileSignificance())
## Plot the flags
for (i, indicator) in enumerate(indicators)
vlines!(fig[i+1, 1], results.t_change[flags[:, i]];
label = "quantile flags", color = Cycled(3), linestyle = :dash, linewidth = 3
color = Cycled(3), linestyle = :dash, linewidth = 3
)
axislegend(content(fig[i+1, 1]); unique = true, position = :lt)
end
content(fig[1, 1]).title = "significance from quantile"
fig
8 changes: 4 additions & 4 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ Performing the step-by-step analysis of transition indicators is possible and mi

TransitionsInTimeseries.jl wraps this typical workflow into a simple, extendable, and modular API that researchers can use with little effort. In addition, it allows performing the same analysis for several indicators / change metrics in one go.

The interface is simple, and directly parallelizes the [Workflow](@ref). It is based on the creation of a [`TransitionsSurrogatesConfig`](@ref), which contains a list of indicators, and corresponding metrics, to use for doing the above analysis. It also specifies what kind of surrogates to generate.
The interface is simple, and directly parallelizes the [Workflow](@ref). It is based on the creation of a [`WindowedIndicatorConfig`](@ref), which contains a list of indicators, and corresponding metrics, to use for doing the above analysis.

The following blocks illustrate how the above extensive example is re-created in TransitionsInTimeseries.jl
The following blocks illustrate how the above extensive example is re-created in TransitionsInTimeseries.jl. First, let's load the timeseries again.

```@example MAIN
using TransitionsInTimeseries, CairoMakie
Expand All @@ -195,7 +195,7 @@ fig
To perform all of the above analysis we follow a 2-step process.

Step 1, we decide what indicators and change metrics to use in [`WindowedIndicatorConfig`](@ref) and apply those via
a sliding window to the input timeseries using [`estimate_transitions`](@ref).
a sliding window to the input timeseries using [`estimate_indicator_changes`](@ref).

```@example MAIN
# These indicators are suitable for Critical Slowing Down
Expand All @@ -210,7 +210,7 @@ config = WindowedIndicatorConfig(indicators, change_metrics;
)
# choices are processed
results = estimate_transitions(config, input, t)
results = estimate_indicator_changes(config, input, t)
```

From `result` we can plot the change metric timeseries
Expand Down
3 changes: 2 additions & 1 deletion src/TransitionsInTimeseries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ include("change_metrics/slope.jl")
include("change_metrics/valuediff.jl")

include("analysis/windowed_indicators.jl")
include("analysis/api_significance.jl")
include("analysis/surrogates_significance.jl")
include("analysis/quantile_significance.jl")

Expand All @@ -51,7 +52,7 @@ export RidgeRegressionSlope, PrecomputedRidgeRegressionSlope
export difference_of_means

# analysis
export WindowedIndicatorConfig, estimate_transitions, WindowedIndicatorResults
export WindowedIndicatorConfig, estimate_indicator_changes, WindowedIndicatorResults
export TransitionsSignificance, significant_transitions
export QuantileSignificance, SurrogatesSignificance

Expand Down
19 changes: 19 additions & 0 deletions src/analysis/api_significance.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
TransitionsSignificance
Supertype used to test for significance in [`significant_transitions`](@ref).
Valid subtypes are:
- [`SurrogatesSignificance`](@ref).
"""
abstract type TransitionsSignificance end


"""
significant_transitions(res::WindowedIndicatorResults, signif::TransitionsSignificance)
Estimate significant transtions in `res` using the method described by `signif`.
Return `flags`, a Boolean matrix with identical size as `res.x_change`.
It contains trues wherever a change metric of `res` is deemed significant.
"""
function significant_transitions(::WindowedIndicatorResults, ::TransitionsSignificance) end
2 changes: 1 addition & 1 deletion src/analysis/quantile_significance.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
QuantileSignificance(; p = 0.95, tail = :right) <: TransitionsSignificance
QuantileSignificance(; p = 0.95, tail = :right) <: TransitionsSignificance
A configuration struct for significance testing [`significant_transitions`](@ref).
When used with [`WindowedIndicatorResults`](@ref), significance is estimated as
Expand Down
27 changes: 6 additions & 21 deletions src/analysis/surrogates_significance.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
"""
TransitionsSignificance
Supertype used to test for significance in [`significant_transitions`](@ref).
Valid subtypes are:
- [`SurrogatesSignificance`](@ref).
"""
abstract type TransitionsSignificance end


"""
SurrogatesSignificance <: TransitionsSignificance
SurrogatesSignificance(; surrogate = RandomFourier(), n = 10_000, tail = :both, rng)
Expand Down Expand Up @@ -40,12 +29,16 @@ in the field `.pvalues` (to e.g., threshold with different `p`).
The p-value is simply the proportion of surrogate change metric values
that exceed (for `tail = :right`) or subseed (`tail = :left`) the original change metric
at each given time point. Use `tail = :left` if the surrogate data are expected to have
higher change metric, discriminatory statistic values. This is the case for statistics
at each given time point. Use `tail = :left` if the surrogate data are expected to have
higher change metric, discriminatory statistic values. This is the case for statistics
that quantify entropy. For statistics that quantify autocorrelation, use `tail = :right`
instead. For anything else, use the default `tail = :both`.
An iterable of `tail` values can also be given, in which case a specific `tail`
is used for each change metric in [`WindowedIndicatorResults`](@ref).
Note that the raw p-values can be accessed in the field `.pvalues`, after calling the
[`significant_transitions`](@ref) function with `SurrogatesSignificance`, in case you wish
to obtain a different threshold of the p-values.
"""
mutable struct SurrogatesSignificance{S<:Surrogate, T, R} <: TransitionsSignificance
surrogate::S
Expand All @@ -64,14 +57,6 @@ function SurrogatesSignificance(;
return SurrogatesSignificance(surromethod, n, tail, rng, p, zeros(1,1))
end


"""
significant_transitions(res::WindowedIndicatorResults, signif::TransitionsSignificance)
Estimate significant transtions in `res` using the method described by `signif`.
Return `flags`, a Boolean matrix with identical size as `res.x_change`.
It contains trues wherever a change metric of `res` is deemed significant.
"""
function significant_transitions(res::WindowedIndicatorResults, signif::SurrogatesSignificance)
(; indicators, change_metrics) = res.wim
tail = signif.tail
Expand Down
14 changes: 7 additions & 7 deletions src/analysis/windowed_indicators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
WindowedIndicatorConfig(indicators, change_metrics; kwargs...) → config
A configuration struct for TransitionsInTimeseries.jl that collects
what indicators and corresponding metrics to use in the [`transitions_analysis`](@ref).
what indicators and corresponding metrics to use in the [`estimate_indicator_changes`](@ref).
`indicators` is a tuple of indicators (or a single indicator).
`change_metrics` is also a tuple or a single function. If a single function,
Expand All @@ -21,7 +21,7 @@ for more information.
- `width_cha::Int=50, stride_cha::Int=1`: width and stride given to [`WindowViewer`](@ref)
to compute the change metric timeseries from the indicator timeseries.
- `whichtime = midpoint`: The time vector corresponding to the indicators / change metric
timeseries is obtained from `t` in [`transitions_analysis`](@ref) using the keyword
timeseries is obtained from `t` in [`estimate_indicator_changes`](@ref) using the keyword
`whichtime`. Options include:
- `last`: use the last timepoint of each window
- `midpoint`: use the mid timepoint of each time window
Expand Down Expand Up @@ -79,7 +79,7 @@ function WindowedIndicatorConfig(
end

"""
estimate_transitions(config::WindowedIndicatorConfig, x [,t]) → output
estimate_indicator_changes(config::WindowedIndicatorConfig, x [,t]) → output
Estimate possible transitions for input timeseries `x` using a sliding window approach
as described by `config`:
Expand All @@ -94,12 +94,12 @@ Return the output as [`WindowedIndicatorResults`](@ref) which can be given to
[`significant_transitions`](@ref) to deduce which possible transitions are statistically
significant using a variety of significance tests.
"""
function estimate_transitions(x, config::WindowedIndicatorConfig)
function estimate_indicator_changes(x, config::WindowedIndicatorConfig)
t = eachindex(x)
return estimate_transitions(t, x, config)
return estimate_indicator_changes(t, x, config)
end

function estimate_transitions(config::WindowedIndicatorConfig, x, t = eachindex(x))
function estimate_indicator_changes(config::WindowedIndicatorConfig, x, t = eachindex(x))
# initialize time vectors
t_indicator = windowmap(config.whichtime, t; width = config.width_ind,
stride = config.stride_ind)
Expand Down Expand Up @@ -139,7 +139,7 @@ end
"""
WindowedIndicatorResults
A struct containing the output of [`estimate_transitions`](@ref) used with
A struct containing the output of [`estimate_indicator_changes`](@ref) used with
[`WindowedIndicatorConfig`](@ref).
It can be used for further analysis, visualization,
or given to [`significant_transitions`](@ref).
Expand Down
2 changes: 1 addition & 1 deletion src/indicators/critical_slowing_down.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using Statistics: var
"""
ar1_whitenoise(x::AbstractVector)
Return the AR1 regression coefficient $$$ of a time series $$x$$ by computing
Return the AR1 regression coefficient of a time series `x` by computing
the analytic solution of the least-square parameter estimation under white-noise
assumption for the data-generating process:
Expand Down
2 changes: 1 addition & 1 deletion test/full_analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ using TransitionsInTimeseries, Test
width_cha = 100, stride_cha = 1, whichtime = last,
)

res = estimate_transitions(config, x, t)
res = estimate_indicator_changes(config, x, t)

# The trend of mean(windowview) is the stride for x=t
meantrend_ground_truth = fill(1, length(res.t_change))
Expand Down

0 comments on commit cf27bb1

Please sign in to comment.