Last updated: 2020-06-23

Checks: 7 0

Knit directory: genes-to-foodweb-stability/

This reproducible R Markdown analysis was created with workflowr (version 1.6.0). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.


Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.

Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.

The command set.seed(20200205) was run prior to running the code in the R Markdown file. Setting a seed ensures that any results that rely on randomness, e.g. subsampling or permutations, are reproducible.

Great job! Recording the operating system, R version, and package versions is critical for reproducibility.

Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.

Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.

Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility. The version displayed above was the version of the Git repository at the time these results were generated.

Note that you need to be careful to ensure that all relevant files for the analysis have been committed to Git prior to generating the results (you can use wflow_publish or wflow_git_commit). workflowr only checks the R Markdown file, but you know if there are other scripts or data files that it depends on. Below is the status of the Git repository when the results were generated:


Ignored files:
    Ignored:    .Rhistory
    Ignored:    .Rproj.user/
    Ignored:    code/.Rhistory

Untracked files:
    Untracked:  figures/rich-geno-critical-transition.pdf

Unstaged changes:
    Modified:   figures/rich-geno-critical-transition-v4.pdf
    Modified:   output/critical-transitions.RData

Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.


These are the previous versions of the R Markdown and HTML files. If you’ve configured a remote Git repository (see ?wflow_git_remote), click on the hyperlinks in the table below to view them.

File Version Author Date Message
Rmd 86116c8 mabarbour 2020-06-23 bioRxiv version of code and data.
html 86116c8 mabarbour 2020-06-23 bioRxiv version of code and data.

Setup

# Load and manage data
df <- read_csv("data/arabidopsis_clean_df.csv") %>%
  # renaming for brevity
  rename(cage = Cage, 
         com = Composition,
         week = Week,
         temp = Temperature,
         rich = Richness) %>%
  mutate(cage = as.character(cage),
         fweek = factor(ifelse(week < 10, paste("0", week, sep=""), week)),
         temp = ifelse(temp=="20 C", 0, 1)) %>%
  arrange(cage, week)

# create data for multi-state survival analysis
state_df <- df %>%
  # counter information is not relevant (because it is the same), so we summarise across it
  group_by(cage, fweek, week, temp, rich, Col, gsm1, AOP2, AOP2.gsoh, com) %>% 
  summarise_at(vars(BRBR_Survival, LYER_Survival, Mummy_Ptoids_Survival), list(mean)) %>% 
  ungroup() %>%
  # create possible food-web states
  mutate(BRBR = ifelse(BRBR_Survival == 1, "BRBR", ifelse(BRBR_Survival == 0, "0", NA)),
         LYER = ifelse(LYER_Survival == 1, "LYER", ifelse(LYER_Survival == 0, "0", NA)),
         Ptoid = ifelse(Mummy_Ptoids_Survival == 1, "Ptoid", ifelse(Mummy_Ptoids_Survival == 0, "0", NA))) %>%
  mutate(state = paste(BRBR, LYER, Ptoid, sep = "-"),
         cage = as.character(cage)) %>%
  # these variables are no longer needed
  select(-BRBR, -LYER, -Ptoid) %>%
  # remove all instances where all species have been labelled extinct for more than 1 week "NA-NA-NA".
  # also, only keep observations after 2 weeks when we added the parasitoid (full community)
  filter(state != "NA-NA-NA", week > 2) %>%
  mutate(week_since = week - 2)

# replace NA with zeros for state variable
state_df$state <- gsub("NA","0", state_df$state)

# everything appears in order
arrange(state_df, cage, week_since) %>% select(cage, week_since, BRBR_Survival, LYER_Survival, Mummy_Ptoids_Survival, state) 
# A tibble: 710 x 6
   cage  week_since BRBR_Survival LYER_Survival Mummy_Ptoids_Surviv… state      
   <chr>      <dbl>         <dbl>         <dbl>                <dbl> <chr>      
 1 1              1             1             1                    1 BRBR-LYER-…
 2 1              2             1             1                    1 BRBR-LYER-…
 3 1              3             1             1                    1 BRBR-LYER-…
 4 1              4             1             1                    1 BRBR-LYER-…
 5 1              5             0             0                    1 0-0-Ptoid  
 6 1              6            NA            NA                    0 0-0-0      
 7 10             1             1             1                    1 BRBR-LYER-…
 8 10             2             1             1                    1 BRBR-LYER-…
 9 10             3             1             1                    1 BRBR-LYER-…
10 10             4             1             1                    1 BRBR-LYER-…
# … with 700 more rows
# source in useful functions for analyses
source('code/glm-ftest.R') # ANOVA GLM
delta_prob = function(x) round(exp(x) - 1,2) # change in probability of transtion from discrete-time survival analysis

Inspect critical transitions

Let’s look at the possible state transitions for all cages and time points

# state transitions for all cages and time points
msm::statetable.msm(state = state, subject = cage, data = state_df)
                 to
from              0-0-0 0-0-Ptoid 0-LYER-0 0-LYER-Ptoid BRBR-LYER-Ptoid
  0-0-Ptoid          22         7        0            0               0
  0-LYER-0            1         0      131            0               0
  0-LYER-Ptoid        5        12       26          242               0
  BRBR-LYER-Ptoid     0        10        0           50             144

The most common transition from the full community BRBR-LYER-Ptoid is to lose BRBR 0-LYER-Ptoid and less common to collapse by losing both aphids 0-0-Ptoid. The 0-0-Ptoid state never lasts long, since the parasitoid no longer has any resources, and collapses to 0-0-0.

From the 0-LYER-Ptoid community, losing the parasitoid 0-LYER-0 is the most common transition, but losing LYER 0-0-Ptoid is also common. Note that transitions to complete collapse 0-0-0 were more than likely to occur by losing LYER then the parasitoid, rather than losing the parasitoid and then LYER. This is because collapsing from the 0-LYER-0 state is rare (1 / (131 + 1) = 0.7575758% chance per week).

These transition patterns suggest the following:

  • Since the 0-0-Ptoid state always collapses to 0-0-0, I’m going to convert the 0-0-Ptoid state into 0-0-0. This will reduce the number of state transitions I need to model, but it won’t sacrifice modeling the biological process.
  • Since the 0-LYER-0 to 0-0-0 transition is so rare, it does not make sense to model this transition with any covariates.

Critical transition models

Below, we reproduce the analysis of deviance for discrete-time survival models presented in Tables S1-S2 in the Supplementary Material.

BRBR-LYER-Ptoid transition (Table S1)

# filter data
full_transit_df <- state_df %>% 
  # removes all transitions after the first one, this focuses
  # on transition from the full community
  na.omit() 
# check for weekly variation in critical transitions. 
full_transit_df %>%
  mutate(CT_from_full = ifelse(state %in% c("0-LYER-Ptoid","0-0-Ptoid"), 1, 0)) %>%
  group_by(fweek) %>%
  summarise_at("CT_from_full", mean)
# A tibble: 7 x 2
  fweek CT_from_full
  <fct>        <dbl>
1 03          0     
2 04          0.0833
3 05          0.127 
4 06          0.333 
5 07          0.75  
6 08          0.875 
7 09          1     

We only observe variation between weeks 4 and 8, so we restrict the data to those weeks for the analysis. It does not make sense to include weeks where there is no variation. If we did, then the factor fweek would cause predict all of the variation in the week with no variation.

# filter data again
full_transit_df <- filter(full_transit_df, week %in% c(4:8))
# adjust rich and temp so coefficients = +1 genotype and +1 C, respectively
full_transit_df <- full_transit_df %>%
  mutate(rich  = rich - 1,
         temp = temp * 3)

Now we’ve put the coefficients on a similar and more intuitive scale with respect to each other.

BRBR-LYER-Ptoid \(\rightarrow\) any critical transition

full_any_glmf <- glm.ftest.v2(
  model = glm(data = full_transit_df,
              family = quasibinomial(link = "cloglog"), 
              formula = terms(state %in% c("0-LYER-Ptoid","0-0-Ptoid") ~
                        fweek + temp + rich + com + temp:rich + temp:com, 
                        keep.order = T)),
  test.formula = list(
    c("fweek","Residuals"),
    c("temp","temp:com"),
    c("rich","com"),
    c("temp:rich","temp:com"))
)[[3]] %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error) 
Warning: glm.fit: algorithm did not converge
full_any_glmf %>%
  kable(., caption = "Analysis of deviance for critical transition from initial food web.", booktabs = T) %>%
  kable_styling(latex_options = c("striped", "hold_position"))
Analysis of deviance for critical transition from initial food web.
Source df (Source) df (Error) Deviance Mean Deviance F P Error
fweek 4 177 65.23 16.31 20.854 <0.001 Residuals
temp 1 9 14.81 14.81 11.132 0.009 temp:com
rich 1 9 2.18 2.18 1.713 0.223 com
temp:rich 1 9 0.66 0.66 0.498 0.498 temp:com

The model didn’t converge…Let’s try the standard link = "logit".

glm.ftest.v2(
  model = glm(data = full_transit_df,
              family = quasibinomial(link = "logit"), 
              formula = terms(state %in% c("0-LYER-Ptoid","0-0-Ptoid") ~
                        fweek + temp + rich + com + temp:rich + temp:com, 
                        keep.order = T)),
  test.formula = list(
    c("fweek","Residuals"),
    c("temp","temp:com"),
    c("rich","com"),
    c("temp:rich","temp:com"))
)[[3]] %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error)
     Source df (Source) df (Error) Deviance Mean Deviance      F      P
1     fweek           4        177    65.23         16.31 21.197 <0.001
2      temp           1          9    21.87         21.87 18.402  0.002
3      rich           1          9     0.84          0.84  0.793  0.396
4 temp:rich           1          9     0.42          0.42  0.354  0.566
      Error
1 Residuals
2  temp:com
3       com
4  temp:com

Everything converges now and the results are qualitatively the same as when link = "cloglog". To maintain consistency with the other models, I report the results from link = "cloglog".

BRBR-LYER-Ptoid \(\rightarrow\) 0-LYER-Ptoid

# check for weekly variation in critical transitions.
full_transit_df %>%
  mutate(CT_to_0LYERPtoid = ifelse(state %in% "0-LYER-Ptoid", 1, 0)) %>%
  group_by(week) %>%
  summarise_at("CT_to_0LYERPtoid", mean)
# A tibble: 5 x 2
   week CT_to_0LYERPtoid
  <dbl>            <dbl>
1     4           0.0833
2     5           0.127 
3     6           0.333 
4     7           0.5   
5     8           0.75  

We keep the full_transit_df since we observe variation in all weeks.

# fit ANOVA GLM
full_LP_glmf <- glm.ftest.v2(
  model = glm(data = full_transit_df,
              family = quasibinomial(link = "cloglog"), 
              formula = state %in% c("0-LYER-Ptoid") ~
                fweek + temp + rich + com + temp:rich + temp:com),
  test.formula = list(
    c("fweek","Residuals"),
    c("temp","temp:com"),
    c("rich","com"),
    c("temp:rich","temp:com"))
)[[3]] %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error)

# table of results
full_LP_glmf %>%
  kable(., caption = "Analysis of Deviance for critical transition from initial food web to food chain with dominant aphid and parasitoid.", booktabs = T) %>%
  kable_styling(latex_options = c("striped", "hold_position"))
Analysis of Deviance for critical transition from initial food web to food chain with dominant aphid and parasitoid.
Source df (Source) df (Error) Deviance Mean Deviance F P Error
fweek 4 177 35.83 8.96 10.916 <0.001 Residuals
temp 1 9 20.19 20.19 11.202 0.009 temp:com
rich 1 9 0.38 0.38 0.407 0.539 com
temp:rich 1 9 0.38 0.38 0.210 0.658 temp:com
# calculate effects sizes for temp, Fig. 2 of main text
conf_int(
  glm(data = full_transit_df,
              family = quasibinomial(link = "cloglog"),
              formula = state %in% c("0-LYER-Ptoid") ~
                fweek + temp),
  vcov = "CR2",
  test = "naive-t",
  cluster = with(full_transit_df, paste(temp, com)),
  coefs = "temp"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1       0.59
# calculate effects sizes for rich, Fig. 2 of main text
conf_int(
  glm(data = full_transit_df,
              family = quasibinomial(link = "cloglog"),
              formula = state %in% c("0-LYER-Ptoid") ~
                fweek + temp + rich),
  vcov = "CR2",
  test = "naive-t",
  cluster = full_transit_df$com,
  coefs = "rich"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1      -0.09

BRBR-LYER-Ptoid \(\rightarrow\) 0-0-Ptoid

# check for weekly variation in critical transitions.
full_transit_df %>%
  mutate(CT_to_00Ptoid = ifelse(state %in% "0-0-Ptoid", 1, 0)) %>%
  group_by(week) %>%
  summarise_at("CT_to_00Ptoid", mean)
# A tibble: 5 x 2
   week CT_to_00Ptoid
  <dbl>         <dbl>
1     4         0    
2     5         0    
3     6         0    
4     7         0.25 
5     8         0.125

We only see variation in weeks 7 and 8, so we restrict the data to those weeks.

# check frequency of critical transitions
with(filter(full_transit_df, week %in% 7:8), table(state %in% c("0-0-Ptoid")))

FALSE  TRUE 
   31     9 

And since there were only 9 total transitions to this state, we don’t attempt to fit the interaction term temp:rich.

glm.ftest.v2(
  model = glm(data = filter(full_transit_df, week %in% 7:8),
              family = quasibinomial(link = "cloglog"),
              formula = state %in% c("0-0-Ptoid") ~
                fweek + temp + rich + com + temp:com),
  test.formula = list(
    c("fweek","Residuals"),
    c("temp","temp:com"),
    c("rich","com"))
)[[3]] %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error) %>%
  kable(., caption = "Analysis of deviance for critical transition from initial food web to extinction of all species.", booktabs = T) %>%
  kable_styling(latex_options = c("striped", "hold_position"))
Analysis of deviance for critical transition from initial food web to extinction of all species.
Source df (Source) df (Error) Deviance Mean Deviance F P Error
fweek 1 21 0.64 0.64 0.631 0.436 Residuals
temp 1 6 0.93 0.93 1.056 0.344 temp:com
rich 1 9 0.42 0.42 0.262 0.621 com
# calculate effects sizes for temp, Fig. 2 of main text
conf_int(
  glm(data = filter(full_transit_df, week %in% 7:8),
              family = quasibinomial(link = "cloglog"),
              formula = state %in% c("0-0-Ptoid") ~
                fweek + temp),
  vcov = "CR2",
  test = "naive-t",
  cluster = with(filter(full_transit_df, week %in% 7:8), paste(temp, com)),
  coefs = "temp"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1      -0.26
# calculate effects sizes for rich, Fig. 2 of main text
conf_int(
  glm(data = filter(full_transit_df, week %in% 7:8),
              family = quasibinomial(link = "cloglog"),
              formula = state %in% c("0-0-Ptoid") ~
                fweek + temp + rich),
  vcov = "CR2",
  test = "naive-t",
  cluster = filter(full_transit_df, week %in% 7:8)$com,
  coefs = "rich"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1      -0.22

0-LYER-Ptoid transition (Table S2)

## Organize data for analysis

# LYER-Ptoid cages at least at one time point
LP_cages <- unique(filter(state_df, state == "0-LYER-Ptoid")$cage)
# should be 50 cages, and it is
length(LP_cages)
[1] 50
# filter and manage data
LP_transit_df <- state_df %>% 
  filter(cage %in% LP_cages, state != "BRBR-LYER-Ptoid") %>%
  # omit BRBR from consideration
  select(-BRBR_Survival) %>% 
  # omit rows where we already know either LYER or Ptoid went extinct
  na.omit() %>% 
  mutate(# assume these two states are the same, i.e. to get to 0-0-0, had to go through 0-0-Ptoid
         state_adj = ifelse(state %in% c("0-0-Ptoid","0-0-0"), "0-0-Ptoid", state))

# confirm levels
unique(LP_transit_df$state_adj) #fstate)
[1] "0-LYER-Ptoid" "0-LYER-0"     "0-0-Ptoid"   
# check for weekly variation in critical transitions
LP_transit_df %>%
  mutate(CT_from_FoodChain = ifelse(state_adj %in% c("0-0-Ptoid","0-LYER-0"), 1, 0)) %>%
  group_by(fweek) %>%
  summarise_at("CT_from_FoodChain", mean)
# A tibble: 14 x 2
   fweek CT_from_FoodChain
   <fct>             <dbl>
 1 04               0     
 2 05               0     
 3 06               0     
 4 07               0.136 
 5 08               0.205 
 6 09               0.143 
 7 10               0.1   
 8 11               0.0370
 9 12               0.0769
10 13               0.0833
11 14               0.136 
12 15               0.368 
13 16               0.417 
14 17               0     

We only observe variation between weeks 7 and 16, so we restrict the data to those weeks for the analysis.

LP_transit_df <- filter(LP_transit_df, week %in% c(7:16))
# adjust rich and temp coefficients, so +1 genotype corresponds to +1 C
LP_transit_df <- LP_transit_df %>%
  mutate(rich = rich-1,
         temp = temp*3)

0-LYER-Ptoid \(\rightarrow\) any critical transition (Fig. 3 of main text)

with(LP_transit_df, table(state_adj))
state_adj
   0-0-Ptoid     0-LYER-0 0-LYER-Ptoid 
          17           26          240 
# fit ANOVA glm
LP_any_glmf <- glm.ftest.v2(
  model = glm(data = LP_transit_df,
              family = quasibinomial(link = "cloglog"), 
              formula = state_adj %in% c("0-LYER-0","0-0-Ptoid") ~
                fweek + temp + rich + com + temp:rich + temp:com),
  test.formula = list(
    c("fweek","Residuals"),
    c("temp","temp:com"),
    c("rich","com"),
    c("temp:rich","temp:com"))
)[[3]] %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error) 

# table of results
LP_any_glmf %>%
  kable(., caption = "Analysis of deviance for critical transition from food chain.", booktabs = T) %>%
  kable_styling(latex_options = c("striped", "hold_position"))
Analysis of deviance for critical transition from food chain.
Source df (Source) df (Error) Deviance Mean Deviance F P Error
fweek 9 252 18.05 2.01 2.544 0.008 Residuals
temp 1 9 0.01 0.01 0.005 0.946 temp:com
rich 1 9 5.32 5.32 9.006 0.015 com
temp:rich 1 9 0.85 0.85 0.592 0.461 temp:com
# rich effect sizes reported in Fig. 3A of main text
rich_HR_CI <- conf_int(
  glm(data = LP_transit_df, 
      family = binomial(link = "cloglog"),
      formula = state_adj %in% c("0-LYER-0","0-0-Ptoid") ~ fweek + temp + rich),
  vcov = "CR2", 
  test = "naive-t", 
  coefs = "rich", 
  cluster = LP_transit_df$com) %>%
  data.frame() %>%
  rownames_to_column(var = "term")

# genotype-specific effects sizes reported in Fig. 3B of main text
geno_HR_CI <- conf_int(
  glm(data = LP_transit_df, 
      family = binomial(link = "cloglog"),
      formula = state_adj %in% c("0-LYER-0","0-0-Ptoid") ~ fweek + temp + Col + gsm1 + AOP2 + AOP2.gsoh), # fstate
  vcov = "CR2", 
  test = "naive-t", 
  coefs = c("Col","gsm1","AOP2","AOP2.gsoh"), 
  cluster = LP_transit_df$com) %>%
  data.frame() %>%
  rownames_to_column(var = "term")

# reproduce Fig. 3B in main text
geno_HR_CI %>%
  mutate(term = factor(term, 
                       levels = c("Col","gsm1","AOP2","AOP2.gsoh"))) %>%
  ggplot(aes(x = term, y = (exp(beta)-1)*100, color = term)) +
  geom_point(size = 3) +
  geom_hline(yintercept = 0, linetype = "dotted") +
  geom_linerange(aes(ymax = (exp(CI_U)-1)*100, ymin = (exp(CI_L)-1)*100)) +
  geom_linerange(aes(ymin = (exp(beta - SE)-1)*100, ymax = (exp(beta + SE)-1)*100), size = 1.25) +
  scale_y_continuous(name = expression("Risk of critical transition "(Delta~"%"))) +
  scale_x_discrete(labels = c("Col-0","gsm1","AOP2","AOP2/gsoh")) +
  scale_color_manual(values = c("darkgreen","steelblue","darkorange","firebrick1"), guide = F) +
  xlab("") +
  theme_cowplot(font_size = 18, line_size = 1)

Version Author Date
86116c8 mabarbour 2020-06-23
ggsave("figures/rich-geno-critical-transition-v4.pdf", width = 8, height = 8, units = "in")

0-LYER-Ptoid \(\rightarrow\) 0-0-Ptoid

# check for weekly variation in critical transitions
LP_transit_df %>%
  mutate(CT_FoodChain_to_00Ptoid = ifelse(state_adj == c("0-0-Ptoid"), 1, 0)) %>%
  group_by(week) %>%
  summarise_at("CT_FoodChain_to_00Ptoid", mean)
# A tibble: 10 x 2
    week CT_FoodChain_to_00Ptoid
   <dbl>                   <dbl>
 1     7                   0.114
 2     8                   0.136
 3     9                   0    
 4    10                   0    
 5    11                   0    
 6    12                   0    
 7    13                   0    
 8    14                   0    
 9    15                   0.211
10    16                   0.167

Only observe variation in weeks 7 and 8 and 15 and 16.

# fit ANOVA GLM
LP_to_00Ptoid_glmf <- glm.ftest.v2(
  model = glm(data = filter(LP_transit_df, week %in% c(7,8,15,16)),
              family = quasibinomial(link = "cloglog"), 
              formula = state_adj %in% c("0-0-Ptoid") ~
                fweek + temp + rich + com + temp:rich + temp:com),
  test.formula = list(
    c("fweek","Residuals"),
    c("temp","temp:com"),
    c("rich","com"),
    c("temp:rich","temp:com"))
)[[3]] %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error) 

# table of results
LP_to_00Ptoid_glmf %>%
  kable(., caption = "Analysis of deviance for critical transition from food chain to a complete collapse (loss of both aphid and parasitoid).", booktabs = T) %>%
  kable_styling(latex_options = c("striped", "hold_position"))
Analysis of deviance for critical transition from food chain to a complete collapse (loss of both aphid and parasitoid).
Source df (Source) df (Error) Deviance Mean Deviance F P Error
fweek 3 94 1.03 0.34 0.469 0.705 Residuals
temp 1 9 0.51 0.51 0.373 0.557 temp:com
rich 1 9 5.42 5.42 5.477 0.044 com
temp:rich 1 9 0.70 0.70 0.518 0.490 temp:com
# temp effect sizes reported in Fig. 2 of main text
conf_int(
  glm(data = filter(LP_transit_df, week %in% c(7,8,15,16)),
              family = quasibinomial(link = "cloglog"), 
              formula = state_adj %in% c("0-0-Ptoid") ~
                fweek + temp),
  vcov = "CR2",
  test = "naive-t",
  cluster = with(filter(LP_transit_df, week %in% c(7,8,15,16)), paste(temp,com)),
  coefs = "temp"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1      -0.11
# rich effect sizes reported in Fig. 2 of main text
conf_int(
  glm(data = filter(LP_transit_df, week %in% c(7,8,15,16)),
              family = quasibinomial(link = "cloglog"), 
              formula = state_adj %in% c("0-0-Ptoid") ~
                fweek + temp + rich),
  vcov = "CR2",
  test = "naive-t",
  cluster = filter(LP_transit_df, week %in% c(7,8,15,16))$com,
  coefs = "rich"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1      -0.53

0-LYER-Ptoid \(\rightarrow\) 0-LYER-0

LP_transit_df %>%
  mutate(CT_FoodChain_to_0LYER0 = ifelse(state_adj == c("0-LYER-0"), 1, 0)) %>%
  group_by(week) %>%
  summarise_at("CT_FoodChain_to_0LYER0", mean)
# A tibble: 10 x 2
    week CT_FoodChain_to_0LYER0
   <dbl>                  <dbl>
 1     7                 0.0227
 2     8                 0.0682
 3     9                 0.143 
 4    10                 0.1   
 5    11                 0.0370
 6    12                 0.0769
 7    13                 0.0833
 8    14                 0.136 
 9    15                 0.158 
10    16                 0.25  

We observe variation in all of the same weeks as LP_transit_df, so we continue with it for the analysis.

# fit ANOVA glm
glm.ftest.v2(
  model = glm(data = LP_transit_df,
              family = quasibinomial(link = "cloglog"), 
              formula = state_adj %in% c("0-LYER-0") ~
                fweek + temp + rich + com + temp:rich + temp:com),
  test.formula = list(
    c("fweek","Residuals"),
    c("temp","temp:com"),
    c("rich","com"),
    c("temp:rich","temp:com"))
)[[3]] %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error) %>%
  # table of results
  kable(., caption = "Analysis of deviance for critical transition from food chain to an aphid only food web.", booktabs = T) %>%
  kable_styling(latex_options = c("striped", "hold_position"))
Analysis of deviance for critical transition from food chain to an aphid only food web.
Source df (Source) df (Error) Deviance Mean Deviance F P Error
fweek 9 252 9.99 1.11 2.094 0.031 Residuals
temp 1 9 0.36 0.36 0.238 0.637 temp:com
rich 1 9 0.81 0.81 0.508 0.494 com
temp:rich 1 9 1.07 1.07 0.715 0.420 temp:com
# temp effect sizes reported in Fig. 2 of main text
conf_int(
  glm(data = LP_transit_df,
              family = quasibinomial(link = "cloglog"), 
              formula = state_adj %in% c("0-LYER-0") ~
                fweek + temp),
  vcov = "CR2",
  test = "naive-t",
  cluster = with(LP_transit_df, paste(temp,com)),
  coefs = "temp"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1       0.08
# rich effect sizes reported in Fig. 2 of main text
conf_int(
  glm(data = LP_transit_df,
              family = quasibinomial(link = "cloglog"), 
              formula = state_adj %in% c("0-LYER-0") ~
                fweek + temp + rich),
  vcov = "CR2",
  test = "naive-t",
  cluster = LP_transit_df$com,
  coefs = "rich"
) %>%
  data.frame() %>%
  rownames_to_column(var = "term") %>%
  summarise_at(vars(beta), list(delta_prob = delta_prob))
  delta_prob
1      -0.19

Multinomial models

Focus on states in last week for multinomial model

state_df_17 <- df %>%
  # counter information is not relevant (because it is the same), so we summarise across it
  group_by(cage, fweek, week, temp, rich, Col, gsm1, AOP2, AOP2.gsoh, com) %>% 
  summarise_at(vars(BRBR_Survival, LYER_Survival, Mummy_Ptoids_Survival), list(mean)) %>%
  ungroup() %>%
  # we need the dataset to go through week 17, rather than removing cages as they transition
  # to a collapsed community as in `state_df`
  mutate(BRBR = ifelse(is.na(BRBR_Survival) == T, 0, BRBR_Survival),
         LYER = ifelse(is.na(LYER_Survival) == T, 0, LYER_Survival),
         Ptoid = ifelse(is.na(Mummy_Ptoids_Survival) == T, 0, Mummy_Ptoids_Survival),
         state = paste(BRBR, LYER, Ptoid, sep = "-"),
         value = 1) %>%
  filter(week == 17) 

Now let’s inspect the possible states

with(state_df_17, table(state))
state
0-0-0 0-1-0 0-1-1 
   28    25     7 

Note there are only several 0-1-1 states. It seems unwise to try and fit the statistical interaction temp:rich to test whether there is a non-additive effect of rich and temp on the probability of this state. Moreover, we have no evidence for non-additive effects between rich and temp from our critical transition analysis. Therefore, we fit a simpler model with the interaction term temp:rich.

## Organize data 

# we use the "Poisson trick" so we can analyze the multinomial model as a Poisson GLM.
# note that they are equivalent, and this allowed us to conduct our ANOVA GLM and test
# terms agains the appropriate error term.
pois.trans_state_df_17 <- state_df_17 %>%
  mutate(value = 1) %>%
  select(cage, temp, rich, com, state, value) %>%
  pivot_wider(id_cols = c("cage","temp","rich","com"), 
              names_from = state, 
              values_from = value,
              values_fill = list(value = 0)) %>%
  gather(state, value, -(cage:com)) %>%
  ungroup() %>%
  mutate(# place coefficients on comparable scale
         rich = rich - 1, # now monoculture corresponds to the intercept term in the model
         temp = temp * 3,
         # set different baselines for testing
         state000_ = factor(state, levels = c("0-0-0","0-1-0","0-1-1")),
         state011_ = factor(state, levels = c("0-1-1","0-1-0","0-0-0")))
# fit ANOVA GLM
multinom_glmf <- glm.ftest.v2(
  model = glm(data = pois.trans_state_df_17,
              family = quasipoisson(link = "log"), 
              formula = terms(value ~ state000_ + rich + temp + com + temp:rich + temp:com + cage + # baseline parameters
                state000_:temp + state000_:rich + state000_:com + state000_:temp:com,
                keep.order = T)), 
  test.formula = list(
    c("state000_:temp","state000_:temp:com"),
    c("state000_:rich","state000_:com"))
)[[3]] %>%
  mutate(treatment = c("temp","rich"), error = c("temp:com","com")) %>%
  select(Source = treatment, 
         `df (Source)` = num_df, 
         `df (Error)` = den_df,
         Deviance = deviance,
         `Mean Deviance` = mean_deviance,
         F = F, P = P, Error = error) 

# table of results, which we report directly in main text
multinom_glmf %>%
  kable(., caption = "Analysis of deviance for multinomial model of food-web structures at the end of the experiment.", booktabs = T) %>%
  kable_styling(latex_options = c("striped", "hold_position"))
Analysis of deviance for multinomial model of food-web structures at the end of the experiment.
Source df (Source) df (Error) Deviance Mean Deviance F P Error
temp 2 20 6.97 3.48 4.031 0.034 temp:com
rich 2 18 6.56 3.28 3.608 0.048 com
# calculate temp effect size on each state relative to complete collapse
multi_temp_CI <- conf_int(
  glm(data = pois.trans_state_df_17,
              family = quasipoisson(link = "log"), 
              formula = value ~ state000_ + rich + temp + com + temp:com + #cage + # baseline parameters
                state000_:temp),
  vcov = "CR2",
  test = "naive-t",
  cluster = with(pois.trans_state_df_17, paste(state000_, temp, com)),
  coefs = c("state000_0-1-0:temp","state000_0-1-1:temp")
) %>%
  data.frame() %>%
  rownames_to_column(var = "term")

# calculate rich effect size on each state relative to complete collapse
multi_rich_CI <- conf_int(
  glm(data = pois.trans_state_df_17,
              family = quasipoisson(link = "log"), 
              formula = value ~ state000_ + rich + temp + com + temp:com + #cage + # baseline parameters
                state000_:temp + state000_:rich),
  vcov = "CR2",
  test = "naive-t",
  cluster = with(pois.trans_state_df_17, paste(state000_, com)),
  coefs = c("state000_0-1-0:rich","state000_0-1-1:rich")
) %>%
  data.frame() %>%
  rownames_to_column(var = "term")

# reproduce Fig. S4 in Supplementary Material
bind_rows(multi_rich_CI, multi_temp_CI) %>%
  mutate(term = factor(term, 
                       levels = c("state000_0-1-0:temp","state000_0-1-1:temp",
                                  "state000_0-1-0:rich","state000_0-1-1:rich"))) %>%
  separate(term, into = c("state_comparison","treatment"), sep = ":") %>%
  mutate(treatment = factor(treatment, 
                            levels = c("rich","temp"))) %>%
  mutate(state_comparison = factor(state_comparison, 
                                   levels = c("state000_0-1-1","state000_0-1-0"),
                                   labels = c("Food chain vs. complete collapse",
                                              "Aphid only vs. complete collapse"))) %>%
  ggplot(aes(x = treatment, y = exp(beta))) +
  geom_point(size = 3) +
  geom_hline(yintercept = 1, linetype = "dotted") +
  geom_linerange(aes(ymax = exp(CI_U), ymin = exp(CI_L))) +
  geom_linerange(aes(ymin = exp(beta - SE), ymax = exp(beta + SE)), size = 1.25) +
  scale_y_continuous(name = "Odds ratio") +
  scale_x_discrete(labels = c("Genetic diversity\n(+1 genotype)","Warming\n (+1°C)")) +
  xlab("") +
  facet_wrap(~state_comparison, ncol = 1) +
  theme_cowplot(font_size = 18, line_size = 1)

Version Author Date
86116c8 mabarbour 2020-06-23

Save analysis

Write out an .RData file to use for creating the Supplementary Material Results.

save.image(file = "output/critical-transitions.RData")

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.6 LTS

Matrix products: default
BLAS:   /usr/lib/libblas/libblas.so.3.6.0
LAPACK: /usr/lib/lapack/liblapack.so.3.6.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] kableExtra_1.1.0   clubSandwich_0.3.5 msm_1.6.8          cowplot_1.0.0     
 [5] forcats_0.4.0      stringr_1.4.0      dplyr_0.8.3        purrr_0.3.3       
 [9] readr_1.3.1        tidyr_1.0.2        tibble_2.1.3       ggplot2_3.2.1     
[13] tidyverse_1.3.0   

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.2        lubridate_1.7.4   mvtnorm_1.0-10    lattice_0.20-38  
 [5] zoo_1.8-6         utf8_1.1.4        assertthat_0.2.1  rprojroot_1.3-2  
 [9] digest_0.6.20     R6_2.4.0          cellranger_1.1.0  backports_1.1.4  
[13] reprex_0.3.0      evaluate_0.14     highr_0.8         httr_1.4.1       
[17] pillar_1.4.2      rlang_0.4.4       lazyeval_0.2.2    readxl_1.3.1     
[21] rstudioapi_0.10   whisker_0.3-2     Matrix_1.2-17     rmarkdown_2.0    
[25] labeling_0.3      splines_3.6.3     webshot_0.5.1     munsell_0.5.0    
[29] broom_0.5.2       compiler_3.6.3    httpuv_1.5.1      modelr_0.1.5     
[33] xfun_0.9          pkgconfig_2.0.2   htmltools_0.3.6   tidyselect_0.2.5 
[37] expm_0.999-4      workflowr_1.6.0   fansi_0.4.0       viridisLite_0.3.0
[41] crayon_1.3.4      dbplyr_1.4.2      withr_2.1.2       later_1.0.0      
[45] grid_3.6.3        nlme_3.1-140      jsonlite_1.6      gtable_0.3.0     
[49] lifecycle_0.1.0   DBI_1.0.0         git2r_0.26.1      magrittr_1.5     
[53] scales_1.0.0      cli_1.1.0         stringi_1.4.3     fs_1.3.1         
[57] promises_1.0.1    xml2_1.2.2        ellipsis_0.3.0    generics_0.0.2   
[61] vctrs_0.2.2       sandwich_2.5-1    tools_3.6.3       glue_1.3.1       
[65] hms_0.5.3         survival_3.1-8    yaml_2.2.0        colorspace_1.4-1 
[69] rvest_0.3.5       knitr_1.26        haven_2.2.0