# Jacobian stability properties

Code

    library(estar)
    library(tidyr)
    library(dplyr)
    library(tibble)
    library(ggplot2)
    library(purrr)
    library(viridis)
    library(MARSS)
    library(hesim)
    library(kableExtra)
    source("custom_aesthetics.R")

    label_intensity <- function(intensity, mu){
      paste0("Conc = ", intensity, " micro g/L")
    }

Calculation of the Jacobian stability properties (in the context of the `estar` package, Fig. 1) relies on the interaction strengths between species composing a community.

Figure 1: Jacobian stability properties calculated by `estar`: reactivity (\(R_a\)), calculated at the first time step following disturbance (\(t_{d+1}\)); maximal amplification (\(A_{max}\)); initial resilience (\(R_0\)); asymptotic resilience (R∞); and intrinsic stochastic invariability (\(I_S\)). For simplicity, the properties are illustrated as being calculated in relation to a state variable (\(v\)) disturbed at time \(t_d\), but the properties are calculated from the community’s species interactions matrix and not a single state variable.

This information is classically summarized in the community matrix (sensu Novak et al. 2016, detailed in the Glossary in the main text). In community models, this is equivalent to a time-discrete version (Downing et al. 2020) of the Jacobian matrix composed of the partial derivatives of the dynamical equations describing each species population growth (Domínguez-García et al. 2019). The Jacobian matrix consists of \(m \times m\) entries (for \(m\) species). Each entry reflects the effect of the population abundance of the species in the column on the population growth rate of the species in the row. (Fig. 2)

Figure 2: The \(B\) matrix that the `estar` Jacobian functions take in as argument is the \(m \times m\) Jacobian community matrix (for \(m\) species). Each entry reflects the effect of the population abundance of the species in the column on the population growth rate of the species in the row

In real-world communities, interaction strengths can be estimated with multivariate autoregressive state-space (MARSS) models. To assist `estar` users, in this vignette, we focus on explaining how a MARSS model works and how these models are used with the functions provided by the `estar` package. To facilitate understanding, we illustrate the definition of a MARSS model in the context of this application to an aquatic community. For a more general and formal definition, refer to the `MARSS` package documentation.

# Overview of demonstration data

Similar to what we did for the functional properties, we illustrate the use of Jacobian properties on a dataset compiled from an ecotoxicological study about the effects of insecticide on a community of aquatic macroinvertebrates (Brink et al. 1996; Wijngaarden et al. 1996). The insecticide chlorpyrifos negatively affects freshwater macroinvertebrates, and, to a lesser degree, zooplankton species. This insecticide was applied at four different concentrations (0.1, 0.9, 6, and 44 \(\mu\)g/ L), with two replicates per concentration level. Additionally, four replicates were used as control, i.e. an undisturbed systems (‘baseline’ in `estar` terminology). The community is composed of 128 species, classified into 5 functional groups: herbivores, detritiherbivores, carnivores, omnivores, and detritivores (Fig. 3).

Figure 3: Organism mean count (log axis) of the five functional groups over the course of ecotoxicological experiment. The vertical dashed line identifies the time when chloryfiros insecticide was applied (at week 0), and facets represent the different concentrations (micro g/L).

# MARSS models

## Introduction to MARSS models

A complete MARSS model that describes a state process (\(\mathbf{X}\)) assessed via an observation process (\(\mathbf{Y}\)) is defined as: \[\begin{equation} \begin{gathered} \mathbf{x}_t = \mathbf{B}\mathbf{x}_{t-1}+\mathbf{u}+\mathbf{C}\mathbf{c}_{t}+\mathbf{w}_t \text{ where } \mathbf{w}_t \sim \ \text{MVN}(0,\mathbf{Q}) \\ \mathbf{y}_t = \mathbf{Z}\mathbf{x}_t+\mathbf{a}+\mathbf{D}\mathbf{d}_{t}+\mathbf{v}_t \text{ where } \mathbf{v}_t \sim \ \text{MVN}(0,\mathbf{R}) \\ \mathbf{x}_1 \sim\ \text{MVN}(\pi,\Lambda) \text{ or } \mathbf{x}_0 \sim\ \text{MVN}(\pi,\Lambda) \end{gathered} \end{equation}\]

where

- \(\mathbf{X}\) is a \(m \times T\) matrix, composed of the unknown abundance values of \(m\) species or functional groups (\(fg\), rows), over time \(T\) (\(t\), columns)

\[\begin{equation} X= \begin{bmatrix} x_{fg1, t1} & x_{fg1, t2} & ... & x_{fg1, tT}\\ x_{fg2, t1} & x_{fg2, t2} & ... & x_{fg2, tT}\\ ... & ... & ... & ...\\ x_{fgm, t1} & x_{fgm, t2} & ... & x_{fgm, tT} \end{bmatrix} \end{equation}\]

- \(\mathbf{Y}\) is a \(n \times T\) matrix, composed of \(n\) observations (rows) over time \(T\) (\(t\), columns)

\[\begin{equation} Y= \begin{bmatrix} y_{1, t1} & y_{1, t2} & ... & y_{1, tT}\\ y_{2, t1} & y_{2, t2} & ... & y_{2, tT}\\ ... & ... & ... & ...\\ y_{n, t1} & y_{n, t2} & ... & y_{n, tT} \end{bmatrix} \end{equation}\]

- \(\pi\) is either a parameter or a fixed prior. It is a \(m \times 1\) matrix.

Note that:

- considering that the observations in \(\mathbf{Y}\) refer to processes \(\mathbf{X}\), \(n \geq m\). Missing values can be passed as `NA`.
- from \(\mathbf{X}\) and \(\mathbf{Y}\) alone, it is impossible to trace how observations refer to processes. We will address this in the description of \(\mathbf{Y}\).

The state process (\(\mathbf{X}\)) is described by:

- \(\mathbf{x}_t\) is a \(m \times 1\) vector of unknown abundances of the \(m\) species at time \(t\).
- \(\mathbf{B}\) is a \(m \times m\) matrix of states covariances. This is the matrix of interactions strengths (\(\beta\)) we want to estimate to calculate Jacobian properties. The estimated \(\beta_{(i,j)}\) reflect the effect of the abundances of species in the column (species j) on the growth rate of the species in the row (species i).
- \(\mathbf{u}\) is a \(m \times 1\) vector of states fixed values. For example, expected abundance values for each functional group.
- \(\mathbf{C}\) is a \(m \times p\) matrix of coefficients (\(\gamma\)) of each of the \(p\) covariates (enviromental conditions) effects on each of the \(m\) states (abundances).
- \(\mathbf{c}_t\) is a \(p \times 1\) vector of covariate values. For example, environmental conditions which are expected to affect abundances.
- \(\mathbf{w}_t\) is a \(m \times 1\) vector of the state (\(X\)) process errors. These account for the environmental stochasticity of the system.

Thus, in matrix formulation:

\[\begin{equation} \begin{bmatrix} x_{1}\\ ... \\ x_{m} \end{bmatrix}_t = \begin{bmatrix} \beta_{\mathrm{1,1}} & ... & \beta_{\mathrm{1,m}} \\ ... & ... & ... \\ \beta_{\mathrm{m,1}} & ... & \beta_{\mathrm{m,m}} \end{bmatrix} \begin{bmatrix} x_{1}\\ ... \\ x_{m} \end{bmatrix}_{t-1} + \begin{bmatrix} u_{1}\\ ...\\ u_{m} \end{bmatrix} + \begin{bmatrix} \gamma_{\mathrm{1,1}} & ... & \gamma_{\mathrm{1,p}} \\ ... & ... & ... \\ \gamma_{\mathrm{m,1}} & ... & \gamma_{\mathrm{m,p}} \end{bmatrix} \begin{bmatrix} c_{\mathrm{1}} \\ ... \\ c_{\mathrm{m}} \end{bmatrix}_{t} + \begin{bmatrix} w_{1}\\ ...\\ w_{m} \end{bmatrix}_t \\ \end{equation}\]

The \(w_{t}\) errors follow a multivariate normal distribution of mean 0, and covariance \(\mathbf{Q}\) (a \(m \times m\) matrix). \[\begin{equation} \mathbf{w}_t \sim \,\text{MVN} \begin{pmatrix} 0, \begin{bmatrix} \sigma_{\mathrm{1}} & ... & \sigma_{\mathrm{1,m}} \\ ... & ... & ... \\ \sigma_{\mathrm{m,1}} & ... & \sigma_{\mathrm{m}} \\ \end{bmatrix} \end{pmatrix}\\ \end{equation}\]

where (\(\sigma\)) refer to variances of abundances (diagonal) and covariances among abundances of different species (off-diagonal).

The model initial conditions can be specified at \(t=0\) (\(x_0\)) or \(t=1\) (\(x_1\)).

\[\begin{equation} \begin{bmatrix} x_{fg1}\\ ...\\ x_{fgm} \end{bmatrix}_0 = \begin{bmatrix} \mu_1\\ ...\\ \mu_m \end{bmatrix}_t \end{equation}\]

The observation process (\(\mathbf{Y}\)) is described by:

- \(\mathbf{y}_t\) is a \(m \times 1\) vector with the number of \(n\) observations taken at time \(t\).
- \(\mathbf{Z}\) is a \(n \times m\) matrix which describes the relationships (\(\zeta\)) between hidden states \(\mathbf{x}\) (real abundances of functional groups) and the observations \(\mathbf{y}\) (samples). \(\mathbf{Z}\) can accommodate replicates of observations.
- \(\mathbf{a}\) is a \(n \times 1\) vector of fixed values. It reflects biases that offset observations from process values.
- \(\mathbf{D}\) is a \(n \times q\) matrix of \(\delta\) coefficients of each of the \(q\) covariates on each of the \(n\) observations
- \(\delta\) is either a parameter or a fixed prior. It is a \(m \times m\) variance-covariance matrix.
- \(\mathbf{d}_t\) s a \(q \times 1\) vector of covariate values affecting the observation process.
- \(\mathbf{v}_t\) is a \(n \times 1\) vector of observation errors. They follow a multivariate normal distribution with mean 0, and covariance \(\mathbf{R}\) (a \(n \times n\) matrix) .

In matrix formulation:

\[\begin{equation} \begin{bmatrix} y_{1}\\ ... \\ y_{n} \end{bmatrix}_t = \begin{bmatrix} \zeta_{\mathrm{1,1}} & ... & \zeta_{\mathrm{1,m}} \\ ... & ... & ... \\ \zeta_{\mathrm{m,1}} & ... & \zeta_{\mathrm{m,m}} \end{bmatrix} \begin{bmatrix} x_{1}\\ ... \\ x_{m} \end{bmatrix}_{t-1} + \begin{bmatrix} a_{obs1}\\ ...\\ a_{obsn} \end{bmatrix} + \begin{bmatrix} \delta_{\mathrm{1,1}} & ... & \delta_{\mathrm{1,q}} \\ ... & ... & ... \\ \delta_{\mathrm{n,1}} & ... & \delta_{\mathrm{q,q}} \end{bmatrix} \begin{bmatrix} d_{\mathrm{1}} \\ ... \\ d_{\mathrm{q}} \end{bmatrix}_{t} + \begin{bmatrix} v_{obs1}\\ ...\\ v_{obsn} \end{bmatrix}_t \\ \end{equation}\]

The \(v_{t}\) errors follow a multivariate normal distribution of mean 0, and covariance \(\mathbf{R}\) (a \(n \times n\) matrix). \[\begin{equation} \mathbf{v}_t \sim \,\text{MVN} \begin{pmatrix} 0, \begin{bmatrix} \sigma_{\mathrm{1}} & ... & \sigma_{\mathrm{1,n}} \\ ... & ... & ... \\ \sigma_{\mathrm{n,1}} & ... & \sigma_{\mathrm{n}} \\ \end{bmatrix} \end{pmatrix}\\ \end{equation}\]

The terms \(\mathbf{y}_t\), \(\mathbf{c}_t\) and \(\mathbf{d}_t\) are empirical data that are provided by the user. The remaining terms \(\mathbf{B}\), \(\mathbf{u}\), \(\mathbf{C}\), \(\mathbf{Q}\), \(\mathbf{Z}\), \(\mathbf{a}\), \(\mathbf{D}\), and \(\mathbf{R}\) can be estimated or some of them (e.g. \(\mathbf{u}\), \(\mathbf{Q}\)) can be fixed to certain values based on the assumptions about the system. The terms \(\mathbf{c}_t\) and \(\mathbf{d}_t\) must be given as an input, without any missing values.

Important note: The observed abundances are usually log-transformed prior to the analyses to comply with the multiplicative process.

## MARSS in estar

When calculating the Jacobian properties, the user can either provide the required community matrix (\(\mathbf{B}\)) or have it estimated from the empirical data using a set of predefined assumptions about the modeled community. Empirical data in this case constitutes time series of population abundances (or biomass) of all the species (or functional groups) in the community. None of the parameters detailed here have default values in functions that calculate the Jacobian properties. The motivation for it is to avoid the indiscriminate use, and thus force the user to take into consideration the implications of the chosen assumptions.

When fitting MARSS models, the user can have up to eight matrices or vectors of parameters estimated (Holmes, Scheuerell, et al., 2024). This is true for all other parameters of the model, which refer to the (co-) variances and errors of abundance and observation processes. The species interaction matrix (B) is usually fully estimated, i.e. all interaction strengths are estimated. It is however possible to constrain values to specific values or in relation to each other. We showcase the use of MARSS in a system assumed to be at equilibrium, with zero observational error, zero covariates (e..g. environmental conditions affecting abundances or the observation). These assumptions reflect knowledge about the system, which, in our case we assume to be minimal. However, for that reason, we also leave it for the user to define their own assumptions. They will affect the estimation of B. Moreover, it is important to point out that the calculation of asymptotic resilience (Downing et al., 2020) implemented in the package requires that process error and constant covariates.

Pay attention to careful specification of the parameters for the options. For example, a care should be made when specifying the initial and the final time step of the time series. If the user knows the exact timing when the disturbance took place, then the time series should start right from that time point or one time step (whatever appropriate time step is useful) after.

We present a minimal case of one single observation per state process and other simplifying assumptions.

## Minimal case

The calculation of asymptotic resilience (Downing et al. (2020)) that is used in the package requires zero process error and constant covariates. Therefore, our minimal case assumes that:

- Each observation corresponds to one state process (\(y_i = x_i\)), thus \(\mathbf{Z}\) = “identity”. In our case we have 5 functional groups, therefore \(\mathbf{Z}\) corresponds to a \(5 \times 5\) identity matrix, since \(n = m\):

Code

    Z_I5 <- matrix(list(0), 5, 5)
    diag(Z_I5) <- 1

- All functional groups have the same state processes variance: \(w_t = \sigma\), thus `Q = "diagonal and equal"` (in MARSS terminology), which equates to \[\begin{equation} \begin{bmatrix} \sigma & 0 & 0 & 0 & 0 \\ 0 & \sigma & 0 & 0 & 0 \\ 0 & 0 & \sigma & 0 & 0 \\ 0 & 0 & 0 & \sigma & 0 \\ 0 & 0 & 0 & 0 & \sigma\\ \end{bmatrix} \end{equation}\]
- Observational error is the same for all functional groups and is assumed to be 0 (we assume complete knowledge of the system): \(v_t = v\), thus `R = 0`
- Constant covariates: \(C = C\) and \(c_{t} = c = 0\)
- We assume that the system is at equilibrium, therefore: \(U\) = \(A\) = 0
- \(X_0 = X_{t=1}\), which is passed to the `MARSS()` as the argument `tinitx = 1`

Such model is formalized as:

\[\begin{equation} X = \mathbf{B} \times X_{t-1} + C.0 + 0 + 0 \\ Y = Z \times X_t + 0 + D.0 + v \\ \end{equation}\]

Thus, our aquatic commmunity data must be split by the concentration of the pesticide, into matrices \(Y_{control}, Y_{0.1}, Y_{0.9}, Y_6, Y_{44}\), each containing the time-series of functional group counts under one treatment. We have two replicates per treatment and four replicates for the control but we are modelling one observation as corresponding to one state processes, therefore a choice must be made how to deal with this. One possibility is to randomly pick one of the replicates per treatment. Alternatively, one can choose to average the data over replicates. We here use the mean of species abundances over all replicates:

Code

    ## calculate z-scores of abundances
    data_zscores <- aquacomm_fgps %>%
      dplyr::filter(time == 0.14 | (time >= 4 && time <= 28)) %>%
      dplyr::mutate_at(vars(herb, detr_herb, carn, omni, detr),
                       ~if_else(. == 0, log(. + 0.001), log(.))) %>%
      ungroup() %>%
      group_by(., repli) %>%
      dplyr::mutate_at(vars(herb, detr_herb, carn, omni, detr),
                       ~MARSS::zscore(.)) %>%
      tidyr::pivot_longer(cols = c(herb, detr_herb, carn, omni, detr),
                          names_to = "fgp",
                          values_to = "abund_z")

    ## summarize abunds over replicates
    data_zsumm <- data_zscores %>%
        dplyr::group_by(time, treat, fgp) %>%
        dplyr::summarize(abundz_mu = mean (abund_z),
                         abundz_sd = sd(abund_z)) %>%
        dplyr::ungroup()
    #> `summarise()` has grouped output by 'time', 'treat'. You can override using the
    #> `.groups` argument.

    ## convert into time-series matrix
    data_matrix <- data_zsumm %>%
        dplyr::select(time, treat, fgp, abundz_mu) %>%
        split(.$treat) %>%
        purrr::map(~ dplyr::select(., time, fgp, abundz_mu) %>%
            unique() %>%
            tidyr::pivot_wider(id_cols = fgp,
                               names_from = time, values_from = "abundz_mu",
                               names_prefix = "time ") %>%
            tibble::column_to_rownames(var = "fgp") %>%
            as.matrix())

Next, we prepare the arguments to be used for specifying the MARSS model according to the minimal requirements case outlined above. As we want to estimate all interaction strengths, we set `B = "unconstrained"`.

Code

    ## 5 groups
    ### no observation error variance: 0 reps (summarized data), 5 gps
    R_05 <- matrix(list(0), 5, 5)

    data_marss <- data_matrix %>%
        purrr::map(~ MARSS(.,
                    list(B = "unconstrained",
                         U = "zero", A = "zero",
                         Z = "identity", ## Z_I5 could also have been provided here
                         Q = "diagonal and equal",
                         R = R_05,
                         tinitx = 1),
                    method = "BFGS"))
    #> Success! Converged in 269 iterations.
    #> Function MARSSkfas used for likelihood calculation.
    #>
    #> MARSS fit is
    #> Estimation method: BFGS
    #> Estimation converged in 269 iterations.
    #> Log-likelihood: 0.4622099
    #> AIC: 61.07558   AICc: 309.0756
    #>
    #>                Estimate
    #> B.(1,1)          0.5219
    #> B.(2,1)         -0.0108
    #> B.(3,1)         -0.2044
    #> B.(4,1)         -0.0716
    #> B.(5,1)         -0.3629
    #> B.(1,2)         -0.8437
    #> B.(2,2)         -0.9161
    #> B.(3,2)          0.3779
    #> B.(4,2)         -1.2660
    #> B.(5,2)         -0.2319
    #> B.(1,3)         -0.1394
    #> B.(2,3)         -2.0305
    #> B.(3,3)         -0.2166
    #> B.(4,3)         -0.8972
    #> B.(5,3)         -0.7635
    #> B.(1,4)          0.4447
    #> B.(2,4)          0.3558
    #> B.(3,4)         -0.1875
    #> B.(4,4)          0.3125
    #> B.(5,4)          0.0178
    #> B.(1,5)         -0.1898
    #> B.(2,5)         -0.1782
    #> B.(3,5)          0.4754
    #> B.(4,5)          0.2843
    #> B.(5,5)          0.1373
    #> Q.diag           0.0570
    #> x0.X.carn        5.2995
    #> x0.X.detr       -5.0893
    #> x0.X.detr_herb  -1.2040
    #> x0.X.herb      -19.7863
    #> x0.X.omni       -2.0518
    #> Initial states (x0) defined at t=1
    #>
    #> Standard errors have not been calculated.
    #> Use MARSSparamCIs to compute CIs and bias estimates.
    #>
    #> Success! Converged in 457 iterations.
    #> Function MARSSkfas used for likelihood calculation.
    #>
    #> MARSS fit is
    #> Estimation method: BFGS
    #> Estimation converged in 457 iterations.
    #> Log-likelihood: 1.478126
    #> AIC: 59.04375   AICc: 307.0437
    #>
    #>                Estimate
    #> B.(1,1)         -0.4051
    #> B.(2,1)         -0.3550
    #> B.(3,1)         -0.0627
    #> B.(4,1)         -0.1959
    #> B.(5,1)         -0.1851
    #> B.(1,2)         -0.5913
    #> B.(2,2)          0.0666
    #> B.(3,2)         -0.4427
    #> B.(4,2)         -1.0640
    #> B.(5,2)         -0.0149
    #> B.(1,3)         -0.1921
    #> B.(2,3)          1.0615
    #> B.(3,3)         -0.5267
    #> B.(4,3)         -0.3003
    #> B.(5,3)          0.3884
    #> B.(1,4)          0.9893
    #> B.(2,4)         -0.4152
    #> B.(3,4)          0.9427
    #> B.(4,4)          0.6449
    #> B.(5,4)          0.0324
    #> B.(1,5)          0.7834
    #> B.(2,5)          0.0892
    #> B.(3,5)         -0.2119
    #> B.(4,5)          0.5804
    #> B.(5,5)         -0.1663
    #> Q.diag           0.0538
    #> x0.X.carn       53.2560
    #> x0.X.detr       -1.1163
    #> x0.X.detr_herb  25.1333
    #> x0.X.herb       18.6535
    #> x0.X.omni        8.4481
    #> Initial states (x0) defined at t=1
    #>
    #> Standard errors have not been calculated.
    #> Use MARSSparamCIs to compute CIs and bias estimates.
    #>
    #> Success! Converged in 447 iterations.
    #> Function MARSSkfas used for likelihood calculation.
    #>
    #> MARSS fit is
    #> Estimation method: BFGS
    #> Estimation converged in 447 iterations.
    #> Log-likelihood: -2.162082
    #> AIC: 66.32416   AICc: 314.3242
    #>
    #>                Estimate
    #> B.(1,1)         -0.3358
    #> B.(2,1)         -0.7934
    #> B.(3,1)         -0.2359
    #> B.(4,1)         -0.0999
    #> B.(5,1)          0.3694
    #> B.(1,2)          0.1171
    #> B.(2,2)         -0.5162
    #> B.(3,2)          0.4586
    #> B.(4,2)          0.2081
    #> B.(5,2)         -1.3653
    #> B.(1,3)          0.2567
    #> B.(2,3)          0.3787
    #> B.(3,3)         -0.6412
    #> B.(4,3)          0.0282
    #> B.(5,3)          0.4439
    #> B.(1,4)          1.3040
    #> B.(2,4)          0.9217
    #> B.(3,4)          0.5280
    #> B.(4,4)          0.2357
    #> B.(5,4)          0.7576
    #> B.(1,5)         -0.0258
    #> B.(2,5)          0.7177
    #> B.(3,5)         -0.2842
    #> B.(4,5)         -0.9181
    #> B.(5,5)          1.2020
    #> Q.diag           0.0663
    #> x0.X.carn        0.4822
    #> x0.X.detr       -0.0667
    #> x0.X.detr_herb   0.1748
    #> x0.X.herb       -0.6885
    #> x0.X.omni       -0.4695
    #> Initial states (x0) defined at t=1
    #>
    #> Standard errors have not been calculated.
    #> Use MARSSparamCIs to compute CIs and bias estimates.
    #>
    #> Success! Converged in 543 iterations.
    #> Function MARSSkfas used for likelihood calculation.
    #>
    #> MARSS fit is
    #> Estimation method: BFGS
    #> Estimation converged in 543 iterations.
    #> Log-likelihood: -4.369171
    #> AIC: 70.73834   AICc: 318.7383
    #>
    #>                Estimate
    #> B.(1,1)         -0.1185
    #> B.(2,1)         -0.5161
    #> B.(3,1)          1.0039
    #> B.(4,1)         -1.2459
    #> B.(5,1)         -1.1696
    #> B.(1,2)          0.8741
    #> B.(2,2)         -0.7258
    #> B.(3,2)          0.7503
    #> B.(4,2)         -1.0504
    #> B.(5,2)         -0.9817
    #> B.(1,3)          0.1451
    #> B.(2,3)          0.3086
    #> B.(3,3)         -0.5998
    #> B.(4,3)         -0.1112
    #> B.(5,3)          0.3521
    #> B.(1,4)          1.5218
    #> B.(2,4)         -0.3332
    #> B.(3,4)          1.6816
    #> B.(4,4)         -0.3687
    #> B.(5,4)          0.1060
    #> B.(1,5)         -1.7381
    #> B.(2,5)          0.9574
    #> B.(3,5)         -2.2203
    #> B.(4,5)          1.9793
    #> B.(5,5)          1.2918
    #> Q.diag           0.0751
    #> x0.X.carn       40.9545
    #> x0.X.detr       41.4743
    #> x0.X.detr_herb  27.2801
    #> x0.X.herb       41.1738
    #> x0.X.omni       56.5749
    #> Initial states (x0) defined at t=1
    #>
    #> Standard errors have not been calculated.
    #> Use MARSSparamCIs to compute CIs and bias estimates.
    #>
    #> Success! Converged in 395 iterations.
    #> Function MARSSkfas used for likelihood calculation.
    #>
    #> MARSS fit is
    #> Estimation method: BFGS
    #> Estimation converged in 395 iterations.
    #> Log-likelihood: -0.5253192
    #> AIC: 63.05064   AICc: 311.0506
    #>
    #>                 Estimate
    #> B.(1,1)         -0.62371
    #> B.(2,1)          0.29416
    #> B.(3,1)         -0.64355
    #> B.(4,1)         -0.00904
    #> B.(5,1)          0.46809
    #> B.(1,2)          0.41482
    #> B.(2,2)          0.06580
    #> B.(3,2)          0.15270
    #> B.(4,2)          0.79525
    #> B.(5,2)         -0.05974
    #> B.(1,3)         -0.23478
    #> B.(2,3)          0.09223
    #> B.(3,3)         -0.47154
    #> B.(4,3)         -0.67455
    #> B.(5,3)          0.61691
    #> B.(1,4)          0.19469
    #> B.(2,4)          0.08880
    #> B.(3,4)          0.42072
    #> B.(4,4)         -0.07621
    #> B.(5,4)          0.18910
    #> B.(1,5)         -1.06069
    #> B.(2,5)          0.20993
    #> B.(3,5)          0.41511
    #> B.(4,5)         -0.91891
    #> B.(5,5)          0.65924
    #> Q.diag           0.06034
    #> x0.X.carn      -10.37101
    #> x0.X.detr        9.41935
    #> x0.X.detr_herb   6.29503
    #> x0.X.herb      -18.69159
    #> x0.X.omni        5.89957
    #> Initial states (x0) defined at t=1
    #>
    #> Standard errors have not been calculated.
    #> Use MARSSparamCIs to compute CIs and bias estimates.
    names(data_marss) <- paste0("Conc. = ", c("0", "0.1", "0.9", "6", "44" ), " micro g/L")

We provide the function `estar::extractB()` which extracts \(\mathbf{B}\) in matrix form from the fitted MARSS object and names the interacting groups

Code

    (data_Bs <- data_marss %>%
      purrr::map(~estar::extractB(.,
                    states_names = c("Herbivores", "DetHerbivores", "Carnivores", "Omnivores", "Detrivores"))))

    #> $`Conc. = 0 micro g/L`
    #>                Herbivores DetHerbivores Carnivores   Omnivores Detrivores
    #> Herbivores     0.52191533    -0.8437343 -0.1393525  0.44474807 -0.1898124
    #> DetHerbivores -0.01082672    -0.9160830 -2.0305283  0.35577934 -0.1782185
    #> Carnivores    -0.20437262     0.3778871 -0.2165641 -0.18748412  0.4753576
    #> Omnivores     -0.07158061    -1.2659754 -0.8972238  0.31249500  0.2842709
    #> Detrivores    -0.36287996    -0.2318975 -0.7635137  0.01784029  0.1373144
    #>
    #> $`Conc. = 0.1 micro g/L`
    #>                Herbivores DetHerbivores Carnivores   Omnivores Detrivores
    #> Herbivores    -0.40508310   -0.59125746 -0.1921170  0.98926150  0.7833650
    #> DetHerbivores -0.35496369    0.06656895  1.0614829 -0.41523463  0.0891776
    #> Carnivores    -0.06272273   -0.44269275 -0.5266632  0.94267398 -0.2118632
    #> Omnivores     -0.19594958   -1.06399890 -0.3002983  0.64493541  0.5803856
    #> Detrivores    -0.18507227   -0.01493199  0.3883550  0.03240816 -0.1662643
    #>
    #> $`Conc. = 0.9 micro g/L`
    #>                Herbivores DetHerbivores Carnivores Omnivores  Detrivores
    #> Herbivores    -0.33578147     0.1170750  0.2566978 1.3040044 -0.02575887
    #> DetHerbivores -0.79335961    -0.5161517  0.3786550 0.9217342  0.71766438
    #> Carnivores    -0.23592260     0.4586095 -0.6412405 0.5279731 -0.28424054
    #> Omnivores     -0.09985083     0.2081061  0.0282293 0.2357004 -0.91812344
    #> Detrivores     0.36944296    -1.3652896  0.4439366 0.7575773  1.20200022
    #>
    #> $`Conc. = 6 micro g/L`
    #>               Herbivores DetHerbivores Carnivores  Omnivores Detrivores
    #> Herbivores    -0.1185047     0.8740733  0.1451179  1.5217997 -1.7380682
    #> DetHerbivores -0.5161492    -0.7258430  0.3086290 -0.3332033  0.9573661
    #> Carnivores     1.0038941     0.7502728 -0.5998374  1.6815868 -2.2203463
    #> Omnivores     -1.2459142    -1.0504228 -0.1111658 -0.3687334  1.9792561
    #> Detrivores    -1.1695548    -0.9817327  0.3520671  0.1059634  1.2918341
    #>
    #> $`Conc. = 44 micro g/L`
    #>                 Herbivores DetHerbivores Carnivores   Omnivores Detrivores
    #> Herbivores    -0.623708609    0.41482382 -0.2347751  0.19468717 -1.0606915
    #> DetHerbivores  0.294157577    0.06579711  0.0922320  0.08880025  0.2099330
    #> Carnivores    -0.643547739    0.15269944 -0.4715450  0.42071629  0.4151124
    #> Omnivores     -0.009040533    0.79525481 -0.6745531 -0.07621175 -0.9189084
    #> Detrivores     0.468091511   -0.05974069  0.6169072  0.18910285  0.6592410

Finally, having estimated \(\mathbf{B}\) we can calculate all the Jacobian properties :

## Reactivity

A positive value indicates the system is reactive and the higher its value, the faster the perturbation can grow. The aquatic community in our example seems to be reactive even when no insecticide is applied.

Code

    data_Bs %>%
      purrr::map(estar::reactivity)

    #> $`Conc. = 0 micro g/L`
    #> [1] 0.8871062
    #>
    #> $`Conc. = 0.1 micro g/L`
    #> [1] 1.416764
    #>
    #> $`Conc. = 0.9 micro g/L`
    #> [1] 1.311026
    #>
    #> $`Conc. = 6 micro g/L`
    #> [1] 2.720098
    #>
    #> $`Conc. = 44 micro g/L`
    #> [1] 1.130322

## Maximal amplification

The largest response of the community to the perturbation, it is a measure of the transient behavior of the system.

Code

    data_Bs %>%
      purrr::map(estar::max_amp)

    #> $`Conc. = 0 micro g/L`
    #> [1] 3.142213
    #>
    #> $`Conc. = 0.1 micro g/L`
    #> [1] 3.866439
    #>
    #> $`Conc. = 0.9 micro g/L`
    #> [1] 3.909772
    #>
    #> $`Conc. = 6 micro g/L`
    #> [1] 7.318028
    #>
    #> $`Conc. = 44 micro g/L`
    #> [1] 3.326966

## Initial resilience

Initial resilience is the initial rate of return to equilibrium. The larger its value, the more stable the system, as its “worst case” initial rate of return to equilibrium is faster (Downing et al. 2020).

Code

    data_Bs %>%
      purrr::map(estar::init_resil)

    #> $`Conc. = 0 micro g/L`
    #> [1] -0.2025027
    #>
    #> $`Conc. = 0.1 micro g/L`
    #> [1] -0.0197047
    #>
    #> $`Conc. = 0.9 micro g/L`
    #> [1] -0.4050878
    #>
    #> $`Conc. = 6 micro g/L`
    #> [1] -0.6631728
    #>
    #> $`Conc. = 44 micro g/L`
    #> [1] 0.01910915

## Asymptotic resilience

The slowest/long-term asymptotic rate of return to equilibrium after a pulse perturbation. Asymptotic resilience can take on positive real numbers. The larger its value, the more stable the system as such communities have a faster long-term return to stable state.

Code

    data_Bs %>%
      purrr::map(estar::asympt_resil)

    #> $`Conc. = 0 micro g/L`
    #> [1] 0.1048614
    #>
    #> $`Conc. = 0.1 micro g/L`
    #> [1] 0.6233977
    #>
    #> $`Conc. = 0.9 micro g/L`
    #> [1] NA
    #>
    #> $`Conc. = 6 micro g/L`
    #> [1] 0.137834
    #>
    #> $`Conc. = 44 micro g/L`
    #> [1] 0.1690019

## Stochastic invariability

Inverse of the maximal response variance to white noise. The larger its value, the more stable the system as it indicates less variability due to random displacements of the system from the equilibrium.

Code

    data_Bs %>%
      map(estar::stoch_var)

    #> $`Conc. = 0 micro g/L`
    #> [1] 14.01421
    #>
    #> $`Conc. = 0.1 micro g/L`
    #> [1] 252.3765
    #>
    #> $`Conc. = 0.9 micro g/L`
    #> [1] 3.007914
    #>
    #> $`Conc. = 6 micro g/L`
    #> [1] 95.11741
    #>
    #> $`Conc. = 44 micro g/L`
    #> [1] 8.474541

# Performance of functions

We conducted the benchmark analysis of the execution time of the different Jacobian properties. The code used for the analysis is available in `performance_analysis.R`.

While the time to run the calls can differ significantly from each other, the execution time did not exceed 0.05 seconds for our example (Fi g. 4).

Code

    load("jacobian_performance.rda")

Code

    jacobian_benchmark %>%
      dplyr::select(-neval) %>%
      kableExtra::kbl() %>%
      kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))

Function call | Min. | Lower quart. | Mean | Median | Upper quart. | Max. | Signif.
--- | --- | --- | --- | --- | --- | --- | ---
reactivity | 0.0002651 | 0.0002924 | 0.0003797 | 0.0003213 | 0.0003614 | 0.0013451 | a
max_amp | 0.0013689 | 0.0014863 | 0.0082912 | 0.0016132 | 0.0019386 | 0.6394164 | a
init_resil | 0.0002659 | 0.0002910 | 0.0003660 | 0.0003150 | 0.0003753 | 0.0016038 | a
asympt_resil | 0.0002217 | 0.0002457 | 0.0003180 | 0.0002694 | 0.0003093 | 0.0016270 | a
stoch_var | 0.0011134 | 0.0012181 | 0.0017989 | 0.0013416 | 0.0016967 | 0.0186624 | a

Figure 4: Density distribution of execution times of 100 runs of each of the different functions used to calculate Jacobian properties, as demonstrated in this vignette.

# References

Session Info
Code

    Sys.time()

    #> [1] "2026-05-22 06:24:52 CEST"

Code

    sessionInfo()

    #> R version 4.4.1 (2024-06-14 ucrt)
    #> Platform: x86_64-w64-mingw32/x64
    #> Running under: Windows 11 x64 (build 26100)
    #>
    #> Matrix products: default
    #>
    #>
    #> locale:
    #> [1] LC_COLLATE=C                     LC_CTYPE=English_Germany.utf8
    #> [3] LC_MONETARY=English_Germany.utf8 LC_NUMERIC=C
    #> [5] LC_TIME=English_Germany.utf8
    #>
    #> time zone: Europe/Berlin
    #> tzcode source: internal
    #>
    #> attached base packages:
    #> [1] stats     graphics  grDevices utils     datasets  methods   base
    #>
    #> other attached packages:
    #>  [1] hesim_0.5.4       MARSS_3.11.9      purrr_1.2.0       tibble_3.3.0
    #>  [5] vegan_2.6-8       lattice_0.22-6    permute_0.9-7     kableExtra_1.4.0
    #>  [9] forcats_1.0.0     viridis_0.6.5     viridisLite_0.4.2 cowplot_1.1.3
    #> [13] ggplot2_4.0.0     dplyr_1.1.4       tidyr_1.3.1       estar_2.0-0
    #>
    #> loaded via a namespace (and not attached):
    #>  [1] gtable_0.3.6       xfun_0.51          bslib_0.9.0        vctrs_0.6.5
    #>  [5] tools_4.4.1        generics_0.1.4     parallel_4.4.1     cluster_2.1.6
    #>  [9] pkgconfig_2.0.3    Matrix_1.7-0       data.table_1.14.8  RColorBrewer_1.1-3
    #> [13] S7_0.2.0           lifecycle_1.0.4    compiler_4.4.1     farver_2.1.2
    #> [17] stringr_1.5.1      htmltools_0.5.8.1  sass_0.4.10        yaml_2.3.10
    #> [21] pillar_1.11.1      jquerylib_0.1.4    MASS_7.3-60.2      cachem_1.1.0
    #> [25] nlme_3.1-164       tidyselect_1.2.1   digest_0.6.37      mvtnorm_1.2-5
    #> [29] stringi_1.8.4      labeling_0.4.3     splines_4.4.1      fastmap_1.2.0
    #> [33] grid_4.4.1         expm_0.999-9       cli_3.6.3          magrittr_2.0.4
    #> [37] survival_3.6-4     withr_3.0.2        KFAS_1.5.1         scales_1.4.0
    #> [41] rmarkdown_2.30     gridExtra_2.3      zoo_1.8-12         msm_1.7.1
    #> [45] evaluate_1.0.5     knitr_1.50         mgcv_1.9-1         rlang_1.1.4
    #> [49] Rcpp_1.1.0         glue_1.8.0         xml2_1.3.5         svglite_2.1.0
    #> [53] rstudioapi_0.16.0  jsonlite_2.0.0     R6_2.6.1           systemfonts_1.3.1

Brink, Paul J. van den, René P. A. Van Wijngaarden, Wil G. H. Lucassen, Theo C. M. Brock, and Peter Leeuwangh. 1996. “Effects of the Insecticide Dursban® 4E (Active Ingredient Chlorpyrifos) in Outdoor Experimental Ditches: II. Invertebrate Community Responses and Recovery.” Environmental Toxicology and Chemistry 15 (7): 1143–53. https://doi.org/http://doi.wiley.com/10.1002/etc.5620150719.
Domínguez-García, Virginia, Vasilis Dakos, and Sonia Kéfi. 2019. “Unveiling Dimensions of Stability in Complex Ecological Networks.” Proceedings of the National Academy of Sciences 116 (51): 25714–20. https://doi.org/10.1073/pnas.1904470116.
Downing, Amy L., Craig Jackson, Claire Plunkett, Jayne Ackerman Lockhart, Shannon M. Schlater, and Mathew A. Leibold. 2020. “Temporal Stability Vs. Community Matrix Measures of Stability and the Role of Weak Interactions.” Ecology Letters 23 (10): 1468–78. https://doi.org/10.1111/ele.13538.
Novak, Mark, Justin D. Yeakel, Andrew E. Noble, et al. 2016. “Characterizing Species Interactions to Understand Press Perturbations: What Is the Community Matrix?” Annual Review of Ecology, Evolution, and Systematics 47 (1): 409–32. https://doi.org/10.1146/annurev-ecolsys-032416-010215.
Wijngaarden, René P. A. van, Paul J. van den Brink, Steven J. H. Crum, Theo C. M. Brock, Peter Leeuwangh, and Oude Jan H. Voshaar. 1996. “Effects of the Insecticide Dursban® 4E (Active Ingredient Chlorpyrifos) in Outdoor Experimental Ditches: I. Comparison of Short-Term Toxicity Between the Laboratory and the Field.” Environmental Toxicology and Chemistry 15 (7): 1133–42. https://doi.org/https://doi.org/10.1002/etc.5620150718.