7. Prey mortality

When prey die during an experiment due to other causes than predation, estimated feeding rates can be biased even when correcting for prey depletion with a dynamical prediction model. The number of available prey declines faster than predicted by feeding only. Instead, the number of available prey \(N(t)\) (i.e. not eaten and not dead) can be modelled with an additional, linear mortality term \[\frac{dN}{dt}=-F(N)P-mN\] where \(F(N)\) is a functional response describing per-capita feeding rate, \(P\) is predator number, and \(m\) is the prey’s mortality rate.

Statistically, it can be difficult to tease apart functional response parameters such as attack rate \(a\) and mortality rate \(m\), when only data from feeding trials are used. Parameter estimates tend to be correlated. As an extreme case, consider the linear type 1 FR \(F(N)=aN\). Here, it is impossible to distinguish between attack and mortality rate, as the model reduces to \(\frac{dN}{dt}=-(a+m)N\). Ideally, control treatments without predators are performed to estimate mortality rate. For \(P=0\), the prediction model reduces to \[\frac{dN}{dt}=-mN\] We are able to fit both the predator and the control treatment data in one joint statistical model. Here, the function Type3GenH_mort_dyn(N0,P0,Time,b,h,q,m) includes a generalized type 3 FR with mortality term \[\frac{dN}{dt}=-\frac{bN^{1+q}}{1+bhN^{1+q}} P-mN\] It predicts the number or eaten or dead prey \(\hat{N}_E=N_0-N_T\). A column \(P_0\) for predator number (\(P_0=0\) for control, \(P_0>0\) for predator treatments) is required in the dataframe. This model also includes the type 2 and type 3 FRs by fixing the exponent \(q\) at 0 Type3GenH_mort_dyn(N0,P0,Time,b,h,0,m) or at 1 Type3GenH_mort_dyn(N0,P0,Time,b,h,1,m), respectively. Because of the mortality term, neither of them has an analytical solution and model predictions are computed numerically.

We work with a dataset from Archer et al. (2019), downloaded from Dryad. It includes lab and field data from a stream system, with two different fly larvae predators feeding on blackfly larvae. Background mortality of prey was observed and control treatments without predators were performed. Number of dead / eaten prey was recorded (without distinction), dead / eaten prey were not replaced. Also includes a temperature gradient.

Minimal example

For introduction, a subset of the data is analyzed with laboratory data from a single temperature treatment and a single predator species.

library(BayesFR)
library(brms)
library(ggplot2)
library(cowplot) # ggplot grids
library(RColorBrewer) # colors palettes

df = df_Archer_et_al_2019_JAE
head(df)
  N0 NE P0 Time           Predator       Prey Temperature    Setting Year
1  1  0  1   24 Limnophora_riparia Simuliidae         5.3 laboratory 2013
2  1  0  1   24 Limnophora_riparia Simuliidae         5.3 laboratory 2013
3  1  0  1   24 Limnophora_riparia Simuliidae         5.3 laboratory 2013
4  1  0  1   24 Limnophora_riparia Simuliidae         5.3 laboratory 2013
5  2  0  1   24 Limnophora_riparia Simuliidae         5.3 laboratory 2013
6  2  1  1   24 Limnophora_riparia Simuliidae         5.3 laboratory 2013
df = subset(df, Year=="2015")
df = subset(df, Setting=="laboratory")
df = subset(df, Predator=="Limnophora_riparia" | Predator=="control")
df = subset(df, Temperature==9.6)

ggplot(df, aes(N0, NE)) +
  geom_jitter(width=0.0, height=0.05, alpha=0.6, size=2.5) +
  facet_wrap(~Predator)

A type 2 response is fitted (setting q=0.0) to both the predator and control treatments (column P0 identifies them) to estimate attack rate, handling time and mortality rate simultaneously.

FR.formula = bf( NE | trials(N0) ~ Type3GenH_mort_dyn(N0,P0,1.0,a,h,0.0,m)/N0,
                 a+h+m~1,
                 nl = TRUE)
FR.priors  = c(prior(exponential(1.0), nlpar="a", lb=0),
               prior(exponential(1.0), nlpar="h", lb=0),
               prior(exponential(1.0), nlpar="m", lb=0))
fit.1 = brm(FR.formula,
            family   = binomial(link="identity"),
            prior    = FR.priors,
            data     = df,
            cores    = 4,
            stanvars = stanvar(scode=Type3GenH_mort_dyn_code, block="functions")
)
expose_functions(fit.1, vectorize=TRUE)
summary(fit.1)
 Family: binomial 
  Links: mu = identity 
Formula: NE | trials(N0) ~ Type3GenH_mort_dyn(N0, P0, 1, a, h, 0, m)/N0 
         a ~ 1
         h ~ 1
         m ~ 1
   Data: df (Number of observations: 39) 
  Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
         total post-warmup draws = 4000

Regression Coefficients:
            Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
a_Intercept     0.43      0.23     0.18     1.00 1.00     1471     1483
h_Intercept     0.25      0.23     0.01     0.86 1.00     1324     1175
m_Intercept     0.09      0.03     0.05     0.15 1.00     2036     1789

Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).

Without the control treatments, we would expect a strong correlation in parameter estimates for, e.g., attack and mortality rate, with high uncertainty and potentially a bias. Here, with control data, we check the pairs plot of the posterior distribution and verify that this is not the case.

pairs(fit.1, variable=c("b_a_Intercept", "b_h_Intercept", "b_m_Intercept"), off_diag_fun="hex")

Regression curves are plotted against the data for predator conditions=data.frame(P0=1) and control treatments conditions=data.frame(P0=0) separately.

df.predator = subset(df, P0==1)
p1 = plot(conditional_effects(fit.1,
                              effects="N0",
                              conditions=data.frame(P0=1),
                              int_conditions=data.frame(N0=1:16)),
          points=FALSE, plot=FALSE)
p1 = p1[[1]] +
  geom_jitter(aes(x=N0, y=NE),
              alpha = 0.5, width=0.0, height=0.05, size=2,
              inherit.aes = FALSE,
              data = df.predator) +
  coord_cartesian(xlim=c(0,48), ylim=c(0,7)) +
  ggtitle("Predator")

df.control = subset(df, P0==0)
p2 = plot(conditional_effects(fit.1,
                              effects="N0",
                              conditions=data.frame(P0=0)),
          points=FALSE, plot=FALSE)
p2 = p2[[1]] +
  geom_jitter(aes(x=N0, y=NE),
              alpha = 0.5, width=0.0, height=0.05, size=2,
              inherit.aes = FALSE,
              data = df.control) +
  coord_cartesian(xlim=c(0,48), ylim=c(0,7)) +
  ggtitle("Control")

plot_grid(p1, p2, ncol=2)

Note that the predator curve above makes predictions using the ODE \(\frac{dN}{dt}=-F(N)P-mN\) that include both the feeding rate and the mortality rate. Visualizing the functional response \(F(N)\) alone is not possible with conditional_effects. Here, we have to use posterior samples of attack rate and handling time to compute posterior samples of \(F(N)\) manually. For each posterior sample \((a_i,h_i)\), a feeding rate curve \(F(N)_i\) is computed, and summarized with median and upper and lower quantiles for plotting.

draws = as_draws_matrix(fit.1, variable=c("b_a_Intercept","b_h_Intercept"))
a = c(draws[, 1])
h = c(draws[, 2])
N0 = 0:30
FR = matrix(NA, nrow=nrow(draws), ncol=length(N0))
# each row is a functional response curve, each column is distribution for specific N0
for(i in 1:nrow(draws)){
  # compute FR formula manually
  FR[i, ] = a[i]*N0 / (1+a[i]*h[i]*N0)
}
# quantiles
FR.qs = apply(FR, 2, function(x) quantile(x, probs=c(0.05,0.5,0.95)))

df.plot = data.frame(
  N0  = N0,
  low = FR.qs[1,],
  med = FR.qs[2,],
  up  = FR.qs[3,])
ggplot(df.plot, aes(N0, med)) +
  geom_ribbon(aes(ymin = low, ymax = up), alpha = 0.25) +
  geom_line(linewidth = 1, col = "blue") +
  labs(y = "Feeding rate")

Continuous predictor

The dataset features a temperature gradient and the research question is if attack rates increase with temperature.

df = df_Archer_et_al_2019_JAE
df = subset(df, Year=="2015")
df = subset(df, Predator=="Limnophora_riparia" | Predator=="control")
ggplot(df, aes(N0, NE, color=Temperature)) +
  geom_jitter(width=0.1, height=0.2, alpha=0.5, size=2) +
  facet_wrap(~Predator)

We can fit this dataset for one of the predators by making one or more parameters temperature dependent, e.g. by replacing a~1 by a~Temperature (and potentially using a log-link to guarantee positive attack rates) as described in Section 5. Archer et al. (2019) used a mechanistic model \[ a = \exp\left(a_0 + a_1 \frac{T-T_0}{kTT_0}\right)\] where \(k=8.62\cdot 10^{-5}\) is the Boltzmann constant, \(T[{\mathrm K}]\) is experimental temperature in Kelvin and \(T_0=283.15\mathrm{K}\) (\(=10^\circ{\mathrm C}\)) is the reference temperature. Free model parameters are \(a_0\) (normalization constant) and \(a_1\) (activation energy). This equation is coded explicitly using nlf(a~...). In this model, \(a\) is strictly positive and parameters \(a_0,a_1\) can be positive or negative, here we use normal priors. Same scaling applies to \(h\) and \(m\).

FR.formula = bf( NE | trials(N0) ~ Type3GenH_mort_dyn(N0,P0,1.0,a,h,0.0,m)/N0,
                 nlf(a~exp(a0+a1*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))),
                 nlf(h~exp(h0+h1*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))),
                 nlf(m~exp(m0+m1*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))),
                 a0+a1+h0+h1+m0+m1~1,
                 nl = TRUE)
FR.priors  = c(prior(normal(0,3), nlpar="a0"),
               prior(normal(0,1), nlpar="a1"),
               prior(normal(0,3), nlpar="h0"),
               prior(normal(0,1), nlpar="h1"),
               prior(normal(0,3), nlpar="m0"),
               prior(normal(0,1), nlpar="m1"))
fit.2 = brm(FR.formula,
            family   = binomial(link="identity"),
            prior    = FR.priors,
            data     = df,
            cores    = 4,
            stanvars = stanvar(scode=Type3GenH_mort_dyn_code, block="functions")
)
expose_functions(fit.2, vectorize=TRUE)
summary(fit.2)
 Family: binomial 
  Links: mu = identity 
Formula: NE | trials(N0) ~ Type3GenH_mort_dyn(N0, P0, 1, a, h, 0, m)/N0 
         a ~ exp(a0 + a1 * (Temperature - 10)/(8.62 * 10^(-5) * (Temperature + 273.15) * 283.15))
         h ~ exp(h0 + h1 * (Temperature - 10)/(8.62 * 10^(-5) * (Temperature + 273.15) * 283.15))
         m ~ exp(m0 + m1 * (Temperature - 10)/(8.62 * 10^(-5) * (Temperature + 273.15) * 283.15))
         a0 ~ 1
         a1 ~ 1
         h0 ~ 1
         h1 ~ 1
         m0 ~ 1
         m1 ~ 1
   Data: df (Number of observations: 287) 
  Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
         total post-warmup draws = 4000

Regression Coefficients:
             Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
a0_Intercept    -0.94      0.21    -1.32    -0.53 1.00     1423     2069
a1_Intercept     0.40      0.20    -0.01     0.80 1.00     2461     2345
h0_Intercept    -2.56      1.10    -5.64    -1.35 1.00     1447     1271
h1_Intercept    -0.35      0.55    -1.44     0.81 1.00     2851     2053
m0_Intercept    -1.86      0.07    -2.01    -1.72 1.00     3062     2518
m1_Intercept     0.68      0.10     0.49     0.87 1.00     2579     2532

Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).

We observe a positive temperature scaling for \(m\) (~2/3 power law) and potentially for \(a\) too (although 95% CI covers 0), while the model is indecisive regarding \(h\) (high uncertainty and CI covering 0). Scaling of any parameter defined in the model formula nlf(...) can be visualized with conditional_effects().

p1 = plot(conditional_effects(fit.2,
                              effects="Temperature",
                              nlpar="a"),
          plot=FALSE)
p2 = plot(conditional_effects(fit.2,
                              effects="Temperature",
                              nlpar="h"),
          plot=FALSE)
p3 = plot(conditional_effects(fit.2,
                              effects="Temperature",
                              nlpar="m"),
          plot=FALSE)
plot_grid(p1[[1]], p2[[1]], p3[[1]], ncol = 3)

To answer the research question, the hypothesis function is used to compute the posterior probability \(P(a_1>0)=0.97\), meaning we are sufficiently certain that attack rates increase with temperature.

hypothesis(fit.2, "a1_Intercept>0")
Hypothesis Tests for class b:
          Hypothesis Estimate Est.Error CI.Lower CI.Upper Evid.Ratio Post.Prob
1 (a1_Intercept) > 0      0.4       0.2     0.06     0.73      37.83      0.97
  Star
1    *
---
'CI': 90%-CI for one-sided and 95%-CI for two-sided hypotheses.
'*': For one-sided hypotheses, the posterior probability exceeds 95%;
for two-sided hypotheses, the value tested against lies outside the 95%-CI.
Posterior probabilities of point hypotheses assume equal prior probabilities.
p1 = plot(hypothesis(fit.2, "a1_Intercept>0"), plot=FALSE)
p1[[1]] + geom_vline(xintercept=0)

Again, we plot plot regression curves against the data separately for predator and control treatments using conditions. Specifying effects="N0:Temperature" automatically chooses 3 levels of temperature (mean, +1 sd, -1sd).

df.predator = subset(df, P0==1)
p1 = plot(conditional_effects(fit.2,
                              effects="N0:Temperature",
                              conditions=data.frame(P0=1),
                              int_conditions=data.frame(N0=1:16)),
          points=FALSE, plot=FALSE)
p1 = p1[[1]] +
  geom_jitter(aes(x=N0, y=NE),
              alpha = 0.5, width=0.2, height=0.1, size=1.5,
              inherit.aes = FALSE,
              data = df.predator) +
  theme(legend.position = c(0.05, 0.95),
        legend.justification = c("left", "top")) +
  ggtitle("Predator")

df.control = subset(df, P0==0)
p2 = plot(conditional_effects(fit.2,
                              effects="N0:Temperature",
                              conditions=data.frame(P0=0)),
          points=FALSE, plot=FALSE)
p2 = p2[[1]] +
  geom_jitter(aes(x=N0, y=NE),
              alpha = 0.5, width=0.2, height=0.1, size=1.5,
              inherit.aes = FALSE,
              data = df.control) +
  theme(legend.position = "none") +
  ggtitle("Control")

plot_grid(p1, p2, ncol=2)

As in the minimal example above, conditional_effects does not plot the actual feeding rates. We compute regression curves manually for 3 temperature levels. Again, we geta posterior distribution of regression curves, which is summarized by medians and quantiles.

draws = as_draws_matrix(fit.2,
                        variable=c("b_a0_Intercept","b_a1_Intercept",
                                   "b_h0_Intercept","b_h1_Intercept"))
a0 = c(draws[, 1])
a1 = c(draws[, 2])
h0 = c(draws[, 3])
h1 = c(draws[, 4])

N0 = 0:30
df.plot = data.frame(N0 = N0)

Temperatures = c(5,10,15)
for(j in 1:3){
  Temperature = Temperatures[j]
  FR = matrix(NA, nrow=nrow(draws), ncol=length(N0))
  for(i in 1:nrow(draws)){
    a = exp(a0[i]+a1[i]*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))
    h = exp(h0[i]+h1[i]*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))
    FR[i, ] = a*N0 / (1+a*h*N0)
  }
  FR.qs = apply(FR, 2, function(x) quantile(x, probs=c(0.05,0.5,0.95)))

  df.help = data.frame(low = FR.qs[1,],
                       med = FR.qs[2,],
                       up  = FR.qs[3,])
  colnames(df.help) = c(paste0("low",j), paste0("med",j), paste0("up",j))
  df.plot = cbind(df.plot, df.help)
}

cols = brewer.pal(3, "Set1")[c(2,3,1)]
p1 = ggplot(df.plot, aes(N0, med1))
p1 = p1 +
  # geom_ribbon(aes(ymin = low1, ymax = up1), alpha = 0.25) +
  # geom_ribbon(aes(ymin = low2, ymax = up2), alpha = 0.25) +
  # geom_ribbon(aes(ymin = low3, ymax = up3), alpha = 0.25) +
  geom_line(aes(x = N0, y = med1), linewidth = 1, col=cols[1]) +
  geom_line(aes(x = N0, y = med2), linewidth = 1, col=cols[2]) +
  geom_line(aes(x = N0, y = med3), linewidth = 1, col=cols[3]) +
  labs(y = "Feeding rate")
p1

Continuous and categorical predictor

So far, we ignored an important feature of the dataset, which includes both laboratory and field experiments. This is encoded in the factorial predictor Setting. Here, we can easily include this in the mechanistical temperature model by making the normalization constants (intercepts) setting-dependent (we assume that the scaling is independent of the setting, though). Note that the formula a0~Setting would use dummy-coding and we use a0~0+Setting here, which estimates 2 separate parameters a0 for lab and field. We are interested if attack rates increase with temperature while we acknowledge different baselines for all parameters depending on the setting.

FR.formula = bf( NE | trials(N0) ~ Type3GenH_mort_dyn(N0,P0,1.0,a,h,0.0,m)/N0,
                 nlf(a~exp(a0+a1*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))),
                 nlf(h~exp(h0+h1*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))),
                 nlf(m~exp(m0+m1*(Temperature-10.0)/(8.62*10^(-5)*(Temperature+273.15)*283.15))),
                 a0+h0+m0 ~ 0+Setting,
                 a1+h1+m1 ~ 1,
                 nl = TRUE)
FR.priors  = c(prior(normal(0,3), nlpar="a0"),
               prior(normal(0,1), nlpar="a1"),
               prior(normal(0,3), nlpar="h0"),
               prior(normal(0,1), nlpar="h1"),
               prior(normal(0,3), nlpar="m0"),
               prior(normal(0,1), nlpar="m1"))
fit.3 = brm(FR.formula,
            family   = binomial(link="identity"),
            prior    = FR.priors,
            data     = df,
            cores    = 4,
            stanvars = stanvar(scode=Type3GenH_mort_dyn_code, block="functions")
)
expose_functions(fit.3, vectorize=TRUE)
summary(fit.3)
 Family: binomial 
  Links: mu = identity 
Formula: NE | trials(N0) ~ Type3GenH_mort_dyn(N0, P0, 1, a, h, 0, m)/N0 
         a ~ exp(a0 + a1 * (Temperature - 10)/(8.62 * 10^(-5) * (Temperature + 273.15) * 283.15))
         h ~ exp(h0 + h1 * (Temperature - 10)/(8.62 * 10^(-5) * (Temperature + 273.15) * 283.15))
         m ~ exp(m0 + m1 * (Temperature - 10)/(8.62 * 10^(-5) * (Temperature + 273.15) * 283.15))
         a0 ~ 0 + Setting
         h0 ~ 0 + Setting
         m0 ~ 0 + Setting
         a1 ~ 1
         h1 ~ 1
         m1 ~ 1
   Data: df (Number of observations: 287) 
  Draws: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
         total post-warmup draws = 4000

Regression Coefficients:
                     Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
a0_Settingfield         -0.56      0.39    -1.31     0.23 1.00     2336
a0_Settinglaboratory    -1.09      0.21    -1.54    -0.66 1.00     2492
h0_Settingfield         -1.33      0.68    -3.02    -0.39 1.00     2357
h0_Settinglaboratory    -4.14      1.65    -7.96    -1.58 1.00     2334
m0_Settingfield         -2.09      0.11    -2.31    -1.89 1.00     3401
m0_Settinglaboratory    -1.55      0.11    -1.77    -1.34 1.00     3174
a1_Intercept             0.56      0.27    -0.04     1.02 1.00     2466
h1_Intercept             0.04      0.42    -0.82     0.90 1.00     2528
m1_Intercept             0.83      0.11     0.62     1.04 1.00     2838
                     Tail_ESS
a0_Settingfield          1370
a0_Settinglaboratory     2219
h0_Settingfield          1195
h0_Settinglaboratory     2413
m0_Settingfield          2774
m0_Settinglaboratory     2592
a1_Intercept             2143
h1_Intercept             1899
m1_Intercept             2987

Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
and Tail_ESS are effective sample size measures, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).

Again, there is a positive effect of temperature on attack rates, which we will quantify with hypothesis() below. Here, handling times don’t seem to be affected by temperature, and we could think of re-fitting the model without this effect (and dropping parameter \(h_1\)).

p1 = plot(conditional_effects(fit.3,
                              effects="Temperature:Setting",
                              nlpar="a"),
          plot=FALSE)
p2 = plot(conditional_effects(fit.3,
                              effects="Temperature:Setting",
                              nlpar="h"),
          plot=FALSE)
p3 = plot(conditional_effects(fit.3,
                              effects="Temperature:Setting",
                              nlpar="m"),
          plot=FALSE)
p1 = p1[[1]] +
  theme(legend.position = c(0.05, 0.95),
        legend.justification = c("left", "top")) +
  coord_cartesian(ylim=c(0,3))
p2 = p2[[1]] +
  theme(legend.position = "none") +
  coord_cartesian(ylim=c(0,1))
p3 = p3[[1]] +
  theme(legend.position = "none") +
  coord_cartesian(ylim=c(0,0.75))
plot_grid(p1, p2, p3, ncol = 3)

We answer the research question: yes, attack rates increase with temperature, \(P(a_1>0)=0.97\).

hypothesis(fit.3, "a1_Intercept>0")
Hypothesis Tests for class b:
          Hypothesis Estimate Est.Error CI.Lower CI.Upper Evid.Ratio Post.Prob
1 (a1_Intercept) > 0     0.56      0.27     0.07     0.96      29.53      0.97
  Star
1    *
---
'CI': 90%-CI for one-sided and 95%-CI for two-sided hypotheses.
'*': For one-sided hypotheses, the posterior probability exceeds 95%;
for two-sided hypotheses, the value tested against lies outside the 95%-CI.
Posterior probabilities of point hypotheses assume equal prior probabilities.
p1 = plot(hypothesis(fit.3, "a1_Intercept>0"), plot=FALSE)
p1[[1]] + geom_vline(xintercept=0)

For completeness, we also plot regression curves, specifying Setting=c("field","laboratory") in conditions.

df.predator = subset(df, P0==1)
p1 = plot(conditional_effects(fit.3,
                              effects="N0:Temperature",
                              conditions=data.frame(Setting=c("field","laboratory"),
                                                    P0=1),
                              int_conditions=data.frame(N0=1:16)),
          points=FALSE, plot=FALSE)
p1 = p1[[1]] +
  facet_wrap(~Setting, nrow=2) +
  theme(legend.position = c(0.05, 0.95),
        legend.justification = c("left", "top")) +
  geom_jitter(aes(x=N0, y=NE),
              alpha = 0.5, width=0.2, height=0.1, size=1.5,
              inherit.aes = FALSE,
              data = df.predator) +
  ggtitle("Predator")

df.control = subset(df, P0==0)
p2 = plot(conditional_effects(fit.3,
                              effects="N0:Temperature",
                              conditions=data.frame(Setting=c("field","laboratory"),
                                                    P0=0)),
          points=FALSE, plot=FALSE)
p2 = p2[[1]] +
  facet_wrap(~Setting, nrow=2) +
  theme(legend.position = "none") +
  geom_jitter(aes(x=N0, y=NE),
              alpha = 0.5, width=0.2, height=0.1, size=1.5,
              inherit.aes = FALSE,
              data = df.control) +
  ggtitle("Control")

plot_grid(p1, p2, ncol=2)

Using LOO for model comparison, we only find very weak evidence that including the setting actually improves the model. Since this was not the main research question we don’t investigate this further.

LOO(fit.2, fit.3)
 model elpd_diff se_diff p_worse diag_diff      diag_elpd
 fit.3       0.0     0.0      NA           1 k_psis > 0.7
 fit.2     -10.1     8.1    0.89                         
bayes_R2(fit.2)
    Estimate  Est.Error      Q2.5     Q97.5
R2 0.6351338 0.02231057 0.5874805 0.6735671
bayes_R2(fit.3)
    Estimate  Est.Error      Q2.5     Q97.5
R2 0.6888583 0.02263277 0.6398411 0.7278063
BayesFR hex sticker