In the Quick Start tutorial, we modelled an experiment which took place in only one aquarium. In this tutorial, we are going to model a foodweb using data from two aquariums, and use each of those aquariums as a replication unit.
It is important to note that in this example, the replication units share the same foodweb topology and the same model parameter values (i.e. uptake rates and loss rates). They are really just that: replication units. The only thing that will be different between them are the initial conditions and the observations.
We keep the foodweb model of this example simple, with only three compartments as before:
NH4
), which is enriched in \(^{15}\)N at the beginning of the experimentalgae
)Let’s get started!
library(isotracer)
library(tidyverse)
The simulated data we use in this example can be loaded into your R session by running the code below:
tibble::tribble(
exp <-~time.day, ~species, ~biomass, ~prop15N, ~aquariumID,
0, "algae", 1.03, 0, "aq01",
2, "algae", NA, 0.08, "aq01",
5, "algae", 0.81, 0.08, "aq01",
8, "algae", 0.82, NA, "aq01",
10, "algae", NA, 0.11, "aq01",
0, "daphnia", 2.07, 0, "aq01",
2, "daphnia", 1.79, NA, "aq01",
5, "daphnia", 2.24, 0.02, "aq01",
8, "daphnia", NA, 0.02, "aq01",
10, "daphnia", 1.86, 0.04, "aq01",
0, "NH4", 0.23, 1, "aq01",
2, "NH4", 0.25, NA, "aq01",
5, "NH4", NA, 0.14, "aq01",
8, "NH4", 0.4, 0.1, "aq01",
10, "NH4", 0.41, 0.07, "aq01",
0, "algae", 1.48, 0, "aq02",
2, "algae", 0.94, 0.09, "aq02",
5, "algae", NA, 0.21, "aq02",
8, "algae", NA, 0.19, "aq02",
10, "algae", 0.67, NA, "aq02",
0, "daphnia", 1.49, 0, "aq02",
2, "daphnia", 1.4, 0.01, "aq02",
5, "daphnia", 1.44, NA, "aq02",
8, "daphnia", NA, 0.08, "aq02",
10, "daphnia", 1.82, 0.08, "aq02",
0, "NH4", 0.51, 0.87, "aq02",
2, "NH4", 0.47, 0.48, "aq02",
5, "NH4", 0.35, 0.37, "aq02",
8, "NH4", NA, 0.27, "aq02",
10, "NH4", 0.39, NA, "aq02"
)
As you can see in this table, the data contains the total amount of nitrogen (biomass
) and the proportion of \(^{15}\)N in this nitrogen (prop15N
) for each compartment (species
) at different time points, in two different aquariums (aquariumID
).
We can visualize the data using ggplot2
:
library(ggplot2)
library(gridExtra)
ggplot(exp, aes(x = time.day, y = biomass, col = species)) +
p1 <- geom_point() + ggtitle("Biomass data") + ylab("Biomass (mg N)") +
facet_wrap(~ aquariumID)
ggplot(exp, aes(x = time.day, y = prop15N, col = species)) +
p2 <- geom_point() + ggtitle("Heavy isotope proportions") + ylab("Proportion of 15N") +
facet_wrap(~ aquariumID)
grid.arrange(p1, p2, nrow = 2)
As in the Quick Start tutorial, we start by creating an empty network model to which we add a network topology:
new_networkModel() %>% set_topo("NH4 -> algae -> daphnia -> NH4")
m <- m
## # A tibble: 1 × 4
## topology initial observations parameters
## <list> <list> <list> <list>
## 1 <topology [3 × 3]> <NULL> <NULL> <tibble [8 × 2]>
For now, the model just contains the topology (and the list of parameters to be estimated by MCMC, which was added automatically).
Let’s add the initial conditions for both aquaria. We can get them by extracting the data from the exp
table at \(t_0\):
exp %>% filter(time.day == 0)
inits <- inits
## # A tibble: 6 × 5
## time.day species biomass prop15N aquariumID
## <dbl> <chr> <dbl> <dbl> <chr>
## 1 0 algae 1.03 0 aq01
## 2 0 daphnia 2.07 0 aq01
## 3 0 NH4 0.23 1 aq01
## 4 0 algae 1.48 0 aq02
## 5 0 daphnia 1.49 0 aq02
## 6 0 NH4 0.51 0.87 aq02
We add them to the model using set_init()
as in the previous tutorial, but note how we use group_by = "aquariumID"
to indicate that we have initial conditions for different replicates:
m %>% set_init(inits, comp = "species", size = "biomass", prop = "prop15N",
m <-group_by = "aquariumID")
Let’s have a look at our model at this stage:
m
## # A tibble: 2 × 5
## topology initial observations parameters group
## <list> <list> <list> <list> <list>
## 1 <topology [3 × 3]> <tibble [3 × 3]> <NULL> <tibble [8 × 2]> <chr [1]>
## 2 <topology [3 × 3]> <tibble [3 × 3]> <NULL> <tibble [8 × 2]> <chr [1]>
We now have two rows in the model table: each row corresponds to one replicate. The initial
column has been populated with the appropriate initial conditions for each replicate and the network topology has been duplicated as many times as needed. The group
column now contains the grouping variables defining the replicates, and the grouping structure can be quickly accessed with the groups()
function:
groups(m)
## # A tibble: 2 × 1
## aquariumID
## <chr>
## 1 aq01
## 2 aq02
In this case we have only one grouping variable (aquariumID
), but we could have more (for example some experimental treatments such as temperature or oxygen manipulation).
The final step of the model preparation is to add the observations. Let’s extract them from the exp
table, keeping only rows after \(t_0\):
exp %>% filter(time.day > 0)
obs <- obs
## # A tibble: 24 × 5
## time.day species biomass prop15N aquariumID
## <dbl> <chr> <dbl> <dbl> <chr>
## 1 2 algae NA 0.08 aq01
## 2 5 algae 0.81 0.08 aq01
## 3 8 algae 0.82 NA aq01
## 4 10 algae NA 0.11 aq01
## 5 2 daphnia 1.79 NA aq01
## 6 5 daphnia 2.24 0.02 aq01
## 7 8 daphnia NA 0.02 aq01
## 8 10 daphnia 1.86 0.04 aq01
## 9 2 NH4 0.25 NA aq01
## 10 5 NH4 NA 0.14 aq01
## # … with 14 more rows
We add them to the model with add_obs()
(note how the same group_by
argument as the one used with set_init()
is used by default):
m %>% set_obs(obs, time = "time.day")
m <- m
## # A tibble: 2 × 5
## topology initial observations parameters group
## <list> <list> <list> <list> <list>
## 1 <topology [3 × 3]> <tibble [3 × 3]> <tibble [12 × 4]> <tibble [8 × 2]> <chr [1]>
## 2 <topology [3 × 3]> <tibble [3 × 3]> <tibble [12 × 4]> <tibble [8 × 2]> <chr [1]>
Perfect! Now the model is ready to be run with the MCMC sampler.
Let’s have a look at the model parameters that are going to be estimated during the MCMC run and set some reasonable priors:
priors(m)
## # A tibble: 8 × 2
## in_model prior
## <chr> <list>
## 1 eta <NULL>
## 2 lambda_algae <NULL>
## 3 lambda_daphnia <NULL>
## 4 lambda_NH4 <NULL>
## 5 upsilon_algae_to_daphnia <NULL>
## 6 upsilon_daphnia_to_NH4 <NULL>
## 7 upsilon_NH4_to_algae <NULL>
## 8 zeta <NULL>
Time values for the observations were in days, so the lambda_*
and upsilon_*
parameters will be rates per day. We wouldn’t expect the whole compartments to be renewed within one day, so a normal prior centered at 0 and with a standard deviation of 1 or 2 is probably quite generous already:
set_priors(m, normal_p(0, 2), "lambda|upsilon") m <-
## Prior modified for parameter(s):
## - lambda_algae
## - lambda_daphnia
## - lambda_NH4
## - upsilon_algae_to_daphnia
## - upsilon_daphnia_to_NH4
## - upsilon_NH4_to_algae
As for eta
and zeta
, they represent coefficients of variation for the observed proportions of marked material and compartment sizes, so again normal_p(0, 2)
is probably fine:
set_priors(m, normal_p(0, 2), "eta") m <-
## Prior modified for parameter(s):
## - eta
## - zeta
priors(m)
## # A tibble: 8 × 2
## in_model prior
## <chr> <list>
## 1 eta <trun_normal(mean=0,sd=2)>
## 2 lambda_algae <trun_normal(mean=0,sd=2)>
## 3 lambda_daphnia <trun_normal(mean=0,sd=2)>
## 4 lambda_NH4 <trun_normal(mean=0,sd=2)>
## 5 upsilon_algae_to_daphnia <trun_normal(mean=0,sd=2)>
## 6 upsilon_daphnia_to_NH4 <trun_normal(mean=0,sd=2)>
## 7 upsilon_NH4_to_algae <trun_normal(mean=0,sd=2)>
## 8 zeta <trun_normal(mean=0,sd=2)>
Those priors will be good enough for the tutorial.
Note that even though we have two experimental replicates in our dataset, they share the same parameter values in our model. We will learn later how to specify some covariates for the parameter estimates (for example when we want to test if a parameter is different between two experimental treatments).
Let’s run the MCMC sampler:
run_mcmc(m, iter = 1000)
run <-plot(run)
# Note: the figure below only shows a few of the traceplots for vignette concision
The traces look nice. If we want to check numerically that the convergence is good, we can use the coda::gelman.diag()
function:
::gelman.diag(run) coda
## Potential scale reduction factors:
##
## Point est. Upper C.I.
## eta 1.01 1.03
## lambda_algae 1.00 1.00
## lambda_daphnia 1.00 1.00
## lambda_NH4 1.00 1.01
## upsilon_algae_to_daphnia 1.00 1.00
## upsilon_daphnia_to_NH4 1.00 1.01
## upsilon_NH4_to_algae 1.00 1.01
## zeta 1.00 1.01
##
## Multivariate psrf
##
## 1.01
All the diagnostic estimates are very close to one, so we can proceed. Let’s have a look at the posterior predictive check to see if our model is consistent with the observations:
predict(m, run)
predictions <-plot(predictions, facet_row = "group")
This looks nice! As far as we can tell, our model is compatible with the observed data.
At this stage you know how to include several replicates in a network model. In a later tutorial, you’ll see how to use this approach to add fixed effects of covariates on model parameters and how to perform model selection.
The next tutorial will teach you how to set some compartments to a steady state, which is useful when some compartments in the foodweb are being constantly renewed (e.g. dissolved inorganic nutrients in a river).