From 42c9c23186f9331d03f4d69298417dd0a93eb5af Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Wed, 24 Jan 2024 18:54:18 +0100 Subject: [PATCH 01/11] refactor visualization + give more precise name --- Project.toml | 2 +- ext/TITVisualizations.jl | 69 ----------- ext/TransitionVisualizations.jl | 208 ++++++++++++++++++++++++++++++++ src/TransitionsInTimeseries.jl | 3 - src/visualizations.jl | 12 +- 5 files changed, 214 insertions(+), 80 deletions(-) delete mode 100644 ext/TITVisualizations.jl create mode 100644 ext/TransitionVisualizations.jl 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/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..49b2b05 --- /dev/null +++ b/ext/TransitionVisualizations.jl @@ -0,0 +1,208 @@ +module TransitionVisualizations + +using TransitionsInTimeseries, Makie + +# Default options for plotting utilities +global default_accent_linewidth = 3 +global default_colors = ["#7143E0", "#0A9A84", "#191E44", "#AF9327", "#701B80", "#2E6137"] + +default_indicator_label(res::IndicatorsChangesResults) = [string(nameof( + res.config.indicators[i])) for i in eachindex(res.config.indicators)] + +function default_chametric_label(res::IndicatorsChangesResults) + labels = String[] + for i in eachindex(res.config.change_metrics) + if res.config.change_metrics[i] isa PrecomputedRidgeRegressionSlope || + res.config.change_metrics[i] isa RidgeRegressionSlope + push!(labels, "Regression slope") + else + push!(labels, string(nameof(res.config.change_metrics[i]))) + end + end + return labels +end + +# Struct to help pass results +struct TransitionVisualization{R<:IndicatorsChangesResults} + fig::Makie.Figure + laxs::Vector{Makie.Axis} + raxs::Vector{Makie.Axis} + res::R +end + +# plot_indicator_changes +function TransitionsInTimeseries.plot_indicator_changes(res::SlidingWindowResults; + colors = default_colors, + indicator_names = default_indicator_label(res), + chametric_names = default_chametric_label(res), + additional_timeseries = nothing, + accent_linewidth = default_accent_linewidth, + ) + + fig, laxs, raxs, n, config = init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, additional_timeseries, accent_linewidth) + lineplot_metrics!(raxs, laxs, n, config, res.t_indicator, res.x_indicator, + res.t_change, res.x_change, colors, accent_linewidth) + + return TransitionVisualization(fig, laxs, raxs, res) +end + +function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResults; + colors = default_colors, + indicator_names = default_indicator_label(res), + chametric_names = default_chametric_label(res), + additional_timeseries = nothing, + accent_linewidth = default_accent_linewidth) + + fig, laxs, raxs, n, config = init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, additional_timeseries, accent_linewidth) + for k in eachindex(res.t_indicator) + Makie.lines!(laxs[1], res.t[k], res.x[k], color = colors[1], + linewidth = accent_linewidth) + lineplot_metrics!(raxs, laxs, n, config, res.t_indicator[k], res.x_indicator[k], + res.t_change[k], res.x_change[k, :], + colors, accent_linewidth) + end + + return TransitionVisualization(fig, laxs, raxs, res) +end + +# utils for plot_indicator_changes +function init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, additional_timeseries, accent_linewidth) + + config = res.config + 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)*rowaspect+1:i*rowaspect, 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)*rowaspect+1:i*rowaspect, 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", "surrogate 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", "surrogate change metrics"]) + width = 1 + end + Legend(fig[0, 1], elements, labels, nbanks = 4, rowgap = 0, colgap = 10, + width = Relative(width)) + + return fig, laxs, raxs, n, config +end + +function lineplot_metrics!(raxs, laxs, 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!(raxs[i], t_ind, x_ind[:, j], color = colors[2], + linewidth = accent_linewidth) + end + if length(t_cha) > 1 + lines!(laxs[i], t_cha, x_cha[:, j], color = colors[1], + linewidth = accent_linewidth) + else + Makie.scatter!(laxs[i], t_cha, x_cha[j], color = colors[1], + markersize = 10) + end + end +end + +# plot_significance! +function TransitionsInTimeseries.plot_significance!( + tv::TransitionVisualization{<:SlidingWindowResults}, + signif::SurrogatesSignificance; + flags = nothing, + colors = default_colors, + nsurro = 20, + ) + + (; fig, laxs, raxs, res) = tv + config = res.config + lines_over_surro!(raxs, laxs, nsurro, res.t, res.t_indicator, res.t_change, res.x, + signif, config, flags, colors) + return tv +end + +function TransitionsInTimeseries.plot_significance!( + tv::TransitionVisualization{<:SegmentedWindowResults}, + signif::SurrogatesSignificance; + flags = nothing, + colors = default_colors, + nsurro = 20, + ) + + (; fig, laxs, raxs, res) = tv + config = res.config + for k in eachindex(res.t_indicator) + lines_over_surro!(raxs, laxs, 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, config, flags[k, :], colors) + end + return tv +end + +# utils for plot_significance! +function lines_over_surro!(raxs, laxs, 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!(laxs[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!(raxs[i+1], 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!(laxs[i+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!(laxs[i+1], t_cha, q[1], color = (colors[1], 2/nsurro), + markersize = 5) + end + if !isnothing(flags) && ns == 1 && length(t_cha) > 1 + Makie.vlines!(laxs[i+1], t_cha[flags[:, i]]; + color = (:black, 0.1)) + elem = [PolyElement(color = (:black, 0.5), strokecolor = :transparent)] + axislegend(laxs[i+1], elem, ["p<$(signif.p)"], position = :lt) + elseif length(t_cha) == 1 + c[i, ns] = q[1] + end + end + end +end + +function plot_changes_significance(res, signif) + tv = plot_indicator_changes(res) + plot_significance!(tv, signif) + return tv +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/visualizations.jl b/src/visualizations.jl index 02cdc63..b90fd93 100644 --- a/src/visualizations.jl +++ b/src/visualizations.jl @@ -1,9 +1,7 @@ -function plot_changes_significance(res::IndicatorsChangesConfig, signif::TransitionsSignificance) - fig = plot_indicator_changes() - plot_significance!() - return fig -end - function plot_indicator_changes end +function plot_significance! end +function plot_changes_significance end -function plot_significance! end \ No newline at end of file +# visualizations +export plot_indicator_changes, plot_significance! +export TransitionVisualization, plot_changes_significance \ No newline at end of file From 762feae4c6ed0aa745a5551f24bb5832b50e29c9 Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Wed, 24 Jan 2024 18:54:43 +0100 Subject: [PATCH 02/11] tutorial with new viz + segmented windows --- docs/src/tutorial.jl | 65 ++++++++++++++++++++++---------------------- docs/src/tutorial.md | 65 ++++++++++++++++++++++---------------------- 2 files changed, 66 insertions(+), 64 deletions(-) diff --git a/docs/src/tutorial.jl b/docs/src/tutorial.jl index 30eb52b..ef43476 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). 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. @@ -181,11 +181,11 @@ As expected, the data generated by the nonlinear model displays a significant in Performing the step-by-step analysis of transition indicators is possible and might be preferred for users wanting high flexibility. However, this results in a substantial amount of code. We therefore provide convenience functions that wrap this analysis, as shown in the next section. -## [Tutorial -- TransitionsInTimeseries.jl] (@id example_fastforward) +## [Tutorial -- Convenient sliding window] (@id example_fastforward) 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). 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 following blocks illustrate how the above extensive example is re-created in TransitionsInTimeseries.jl =# @@ -206,7 +206,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 [`SlidingWindowConfig`](@ref) and apply those via -a sliding window to the input timeseries using [`estimate_indicator_changes`](@ref). +a sliding window to the input timeseries using [`transition_metrics`](@ref). =# ## These indicators are suitable for Critical Slowing Down @@ -223,41 +223,42 @@ 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 +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). +Step 2 is to estimate significance using [`SurrogatesConfig`](@ref) +and the function [`estimate_significance!`](@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!(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: +# [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) +tv = plot_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) +plot_significance!(tv, signif, flags = flags, nsurro = 100) +tv.fig \ No newline at end of file diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index f577901..28344ed 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. @@ -183,11 +183,11 @@ As expected, the data generated by the nonlinear model displays a significant in Performing the step-by-step analysis of transition indicators is possible and might be preferred for users wanting high flexibility. However, this results in a substantial amount of code. We therefore provide convenience functions that wrap this analysis, as shown in the next section. -## [Tutorial -- TransitionsInTimeseries.jl] (@id example_fastforward) +## [Tutorial -- Convenient sliding window] (@id example_fastforward) 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). 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 following blocks illustrate how the above extensive example is re-created in TransitionsInTimeseries.jl @@ -208,7 +208,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 [`SlidingWindowConfig`](@ref) and apply those via -a sliding window to the input timeseries using [`estimate_indicator_changes`](@ref). +a sliding window to the input timeseries using [`transition_metrics`](@ref). ````@example tutorial # These indicators are suitable for Critical Slowing Down @@ -225,43 +225,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). +Step 2 is to estimate significance using [`SurrogatesConfig`](@ref) +and the function [`estimate_significance!`](@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_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) +plot_significance!(tv, signif, flags = flags, nsurro = 100) +tv.fig ```` From e4bf088f2aa7470e967fdc3d9cb2dfeaf28c04d9 Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Thu, 25 Jan 2024 11:08:08 +0100 Subject: [PATCH 03/11] fix references of docs --- docs/make.jl | 2 +- docs/src/api.md | 16 ++++++++++++++++ docs/src/tutorial.jl | 23 +++++++++++++---------- docs/src/tutorial.md | 18 ++++++++++-------- src/misc/windowing.jl | 4 ++-- 5 files changed, 42 insertions(+), 21 deletions(-) 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 ef43476..c7f282e 100644 --- a/docs/src/tutorial.jl +++ b/docs/src/tutorial.jl @@ -8,7 +8,7 @@ Computing transition indicators consists of the following steps: 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. Alternatively, the change metric can be estimated over the whole segment, as examplified in the section [Segmented windows](@ref). +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. @@ -181,11 +181,13 @@ As expected, the data generated by the nonlinear model displays a significant in Performing the step-by-step analysis of transition indicators is possible and might be preferred for users wanting high flexibility. However, this results in a substantial amount of code. We therefore provide convenience functions that wrap this analysis, as shown in the next section. -## [Tutorial -- Convenient sliding window] (@id example_fastforward) +## [Tutorial -- TransitionsInTimeseries.jl] (@id example_fastforward) 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 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 =# @@ -206,7 +208,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 [`SlidingWindowConfig`](@ref) and apply those via -a sliding window to the input timeseries using [`transition_metrics`](@ref). +a sliding window to the input timeseries using [`estimate_indicator_changes`](@ref). =# ## These indicators are suitable for Critical Slowing Down @@ -231,8 +233,8 @@ tv = plot_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) tv.fig #= -Step 2 is to estimate significance using [`SurrogatesConfig`](@ref) -and the function [`estimate_significance!`](@ref). Finally, we can +Step 2 is to estimate significance using [`SurrogatesSignificance`](@ref) +and the function [`significant_transitions`](@ref). Finally, we can conveniently plot the results obtained by updating the figure obtained above with `plot_significance!`: =# @@ -243,7 +245,7 @@ plot_significance!(tv, signif, flags = flags) tv.fig #= -# [Segmented windows] (@id segmented_windows) +### [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 @@ -259,6 +261,7 @@ config = SegmentedWindowConfig(indicators, change_metrics, results = estimate_indicator_changes(config, input, t) signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) -tv = plot_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) -plot_significance!(tv, signif, flags = flags, nsurro = 100) -tv.fig \ No newline at end of file +tv = plot_changes_significance(results, signif, + additional_timeseries = x_nlinear[2:end]) +tv.fig + diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 28344ed..235ded7 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -183,11 +183,13 @@ As expected, the data generated by the nonlinear model displays a significant in Performing the step-by-step analysis of transition indicators is possible and might be preferred for users wanting high flexibility. However, this results in a substantial amount of code. We therefore provide convenience functions that wrap this analysis, as shown in the next section. -## [Tutorial -- Convenient sliding window] (@id example_fastforward) +## [Tutorial -- TransitionsInTimeseries.jl] (@id example_fastforward) 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 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 @@ -208,7 +210,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 [`SlidingWindowConfig`](@ref) and apply those via -a sliding window to the input timeseries using [`transition_metrics`](@ref). +a sliding window to the input timeseries using [`estimate_indicator_changes`](@ref). ````@example tutorial # These indicators are suitable for Critical Slowing Down @@ -233,8 +235,8 @@ tv = plot_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) tv.fig ```` -Step 2 is to estimate significance using [`SurrogatesConfig`](@ref) -and the function [`estimate_significance!`](@ref). Finally, we can +Step 2 is to estimate significance using [`SurrogatesSignificance`](@ref) +and the function [`significant_transitions`](@ref). Finally, we can conveniently plot the results obtained by updating the figure obtained above with `plot_significance!`: @@ -245,7 +247,7 @@ plot_significance!(tv, signif, flags = flags) tv.fig ```` -# Segmented Windows (@id segmented_windows) +### [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 @@ -261,8 +263,8 @@ config = SegmentedWindowConfig(indicators, change_metrics, results = estimate_indicator_changes(config, input, t) signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) -tv = plot_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) -plot_significance!(tv, signif, flags = flags, nsurro = 100) +tv = plot_changes_significance(results, signif, + additional_timeseries = x_nlinear[2:end]) tv.fig ```` 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; From 4084fbdc61b240c115d05f3cd7f616f900eb6536 Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Thu, 25 Jan 2024 11:08:39 +0100 Subject: [PATCH 04/11] plot_changes_significance works with kwargs --- ext/TransitionVisualizations.jl | 69 +++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/ext/TransitionVisualizations.jl b/ext/TransitionVisualizations.jl index 49b2b05..feaccb1 100644 --- a/ext/TransitionVisualizations.jl +++ b/ext/TransitionVisualizations.jl @@ -3,8 +3,8 @@ module TransitionVisualizations using TransitionsInTimeseries, Makie # Default options for plotting utilities -global default_accent_linewidth = 3 -global default_colors = ["#7143E0", "#0A9A84", "#191E44", "#AF9327", "#701B80", "#2E6137"] +const default_accent_linewidth = 3 +const default_colors = ["#7143E0", "#0A9A84", "#191E44", "#AF9327", "#701B80", "#2E6137"] default_indicator_label(res::IndicatorsChangesResults) = [string(nameof( res.config.indicators[i])) for i in eachindex(res.config.indicators)] @@ -22,21 +22,40 @@ function default_chametric_label(res::IndicatorsChangesResults) return labels end -# Struct to help pass results +""" + TransitionVisualization +Contains: + - `fig::Figure` the figure, + - `laxs::Vector{Axis}` the left axes, + - `raxs::Vector{Axis}` the right axes, + - `res::IndicatorsChangesResults` the results of the input signal, + - `colors::Vector` a vector containing the colors to use for plotting. +""" struct TransitionVisualization{R<:IndicatorsChangesResults} fig::Makie.Figure laxs::Vector{Makie.Axis} raxs::Vector{Makie.Axis} res::R + colors::Vector end -# plot_indicator_changes +export TransitionVisualization + +# Plot results from full analysis +function TransitionsInTimeseries.plot_changes_significance(res, signif; kwargs...) + tv = plot_indicator_changes(res; kwargs...) + plot_significance!(tv, signif; kwargs...) + return tv +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), additional_timeseries = nothing, accent_linewidth = default_accent_linewidth, + kwargs..., ) fig, laxs, raxs, n, config = init_rowwise_visualisation(res, colors, indicator_names, @@ -44,7 +63,7 @@ function TransitionsInTimeseries.plot_indicator_changes(res::SlidingWindowResult lineplot_metrics!(raxs, laxs, n, config, res.t_indicator, res.x_indicator, res.t_change, res.x_change, colors, accent_linewidth) - return TransitionVisualization(fig, laxs, raxs, res) + return TransitionVisualization(fig, laxs, raxs, res, colors) end function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResults; @@ -52,7 +71,9 @@ function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResu indicator_names = default_indicator_label(res), chametric_names = default_chametric_label(res), additional_timeseries = nothing, - accent_linewidth = default_accent_linewidth) + accent_linewidth = default_accent_linewidth, + kwargs..., + ) fig, laxs, raxs, n, config = init_rowwise_visualisation(res, colors, indicator_names, chametric_names, additional_timeseries, accent_linewidth) @@ -60,11 +81,10 @@ function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResu Makie.lines!(laxs[1], res.t[k], res.x[k], color = colors[1], linewidth = accent_linewidth) lineplot_metrics!(raxs, laxs, n, config, res.t_indicator[k], res.x_indicator[k], - res.t_change[k], res.x_change[k, :], - colors, accent_linewidth) + res.t_change[k], res.x_change[k, :], colors, accent_linewidth) end - return TransitionVisualization(fig, laxs, raxs, res) + return TransitionVisualization(fig, laxs, raxs, res, colors) end # utils for plot_indicator_changes @@ -95,12 +115,12 @@ function init_rowwise_visualisation(res, colors, indicator_names, elements = [LineElement(color = (colors[1], transparency), linewidth = lw) for (lw, transparency) in [(accent_linewidth, 1), (1, 0.5)]] - labels = ["original signal", "surrogate signals"] + 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", "surrogate change metrics"]) + labels = vcat(labels, ["original change metric", "surro change metrics"]) width = 1 end Legend(fig[0, 1], elements, labels, nbanks = 4, rowgap = 0, colgap = 10, @@ -127,16 +147,16 @@ function lineplot_metrics!(raxs, laxs, n, config, t_ind, x_ind, t_cha, x_cha, end end -# plot_significance! +# Plot results of surrogates function TransitionsInTimeseries.plot_significance!( tv::TransitionVisualization{<:SlidingWindowResults}, signif::SurrogatesSignificance; flags = nothing, - colors = default_colors, nsurro = 20, + kwargs..., ) - (; fig, laxs, raxs, res) = tv + (; fig, laxs, raxs, res, colors) = tv config = res.config lines_over_surro!(raxs, laxs, nsurro, res.t, res.t_indicator, res.t_change, res.x, signif, config, flags, colors) @@ -147,15 +167,22 @@ function TransitionsInTimeseries.plot_significance!( tv::TransitionVisualization{<:SegmentedWindowResults}, signif::SurrogatesSignificance; flags = nothing, - colors = default_colors, nsurro = 20, + kwargs..., ) - (; fig, laxs, raxs, res) = tv + (; fig, laxs, raxs, res, colors) = tv config = res.config for k in eachindex(res.t_indicator) - lines_over_surro!(raxs, laxs, 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, config, flags[k, :], colors) + if isnothing(flags) + lines_over_surro!(raxs, laxs, 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, config, nothing, colors) + else + lines_over_surro!(raxs, laxs, 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, config, flags[k, :], colors) + end end return tv end @@ -199,10 +226,4 @@ function lines_over_surro!(raxs, laxs, nsurro, t, t_ind, t_cha, x, signif, confi end end -function plot_changes_significance(res, signif) - tv = plot_indicator_changes(res) - plot_significance!(tv, signif) - return tv -end - end \ No newline at end of file From c32c0a702bb3b465030a2c25ff05da31471f5a5b Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Thu, 25 Jan 2024 11:08:57 +0100 Subject: [PATCH 05/11] add docstrings for visualization --- src/visualizations.jl | 48 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/visualizations.jl b/src/visualizations.jl index b90fd93..7947edc 100644 --- a/src/visualizations.jl +++ b/src/visualizations.jl @@ -1,7 +1,49 @@ +""" + plot_indicator_changes(res) +Return `tv::TransitionVisualization`, containing the figure and axes 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`. + - `additional_timeseries = nothing` provides an additional timeseries, plotted on + the same panel as the input. Typically, if the input is a residual, you might want + to provide the unfiltered timeseries here to visualise how the results coincide + with the timing of the transition. + - `accent_linewidth = 3` sets the line width for the original signals (the + surrogates have `linewidth = 1`) +""" function plot_indicator_changes end + +""" + plot_significance!(tv, signif) +Update the `tv::TransitionVisualization` with `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) +Return `tv::TransitionVisualization` containing the figure and axes on which +`res::IndicatorsChangesResults` and `signif::SurrogatesSignificance` have been +visualised. The source code is as simple as: + +```julia +tv = plot_indicator_changes(res) +plot_significance!(tv, signif) +``` + +For more information, refer to [`plot_indicator_changes`](@ref) and +[`plot_significance!`](@ref). +""" function plot_changes_significance end -# visualizations -export plot_indicator_changes, plot_significance! -export TransitionVisualization, plot_changes_significance \ No newline at end of file +export plot_indicator_changes, plot_significance!, plot_changes_significance \ No newline at end of file From 1f6a19dc74f2da6786dbe6aa64dadb757cc3e2e4 Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Fri, 2 Feb 2024 10:51:23 +0100 Subject: [PATCH 06/11] remove struct TransitionVisualization --- docs/src/tutorial.jl | 12 ++-- ext/TransitionVisualizations.jl | 111 +++++++++++++------------------- 2 files changed, 47 insertions(+), 76 deletions(-) diff --git a/docs/src/tutorial.jl b/docs/src/tutorial.jl index c7f282e..c4299cd 100644 --- a/docs/src/tutorial.jl +++ b/docs/src/tutorial.jl @@ -229,8 +229,7 @@ We can conveniently plot the information contained in `results` by using `plot_indicator_changes`: =# -tv = plot_indicator_changes(results, additional_timeseries = x_nlinear[2:end]) -tv.fig +fig, axs = plot_indicator_changes(results) #= Step 2 is to estimate significance using [`SurrogatesSignificance`](@ref) @@ -241,8 +240,8 @@ above with `plot_significance!`: signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) -plot_significance!(tv, signif, flags = flags) -tv.fig +plot_significance!(axs, results, signif, flags = flags) +fig #= ### [Segmented windows] (@id segmented_windows) @@ -261,7 +260,4 @@ config = SegmentedWindowConfig(indicators, change_metrics, 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 - +fig, axs = plot_changes_significance(results, signif) \ No newline at end of file diff --git a/ext/TransitionVisualizations.jl b/ext/TransitionVisualizations.jl index feaccb1..3d6c8f8 100644 --- a/ext/TransitionVisualizations.jl +++ b/ext/TransitionVisualizations.jl @@ -6,46 +6,21 @@ using TransitionsInTimeseries, Makie const default_accent_linewidth = 3 const default_colors = ["#7143E0", "#0A9A84", "#191E44", "#AF9327", "#701B80", "#2E6137"] -default_indicator_label(res::IndicatorsChangesResults) = [string(nameof( - res.config.indicators[i])) for i in eachindex(res.config.indicators)] - -function default_chametric_label(res::IndicatorsChangesResults) - labels = String[] - for i in eachindex(res.config.change_metrics) - if res.config.change_metrics[i] isa PrecomputedRidgeRegressionSlope || - res.config.change_metrics[i] isa RidgeRegressionSlope - push!(labels, "Regression slope") - else - push!(labels, string(nameof(res.config.change_metrics[i]))) - end - end - return labels -end +default_indicator_label(res::IndicatorsChangesResults) = [shortname( + ind) for ind in res.config.indicators] -""" - TransitionVisualization -Contains: - - `fig::Figure` the figure, - - `laxs::Vector{Axis}` the left axes, - - `raxs::Vector{Axis}` the right axes, - - `res::IndicatorsChangesResults` the results of the input signal, - - `colors::Vector` a vector containing the colors to use for plotting. -""" -struct TransitionVisualization{R<:IndicatorsChangesResults} - fig::Makie.Figure - laxs::Vector{Makie.Axis} - raxs::Vector{Makie.Axis} - res::R - colors::Vector -end +default_chametric_label(res::IndicatorsChangesResults) = [shortname( + cha_metric) for cha_metric in res.config.change_metrics] -export TransitionVisualization +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...) - tv = plot_indicator_changes(res; kwargs...) - plot_significance!(tv, signif; kwargs...) - return tv + fig, axs = plot_indicator_changes(res; kwargs...) + plot_significance!(axs, res, signif; kwargs...) + return fig, axs end # Plot results of original input signal @@ -53,43 +28,41 @@ function TransitionsInTimeseries.plot_indicator_changes(res::SlidingWindowResult colors = default_colors, indicator_names = default_indicator_label(res), chametric_names = default_chametric_label(res), - additional_timeseries = nothing, accent_linewidth = default_accent_linewidth, kwargs..., ) - fig, laxs, raxs, n, config = init_rowwise_visualisation(res, colors, indicator_names, - chametric_names, additional_timeseries, accent_linewidth) - lineplot_metrics!(raxs, laxs, n, config, res.t_indicator, res.x_indicator, + fig, axs, n, config = init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, accent_linewidth) + lineplot_metrics!(axs, n, config, res.t_indicator, res.x_indicator, res.t_change, res.x_change, colors, accent_linewidth) - return TransitionVisualization(fig, laxs, raxs, res, colors) + return fig, axs end function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResults; colors = default_colors, indicator_names = default_indicator_label(res), chametric_names = default_chametric_label(res), - additional_timeseries = nothing, accent_linewidth = default_accent_linewidth, kwargs..., ) - fig, laxs, raxs, n, config = init_rowwise_visualisation(res, colors, indicator_names, - chametric_names, additional_timeseries, accent_linewidth) + fig, axs, n, config = init_rowwise_visualisation(res, colors, indicator_names, + chametric_names, accent_linewidth) for k in eachindex(res.t_indicator) - Makie.lines!(laxs[1], res.t[k], res.x[k], color = colors[1], + Makie.lines!(axs[1, 1], res.t[k], res.x[k], color = colors[1], linewidth = accent_linewidth) - lineplot_metrics!(raxs, laxs, n, config, res.t_indicator[k], res.x_indicator[k], + lineplot_metrics!(axs, n, config, res.t_indicator[k], res.x_indicator[k], res.t_change[k], res.x_change[k, :], colors, accent_linewidth) end - return TransitionVisualization(fig, laxs, raxs, res, colors) + return fig, axs end # utils for plot_indicator_changes function init_rowwise_visualisation(res, colors, indicator_names, - chametric_names, additional_timeseries, accent_linewidth) + chametric_names, accent_linewidth) config = res.config fig = Makie.Figure(size = (700, 450), fontsize = 12) @@ -126,22 +99,22 @@ function init_rowwise_visualisation(res, colors, indicator_names, Legend(fig[0, 1], elements, labels, nbanks = 4, rowgap = 0, colgap = 10, width = Relative(width)) - return fig, laxs, raxs, n, config + return fig, hcat(laxs, raxs), n, config end -function lineplot_metrics!(raxs, laxs, n, config, t_ind, x_ind, t_cha, x_cha, +function lineplot_metrics!(axs, 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!(raxs[i], t_ind, x_ind[:, j], color = colors[2], + Makie.lines!(axs[i, 2], t_ind, x_ind[:, j], color = colors[2], linewidth = accent_linewidth) end if length(t_cha) > 1 - lines!(laxs[i], t_cha, x_cha[:, j], color = colors[1], + lines!(axs[i, 1], t_cha, x_cha[:, j], color = colors[1], linewidth = accent_linewidth) else - Makie.scatter!(laxs[i], t_cha, x_cha[j], color = colors[1], + Makie.scatter!(axs[i, 1], t_cha, x_cha[j], color = colors[1], markersize = 10) end end @@ -149,51 +122,53 @@ end # Plot results of surrogates function TransitionsInTimeseries.plot_significance!( - tv::TransitionVisualization{<:SlidingWindowResults}, + axs::Matrix{Axis}, + res::SlidingWindowResults, signif::SurrogatesSignificance; + colors = default_colors, flags = nothing, nsurro = 20, kwargs..., ) - (; fig, laxs, raxs, res, colors) = tv config = res.config - lines_over_surro!(raxs, laxs, nsurro, res.t, res.t_indicator, res.t_change, res.x, + lines_over_surro!(axs, nsurro, res.t, res.t_indicator, res.t_change, res.x, signif, config, flags, colors) - return tv + return nothing end function TransitionsInTimeseries.plot_significance!( - tv::TransitionVisualization{<:SegmentedWindowResults}, + axs::Matrix{Axis}, + res::SegmentedWindowResults, signif::SurrogatesSignificance; + colors = default_colors, flags = nothing, nsurro = 20, kwargs..., ) - (; fig, laxs, raxs, res, colors) = tv config = res.config for k in eachindex(res.t_indicator) if isnothing(flags) - lines_over_surro!(raxs, laxs, nsurro, res.t[res.i1[k]:res.i2[k]], + lines_over_surro!(axs, 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, config, nothing, colors) else - lines_over_surro!(raxs, laxs, nsurro, res.t[res.i1[k]:res.i2[k]], + lines_over_surro!(axs, 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, config, flags[k, :], colors) end end - return tv + return nothing end # utils for plot_significance! -function lines_over_surro!(raxs, laxs, nsurro, t, t_ind, t_cha, x, signif, config, +function lines_over_surro!(axs, 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!(laxs[1], t, s; color = (colors[1], 2/nsurro), linewidth = 1) + Makie.lines!(axs[1, 1], t, s; color = (colors[1], 2/nsurro), linewidth = 1) for (i, cha) in enumerate(config.change_metrics) if isnothing(config.indicators) @@ -201,24 +176,24 @@ function lines_over_surro!(raxs, laxs, nsurro, t, t_ind, t_cha, x, signif, confi else p = windowmap(config.indicators[i], s; width = config.width_ind, stride = config.stride_ind) - Makie.lines!(raxs[i+1], t_ind, p; color = (colors[2], 2/nsurro), + Makie.lines!(axs[i+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!(laxs[i+1], t_cha, q; color = (colors[1], 2/nsurro), + Makie.lines!(axs[i+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!(laxs[i+1], t_cha, q[1], color = (colors[1], 2/nsurro), + Makie.scatter!(axs[i+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!(laxs[i+1], t_cha[flags[:, i]]; + Makie.vlines!(axs[i+1, 1], t_cha[flags[:, i]]; color = (:black, 0.1)) elem = [PolyElement(color = (:black, 0.5), strokecolor = :transparent)] - axislegend(laxs[i+1], elem, ["p<$(signif.p)"], position = :lt) + axislegend(axs[i+1, 1], elem, ["p<$(signif.p)"], position = :lt) elseif length(t_cha) == 1 c[i, ns] = q[1] end From 2e745f51c09dac955673e78d6b39c37145114f62 Mon Sep 17 00:00:00 2001 From: George Datseris Date: Fri, 2 Feb 2024 12:03:00 +0000 Subject: [PATCH 07/11] display segmented figure --- docs/src/tutorial.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/tutorial.jl b/docs/src/tutorial.jl index c4299cd..88f64b7 100644 --- a/docs/src/tutorial.jl +++ b/docs/src/tutorial.jl @@ -260,4 +260,5 @@ config = SegmentedWindowConfig(indicators, change_metrics, results = estimate_indicator_changes(config, input, t) signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) -fig, axs = plot_changes_significance(results, signif) \ No newline at end of file +fig, axs = plot_changes_significance(results, signif) +fig # display the figure \ No newline at end of file From 3f9538972e066ed336bf61b8d3c753613784f270 Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Fri, 2 Feb 2024 13:26:59 +0100 Subject: [PATCH 08/11] correct docstrings for viz --- src/visualizations.jl | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/visualizations.jl b/src/visualizations.jl index 7947edc..1b25e33 100644 --- a/src/visualizations.jl +++ b/src/visualizations.jl @@ -1,7 +1,7 @@ """ - plot_indicator_changes(res) -Return `tv::TransitionVisualization`, containing the figure and axes on which -`res::IndicatorsChangesResults` has been visualised. + 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 @@ -10,18 +10,15 @@ Return `tv::TransitionVisualization`, containing the figure and axes on which 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`. - - `additional_timeseries = nothing` provides an additional timeseries, plotted on - the same panel as the input. Typically, if the input is a residual, you might want - to provide the unfiltered timeseries here to visualise how the results coincide - with the timing of the transition. - `accent_linewidth = 3` sets the line width for the original signals (the surrogates have `linewidth = 1`) """ function plot_indicator_changes end """ - plot_significance!(tv, signif) -Update the `tv::TransitionVisualization` with `signif::SurrogatesSignificance`. + 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 @@ -31,14 +28,14 @@ Update the `tv::TransitionVisualization` with `signif::SurrogatesSignificance`. function plot_significance! end """ - plot_changes_significance(res, signif) -Return `tv::TransitionVisualization` containing the figure and axes on which -`res::IndicatorsChangesResults` and `signif::SurrogatesSignificance` have been -visualised. The source code is as simple as: + 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 -tv = plot_indicator_changes(res) -plot_significance!(tv, signif) +fig, axs = plot_indicator_changes(res; kwargs...) +plot_significance!(axs, res, signif; kwargs...) ``` For more information, refer to [`plot_indicator_changes`](@ref) and From 4881030d0dfbc964cffb4cb358f1191644c95dfa Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Fri, 2 Feb 2024 13:30:50 +0100 Subject: [PATCH 09/11] add intermediate display of fig --- docs/src/tutorial.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/tutorial.jl b/docs/src/tutorial.jl index 88f64b7..9e17baa 100644 --- a/docs/src/tutorial.jl +++ b/docs/src/tutorial.jl @@ -230,6 +230,7 @@ We can conveniently plot the information contained in `results` by using =# fig, axs = plot_indicator_changes(results) +fig #= Step 2 is to estimate significance using [`SurrogatesSignificance`](@ref) From 9b807d01f0c0cc2c4e107dc0680fa7b707f879eb Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Fri, 2 Feb 2024 15:26:06 +0100 Subject: [PATCH 10/11] use contents(fig) instead of axs --- docs/src/tutorial.jl | 8 ++-- ext/TransitionVisualizations.jl | 71 ++++++++++++++++----------------- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/docs/src/tutorial.jl b/docs/src/tutorial.jl index 9e17baa..21c6510 100644 --- a/docs/src/tutorial.jl +++ b/docs/src/tutorial.jl @@ -229,8 +229,7 @@ We can conveniently plot the information contained in `results` by using `plot_indicator_changes`: =# -fig, axs = plot_indicator_changes(results) -fig +fig = plot_indicator_changes(results) #= Step 2 is to estimate significance using [`SurrogatesSignificance`](@ref) @@ -241,7 +240,7 @@ above with `plot_significance!`: signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) -plot_significance!(axs, results, signif, flags = flags) +plot_significance!(fig, results, signif, flags = flags) fig #= @@ -261,5 +260,4 @@ config = SegmentedWindowConfig(indicators, change_metrics, results = estimate_indicator_changes(config, input, t) signif = SurrogatesSignificance(n = 1000, tail = [:right, :right]) flags = significant_transitions(results, signif) -fig, axs = plot_changes_significance(results, signif) -fig # display the figure \ No newline at end of file +fig = plot_changes_significance(results, signif) diff --git a/ext/TransitionVisualizations.jl b/ext/TransitionVisualizations.jl index 3d6c8f8..e4e7804 100644 --- a/ext/TransitionVisualizations.jl +++ b/ext/TransitionVisualizations.jl @@ -18,9 +18,9 @@ shortname(metric::RidgeRegressionSlope) = "Regression slope" # Plot results from full analysis function TransitionsInTimeseries.plot_changes_significance(res, signif; kwargs...) - fig, axs = plot_indicator_changes(res; kwargs...) - plot_significance!(axs, res, signif; kwargs...) - return fig, axs + fig = plot_indicator_changes(res; kwargs...) + plot_significance!(fig, res, signif; kwargs...) + return fig end # Plot results of original input signal @@ -32,12 +32,12 @@ function TransitionsInTimeseries.plot_indicator_changes(res::SlidingWindowResult kwargs..., ) - fig, axs, n, config = init_rowwise_visualisation(res, colors, indicator_names, + fig, n = init_rowwise_visualisation(res, colors, indicator_names, chametric_names, accent_linewidth) - lineplot_metrics!(axs, n, config, res.t_indicator, res.x_indicator, + lineplot_metrics!(fig, n, res.config, res.t_indicator, res.x_indicator, res.t_change, res.x_change, colors, accent_linewidth) - return fig, axs + return fig end function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResults; @@ -48,33 +48,32 @@ function TransitionsInTimeseries.plot_indicator_changes(res::SegmentedWindowResu kwargs..., ) - fig, axs, n, config = init_rowwise_visualisation(res, colors, indicator_names, + fig, n = init_rowwise_visualisation(res, colors, indicator_names, chametric_names, accent_linewidth) for k in eachindex(res.t_indicator) - Makie.lines!(axs[1, 1], res.t[k], res.x[k], color = colors[1], + Makie.lines!(contents(fig[1, 1])[1], res.t[k], res.x[k], color = colors[1], linewidth = accent_linewidth) - lineplot_metrics!(axs, n, config, res.t_indicator[k], res.x_indicator[k], + 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, axs + return fig end # utils for plot_indicator_changes function init_rowwise_visualisation(res, colors, indicator_names, chametric_names, accent_linewidth) - config = res.config 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)*rowaspect+1:i*rowaspect, 1], ylabel = rlabels[i], + # 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)*rowaspect+1:i*rowaspect, 1], ylabel = llabels[i], + 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...) @@ -98,23 +97,23 @@ function init_rowwise_visualisation(res, colors, indicator_names, end Legend(fig[0, 1], elements, labels, nbanks = 4, rowgap = 0, colgap = 10, width = Relative(width)) - - return fig, hcat(laxs, raxs), n, config + rowsize!(fig.layout, 0, Auto(0.3)) + return fig, n end -function lineplot_metrics!(axs, n, config, t_ind, x_ind, t_cha, x_cha, +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!(axs[i, 2], t_ind, x_ind[:, j], color = colors[2], + 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!(axs[i, 1], t_cha, x_cha[:, j], color = colors[1], + lines!(contents(fig[i, 1])[1], t_cha, x_cha[:, j], color = colors[1], linewidth = accent_linewidth) else - Makie.scatter!(axs[i, 1], t_cha, x_cha[j], color = colors[1], + Makie.scatter!(contents(fig[i, 1])[1], t_cha, x_cha[j], color = colors[1], markersize = 10) end end @@ -122,7 +121,7 @@ end # Plot results of surrogates function TransitionsInTimeseries.plot_significance!( - axs::Matrix{Axis}, + fig::Figure, res::SlidingWindowResults, signif::SurrogatesSignificance; colors = default_colors, @@ -131,14 +130,13 @@ function TransitionsInTimeseries.plot_significance!( kwargs..., ) - config = res.config - lines_over_surro!(axs, nsurro, res.t, res.t_indicator, res.t_change, res.x, - signif, config, flags, colors) + 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!( - axs::Matrix{Axis}, + fig::Figure, res::SegmentedWindowResults, signif::SurrogatesSignificance; colors = default_colors, @@ -147,28 +145,27 @@ function TransitionsInTimeseries.plot_significance!( kwargs..., ) - config = res.config for k in eachindex(res.t_indicator) if isnothing(flags) - lines_over_surro!(axs, nsurro, res.t[res.i1[k]:res.i2[k]], + 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, config, nothing, colors) + signif, res.config, nothing, colors) else - lines_over_surro!(axs, nsurro, res.t[res.i1[k]:res.i2[k]], + 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, config, flags[k, :], colors) + signif, res.config, flags[k, :], colors) end end return nothing end # utils for plot_significance! -function lines_over_surro!(axs, nsurro, t, t_ind, t_cha, x, signif, config, +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!(axs[1, 1], t, s; color = (colors[1], 2/nsurro), linewidth = 1) + 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) @@ -176,24 +173,24 @@ function lines_over_surro!(axs, nsurro, t, t_ind, t_cha, x, signif, config, else p = windowmap(config.indicators[i], s; width = config.width_ind, stride = config.stride_ind) - Makie.lines!(axs[i+1, 2], t_ind, p; color = (colors[2], 2/nsurro), + 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!(axs[i+1, 1], t_cha, q; color = (colors[1], 2/nsurro), + 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!(axs[i+1, 1], t_cha, q[1], color = (colors[1], 2/nsurro), + 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!(axs[i+1, 1], t_cha[flags[:, i]]; + 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(axs[i+1, 1], elem, ["p<$(signif.p)"], position = :lt) + axislegend(contents(fig[i+1, 1])[1], elem, ["p<$(signif.p)"], position = :lt) elseif length(t_cha) == 1 c[i, ns] = q[1] end From 18e71a776c970d56a5644dab2f9cc7b532e3c838 Mon Sep 17 00:00:00 2001 From: JanJereczek Date: Fri, 2 Feb 2024 15:26:15 +0100 Subject: [PATCH 11/11] ignore ext/ --- codecov.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 codecov.yml 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