knitr::opts_chunk$set(
    fig.align = "center",
    fig.height = 5,
    fig.width = 8,
    message = FALSE,
    warning = FALSE,
    collapse = FALSE,
    cache = FALSE,
    comment = ">",
    tidy=FALSE,
    dpi = 72,
    width = 800
)
library(plyr)
library(invctr)
library(tidyverse)
library(plot3D)

EVALchunk <- TRUE

```{css, echo=FALSE} p code { font-size: 70%; }

This vignette discusses how to conduct a large variety of recurrence-based time series analyses using R-package *casnet*. It is not the only `R` package that can run recurrence analyses, the closest alternative in `R` to `casnet` is probably package [`crqa`](https://cran.r-project.org/web/packages/crqa/index.html). It has a great tutorial paper by [@coco2014]). Several other packages have dedicated functions, e.g. package `nonlinearTseries` has a function `RQA`. There are also many options outside of the `R` framework, see the [Recurrence Plot webpage](http://www.recurrence-plot.tk/programmes.php) for a comprehensive list of software.

There are 3 ways to run Recurrence Quantification Analyses in `casnet`:

* Using functions `rp`, `rp_measures` and `rp_plot`. These functions use a thresholded distance matrix known as a recurrence plot to calculate RQA measures. Use these functions if your time series length is around `2000` data points or less.
* Using function `rqa_par`, which does not construct a distance matrix, but processes matrix diagonals in parallel. The function is very fast and can handle very large time series.
* Using function `rqa_cl` which will run Norbert Marwan's [commandline Recurrence Plots](http://tocsy.pik-potsdam.de/commandline-rp.php). You can run this if your OS allows execution of 32 bit command line executable.

The following examples will demonstrate the basic use of the native `casnet` functions based on the `rqa_par` and `rp` families of functions, see the paragraph [An R interface to Marwan's commandline recurrence plots](#marwan) to learn about using `rqa_cl()` and [An R interface to Marwan's commandline recurrence plots](#marwan).

To learn more about the different types of Recurrence Quantification Analysis that can be conducted in `casnet` please see the chapters on RQA in the Complex Systems Approach book for more details: 

* [RQA on unordered categorical data](https://complexity-methods.github.io/book/unordered-categorical-data.html)

* [RQA on continuous data](https://complexity-methods.github.io/book/lvSystem.html)



# RQA based on a matrix: `rp()`



## Unordered categorical data

```r
emDim   <- 1
emLag   <- 1
emRad   <- 0
theiler <- 0 # Do not include the diagonal

Continuous data

We'll use the examples used in the manual of the PyRQA library for Python. This is convenient, the parameters emDim, emLag and emRad are already given (see the CSA book for examples on how to estimate them with casnet) and it allows for a comparison of the output.

# PyRQA example data
data_points <- as.numeric(c(0.1, 0.5, 1.3, 0.7, 0.8, 1.4, 1.6, 1.2, 0.4, 1.1, 0.8, 0.2, 1.3))

plot(ts(data_points), type = "b")

First, we create a distance matrix based on the (delay embedded) time series and decide on a threshold criterion to turn it into a recurrence matrix of 0s and 1s. We already have a radius given in the PyRQA example, using a convenient graphical tool for visual inspection of the relationship between different thresholds values and the resulting recurrence rate (RR), we can check what this radius will yield.

library(casnet)

emDim   <- 2
emLag   <- 2
emRad   <- 0.65

RM <- rp(y1 = data_points, emDim = emDim, emLag = emLag)
rp_plot(RM, plotDimensions = TRUE, drawGrid = TRUE)

The code below applies the threshold emRad to the distance matrix, which generates a sparse matrix (class dgcMatrix, see package [Matrix]).

(RM <- rp(y1 = data_points, emDim = emDim, emLag = emLag, emRad = emRad))

The relevant analysis parameters, including the embedded series, are stored as attributes of the matrix object.

attributes(RM)

It is custom to represent the recurrence matrix as a plot with coordinate (1,1) in the left lower corner.

rp_plot(RM, plotDimensions = TRUE, drawGrid = TRUE)

Because this analysis concerns Auto-RQA, the diagonal is usually excluded from the calculations of RQA measures. This can be achieved by setting the theiler parameter to 1. This is in fact the default behavior in casnet if the theiler argument is NA and the recurrence matrix is detected to be symmetrical.

# Current value of theiler
attributes(RM)$theiler
# Values on the diagonal
Matrix::diag(RM)

# To explicitly include the diagonal in calculations set theiler to 0
(RM <- rp(y1 = data_points, emDim = emDim, emLag = emLag, emRad = emRad, theiler = 0))
attributes(RM)$theiler

In casnet the theiler correction will affect all calculations. This is different from, for example, PyRQA where it only affects the recurrence rate and measures based on diagonal line structures. Below are the results reported in the online PyRQA manual, with the diagonal included:

RQA Result:
===========

Minimum diagonal line length (L_min): 2
Minimum vertical line length (V_min): 2
Minimum white vertical line length (W_min): 2

Recurrence rate (RR): 0.371901
Determinism (DET): 0.411765
Average diagonal line length (L): 2.333333
Longest diagonal line length (L_max): 3
Divergence (DIV): 0.333333
Entropy diagonal lines (L_entr): 0.636514
Laminarity (LAM): 0.400000
Trapping time (TT): 2.571429
Longest vertical line length (V_max): 4
Entropy vertical lines (V_entr): 0.955700
Average white vertical line length (W): 2.538462
Longest white vertical line length (W_max): 6
Longest white vertical line length inverse (W_div): 0.166667
Entropy white vertical lines (W_entr): 0.839796

Ratio determinism / recurrence rate (DET/RR): 1.107190
Ratio laminarity / determinism (LAM/DET): 0.971429

The same for casnet:

# Including diagonal
RM <- rp(y1 = data_points, emDim = emDim, emLag = emLag, emRad = emRad, theiler = 0)
out_rqa <- rp_measures(RM, silent = FALSE)
# Exclude diagonal
RM2 <- rp(y1 = data_points, emDim = emDim, emLag = emLag, emRad = emRad, theiler = 1)
out_rqa2 <- rp_measures(RM2, silent = TRUE)

compOUT <- data.frame(Quantity = c("RR","DET", "MAX_dl", "MEAN_dl", "ENT_dl", "LAM", "MAX_vl", "MEAN_vl", "ENT_vl"), 
                      casnet = round(c(out_rqa$RR, out_rqa$DET, out_rqa$MAX_dl, out_rqa$MEAN_dl, out_rqa$ENT_dl, out_rqa$LAM_vl, out_rqa$MAX_vl, out_rqa$TT_vl, out_rqa$ENT_vl), digits = 6),
                      casnet = round(c(out_rqa2$RR, out_rqa2$DET, out_rqa2$MAX_dl, out_rqa2$MEAN_dl, out_rqa2$ENT_dl, out_rqa2$LAM_vl, out_rqa2$MAX_vl, out_rqa2$TT_vl, out_rqa2$ENT_vl), digits = 6),
                      PyRQA = c(0.371901, 0.411765, 3, 2.333333, 0.636514, 0.400000, 4, 2.571429, 0.955700),
                      PyRQA = c("Yes","No","No","No","No","Yes","Yes","Yes","Yes"))

Below is a comparison of the output from casnet::rp() and PyRQA. Note that PyRQA includes the main diagonal for RR and measures based on vertical (and horizontal) lines.

library(kableExtra)
knitr::kable(compOUT, align = c("l","c","c","c","c"), col.names = NULL) %>%
 kableExtra::add_header_above(c("\nMeasure","rp\n(theiler = 0)","rp\n(theiler = 1)", "PyRQA\n(theiler_corrector = 1)", "PyRQA\nDiagonal included?"))

RQA based on massively parallel processing: rqa_par()

PyRQA was developed to perform RQA on very long time series, without overloading memory and inflating the computing time to a degree that is unmanageable in a regular workflow. It is not recommended to use matrix based functions like casnet::rp_measures() on time series larger than 5,000 data points, instead, casnet::rqa_par() can be used, which implements some of the methods used in PyRQA. Specifically, the memory management by limiting the precision (package [float]) and using bit compression (package [float]), as well as by computing RQA measures in a massively parallel way (package [parallel]). Compared to casnet::rqa_par(),PyRQA is still faster, but this difference becomes noticeable for very large time series (>100,000).

noise <- casnet::noise_powerlaw(N=10000, seed = 1234)
plot(ts(noise),type = "l")

#est_radius_rqa(ts_embed(noise, emDim = emDim, emLag = emLag), noise, AUTO = TRUE)
RQA Result:
===========

Minimum diagonal line length (L_min): 2
Minimum vertical line length (V_min): 2
Minimum white vertical line length (W_min): 2

Recurrence rate (RR): 0.049585
Determinism (DET): 0.230880
Average diagonal line length (L): 2.149236
Longest diagonal line length (L_max): 7
Divergence (DIV): 0.142857
Entropy diagonal lines (L_entr): 0.443727
Laminarity (LAM): 0.374149
Trapping time (TT): 2.305511
Longest vertical line length (V_max): 8
Entropy vertical lines (V_entr): 0.709903
Average white vertical line length (W): 28.154700
Longest white vertical line length (W_max): 9079
Longest white vertical line length inverse (W_div): 0.000110
Entropy white vertical lines (W_entr): 3.909756

Ratio determinism / recurrence rate (DET/RR): 4.656240
Ratio laminarity / determinism (LAM/DET): 1.620535
# Including diagonal
# emRad <- est_radius(rp(noise,emDim = emDim, emLag = emLag))
emLag <- 100
emDim <- 2
emRad <- .00018
out_rqa_par <-  casnet::rqa_par(y1 = noise, AUTO = TRUE, emDim = emDim, emLag = emLag, emRad = emRad, silent = FALSE, theiler = 0)
# Exclude diagonal
#emRad2 <- est_radius(rp(noise,emDim = emDim, emLag = emLag))
out_rqa_par2 <- rqa_par(y1 = noise, AUTO = TRUE, emDim = emDim, emLag = emLag, emRad = emRad, theiler = 1, silent = TRUE)

compOUT2 <- data.frame(Meausure = c("RR","DET", "MAX_dl", "MEAN_dl", "ENT_dl", "LAM", "MAX_vl", "MEAN_vl", "ENT_vl"), 
                      rqa_theiler0 = round(c(out_rqa_par$RR, out_rqa_par$DET, out_rqa_par$MAX_dl, out_rqa_par$MEAN_dl, out_rqa_par$ENT_dl, out_rqa_par$LAM_vl, out_rqa_par$MAX_vl, out_rqa_par$TT_vl, out_rqa_par$ENT_vl), digits = 6),
                      rqa_theiler1 = round(c(out_rqa_par2$RR, out_rqa_par2$DET, out_rqa_par2$MAX_dl, out_rqa_par2$MEAN_dl, out_rqa_par2$ENT_dl, out_rqa_par2$LAM_vl, out_rqa_par2$MAX_vl, out_rqa_par2$TT_vl, out_rqa_par2$ENT_vl), digits = 6),
                      PyRQA = c(0.049585, 0.230880, 7, 2.149236, 0.443727, 0.374149, 8, 2.305511, 0.709903),
                      Diagonal = c("Yes","No","No","No","No","Yes","Yes","Yes","Yes"))
knitr::kable(compOUT2, col.names = NULL, align = c("l","c","c","c","c")) %>%
 kableExtra::add_header_above(c("\nMeasure","crqa_par\n(theiler = 0)","crqa_par\n(theiler = 1)", "PyRQA\n(theiler_corrector = 1)", "PyRQA\nDiagonal included?"))

An R interface to Marwan's commandline recurrence plots {#marwan}

IMPORTANT: Currently rp_cl can only run on an operating system that allows execution of 32-bit applications!

The crqa_cl() function is a wrapper for the commandline Recurrence Plots executable provided by Norbert Marwan.

The rp executable file is installed on your machine when the function rp_cl() is called for the first time:

If you cannot change the permissions on the folder where rp was downloaded, consider downloading the appropriate executable from the commandline Recurrence Plots site to a directory in which you have such permissions. Then change the path_to_rp option using options(casnet.path_to_rp="YOUR_PATH_TO_RP"). See the manual entry for rp_cl() for more details.


The platform specific rp command line executable files were created by Norbert Marwan and obtained under a Creative Commons License from the website of the Potsdam Institute for Climate Impact Research at: http://tocsy.pik-potsdam.de/

The full copyright statement on the website is as follows:

© 2004-2017 SOME RIGHTS RESERVED
University of Potsdam, Interdisciplinary Center for Dynamics of Complex Systems, Germany
Potsdam Institute for Climate Impact Research, Transdisciplinary Concepts and Methods, Germany
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 2.0 Germany License.

More information about recurrence quantification analysis can be found on the Recurrence Plot website.

Computational load: The Python solution [PyRQA]

When the time series you analyze are very long, the recurrence matrix will become very large and R will become very slow. One solution is to use R to run the Python program PyRQA or perhaps pyunicorn. The options for PyRQA are limited compared to the casnet functions, but the gain in processing speed is remarkable!

What follows is an example of how you could make PyRQA run in R using the package reticulate.

Setup the environment {-}

Suppose you are on a machine that has both R and Python installed then the steps are:

You should only have to create and setup the environment once.

library(reticulate)

# OS requirements
# Python3.X is installed and updated.
# On MacOS you'll probably need to run these commands in a Terminal window:
python3 pip install --update pip # Updates the Python module installer
python3 pip intall pyrqa # Installs the pyrqa module on your machine

# First make sure you use the latest Python version
# You can check your machine by calling: reticulate::py_discover_config()
reticulate::use_python("/usr/local/bin/python3")

# Create a new environment "r-reticulate", the path is stored in vEnv
# On Windows use coda_create() see the reticulate manual.
vEnv <- reticulate::virtualenv_create("r-reticulate")

# Install pyrqa into the virtual environment
reticulate::virtualenv_install("r-reticulate","pyrqa")

# If you wish to remove the environment use: reticulate::virtualenv_remove("r-reticulate")

After the environment is set up:

An important thing to note in the example below is the use of as.integer() to pass integer variables to Python.

# Make sure you associate reticulate with your virtual environment.
reticulate::use_virtualenv("r-reticulate", required = TRUE)

# Import pyrqa into your R session
pyrqa <- reticulate::import("pyrqa")

# Alternatively, you can import from a path in the virtual environment.
# On MacOS this will be a hidden folder in your home directory:
# '.virtualenvs/r-reticulate/lib/Python3.X/site-packages'
# pyrqa <- import_from_path(file.path(vEnv,"/lib/python3.9/site-packages"))

# Now perform RQA on your N = 10,000 time series!
Y <- cumsum(rnorm(10000))

# Automated parameter search will still take some time using casnet
system.time({
  emPar <- casnet::est_parameters(Y, doPlot = FALSE)
  emRad <- casnet::est_radius(y1 = Y, emLag = emPar$optimLag, emDim = emPar$optimDim)
  })
# user    system  elapsed 
# 299.732 89.094  393.620 

# About 5 minutes to find a delay, embedding dimension and radius yielding 5% recurrent points.

# Now do an RQA on the 10,000 x 10,000 matrix using Python
system.time({
time_series <- pyrqa$time_series$TimeSeries(Y, 
                                            embedding_dimension= as.integer(emPar$optimDim), 
                                            time_delay= as.integer(emPar$optimLag))
settings    <- pyrqa$settings$Settings(time_series, 
                                       analysis_type = pyrqa$analysis_type$Classic,
                                       neighbourhood = pyrqa$neighbourhood$FixedRadius(emRad$Radius),
                                       similarity_measure = pyrqa$metric$EuclideanMetric,
                                       theiler_corrector = 0)
computation <- pyrqa$computation$RQAComputation$create(settings)
result      <- computation$run()
})
# user   system  elapsed 
# 2.996  0.069   0.365 

# About 3 seconds for the analysis...
# That's really fast!

print(result)
RQA Result:
===========

Minimum diagonal line length (L_min): 2
Minimum vertical line length (V_min): 2
Minimum white vertical line length (W_min): 2

Recurrence rate (RR): 0.050090
Determinism (DET): 0.955821
Average diagonal line length (L): 10.634044
Longest diagonal line length (L_max): 9866
Divergence (DIV): 0.000101
Entropy diagonal lines (L_entr): 3.064460
Laminarity (LAM): 0.969709
Trapping time (TT): 14.930102
Longest vertical line length (V_max): 345
Entropy vertical lines (V_entr): 3.386939
Average white vertical line length (W): 265.518914
Longest white vertical line length (W_max): 9161
Longest white vertical line length inverse (W_div): 0.000109
Entropy white vertical lines (W_entr): 4.726210

Ratio determinism / recurrence rate (DET/RR): 19.081989
Ratio laminarity / determinism (LAM/DET): 1.014530

You can also save the Recurrence Plot.

RPcomputation <- pyrqa$computation$RPComputation$create(settings)
RPresult <- RPcomputation$run()
pyrqa$image_generator$ImageGenerator$save_recurrence_plot(RPresult$recurrence_matrix_reverse,'recurrence_plot_python.png')
knitr::include_graphics("recurrence_plot_python.png")

Other options: Julia

The Julia software has a number of very powerful packages to simulate and analyze dynamical systems, see: https://juliadynamics.github.io/DynamicalSystems.jl/latest/

You can perform RQA (https://juliadynamics.github.io/DynamicalSystems.jl/latest/tutorial/#State-space-sets), but perhaps more interestingly, you can use a powerful automatic parameter selection algorithm for embedding called PECUZAL: https://juliadynamics.github.io/DelayEmbeddings.jl/stable/unified/

I use the freely available software Visual Studio Code to write and run Python (https://code.visualstudio.com/docs/languages/python) and Julia (https://code.visualstudio.com/docs/languages/julia)



FredHasselman/casnet documentation built on April 20, 2024, 3:05 p.m.