diff --git a/Project.toml b/Project.toml index 47078df..40f06ce 100644 --- a/Project.toml +++ b/Project.toml @@ -20,7 +20,7 @@ TimeseriesSurrogates = "c804724b-8c18-5caa-8579-6025a0767c70" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [extensions] -TITVisualizations = "Makie" +TransitionVisualizations = "Makie" [compat] DelimitedFiles = "1" diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..4ea418d --- /dev/null +++ b/codecov.yml @@ -0,0 +1,3 @@ +# sample regex patterns +ignore: + - "ext/*" \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 93c75ba..c2069c6 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -33,4 +33,4 @@ bib = CitationBibliography(joinpath(@__DIR__, "src", "refs.bib"); style=:authory build_docs_with_style(pages, TransitionsInTimeseries, StatsBase; authors = "Jan Swierczek-Jereczek , "* - "George Datseris ", warnonly = true, bib) \ No newline at end of file + "George Datseris ", bib) \ No newline at end of file diff --git a/docs/src/api.md b/docs/src/api.md index 4bd25ab..81dfb47 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -44,6 +44,7 @@ ar1_whitenoise ```@docs LowfreqPowerSpectrum +PrecomputedLowfreqPowerSpectrum ``` ### Nonlinear dynamics @@ -68,6 +69,7 @@ and giving the created `indicator` to e.g., [`SlidingWindowConfig`](@ref). kendalltau spearman RidgeRegressionSlope +PrecomputedRidgeRegressionSlope ``` ### Value distribution differences @@ -103,3 +105,17 @@ windowmap! ```@docs load_linear_vs_doublewell() ``` + +## Visualization + +```@docs +plot_indicator_changes +plot_significance! +plot_changes_significance +``` + +## Utils + +```docs +default_window_width +``` \ No newline at end of file diff --git a/docs/src/tutorial.jl b/docs/src/tutorial.jl index 30eb52b..21c6510 100644 --- a/docs/src/tutorial.jl +++ b/docs/src/tutorial.jl @@ -2,13 +2,13 @@ # Tutorial -## [Workflow](@id workflow) +## [Workflow] (@id workflow) Computing transition indicators consists of the following steps: -1. Doing any preprocessing of raw data first, such as detrending (_not part of TransitionsInTimeseries.jl_). This yields the **input timeseries**. +1. Doing any preprocessing of raw data first, such as detrending. _This not part of TransitionsInTimeseries.jl_ and yields the **input timeseries**. 2. Estimating the timeseries of an indicator by sliding a window over the input timeseries. -3. Computing the changes of the indicator by sliding a window over its timeseries. +3. Computing the changes of the indicator by sliding a window over its timeseries. Alternatively, the change metric can be estimated over the whole segment, as examplified in the section [Segmented windows](@ref segmented_windows). 4. Generating many surrogates that preserve important statistical properties of the original timeseries. 5. Performing step 2 and 3 for the surrogate timeseries. 6. Checking whether the indicator change timeseries of the real timeseries shows a significant feature (trend, jump or anything else) when compared to the surrogate data. @@ -185,7 +185,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). +The interface is simple, and directly parallelizes the [Workflow](@ref workflow). It is based on the creation of a [`IndicatorsChangesConfig`](@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. + +### Sliding windows The following blocks illustrate how the above extensive example is re-created in TransitionsInTimeseries.jl =# @@ -223,41 +225,39 @@ config = SlidingWindowConfig(indicators, change_metrics; results = estimate_indicator_changes(config, input, t) #= -From `result` we can plot the change metric timeseries: +We can conveniently plot the information contained in `results` by using +`plot_indicator_changes`: =# -fig, axs = gridfig(3, 1) -lines!(axs[1], t, input; label = "input", color = Cycled(2)) -scatter!(axs[2], results.t_change, results.x_change[:, 1]; - label = "var slopes", color = Cycled(3)) -scatter!(axs[3], results.t_change, results.x_change[:, 2]; - label = "ar1 slopes", color = Cycled(4)) -[xlims!(ax, 0, 50) for ax in axs] -fig +fig = plot_indicator_changes(results) #= Step 2 is to estimate significance using [`SurrogatesSignificance`](@ref) -and the function [`significant_transitions`](@ref). +and the function [`significant_transitions`](@ref). Finally, we can +conveniently plot the results obtained by updating the figure obtained +above with `plot_significance!`: =# signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) +plot_significance!(fig, results, signif, flags = flags) +fig #= -We can now plot the p-values corresponding to each time series of the change metrics. From the `flags` we can additionally obtain the time points where _both_ indicators show significance, via a simple reduction: +### [Segmented windows] (@id segmented_windows) + +The analysis shown so far relies on sliding windows of the change metric. +This is particularly convenient for transition detection tasks. Segmented +windows for the change metric computation are however preferable when it +comes to prediction tasks. By only slightly modifying the syntax used so far, +one can perform the same computations on segmented windows, as well as +visualise the results conveniently: =# -fig, axs = gridfig(2, 1) -lines!(axs[1], vcat(0.0, t), x_nlinear; label = "raw", color = Cycled(1)) -lines!(axs[1], t, input; label = "input", color = Cycled(2)) -scatter!(axs[2], results.t_change, signif.pvalues[:, 1]; - label = "var p-values", color = Cycled(3)) -scatter!(axs[2], results.t_change, signif.pvalues[:, 2]; - label = "ar1 p-values", color = Cycled(4)) - -flagsboth = vec(reduce(&, flags; dims = 2)) -vlines!(axs[1], results.t_change[flagsboth]; label = "flags", color = ("black", 0.1)) - -[axislegend(ax) for ax in axs] -[xlims!(ax, 0, 50) for ax in axs] -fig \ No newline at end of file +config = SegmentedWindowConfig(indicators, change_metrics, + t[1:1], t[1200:1200]; whichtime = last, width_ind = 200, + min_width_cha = 100) +results = estimate_indicator_changes(config, input, t) +signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) +flags = significant_transitions(results, signif) +fig = plot_changes_significance(results, signif) diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index f577901..235ded7 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -4,13 +4,13 @@ EditURL = "tutorial.jl" # Tutorial -## [Workflow](@id workflow) +## [Workflow] (@id workflow) Computing transition indicators consists of the following steps: -1. Doing any preprocessing of raw data first, such as detrending (_not part of TransitionsInTimeseries.jl_). This yields the **input timeseries**. +1. Doing any preprocessing of raw data first, such as detrending. _This not part of TransitionsInTimeseries.jl_ and yields the **input timeseries**. 2. Estimating the timeseries of an indicator by sliding a window over the input timeseries. -3. Computing the changes of the indicator by sliding a window over its timeseries. +3. Computing the changes of the indicator by sliding a window over its timeseries. Alternatively, the change metric can be estimated over the whole segment, as examplified in the section [Segmented windows](@ref segmented_windows). 4. Generating many surrogates that preserve important statistical properties of the original timeseries. 5. Performing step 2 and 3 for the surrogate timeseries. 6. Checking whether the indicator change timeseries of the real timeseries shows a significant feature (trend, jump or anything else) when compared to the surrogate data. @@ -187,7 +187,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 [`SurrogatesSignificance`](@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 workflow). It is based on the creation of a [`IndicatorsChangesConfig`](@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. + +### Sliding windows The following blocks illustrate how the above extensive example is re-created in TransitionsInTimeseries.jl @@ -225,43 +227,44 @@ config = SlidingWindowConfig(indicators, change_metrics; results = estimate_indicator_changes(config, input, t) ```` -From `result` we can plot the change metric timeseries: +We can conveniently plot the information contained in `results` by using +`plot_indicator_changes`: ````@example tutorial -fig, axs = gridfig(3, 1) -lines!(axs[1], t, input; label = "input", color = Cycled(2)) -scatter!(axs[2], results.t_change, results.x_change[:, 1]; - label = "var slopes", color = Cycled(3)) -scatter!(axs[3], results.t_change, results.x_change[:, 2]; - label = "ar1 slopes", color = Cycled(4)) -[xlims!(ax, 0, 50) for ax in axs] -fig +tv = plot_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) +tv.fig ```` Step 2 is to estimate significance using [`SurrogatesSignificance`](@ref) -and the function [`significant_transitions`](@ref). +and the function [`significant_transitions`](@ref). Finally, we can +conveniently plot the results obtained by updating the figure obtained +above with `plot_significance!`: ````@example tutorial signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) +plot_significance!(tv, signif, flags = flags) +tv.fig ```` -We can now plot the p-values corresponding to each time series of the change metrics. From the `flags` we can additionally obtain the time points where _both_ indicators show significance, via a simple reduction: - -````@example tutorial -fig, axs = gridfig(2, 1) -lines!(axs[1], vcat(0.0, t), x_nlinear; label = "raw", color = Cycled(1)) -lines!(axs[1], t, input; label = "input", color = Cycled(2)) -scatter!(axs[2], results.t_change, signif.pvalues[:, 1]; - label = "var p-values", color = Cycled(3)) -scatter!(axs[2], results.t_change, signif.pvalues[:, 2]; - label = "ar1 p-values", color = Cycled(4)) +### [Segmented windows] (@id segmented_windows) -flagsboth = vec(reduce(&, flags; dims = 2)) -vlines!(axs[1], results.t_change[flagsboth]; label = "flags", color = ("black", 0.1)) +The analysis shown so far relies on sliding windows of the change metric. +This is particularly convenient for transition detection tasks. Segmented +windows for the change metric computation are however preferable when it +comes to prediction tasks. By only slightly modifying the syntax used so far, +one can perform the same computations on segmented windows, as well as +visualise the results conveniently: -[axislegend(ax) for ax in axs] -[xlims!(ax, 0, 50) for ax in axs] -fig +````@example tutorial +config = SegmentedWindowConfig(indicators, change_metrics, + t[1:1], t[1200:1200]; whichtime = last, width_ind = 200, + min_width_cha = 100) +results = estimate_indicator_changes(config, input, t) +signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) +flags = significant_transitions(results, signif) +tv = plot_changes_significance(results, signif, + additional_timeseries = x_nlinear[2:end]) +tv.fig ```` diff --git a/ext/TITVisualizations.jl b/ext/TITVisualizations.jl deleted file mode 100644 index 2a2f843..0000000 --- a/ext/TITVisualizations.jl +++ /dev/null @@ -1,69 +0,0 @@ -module TITVisualizations - -using TransitionsInTimeseries, Makie - -function TransitionsInTimeseries.plot_indicator_changes(res::SlidingWindowResults, - colors = ["#7143E0", "#0A9A84", "#191E44", "#AF9327", "#701B80", "#2E6137",], - skip = Int[], - ) - config = res.config - fig = Figure() - axts = Axis(fig[1,1]; ylabel = "input") - if isnothing(config.indicators) - axcha = Axis(fig[2,1]; ylabel = "changes") - linkxaxes!(axts, axcha) - else - axind = Axis(fig[2,1]; ylabel = "indicators") - axcha = Axis(fig[3,1]; ylabel = "changes", xlabel = "time") - linkxaxes!(axts, axind, axcha) - hidexdecorations!(axind; grid = false) - end - hidexdecorations!(axts; grid = false) - - lines!(axts, res.t, res.x; color = "black", linewidth = 3) - for (i, cha) in enumerate(config.change_metrics) - i ∈ skip && continue - if !isnothing(config.indicators) - lines!(axind, res.t_indicator, res.x_indicator[:, i]; - color = colors[i], linewidth = 3, label = string(nameof(config.indicators[i])) - ) - end - lines!(axcha, res.t_change, res.x_change[:, i]; - color = colors[i], linewidth = 3, label = string(nameof(cha)) - ) - end - - !isnothing(config.indicators) && axislegend(axind) - axislegend(axcha) - return fig -end - -function TransitionsInTimeseries.plot_significance!(fig::Figure, res::SlidingWindowResults, signif::SurrogatesSignificance; - colors = ["#7143E0", "#0A9A84", "#191E44", "#AF9327", "#701B80", "#2E6137",], - skip = Int[], - nsurro = 20, - ) - config = res.config - for (i, cha) in enumerate(config.change_metrics) - i ∈ skip && continue - - for _ in 1:nsurro - s = TimeseriesSurrogates.surrogate(res.x, signif.surrogate) - if isnothing(config.indicators) - p = s - chaj = 2 - else - p = windowmap(config.indicators[i], s; width = config.width_ind, stride = config.stride_ind) - lines!(fig[2, 1], res.t_indicator, q; color = (colors[i], 2/nsurro), linewidth = 1) - chaj = 3 - end - q = windowmap(cha, p; width = config.width_cha, stride = config.stride_cha) - lines!(fig[chaj, 1], res.t_change, q; color = (colors[i], 2/nsurro), linewidth = 1) - end - end - - return fig -end - - -end \ No newline at end of file diff --git a/ext/TransitionVisualizations.jl b/ext/TransitionVisualizations.jl new file mode 100644 index 0000000..e4e7804 --- /dev/null +++ b/ext/TransitionVisualizations.jl @@ -0,0 +1,201 @@ +module TransitionVisualizations + +using TransitionsInTimeseries, Makie + +# Default options for plotting utilities +const default_accent_linewidth = 3 +const default_colors = ["#7143E0", "#0A9A84", "#191E44", "#AF9327", "#701B80", "#2E6137"] + +default_indicator_label(res::IndicatorsChangesResults) = [shortname( + ind) for ind in res.config.indicators] + +default_chametric_label(res::IndicatorsChangesResults) = [shortname( + cha_metric) for cha_metric in res.config.change_metrics] + +shortname(metric) = string(nameof(metric)) +shortname(metric::PrecomputedRidgeRegressionSlope) = "Regression slope" +shortname(metric::RidgeRegressionSlope) = "Regression slope" + +# Plot results from full analysis +function TransitionsInTimeseries.plot_changes_significance(res, signif; kwargs...) + fig = plot_indicator_changes(res; kwargs...) + plot_significance!(fig, res, signif; kwargs...) + return fig +end + +# Plot results of original input signal +function TransitionsInTimeseries.plot_indicator_changes(res::SlidingWindowResults; + colors = default_colors, + indicator_names = default_indicator_label(res), + chametric_names = default_chametric_label(res), + accent_linewidth = default_accent_linewidth, + kwargs..., + ) + + fig, n = init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, accent_linewidth) + lineplot_metrics!(fig, n, res.config, res.t_indicator, res.x_indicator, + res.t_change, res.x_change, colors, accent_linewidth) + + return fig +end + +function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResults; + colors = default_colors, + indicator_names = default_indicator_label(res), + chametric_names = default_chametric_label(res), + accent_linewidth = default_accent_linewidth, + kwargs..., + ) + + fig, n = init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, accent_linewidth) + for k in eachindex(res.t_indicator) + Makie.lines!(contents(fig[1, 1])[1], res.t[k], res.x[k], color = colors[1], + linewidth = accent_linewidth) + lineplot_metrics!(fig, n, res.config, res.t_indicator[k], res.x_indicator[k], + res.t_change[k], res.x_change[k, :], colors, accent_linewidth) + end + + return fig +end + +# utils for plot_indicator_changes +function init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, accent_linewidth) + + fig = Makie.Figure(size = (700, 450), fontsize = 12) + rlabels = vcat([""], indicator_names) + llabels = vcat(["Input"], chametric_names) + n = length(llabels) + + # rowaspect = 5 + raxs = [Makie.Axis(fig[i, 1], ylabel = rlabels[i], + xticklabelsvisible = false, yaxisposition = :right, ygridvisible = false, + ylabelcolor = colors[2], yticklabelcolor = colors[2]) for i in 1:n] + laxs = [Makie.Axis(fig[i, 1], ylabel = llabels[i], + xticklabelsvisible = false, ygridvisible = false, + ylabelcolor = colors[1], yticklabelcolor = colors[1]) for i in 1:n] + Makie.linkxaxes!(laxs..., raxs...) + + hidedecorations!(raxs[1]) + Makie.lines!(laxs[1], res.t, res.x, color = colors[1], linewidth = accent_linewidth) + + raxs[end].xticklabelsvisible = true + raxs[end].xlabel = "Time" + Makie.rowgap!(fig.layout, 10) + + elements = [LineElement(color = (colors[1], transparency), linewidth = lw) for + (lw, transparency) in [(accent_linewidth, 1), (1, 0.5)]] + labels = ["original signal", "surro signals"] + width = 0.5 + if length(res.t_indicator[1]) > 1 + elements = vcat(elements, [MarkerElement(marker = :circle, color = colors[1], + strokecolor = :transparent, markersize = ms) for ms in [10, 5]]) + labels = vcat(labels, ["original change metric", "surro change metrics"]) + width = 1 + end + Legend(fig[0, 1], elements, labels, nbanks = 4, rowgap = 0, colgap = 10, + width = Relative(width)) + rowsize!(fig.layout, 0, Auto(0.3)) + return fig, n +end + +function lineplot_metrics!(fig, n, config, t_ind, x_ind, t_cha, x_cha, + colors, accent_linewidth) + for i in 2:n + j = i-1 + if !isnothing(config.indicators) + Makie.lines!(contents(fig[i, 1])[2], t_ind, x_ind[:, j], color = colors[2], + linewidth = accent_linewidth) + end + if length(t_cha) > 1 + lines!(contents(fig[i, 1])[1], t_cha, x_cha[:, j], color = colors[1], + linewidth = accent_linewidth) + else + Makie.scatter!(contents(fig[i, 1])[1], t_cha, x_cha[j], color = colors[1], + markersize = 10) + end + end +end + +# Plot results of surrogates +function TransitionsInTimeseries.plot_significance!( + fig::Figure, + res::SlidingWindowResults, + signif::SurrogatesSignificance; + colors = default_colors, + flags = nothing, + nsurro = 20, + kwargs..., + ) + + lines_over_surro!(fig, nsurro, res.t, res.t_indicator, res.t_change, res.x, + signif, res.config, flags, colors) + return nothing +end + +function TransitionsInTimeseries.plot_significance!( + fig::Figure, + res::SegmentedWindowResults, + signif::SurrogatesSignificance; + colors = default_colors, + flags = nothing, + nsurro = 20, + kwargs..., + ) + + for k in eachindex(res.t_indicator) + if isnothing(flags) + lines_over_surro!(fig, nsurro, res.t[res.i1[k]:res.i2[k]], + res.t_indicator[k], res.t_change[k], res.x[res.i1[k]:res.i2[k]], + signif, res.config, nothing, colors) + else + lines_over_surro!(fig, nsurro, res.t[res.i1[k]:res.i2[k]], + res.t_indicator[k], res.t_change[k], res.x[res.i1[k]:res.i2[k]], + signif, res.config, flags[k, :], colors) + end + end + return nothing +end + +# utils for plot_significance! +function lines_over_surro!(fig, nsurro, t, t_ind, t_cha, x, signif, config, + flags, colors) + c = zeros(length(config.change_metrics), nsurro) + for ns in 1:nsurro + s = TimeseriesSurrogates.surrogate(x, signif.surrogate) + Makie.lines!(contents(fig[1, 1])[1], t, s; color = (colors[1], 2/nsurro), linewidth = 1) + for (i, cha) in enumerate(config.change_metrics) + + if isnothing(config.indicators) + p = s + else + p = windowmap(config.indicators[i], s; width = config.width_ind, + stride = config.stride_ind) + Makie.lines!(contents(fig[i+1, 1])[2], t_ind, p; color = (colors[2], 2/nsurro), + linewidth = 1) + end + if length(t_cha) > 1 + q = windowmap(cha, p; width = config.width_cha, stride = config.stride_cha) + Makie.lines!(contents(fig[i+1, 1])[1], t_cha, q; color = (colors[1], 2/nsurro), + linewidth = 1) + else + cha = precompute(cha, eachindex(p)) + q = windowmap(cha, p; width = length(p), stride = 1) + Makie.scatter!(contents(fig[i+1, 1])[1], t_cha, q[1], color = (colors[1], 2/nsurro), + markersize = 5) + end + if !isnothing(flags) && ns == 1 && length(t_cha) > 1 + Makie.vlines!(contents(fig[i+1, 1])[1], t_cha[flags[:, i]]; + color = (:black, 0.1)) + elem = [PolyElement(color = (:black, 0.5), strokecolor = :transparent)] + axislegend(contents(fig[i+1, 1])[1], elem, ["p<$(signif.p)"], position = :lt) + elseif length(t_cha) == 1 + c[i, ns] = q[1] + end + end + end +end + +end \ No newline at end of file diff --git a/src/TransitionsInTimeseries.jl b/src/TransitionsInTimeseries.jl index f7bea2e..0f1d6cb 100644 --- a/src/TransitionsInTimeseries.jl +++ b/src/TransitionsInTimeseries.jl @@ -68,7 +68,4 @@ export default_window_width # load_data.jl export load_linear_vs_doublewell -# visualizations -export plot_indicator_changes, plot_significance! - end # module TransitionsInTimeseries \ No newline at end of file diff --git a/src/misc/windowing.jl b/src/misc/windowing.jl index 9f8d983..930cc4d 100644 --- a/src/misc/windowing.jl +++ b/src/misc/windowing.jl @@ -13,8 +13,8 @@ window with a given `width`, incrementing the window views with the given `stride`. You can use this directly with `map`, such as `map(std, WindowViewer(x, ...))` would give you the moving-window-timeseries of the `std` of `x`. -If not given, the keywords `width, stride` are taken as -[`default_window_width(x)`](@ref) and `1`. +If not given, the keywords `width, stride` are respectively taken as +`default_window_width(x)` and `1`. """ function WindowViewer( x::AbstractVector; diff --git a/src/visualizations.jl b/src/visualizations.jl index 02cdc63..1b25e33 100644 --- a/src/visualizations.jl +++ b/src/visualizations.jl @@ -1,9 +1,46 @@ -function plot_changes_significance(res::IndicatorsChangesConfig, signif::TransitionsSignificance) - fig = plot_indicator_changes() - plot_significance!() - return fig -end +""" + plot_indicator_changes(res) → (fig, axs) +Return `fig::Figure` and `axs::Matrix{Axis}`, on which `res::IndicatorsChangesResults` +has been visualised. +## Keyword arguments: + - `colors = default_colors` sets the colors of the line plots that are to + be cycled through. The default correspond to the color scheme of Julia Dynamics. + - `indicator_names = default_indicator_label(res), chametric_names = + default_chametric_label(res)` sets the labels for the indicators and the change + metrics, with the default inferring them from the names of `res.config.indicators` + and `res.config.change_metrics`. + - `accent_linewidth = 3` sets the line width for the original signals (the + surrogates have `linewidth = 1`) +""" function plot_indicator_changes end -function plot_significance! end \ No newline at end of file +""" + plot_significance!(axs, res, signif) +Update the `axs::Matrix{Axis}` of a figure obtained with `plot_indicator_changes(res)` +with the `signif::SurrogatesSignificance`. + +## Keyword arguments: + - `flags = nothing` provides the significance flags, for instance obtained by + thresholding the pvalues. + - `nsurro = 20` sets the number of surrogates to plot. +""" +function plot_significance! end + +""" + plot_changes_significance(res, signif) → (fig, axs) +Return `fig::Figure` and `axs::Matrix{Axis}`, on which `res::IndicatorsChangesResults` +and `signif::SurrogatesSignificance` have been visualised. +The source code is as simple as: + +```julia +fig, axs = plot_indicator_changes(res; kwargs...) +plot_significance!(axs, res, signif; kwargs...) +``` + +For more information, refer to [`plot_indicator_changes`](@ref) and +[`plot_significance!`](@ref). +""" +function plot_changes_significance end + +export plot_indicator_changes, plot_significance!, plot_changes_significance \ No newline at end of file