library(tidyverse)
library(mnsentinellakes)
library(mgcv)
library(gratia)
library(mwlaxeref)
library(cowplot)
options(scipen = 999)


zoop <- read.csv("./data/ramsey_county_zooplankton.csv") 

# These are calulated from all of white bear ice data (not just post-1980)
early.anomaly = -17.52
late.anomaly = 14

Combine ice data with zooplankton data

Use ice data only from White Bear (DOW = 82016700)

ice_off <- read.csv("./data/ice_off_summarized.csv") %>%
  mutate(DNRID = fixlakeid(DOW)) %>% 
  dplyr::select(DNRID, year, min_ice_off_julian, min_ice_off_date) %>%
  dplyr::filter(DNRID == "82016700")


mean_ice <- ice_off  %>%
  dplyr::filter(year < 1980) %>%
  summarise(mean_ice_off = mean(min_ice_off_julian))

# Negative values indicate earlier anomalies
wb.ice = ice_off %>%
  add_row(data.frame(year = 2024, min_ice_off_julian = 67, min_ice_off_date = "2024-03-08")) %>%
  mutate(ice_off_anomaly = min_ice_off_julian - mean_ice$mean_ice_off) %>% 
  select(!DNRID) %>% 
  filter(year > 1980)

temp <- merge(wb.ice, zoop, by = c("year")) %>% 
  mutate(days_since_ice = JULIANDAY - min_ice_off_julian) %>%
  dplyr::filter(!is.na(CYCLO.THOUS.M3)) %>%
  mutate(ice_off_anomaly = as.numeric(ice_off_anomaly),
         JULIANDAY = as.numeric(JULIANDAY),
         year = as.numeric(year),
         year_f = as.factor(year),
         DNRID = as.factor(DNRID), 
         ice_off_anomaly_abs = abs(ice_off_anomaly)) %>%
  # Subjective but remove early (pre-february) and late (post Decemeber) dates
  dplyr::filter(JULIANDAY > 60) %>%  
  dplyr::filter(JULIANDAY < 330) %>% 
  rowwise() %>%
  mutate('ZOOP.THOUS.M3' = sum(c_across(CYCLO.THOUS.M3:DIAPHAN.THOUS.M3))) %>% 
  select(year, min_ice_off_julian, min_ice_off_date, ice_off_anomaly, 
         DNRID, SITE, DATE, TOW, ZOOP.THOUS.M3, everything()) %>% ungroup() %>% as.data.frame()

sampling.events <- temp %>% group_by(DNRID, year) %>% count() #%>% filter(n > 200)
median(sampling.events$n)
## [1] 8

Run all GAMS

# loops through columns 10-20 in the dataframe temp and runs nb GAMMs
# TO DO - Play with offsets and counts 
models <- list()

for (i in 10:18) { #10-18
  
  single.model <- gam(temp[,i] ~ s(ice_off_anomaly) +  
                      s(JULIANDAY) + 
                      ti(ice_off_anomaly, JULIANDAY) +
                      s(DNRID, bs = "re") + s(year_f, bs = "re"),
                    method = "REML", select = T,
                    data = temp, family = nb())
  print(paste0("Finished ", colnames(temp)[i]))
  models[[paste0(colnames(temp)[i])]] <- single.model
}
## [1] "Finished CYCLO.THOUS.M3"
## [1] "Finished CALA.THOUS.M3"
## [1] "Finished NAUP.THOUS.M3"
## [1] "Finished ROTIF.THOUS.M3"
## [1] "Finished DAPH.THOUS.M3"
## [1] "Finished BOSM.THOUS.M3"
## [1] "Finished CHYD.THOUS.M3"
## [1] "Finished CERIOD.THOUS.M3"
## [1] "Finished DIAPHAN.THOUS.M3"
summary(models$CYCLO.THOUS.M3)
## 
## Family: Negative Binomial(1.482) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value            Pr(>|z|)    
## (Intercept)    2.372      0.178   13.33 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                                     edf Ref.df  Chi.sq             p-value    
## s(ice_off_anomaly)             0.001762      9     0.0               0.965    
## s(JULIANDAY)                   7.798189      9  2387.2 <0.0000000000000002 ***
## ti(ice_off_anomaly,JULIANDAY) 14.416896     16   668.7 <0.0000000000000002 ***
## s(DNRID)                      26.720666     27 10671.9 <0.0000000000000002 ***
## s(year_f)                     42.568367     43 17829.9 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.287   Deviance explained = 58.9%
## -REML =  19583  Scale est. = 1         n = 5990
summary(models$CALA.THOUS.M3)
## 
## Family: Negative Binomial(1.244) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value            Pr(>|z|)    
## (Intercept)   1.6784     0.1467   11.44 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                                     edf Ref.df  Chi.sq             p-value    
## s(ice_off_anomaly)             0.005409      9    0.02                0.65    
## s(JULIANDAY)                   7.173303      9 1970.56 <0.0000000000000002 ***
## ti(ice_off_anomaly,JULIANDAY) 10.558149     16  204.97 <0.0000000000000002 ***
## s(DNRID)                      26.077445     27 1622.64 <0.0000000000000002 ***
## s(year_f)                     42.424057     43 7486.51 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.287   Deviance explained = 42.6%
## -REML =  16278  Scale est. = 1         n = 5990
summary(models$NAUP.THOUS.M3)
## 
## Family: Negative Binomial(1.835) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value            Pr(>|z|)    
## (Intercept)   3.1067     0.1708   18.19 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                                     edf Ref.df  Chi.sq              p-value    
## s(ice_off_anomaly)             0.001765      9     0.0                0.989    
## s(JULIANDAY)                   7.502667      9  2756.9 < 0.0000000000000002 ***
## ti(ice_off_anomaly,JULIANDAY)  8.348112     16    75.5            0.0000343 ***
## s(DNRID)                      26.744701     27 16289.4 < 0.0000000000000002 ***
## s(year_f)                     42.680855     43 28487.7 < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.233   Deviance explained = 59.4%
## -REML =  23394  Scale est. = 1         n = 5990
summary(models$ROTIF.THOUS.M3)
## 
## Family: Negative Binomial(0.544) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value            Pr(>|z|)    
## (Intercept)   2.4417     0.2483   9.835 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                                     edf Ref.df    Chi.sq              p-value
## s(ice_off_anomaly)             0.001706      9     0.003                0.803
## s(JULIANDAY)                   7.469614      9   940.899 < 0.0000000000000002
## ti(ice_off_anomaly,JULIANDAY)  9.328772     16    90.890            0.0000105
## s(DNRID)                      26.380036     27  6147.263 < 0.0000000000000002
## s(year_f)                     42.606172     43 18818.082 < 0.0000000000000002
##                                  
## s(ice_off_anomaly)               
## s(JULIANDAY)                  ***
## ti(ice_off_anomaly,JULIANDAY) ***
## s(DNRID)                      ***
## s(year_f)                     ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.065   Deviance explained = 46.7%
## -REML =  19053  Scale est. = 1         n = 5990
summary(models$DAPH.THOUS.M3)
## 
## Family: Negative Binomial(0.832) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value            Pr(>|z|)    
## (Intercept)   1.4713     0.1572   9.361 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                                   edf Ref.df Chi.sq             p-value    
## s(ice_off_anomaly)             0.4554      9  603.3               0.178    
## s(JULIANDAY)                   7.5484      9 1606.5 <0.0000000000000002 ***
## ti(ice_off_anomaly,JULIANDAY) 12.6630     16  418.9 <0.0000000000000002 ***
## s(DNRID)                      26.2236     27 1365.0 <0.0000000000000002 ***
## s(year_f)                     41.6616     43 4571.9 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.283   Deviance explained = 37.9%
## -REML =  15347  Scale est. = 1         n = 5990
summary(models$BOSM.THOUS.M3)
## 
## Family: Negative Binomial(0.437) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value       Pr(>|z|)    
## (Intercept)   2.0989     0.3403   6.169 0.000000000689 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                                    edf Ref.df    Chi.sq             p-value    
## s(ice_off_anomaly)             0.00232      9     0.016               0.188    
## s(JULIANDAY)                   7.62130      9  1744.395 <0.0000000000000002 ***
## ti(ice_off_anomaly,JULIANDAY) 12.31899     16   355.786 <0.0000000000000002 ***
## s(DNRID)                      26.84852     27 10673.965 <0.0000000000000002 ***
## s(year_f)                     42.02908     43 14766.037 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =   0.15   Deviance explained = 52.7%
## -REML =  17174  Scale est. = 1         n = 5990
summary(models$CHYD.THOUS.M3)
## 
## Family: Negative Binomial(0.334) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value Pr(>|z|)
## (Intercept)   0.2556     0.3278    0.78    0.436
## 
## Approximate significance of smooth terms:
##                                     edf Ref.df    Chi.sq             p-value
## s(ice_off_anomaly)             0.002075      9     0.004               0.493
## s(JULIANDAY)                   6.820935      9   857.528 <0.0000000000000002
## ti(ice_off_anomaly,JULIANDAY)  6.120625     16   106.526 <0.0000000000000002
## s(DNRID)                      26.614806     27  4909.712 <0.0000000000000002
## s(year_f)                     41.951927     43 11242.338 <0.0000000000000002
##                                  
## s(ice_off_anomaly)               
## s(JULIANDAY)                  ***
## ti(ice_off_anomaly,JULIANDAY) ***
## s(DNRID)                      ***
## s(year_f)                     ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.0582   Deviance explained =   48%
## -REML = 9548.8  Scale est. = 1         n = 5990
summary(models$CERIOD.THOUS.M3)
## 
## Family: Negative Binomial(0.213) 
## Link function: log 
## 
## Formula:
## temp[, i] ~ s(ice_off_anomaly) + s(JULIANDAY) + ti(ice_off_anomaly, 
##     JULIANDAY) + s(DNRID, bs = "re") + s(year_f, bs = "re")
## 
## Parametric coefficients:
##             Estimate Std. Error z value Pr(>|z|)  
## (Intercept)  -0.8868     0.4878  -1.818   0.0691 .
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                                     edf Ref.df   Chi.sq             p-value    
## s(ice_off_anomaly)             0.005018      9    0.001               0.812    
## s(JULIANDAY)                   6.940875      9 2149.233 <0.0000000000000002 ***
## ti(ice_off_anomaly,JULIANDAY)  7.719588     16  132.333 <0.0000000000000002 ***
## s(DNRID)                      26.674936     27 4367.261 <0.0000000000000002 ***
## s(year_f)                     41.221625     43 9562.052 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  -0.951   Deviance explained = 61.8%
## -REML = 6651.2  Scale est. = 1         n = 5990
appraise(models$CYCLO.THOUS.M3)

appraise(models$CALA.THOUS.M3)

appraise(models$NAUP.THOUS.M3)

appraise(models$ROTIF.THOUS.M3)

appraise(models$DAPH.THOUS.M3)

appraise(models$BOSM.THOUS.M3)

appraise(models$CHYD.THOUS.M3)

appraise(models$CERIOD.THOUS.M3)

k.check(models$CYCLO.THOUS.M3)
##                               k'          edf   k-index p-value
## s(ice_off_anomaly)             9  0.001761511 0.9056888  0.0550
## s(JULIANDAY)                   9  7.798189230 0.9211881  0.3125
## ti(ice_off_anomaly,JULIANDAY) 16 14.416895885 0.8098815  0.0000
## s(DNRID)                      28 26.720666299        NA      NA
## s(year_f)                     44 42.568366803        NA      NA
k.check(models$CALA.THOUS.M3)
##                               k'          edf   k-index p-value
## s(ice_off_anomaly)             9  0.005408851 0.9186060  0.9125
## s(JULIANDAY)                   9  7.173302533 0.9025275  0.5475
## ti(ice_off_anomaly,JULIANDAY) 16 10.558148938 0.8433064  0.0000
## s(DNRID)                      28 26.077445209        NA      NA
## s(year_f)                     44 42.424057243        NA      NA
k.check(models$NAUP.THOUS.M3)
##                               k'          edf   k-index p-value
## s(ice_off_anomaly)             9  0.001764526 0.9296690  0.3050
## s(JULIANDAY)                   9  7.502666963 0.9289166  0.2475
## ti(ice_off_anomaly,JULIANDAY) 16  8.348112192 0.8387346  0.0000
## s(DNRID)                      28 26.744700867        NA      NA
## s(year_f)                     44 42.680855294        NA      NA
k.check(models$ROTIF.THOUS.M3)
##                               k'          edf   k-index p-value
## s(ice_off_anomaly)             9  0.001706475 0.8018154   0.425
## s(JULIANDAY)                   9  7.469613965 0.7688942   0.000
## ti(ice_off_anomaly,JULIANDAY) 16  9.328772372 0.7267178   0.000
## s(DNRID)                      28 26.380035520        NA      NA
## s(year_f)                     44 42.606172435        NA      NA
k.check(models$DAPH.THOUS.M3)
##                               k'        edf   k-index p-value
## s(ice_off_anomaly)             9  0.4553822 0.8728431  0.3700
## s(JULIANDAY)                   9  7.5484387 0.8706399  0.2975
## ti(ice_off_anomaly,JULIANDAY) 16 12.6629806 0.8288372  0.0000
## s(DNRID)                      28 26.2236144        NA      NA
## s(year_f)                     44 41.6615817        NA      NA
k.check(models$BOSM.THOUS.M3)
##                               k'          edf   k-index p-value
## s(ice_off_anomaly)             9  0.002320306 0.7887612  0.4725
## s(JULIANDAY)                   9  7.621298026 0.7925971  0.5625
## ti(ice_off_anomaly,JULIANDAY) 16 12.318992891 0.7481422  0.0000
## s(DNRID)                      28 26.848520122        NA      NA
## s(year_f)                     44 42.029079770        NA      NA
k.check(models$CHYD.THOUS.M3)
##                               k'         edf   k-index p-value
## s(ice_off_anomaly)             9  0.00207526 0.7522163  0.1225
## s(JULIANDAY)                   9  6.82093486 0.7785335  0.9425
## ti(ice_off_anomaly,JULIANDAY) 16  6.12062519 0.7295041  0.0000
## s(DNRID)                      28 26.61480559        NA      NA
## s(year_f)                     44 41.95192713        NA      NA
k.check(models$CERIOD.THOUS.M3)
##                               k'          edf   k-index p-value
## s(ice_off_anomaly)             9  0.005017647 0.7613685  0.5925
## s(JULIANDAY)                   9  6.940874642 0.7431696  0.0875
## ti(ice_off_anomaly,JULIANDAY) 16  7.719588431 0.7194170  0.0000
## s(DNRID)                      28 26.674935555        NA      NA
## s(year_f)                     44 41.221625156        NA      NA
write_rds(models, "./models/zoop.models.rds")

Read-in outputted models

models <- read_rds("./models/zoop.models.rds")

DOY change function

# Ouputs a figure for changes in zoop dynamics across a year
plot.zoop.doy = function(model = NA, var.name = NA, y.label = NA, 
                         early.anomaly = -17.52, late.anomaly = 14, start.date = 80, end.date = 274) {
  if (is.na(model)) {
    model <- models[[var.name]]
  }
  # Year and DNRID aren't used but are required for predict.gam
  length <- length(seq(start.date, end.date, by = 1))
  pred.df <- data.frame(ice_off_anomaly_abs = 
                          c(rep(early.anomaly, length), rep(0, length), rep(late.anomaly, length)),
                        ice_off_anomaly = 
                          c(rep(early.anomaly, length), rep(0, length), rep(late.anomaly, length)),
                        JULIANDAY = seq(start.date, end.date, by = 1), 
                        DNRID = "62001200", year_f = "2000") 
  
  fit <- predict.gam(model, pred.df,
                     unconditional = T, # Gives simultaneous
                     type = "response",
                     exclude = c('s(DNRID)', 
                                's(year_f)'),
                     se.fit = T)
  pred.df$predictions = fit$fit
  pred.df$se = fit$se.fit
  
  y.label <- species.labels %>% filter(variable == var.name) %>% 
        select(axis.label) %>% as.character()
  plot <- pred.df %>%
      mutate(ice_off_anomaly = as.factor(ice_off_anomaly),
             ice_off_f = 
               ifelse(ice_off_anomaly == early.anomaly, "Early", "Normal"),
             ice_off_f = 
               ifelse(ice_off_anomaly == late.anomaly, "Late", ice_off_f),
             ice_off_f = factor(ice_off_f, 
                                levels = c("Early", "Normal", "Late"))) %>%
      ggplot(aes(y = predictions, x = as.Date(JULIANDAY, origin = as.Date("2018-01-01")), 
                                              color = ice_off_anomaly)) +
      geom_line(aes(y = predictions, x = as.Date(JULIANDAY, origin = as.Date("2018-01-01")), 
                                                 color = ice_off_f), lwd = 2) +
      theme_classic() +
      scale_x_date(date_breaks = "months" , date_labels = "%b") + 
      scale_color_manual(values = c("#FF0000", "#000000", "#0000FF")) +
      labs(y = paste0(y.label), x = "Julian Day", 
           color = "Ice-Off", fill = "Ice-Off") +
      
      theme(axis.text = element_text(size = 14),
        axis.title = element_text(size = 18),
        legend.title = element_text(size = 16), 
        legend.text = element_text(size = 16))
  
  return(plot)
}

Plot DOY change - FIGURE S9

hist(temp$JULIANDAY) # Approx 125-275 for peak sampling, look at 100 - 300

min(temp$JULIANDAY)
## [1] 64
# Thoughts to do - Change x-axis to Months
species.labels <- data.frame(variable = c(names(models)), 
           axis.label = c("Cyclopoid Density", "Calanoid Density", 
                          "Nauplii Density", "Rotifer Density", 
                          "Daphnia Density", "Bosmina Density", 
                          "Chydoridae Density", "Ceriodaphnia Density", 
                          "Diaphanosoma Density"))


cyclo.doy <- plot.zoop.doy(var.name = "CYCLO.THOUS.M3") + theme(legend.position = "none")
#cyclo.doy 
cala.doy <- plot.zoop.doy(var.name = "CALA.THOUS.M3") + 
  theme(legend.position = "none")
naup.doy <- plot.zoop.doy(var.name = "NAUP.THOUS.M3") + 
  theme(legend.position = "none")
rotif.doy <- plot.zoop.doy(var.name = "ROTIF.THOUS.M3") + 
  theme(legend.position = "none")
daph.doy <- plot.zoop.doy(var.name = "DAPH.THOUS.M3") + 
  theme(legend.position = "none")
bosm.doy <- plot.zoop.doy(var.name = "BOSM.THOUS.M3") + 
  theme(legend.position = "none")
ceriod.doy <- plot.zoop.doy(var.name = "CERIOD.THOUS.M3") + 
  theme(legend.position = "none")
diaphan.doy <- plot.zoop.doy(var.name = "DIAPHAN.THOUS.M3") + 
  theme(legend.position = c(0.25, 0.75))
## Warning: A numeric `legend.position` argument in `theme()` was deprecated in ggplot2
## 3.5.0.
## i Please use the `legend.position.inside` argument of `theme()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
zoop <- cowplot::plot_grid(cyclo.doy, cala.doy, naup.doy, rotif.doy, daph.doy, 
                   bosm.doy, ceriod.doy, diaphan.doy)
zoop

#Figure S9
ggsave("./figures/FigureS9.jpeg", height = 10, width = 12)

Bayesian peak timing - FIGURE S2 (and Code for other figures)

toy.df <- data.frame(ice_off_anomaly = c(rep(early.anomaly, 201), rep(0, 201), 
                                          rep(late.anomaly, 201)),
                      JULIANDAY = seq(80, 280, by = 1), 
                      DNRID = "82016700", year_f = "2000") %>%
    mutate(.row = row_number()) %>% 
    mutate(ice_off_f =  ifelse(ice_off_anomaly == early.anomaly, "Early", "Normal"),
           ice_off_f =  ifelse(ice_off_anomaly == late.anomaly, "Late", ice_off_f))


# Models to loop through
iters <- names(models)

timing.cis <- data.frame(group = character(0), 
                   numeric = numeric(0),
                   timing = character(0),
                   median = numeric(0),
                   lower.95 = numeric(0),
                   upper.95 = numeric(0),
                   lower.75 = numeric(0),
                   upper.75 = numeric(0))

pred.df = toy.df

for (i in 1:length(iters)) {
  model = models[[iters[i]]]
  
  if (iters[i] %in% c("BOSM.THOUS.M3")) {
    pred.df = pred.df %>% filter(JULIANDAY < 182)
  }
  
  pred.df <- pred.df %>% mutate(.row = row_number())
  
  # Use posterior dist of the mean to get posterior fitted values
  sims <- fitted_samples(model, exclude = c('s(DNRID)',  's(year_f)'),
                         data = pred.df, n = 1000, seed = 1024) |>
    left_join(pred.df |> select(.row, ice_off_anomaly, JULIANDAY, ice_off_f), 
              by = join_by(.row == .row))
  
  # Get the peak timing when the ice off anomaly is big (ice-off happens late)
  early <- sims %>% filter(ice_off_anomaly == early.anomaly) %>% 
    group_by(.draw) %>% slice_max(.fitted)
  early.quant <- quantile(early$JULIANDAY, probs = c(.025,.975))
  early.quant.75 <- quantile(early$JULIANDAY, probs = c(.125, .875))
  early.median <- median(early$JULIANDAY)

  # Get the peak timing when the ice off anomaly is big (ice-off happens late)
  late <- sims %>% filter(ice_off_anomaly == late.anomaly) %>% 
    group_by(.draw) %>% slice_max(.fitted)
  late.quant <- quantile(late$JULIANDAY, probs = c(.025,.975))
  late.quant.75 <- quantile(late$JULIANDAY, probs = c(.125, .875))
  late.median <- median(late$JULIANDAY)
  
  # Get the peak timing when the ice off anomaly is normal (ice-off is average)
  average <- sims %>% filter(ice_off_anomaly == 0) %>% 
    group_by(.draw) %>% slice_max(.fitted)
  average.quant <- quantile(average$JULIANDAY, probs = c(.025,.975))
  average.quant.75 <- quantile(average$JULIANDAY, probs = c(.125, .875))
  average.median <- median(average$JULIANDAY)

  # Get the difference between peak timings
  diff.early <- early$JULIANDAY - average$JULIANDAY
  diff.earlymedian <- median(diff.early)
  diff.earlyquant <- quantile(diff.early, probs = c(.025,.975))
  diff.earlyquant.75 <- quantile(diff.early, probs = c(.125, .875))
  
  diff.late <- late$JULIANDAY - average$JULIANDAY
  diff.latemedian <- median(diff.late)
  diff.latequant <- quantile(diff.late, probs = c(.025,.975))
  diff.latequant.75 <- quantile(diff.late, probs = c(.125, .875))

  # Make new data frame to rbind to big data frame
  df <- data.frame(group = iters[[i]], 
                   numeric = i,
                   timing = c("Early", "Late", "Average", "Early Difference", "Late Difference"),
                   median = c(early.median, late.median, average.median, 
                              diff.earlymedian, diff.latemedian),
                   lower.95 = c(early.quant[1], late.quant[1], average.quant[1], 
                                diff.earlyquant[1], diff.latequant[1]),
                   upper.95 = c(early.quant[2], late.quant[2], average.quant[2],
                                diff.earlyquant[2], diff.latequant[2]),
                   lower.75 = c(early.quant.75[1], late.quant.75[1], average.quant.75[1],
                                diff.earlyquant.75[1], diff.latequant.75[1]),
                   upper.75 = c(early.quant.75[2], late.quant.75[2], average.quant.75[2],
                                diff.earlyquant.75[2], diff.latequant.75[2]))
  
  timing.cis <- rbind(timing.cis, df)
  pred.df = toy.df
}


row.names(timing.cis) <- NULL


species.labels <- data.frame(variable = names(models), 
  axis.label = c("Cyclopoid Peak", "Calanoid Peak", 
                 "Nauplii Peak", "Rotifer Peak", "Daphnia Peak", 
                 "Bosmina Peak",  "Chydoridae Peak", "Ceriodaphnia Peak", 
                 "Diaphanosoma Peak"))

timing.cis.plot <- timing.cis %>%
  mutate(jit = ifelse(timing == "Early", -0.15, 0.15),
         jit = ifelse(timing == "Average", 0, jit),
         jit = ifelse(timing == "Early Difference", -0.15, jit)) %>%
  merge(y = species.labels, by.x = "group", by.y = "variable") %>%
  mutate(axis.label = factor(axis.label, 
                             levels = c(
    "Cyclopoid", "Calanoid",
    "Nauplii", "Rotifer", "Daphnia", "Bosmina",  
    "Chydoridae", "Ceriodaphnia", "Diaphanosoma")))

timing.cis.plot$timing <- factor(timing.cis.plot$timing, 
                                 levels = c("Early", "Average", "Late", 
                                            "Early Difference", "Late Difference"))


timing.cis.plot %>%
  filter(!grepl("Difference", timing)) %>%
  ggplot() + 
  geom_point(aes(x = as.Date(median, origin = as.Date("2018-01-01")), y = -1*(numeric + jit),
                 color = timing), size = 2) + 
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = as.Date(lower.95, origin = as.Date("2018-01-01")), 
                   xend = as.Date(upper.95, origin = as.Date("2018-01-01")), color = timing)) +
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = as.Date(lower.75, origin = as.Date("2018-01-01")), 
                   xend = as.Date(upper.75, origin = as.Date("2018-01-01")), color = timing), 
               linewidth = 1.5) +
  #facet_grid(group ~., switch = "y") +
  theme_classic() +
  labs(y = "",
       x = "Day Peak Density Occurs", color = "Ice-off Anomaly") +
  theme(panel.grid.major.x = element_line(),
        panel.grid.minor.x = element_line(),
        strip.text.y.left = element_text(angle = 0, size = 12, face = 2),
        plot.title = element_text(angle = 0, size = 14, face = 2),
        strip.placement = "outside",
        strip.background = element_blank(),
        axis.text.y = element_text(size = 14),
        axis.text.x = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        legend.title = element_text(size = 14), 
        legend.text = element_text(size = 12)
        ) +
  scale_color_manual(values = c("#FF0000", "#000000", "#0000FF")) +
  scale_y_continuous(breaks = -1:-9, labels = levels(timing.cis.plot$axis.label)) +
  scale_x_date(date_labels = "%b")

  #scale_x_continuous(breaks = seq(100, 275, by = 25))

# Figure S2
ggsave("./figures/FigureS2.jpeg", height = 10, width = 10)

FIGURE 2A - Bayesian peak timing - SUBSET - Calendar DOY

#read in more data
timing.cis.wae <- read.csv("./data/wae_spawning_modeled.csv")
timing.cis.phyto <- read.csv("./data/phyto_modeled_peak.csv")

# y-axis plot labels
subset.labels = c("Chrysophyte","Dinoflagellate",
                  "Diatom",  
                  "Cyclopoid", "Calanoid",
                  "Daphnia", "Walleye \n Peak Spawning")

# groups to keep while filtering
groups_to_keep <- c("CHRYSOS", "DINOS","DIATOM",
                    "CALA.THOUS.M3", "CYCLO.THOUS.M3", "DAPH.THOUS.M3", 
                    "peak")

# x-axis plot labels
labels = c("April 1st", "May 1st",  
           "June 1st", "July 1st")

# bind data together
timing.cis.plot.all <- rbind(timing.cis.plot, timing.cis.wae, timing.cis.phyto) %>% 
  filter(group != "start")

plankton_subset_wae <- timing.cis.plot.all %>%
  filter(!grepl("Difference", timing)) %>%
  filter(group %in% groups_to_keep) %>%
  mutate(jit = ifelse(timing == "Early", -0.15, 0.15),
         jit = ifelse(timing == "Average", 0, jit),
         jit = ifelse(timing == "Early Difference", -0.15, jit)) %>%
  mutate(numeric = ifelse(group == "CHRYSOS", 1, numeric),
         numeric = ifelse(group == "DINOS", 2, numeric),
         numeric = ifelse(group == "DIATOMS", 3, numeric),
         numeric = ifelse(group == "CYCLO.THOUS.M3", 4, numeric),
         numeric = ifelse(group == "CALA.THOUS.M3", 5, numeric),
         numeric = ifelse(group == "DAPH.THOUS.M3", 6, numeric),
         numeric = ifelse(group == "peak", 7, numeric)) %>%
  ggplot() + 
  geom_point(aes(x = as.Date(median, origin = as.Date("2018-01-01")), y = -1*(numeric + jit),
                 color = timing), size = 2) + 
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = as.Date(lower.95, origin = as.Date("2018-01-01")), 
                   xend = as.Date(upper.95, origin = as.Date("2018-01-01")), color = timing)) +
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = as.Date(lower.75, origin = as.Date("2018-01-01")), 
                   xend = as.Date(upper.75, origin = as.Date("2018-01-01")), color = timing),
               linewidth = 1.5) +
  #facet_grid(group ~., switch = "y") +
  theme_classic() +
  labs(y = "",
       x = "Day Peak Density Occurs\n ", color = "Ice-off Anomaly") +
  theme(panel.grid.major.x = element_line(),
        panel.grid.minor.x = element_line(),
        strip.text.y.left = element_text(angle = 0, size = 12, face = 2),
        plot.title = element_text(angle = 0, size = 14, face = 2),
        strip.placement = "outside",
        strip.background = element_blank(),
        axis.text.y = element_text(size = 14),
        axis.text.x = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        legend.title = element_text(size = 14), 
        legend.text = element_text(size = 12)
        ) +
  theme(legend.position = c(.8, .1)) +
  scale_color_manual(values = c("#FF0000", "#000000",  "#0000FF")) +
  scale_y_continuous(breaks = -1:-7, labels = subset.labels) +
  scale_x_date(breaks = as.Date(c("2018-04-01", "2018-05-01",  
                                  "2018-06-01",  "2018-07-01")),
               label = labels) + 
  theme(plot.margin = margin(0,0,0,1, "cm"))

plankton_subset_wae

# Figure 2A
ggsave("./figures/Figure2A.jpeg", height = 8, width = 10)

FIGURE 2B - Bayesian peak timing - SUBSET - rate of change

# y-axis plot labels
subset.labels = c("Chrysophyte","Dinoflagellate",
                  "Diatom",  
                  "Cyclopoid", "Calanoid",
                  "Daphnia", "Walleye \n Peak Spawning")

# groups to keep while filtering
groups_to_keep <- c("CHRYSOS", "DINOS","DIATOM",
                    "CALA.THOUS.M3", "CYCLO.THOUS.M3", "DAPH.THOUS.M3", 
                    "peak")


rate_of_change <- timing.cis.plot.all %>%
  filter(grepl("Difference", timing)) %>%
  filter(group %in% c(groups_to_keep)) %>%
  mutate(jit = ifelse(timing == "Early", -0.15, 0.15),
         jit = ifelse(timing == "Average", 0, jit),
         jit = ifelse(timing == "Early Difference", -0.15, jit)) %>%
  mutate(numeric = ifelse(group == "CHRYSOS", 1, numeric),
         numeric = ifelse(group == "DINOS", 2, numeric),
         numeric = ifelse(group == "DIATOMS", 3, numeric),
         numeric = ifelse(group == "CYCLO.THOUS.M3", 4, numeric),
         numeric = ifelse(group == "CALA.THOUS.M3", 5, numeric),
         numeric = ifelse(group == "DAPH.THOUS.M3", 6, numeric),
         numeric = ifelse(group == "peak", 7, numeric)) %>%
  mutate(timing = ifelse(timing == "Early Difference", 
                         "Difference between early \nand average ice off", 
                         "Difference between late \nand average ice off")) %>%
  mutate(median = ifelse(timing == "Difference between early \nand average ice off", 
                         (median/(early.anomaly))*early.anomaly, (median/late.anomaly)*late.anomaly),
         upper.95 = ifelse(timing == "Difference between early \nand average ice off",
                           (upper.95/(early.anomaly))*early.anomaly, (upper.95/late.anomaly)*late.anomaly),
         lower.95 = ifelse(timing == "Difference between early \nand average ice off",
                           (lower.95/(early.anomaly))*early.anomaly, (lower.95/late.anomaly)*late.anomaly),
         upper.75 = ifelse(timing == "Difference between early \nand average ice off",
                           (upper.75/(early.anomaly))*early.anomaly, (upper.75/late.anomaly)*late.anomaly),
         lower.75 = ifelse(timing == "Difference between early \nand average ice off",
                           (lower.75/(early.anomaly))*early.anomaly, (lower.75/late.anomaly)*late.anomaly)) %>%
  ggplot() + 
  geom_point(aes(x = median, y = -1*(numeric + jit),
                 color = timing), size = 2) + 
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = lower.95, 
                   xend = upper.95, color = timing)) +
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = lower.75, xend = upper.75, color = timing), linewidth = 1.5) +
  geom_vline(xintercept = 0, color = "black", linewidth = 1) +
  geom_vline(xintercept = early.anomaly, color = "grey", linewidth = .75) +
  geom_vline(xintercept = late.anomaly, color = "grey", linewidth = .75) +
  theme_classic() +
  labs(y = "",
       x = "Change in Days Event Occurs Per\n Day Change in Ice-Off", 
       color = "Ice-off Anomaly") +
  theme(panel.grid.major.x = element_line(),
        panel.grid.minor.x = element_line(),
        strip.text.y.left = element_text(angle = 0, size = 12, face = 2),
        plot.title = element_text(angle = 0, size = 14, face = 2),
        strip.placement = "outside",
        strip.background = element_blank(),
        axis.text.y = element_text(size = 14),
        axis.text.x = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        legend.title = element_text(size = 14), 
        legend.text = element_text(size = 12)
        ) +
  scale_color_manual(values = c("#FF0000", "#0000FF")) +
  scale_y_continuous(breaks = -1:-7, labels = subset.labels) + theme(legend.position="none")



rate_of_change <- timing.cis.plot.all %>%
  filter(grepl("Difference", timing)) %>%
  filter(group %in% c(groups_to_keep)) %>%
  mutate(jit = ifelse(timing == "Early", -0.15, 0.15),
         jit = ifelse(timing == "Average", 0, jit),
         jit = ifelse(timing == "Early Difference", -0.15, jit)) %>%
  mutate(numeric = ifelse(group == "CHRYSOS", 1, numeric),
         numeric = ifelse(group == "DINOS", 2, numeric),
         numeric = ifelse(group == "DIATOMS", 3, numeric),
         numeric = ifelse(group == "CYCLO.THOUS.M3", 4, numeric),
         numeric = ifelse(group == "CALA.THOUS.M3", 5, numeric),
         numeric = ifelse(group == "DAPH.THOUS.M3", 6, numeric),
         numeric = ifelse(group == "peak", 7, numeric)) %>%
  mutate(timing = ifelse(timing == "Early Difference", 
                         "Difference between early \nand average ice off", 
                         "Difference between late \nand average ice off")) %>%
  mutate(median = ifelse(timing == "Difference between early \nand average ice off", 
                         median/(-1*early.anomaly), (median/late.anomaly)),
         upper.95 = ifelse(timing == "Difference between early \nand average ice off",
                           upper.95/(-1*early.anomaly), upper.95/late.anomaly),
         lower.95 = ifelse(timing == "Difference between early \nand average ice off",
                           lower.95/(-1*early.anomaly), lower.95/late.anomaly),
         upper.75 = ifelse(timing == "Difference between early \nand average ice off",
                           upper.75/(-1*early.anomaly), upper.75/late.anomaly),
         lower.75 = ifelse(timing == "Difference between early \nand average ice off",
                           lower.75/(-1*early.anomaly), lower.75/late.anomaly)) %>%
  ggplot() + 
  geom_point(aes(x = median, y = -1*(numeric + jit),
                 color = timing), size = 2) + 
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = lower.95, 
                   xend = upper.95, color = timing)) +
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = lower.75, xend = upper.75, color = timing), linewidth = 1.5) +
  geom_vline(xintercept = 0, color = "black", linewidth = 1) +
  geom_vline(xintercept = -1, color = "grey", linewidth = .75) +
  geom_vline(xintercept = 1, color = "grey", linewidth = .75) +
  theme_classic() +
  labs(y = "",
       x = "Change in Days Event Occurs Per\n Day Change in Ice-Off", 
       color = "Ice-off Anomaly") +
  theme(panel.grid.major.x = element_line(),
        panel.grid.minor.x = element_line(),
        strip.text.y.left = element_text(angle = 0, size = 12, face = 2),
        plot.title = element_text(angle = 0, size = 14, face = 2),
        strip.placement = "outside",
        strip.background = element_blank(),
        axis.text.y = element_text(size = 14),
        axis.text.x = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        legend.title = element_text(size = 14), 
        legend.text = element_text(size = 12)
        ) +
  scale_color_manual(values = c("#FF0000", "#0000FF")) +
  scale_y_continuous(breaks = -1:-7, labels = subset.labels) + theme(legend.position="none")

rate_of_change

# Figure 2B
ggsave("./figures/Figure2B_alt.jpeg", height = 8, width = 9)

Figure 2 - Combining two figures

cowplot::plot_grid(plankton_subset_wae, rate_of_change, labels = "AUTO")

ggsave("./figures/Figure2.jpeg", height = 10, width = 15)

SETUP FIGURE 4 - Recreating Feiner et al. 2022 figure

green_mean_ice_off <- 108.5

feiner.df <- data.frame(ice_off_anomaly = numeric(0),
                        JULIANDAY = numeric(0),
                        DNRID = numeric(0), year_f = numeric(0),
                        name = character(0), count = numeric(0)
                        )
anomaly = c(early.anomaly, 0, late.anomaly)

for (i in 1:3) {
  pred.df <- data.frame(ice_off_anomaly = anomaly[i],
                        JULIANDAY = seq(80, 280, by = 1), 
                        DNRID = "62001200", year_f = "2000") 
    
  fit <- predict.gam(models$CYCLO.THOUS.M3, pred.df,
                     unconditional = T, # Gives simultaneous
                     type = "response",
                     exclude = c('s(DNRID)', 's(year_f)'),
                     se.fit = T)
  pred.df$Cyclopoid = fit$fit
  
  fit.daph <- predict.gam(models$DAPH.THOUS.M3, pred.df,
                     unconditional = T, # Gives simultaneous
                     type = "response",
                     exclude = c('s(DNRID)', 's(year_f)'),
                     se.fit = T)
  
  pred.df$Daphnia = fit.daph$fit
  
  
  pred.df.long <- pred.df %>% pivot_longer(cols = Cyclopoid:Daphnia, values_to = "count") %>%
    mutate(JULIANDAY = as.Date(JULIANDAY, origin = as.Date("2018-01-01")))
  feiner.df = rbind(feiner.df, pred.df.long)
}

peaks <- feiner.df %>% group_by(ice_off_anomaly, name) %>% slice_max(count) %>%
  mutate(JULIANDAY = as.Date(JULIANDAY, origin = as.Date("2018-01-01")))

# Make a theme for all plots
personal_theme = theme_classic() +
  theme(axis.text.y = element_text(size = 14),
        axis.text.x = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        axis.title.y = element_text(size = 16),
        legend.title = element_text(size = 14), 
        legend.text = element_text(size = 12)) +
  theme(plot.title = element_text(size = 16, hjust = 0.5))

walleye_spawn_timing = data.frame(ice_off_anomaly = c(early.anomaly, 0, late.anomaly), 
                                  spawn = c(as.Date(106.0, origin = as.Date("2018-01-01")), 
                                            as.Date(114.0, origin = as.Date("2018-01-01")),
                                            as.Date(125, origin = as.Date("2018-01-01"))))

# Make function to label plot
add_annotation <- function(group, ice_off) {
  subset = peaks %>% filter(name == group) %>% filter(ice_off_anomaly == ice_off)
  spawn_date = walleye_spawn_timing %>% filter(ice_off_anomaly == ice_off)
  
  if(group == "Cyclopoid"){
    text_location = spawn_date$spawn + (yday(subset$JULIANDAY)+1 - yday(spawn_date$spawn))/2
  }
  else{
    text_location = subset$JULIANDAY - 9
  }
  
  # the function will return the last object it creates, ie this list with two objects
  list(            
    annotate("segment", color = "black",
      x = subset$JULIANDAY, 
      xend = spawn_date$spawn,
      y = subset$count, yend = subset$count),

    annotate("text", color = "black", size = 5,
      x = text_location, y = subset$count + 1.5,
      label = paste(yday(subset$JULIANDAY) - yday(spawn_date$spawn), "days")
  )
  )
}

FIGURE 4 - Recreating Feiner et al. 2022 figure

# Plot phenology when ice-off is early
early.anomaly.plot <- feiner.df %>%
  filter(ice_off_anomaly == early.anomaly) %>%
  ggplot() + 
  # Plot where ice-off and spawning occur
  geom_vline(aes(xintercept = as.Date(green_mean_ice_off + early.anomaly, origin = as.Date("2018-01-01")), 
                 color = "Ice-Off"), linewidth = 1.5) +
  geom_vline(aes(xintercept = walleye_spawn_timing[1,2],
                 color = "Walleye Spawning"), linewidth = 1) + 
  # Plot where daphnia and cyclopoid peaks occur and label them
  geom_segment(data = peaks %>% filter(name == "Cyclopoid" & ice_off_anomaly == early.anomaly),
             aes(x = JULIANDAY, y = count,
                 xend = JULIANDAY, yend = -Inf), 
             color = "#A52A2A") +
  geom_segment(data = peaks %>% filter(name == "Daphnia" & ice_off_anomaly == early.anomaly),
             aes(x = JULIANDAY, y = count,
                 xend = JULIANDAY, yend = -Inf), 
             color = "#00FF00") +
  add_annotation(group = "Cyclopoid", ice_off = early.anomaly) +
  add_annotation(group = "Daphnia", ice_off = early.anomaly) +
  # Change value with how much Walleye spawning gets earlier
  geom_ribbon(aes(x = JULIANDAY, ymax = count, fill = name), ymin = 0, alpha = 0.3) +
  coord_cartesian(xlim = as.Date(c("2018-03-31", "2018-10-15")), 
                  ylim = c(0, 40)) +
  scale_y_continuous(expand = c(0,0)) +
  labs(title = "Early Ice-Off", 
       y = "Indivduals/L",
       x = "Month", fill = "", color = "") + 
  scale_color_manual(guide = "legend", 
                       breaks = c("Ice-Off", "Walleye Spawning"),
                       values = c("Ice-Off" = "Grey", "Walleye Spawning" = "Black")) +
  scale_fill_manual(guide = "legend", breaks = c("Cyclopoid", "Daphnia"), 
                    values = c("Cyclopoid" = "#A52A2A", "Daphnia" = "green")) +
  scale_x_date(breaks = "1 month", date_labels = "%b") +
  personal_theme + theme(legend.position = "none")
  
early.anomaly.plot

# Plot phenology when ice-off is Normal
average.anomaly.plot = feiner.df %>%
  filter(ice_off_anomaly == 0) %>%
  ggplot() + 
  geom_vline(aes(xintercept = as.Date(green_mean_ice_off, origin = as.Date("2018-01-01")), 
                 color = "Ice-Off"), linewidth = 1.5) +
  geom_vline(aes( xintercept = walleye_spawn_timing[2,2], 
                  color = "Walleye Spawning"), 
             linewidth = 1) + 
  # Plot where daphnia and cyclopoid peaks occur and label them
  geom_segment(data = peaks %>% filter(name == "Cyclopoid" & ice_off_anomaly == 0),
             aes(x = JULIANDAY, y = count,
                 xend = JULIANDAY, yend = -Inf), 
             color = "#A52A2A") +
  geom_segment(data = peaks %>% filter(name == "Daphnia" & ice_off_anomaly == 0),
             aes(x = JULIANDAY, y = count,
                 xend = JULIANDAY, yend = -Inf), 
             color = "#00FF00") +
  add_annotation(group = "Cyclopoid", ice_off = 0) +
  add_annotation(group = "Daphnia", ice_off = 0) +
  # Change value with how much Walleye spawning gets earlier
  geom_ribbon(aes(x = JULIANDAY, ymax = count, fill = name),
              ymin = 0, alpha = 0.3) + 
  coord_cartesian(xlim = as.Date(c("2018-03-31", "2018-10-15")), ylim = c(0, 40)) + 
  scale_y_continuous(expand = c(0,0)) +
  labs(title = "Average Ice-Off", 
    y = "Indivduals/L",
    x = "Month", fill = "", color = "") + 
  scale_color_manual(guide = "legend", 
                       breaks = c("Ice-Off", "Walleye Spawning"),
                       values = c("Ice-Off" = "Grey", "Walleye Spawning" = "Black")) +
  scale_fill_manual(guide = "legend", breaks = c("Cyclopoid", "Daphnia"), 
                    values = c("Cyclopoid" = "#A52A2A", "Daphnia" = "green")) +
  scale_x_date(breaks = "1 month", date_labels = "%b") +
  personal_theme + theme(legend.position = c(.8, .7))

average.anomaly.plot

# Plot phenology when ice-off is late
late.anomaly.plot = feiner.df %>%
  filter(ice_off_anomaly == late.anomaly) %>%
  ggplot() + 
  geom_vline(aes(xintercept =  as.Date(green_mean_ice_off + late.anomaly, origin = as.Date("2018-01-01")), 
                 color = "Ice-Off"), linewidth = 1.5) +
  geom_vline(aes(xintercept = walleye_spawn_timing[3,2], 
                 color = "Walleye Spawning"), linewidth = 1) + 
  # Plot where daphnia and cyclopoid peaks occur and label them
  geom_segment(data = peaks %>% filter(name == "Cyclopoid" & ice_off_anomaly == late.anomaly),
             aes(x = JULIANDAY, y = count,
                 xend = JULIANDAY, yend = -Inf), 
             color = "#A52A2A") +
  geom_segment(data = peaks %>% filter(name == "Daphnia" & ice_off_anomaly == late.anomaly),
             aes(x = JULIANDAY, y = count,
                 xend = JULIANDAY, yend = -Inf), 
             color = "#00FF00") +
  add_annotation(group = "Cyclopoid", ice_off = late.anomaly) +
  add_annotation(group = "Daphnia", ice_off = late.anomaly) +
  # Change value with how much Walleye spawning gets earlier
  geom_ribbon(aes(x = JULIANDAY, ymax = count, fill = name), 
              ymin = 0, alpha = 0.3) + 
  coord_cartesian(xlim = as.Date(c("2018-03-31", "2018-10-15")), ylim = c(0, 40)) + 
  scale_y_continuous(expand = c(0,0)) +
  labs(title = "Late Ice-Off",
    y = "Indivduals/L",
    x = "Month", fill = "", color = "") + 
  scale_color_manual(guide = "legend", 
                       breaks = c("Ice-Off", "Walleye Spawning"),
                       values = c("Ice-Off" = "Grey", "Walleye Spawning" = "Black")) +
  scale_fill_manual(guide = "legend", breaks = c("Cyclopoid", "Daphnia"), 
                    values = c("Cyclopoid" = "Brown", "Daphnia" = "green")) +
  scale_x_date(breaks = "1 month", date_labels = "%b") + 
  personal_theme + theme(legend.position = "none") 
late.anomaly.plot

cowplot::plot_grid(early.anomaly.plot, average.anomaly.plot, late.anomaly.plot, 
                   nrow = 3, ncol = 1, labels = "AUTO")

# Figure 4
ggsave("./figures/Figure4.jpeg", height = 10, width = 10)

Figure S5 - Bayesian peak timing - SUBSET - rate of change

subset.labels = c("Bosmina", "Calanoid", "Ceriodaphnia", "Chrysophyte", "Chydorus", 
                  "Cryptomonad", "Cyanobacteria", "Cyclopoid","Daphnia", "Diaphanosoma", 
                  "Diatom", "Dinoflagellate","Green-algae", "Nauplii", "Rotifera", 
                  "Walleye \n Peak Spawning")


all_rate_of_change <- timing.cis.plot.all %>%
  filter(grepl("Difference", timing)) %>%
  filter(!group %in% c("ZOOP.THOUS.M3", "ALL.CELLS")) %>%
  mutate(jit = ifelse(timing == "Early", -0.15, 0.15),
         jit = ifelse(timing == "Average", 0, jit),
         jit = ifelse(timing == "Early Difference", -0.15, jit),
         numeric = as.numeric(factor(group)),
         numeric = ifelse(group == "ROTIF.THOUS.M3", 15, numeric),
         numeric = ifelse(group == "peak", 16, numeric)) %>%
  mutate(timing = ifelse(timing == "Early Difference", 
                         "Early ice-off", 
                         "Late ice-off")) %>%
  mutate(median = ifelse(timing == "Early ice-off", 
                         median/(-1*early.anomaly), median/late.anomaly),
         upper.95 = ifelse(timing == "Early ice-off",
                           upper.95/(-1*early.anomaly), upper.95/late.anomaly),
         lower.95 = ifelse(timing == "Early ice-off",
                           lower.95/(-1*early.anomaly), lower.95/late.anomaly),
         upper.75 = ifelse(timing == "Early ice-off",
                           upper.75/(-1*early.anomaly), upper.75/late.anomaly),
         lower.75 = ifelse(timing == "Early ice-off",
                           lower.75/(-1*early.anomaly), lower.75/late.anomaly)) %>%
  ggplot() + 
  geom_point(aes(x = median, y = -1*(numeric + jit),
                 color = timing), size = 2) + 
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = lower.95, 
                   xend = upper.95, color = timing)) +
  geom_segment(aes(y = -1*(numeric + jit), yend = -1*(numeric + jit), 
                   x = lower.75, xend = upper.75, color = timing), linewidth = 1.5) +
  geom_vline(xintercept = 0, color = "black", linewidth = 1) +
  geom_vline(xintercept = -1, color = "grey", linewidth = .75) +
  geom_vline(xintercept = 1, color = "grey", linewidth = .75) +
  theme_classic() +
  labs(y = "",
       x = "Change in Days Event Occurs Per\n Day Change in Ice-Off", 
       color = "Ice-off Anomaly") +
  theme(panel.grid.major.x = element_line(),
        panel.grid.minor.x = element_line(),
        strip.text.y.left = element_text(angle = 0, size = 12, face = 2),
        plot.title = element_text(angle = 0, size = 14, face = 2),
        strip.placement = "outside",
        strip.background = element_blank(),
        axis.text.y = element_text(size = 14),
        axis.text.x = element_text(size = 14),
        axis.title.x = element_text(size = 16),
        legend.title = element_text(size = 14), 
        legend.text = element_text(size = 12)
        ) +
  scale_color_manual(values = c("#FF0000", "#0000FF")) +
  scale_y_continuous(breaks = -1:-16, labels = subset.labels) + theme(legend.position="none")

all_rate_of_change

# Figure S5
ggsave("./figures/FigureS5.jpeg", height = 8, width = 9)

See if zooplankton bloom size changes with ice-anomaly

Figure S7

species.labels <- data.frame(variable = names(models), 
  axis.label = c("Cyclopoid", "Calanoid", 
                 "Nauplii", "Rotifer", "Daphnia", 
                 "Bosmina",  "Chydoridae", "Ceriodaphnia", 
                 "Diaphanosoma"))


bloom.size <- timing.cis.plot.all %>% 
  filter(!timing %in% c("Early Difference", "Late Difference")) %>% 
  # filter out relative changes in timing
  filter(!group %in% c("peak")) %>% # filter out peak walleye spawning, not relevant
  filter(!group %in% c("CHRYSOS", "CRYPTOS", "CYANOS", "DIATOM", "DINOS", "GREEN")) %>%
  mutate(.row = row_number(), 
         ice_off_anomaly = ifelse(timing == "Early", early.anomaly, 0), 
         ice_off_anomaly = ifelse(timing == "Late",  late.anomaly, ice_off_anomaly))

bloom.size.plot <- data.frame(group = character(0), ice_off_anomaly = numeric(0),
                     median = numeric(0), 
                     q2.5 = numeric(0), q97.5 = numeric(0),
                     q25 = numeric(0), q75 = numeric(0))

groups = unique(bloom.size$group)

for (i in 1:length(groups)) {
  model = models[[groups[i]]]
  
  group.df <- bloom.size %>% filter(group == groups[i])
  
  pred.df <- data.frame(JULIANDAY = 
                          c(
                          seq(group.df$median[1] - 3, group.df$median[1] + 3, by = 1 ),
                          seq(group.df$median[2] - 3, group.df$median[2] + 3, by = 1 ),
                          seq(group.df$median[3] - 3, group.df$median[3] + 3, by = 1 )), 
                        ice_off_anomaly = c(rep(group.df$ice_off_anomaly[1], 7),
                                            rep(group.df$ice_off_anomaly[2], 7),
                                            rep(group.df$ice_off_anomaly[3], 7)),
                        DNRID = "82016700", year_f = "2000",
                        TOW = 6) %>% 
    mutate(.row = row_number()) 
  
  sims <- fitted_samples(model, exclude = c('s(DNRID)',  's(year_f)'),
                         data = pred.df, n = 1000, seed = 1024) |>
    left_join(pred.df |> select(.row, ice_off_anomaly, JULIANDAY), 
              by = join_by(.row == .row))
  
  
  early.diff <- ((sims[sims$ice_off_anomaly == early.anomaly,]$.fitted - 
    sims[sims$ice_off_anomaly == 0,]$.fitted) / 
    sims[sims$ice_off_anomaly == 0,]$.fitted) * 100
  late.diff <- ((sims[sims$ice_off_anomaly == late.anomaly,]$.fitted - 
    sims[sims$ice_off_anomaly == 0,]$.fitted) / 
      sims[sims$ice_off_anomaly == 0,]$.fitted) * 100
  
  temp_toadd <- data.frame(group = groups[i], 
                     ice_off_anomaly = c("Early", "Late"),
                     median = c(median(early.diff), median(late.diff)),
                     q2.5 = c(quantile(early.diff, prob = c(0.025)),
                              quantile(late.diff, prob = c(0.025))),
                     q97.5 = c(quantile(early.diff, prob = c(0.975)),
                              quantile(late.diff, prob = c(0.975))),
                     q25 = c(quantile(early.diff, prob = c(0.75)),
                              quantile(late.diff, prob = c(0.75))),
                     q75 = c(quantile(early.diff, prob = c(0.25)),
                              quantile(late.diff, prob = c(0.25))))
  bloom.size.plot <- rbind(bloom.size.plot, temp_toadd)
  
}

bloom.size.plot %>%
  merge(species.labels, by.y = "variable", by.x = "group") %>%
  ggplot() + 
  geom_point(aes(y = median, x = ice_off_anomaly, color = ice_off_anomaly)) + 
  geom_segment(aes(y = q2.5, yend = q97.5, x = ice_off_anomaly, 
                   xend = ice_off_anomaly, color = ice_off_anomaly)) + 
  geom_segment(aes(y = q25, yend = q75, x = ice_off_anomaly, 
                   xend = ice_off_anomaly, color = ice_off_anomaly), 
               linewidth = 1.5) +
  geom_hline(yintercept = 0, linetype = 'dotted') +
  facet_wrap(vars(axis.label), scales = "free") + 
  scale_color_manual(values = c("#FF0000", "#0000FF")) + 
  labs(y = "% Change in Abundance From Normal Ice-Off",
       x = "Ice-Off Timing", color = "Ice-Off Timing") +
  theme_classic() +
  theme(legend.position = c(.45, .935)) 

# Figure S7
ggsave("./figures/FigureS7.jpeg", height = 8, width = 9)

GRAVEYARD

NOT USED IN PAPER

DOY change function - BAYESIAN

plot.zoop.doy.bayes = function(model = NA, var.name = NA, type = "plot", slice = 1, y.label = NA, 
                         early.anomaly = -17.52, late.anomaly = -14, start.date = 90, end.date = 280) {
  if (is.na(model)) {
    model <- models[[var.name]]
  }
  # Year and DNRID aren't used but are required for predict.gam
  length <- length(seq(start.date, end.date, by = 1))
  pred.df <- data.frame(ice_off_anomaly_abs = 
                          c(rep(early.anomaly, length), rep(0, length), rep(late.anomaly, length)),
                        ice_off_anomaly = 
                          c(rep(early.anomaly, length), rep(0, length), rep(late.anomaly, length)),
                        JULIANDAY = seq(start.date, end.date, by = 1), 
                        days_since_ice = seq(1, length),
                        towdepth = 10,
                        DNRID = "62001200", year_f = "2000") %>%
    mutate(.row = row_number()) %>% 
    mutate(
           ice_off_f =  ifelse(ice_off_anomaly == early.anomaly, "Early", "Normal"),
           ice_off_f =  ifelse(ice_off_anomaly == late.anomaly, "Late", ice_off_f))

  
  
  
  fv <- fitted_values(model, data = pred.df, exclude = c('s(DNRID)',  's(year_f)'))
  fs <- fitted_samples(model, data = pred.df, n = 20, seed = 1024,
                       exclude = c('s(DNRID)',  's(year_f)')) |>
    left_join(pred.df |> select(.row, ice_off_anomaly, JULIANDAY), 
              by = join_by(.row == .row)) %>%
    drop_na()
  fit <- predict.gam(model, pred.df,
                     unconditional = T, # Gives simultaneous
                     type = "response",
                     exclude = c('s(DNRID)', 
                                's(year_f)'),
                     se.fit = T)
  pred.df$predictions = fit$fit
  pred.df$se = fit$se.fit
  
  if (type == "plot") {
    y.label <- species.labels %>% filter(variable == var.name) %>% 
        select(axis.label) %>% as.character()
    plot <- ggplot() +
      #geom_line(data = fs, 
      #          aes(group = draw, 
      #              x = as.Date(JULIANDAY, origin = as.Date("2018-01-01")),
      #              y = fitted, colour = ice_off_f), alpha = 0.4) +
      geom_line(data = pred.df, aes(y = predictions, 
                    x = as.Date(JULIANDAY, origin = as.Date("2018-01-01")), 
                    color = ice_off_f), lwd = 2) +
      geom_ribbon(data = fv, 
                  aes(x = as.Date(JULIANDAY, origin = as.Date("2018-01-01")),
                      y = .fitted, ymin = lower, ymax = upper, 
                      fill = ice_off_f), alpha = 0.3) +
      theme_classic() +
      scale_x_date(date_breaks = "months" , date_labels = "%b") + 
      labs(y = paste0(y.label), x = "Julian Day", 
           color = "Ice-Off", fill = "Ice-Off") +
      theme(axis.text = element_text(size = 14),
        axis.title = element_text(size = 18),
        legend.title = element_text(size = 16), 
        legend.text = element_text(size = 16))
    
    return(plot)
  } # End plot if statement
  
  if (type == "max.date") {
    max.date <- pred.df %>% group_by(ice_off_anomaly) %>% slice_max(predictions, n = slice) %>% 
      mutate(zoop = var.name)
    max.date$predictions <- predict(model, newdata = max.date, 
                                    exclude = c("s(DNRID)", "s(year_f)"), 
                                    type = "response")
    max.date$se <- predict(model, newdata = max.date, exclude = c("s(DNRID)", "s(year_f)"), 
                           type = "response", se.fit = T)$se.fit
    
    return(max.date)
  } # End prediction if statement
}

Plot DOY change - CIs

hist(temp$JULIANDAY) # Approx 125-275 for peak sampling, look at 100 - 300
min(temp$JULIANDAY)

# Thoughts to do - Change x-axis to Months
species.labels <- data.frame(variable = names(models), 
           axis.label = c("Zooplankton Density","Cyclopoid Density", "Calanoid Density", 
                          "Nauplii Density", "Rotifer Density", 
                          "Daphnia Density", "Bosmina Density", 
                          "Chydoridae Density", "Ceriodaphnia Density", 
                          "Diaphanosoma Density"
                          ))


cyclo.doy <- plot.zoop.doy.bayes(var.name = "CYCLO.THOUS.M3") + theme(legend.position="none")
#cyclo.doy 
#ggsave("./figures/cyclo_doy.jpeg", height = 10, width = 10)
cala.doy <- plot.zoop.doy.bayes(var.name = "CALA.THOUS.M3") + theme(legend.position="none")
naup.doy <- plot.zoop.doy.bayes(var.name = "NAUP.THOUS.M3") + theme(legend.position="none")
rotif.doy <- plot.zoop.doy.bayes(var.name = "ROTIF.THOUS.M3") + theme(legend.position="none")
daph.doy <- plot.zoop.doy.bayes(var.name = "DAPH.THOUS.M3") + theme(legend.position="none")
bosm.doy <- plot.zoop.doy.bayes(var.name = "BOSM.THOUS.M3") + theme(legend.position="none")
ceriod.doy <- plot.zoop.doy.bayes(var.name = "CERIOD.THOUS.M3") + theme(legend.position="none")
diaphan.doy <- plot.zoop.doy.bayes(var.name = "DIAPHAN.THOUS.M3") + 
  theme(legend.position = c(0.25, 0.75))
zoop.doy <- plot.zoop.doy.bayes(var.name = "ZOOP.THOUS.M3") + theme(legend.position="none")


zoop <- cowplot::plot_grid(cyclo.doy, cala.doy, naup.doy, rotif.doy, daph.doy, 
                   bosm.doy, ceriod.doy, diaphan.doy, zoop.doy)

title <- ggdraw() + 
  draw_label(
    "Effects of Anomalous Ice-Off on Zooplankton Densities Over the Year",
    fontface = 'bold',
    size = 23,
    x = 0,
    hjust = 0
  ) +
  theme(
    # add margin on the left of the drawing canvas,
    # so title is aligned with left edge of first plot
    plot.margin = margin(0, 0, 0, 7)
  )
plot_grid(
  #title, 
  zoop,
  ncol = 1,
  # rel_heights values control vertical title margins
  rel_heights = c(0.1, 1)
)

#ggsave("./figures/all_zoop_doy.bayes.jpeg", height = 10, width = 12)

Plot Anomaly results

plot.zoop.anomaly = function(var.name){
  
  model <- models[[var.name]]
  
  sm <- smooth_estimates(model)
  sm <- sm %>%
    add_constant(coef(model)["(Intercept)"]) %>% 
    add_confint() %>%
    transform_fun(inv_link(model)) %>% 
    mutate(lower_ci = exp(lower_ci),
           upper_ci = exp(upper_ci))
  
  temp_residuals <- temp %>% add_partial_residuals(model) %>%
    add_constant(coef(model)["(Intercept)"], column = 26) %>%
    transform_fun(inv_link(model), column = 26)
  
  # Creat upper limit for ggplot (1 or 2 really large residuals)
  upper.limit <- quantile(temp_residuals$`s(ice_off_anomaly)`, 0.975)
  
  fig <- sm %>% filter(smooth == "s(ice_off_anomaly)" ) %>%
    ggplot(aes(x = ice_off_anomaly, y = est)) +
    geom_point(aes(x = ice_off_anomaly, y = `s(ice_off_anomaly)`), 
               alpha = 0.1,
               data = temp_residuals) +
    geom_line(lwd = 1.5) +
    geom_ribbon(aes(ymin = lower_ci , ymax = upper_ci), alpha = 0.3) +
    ylim(0, upper.limit) +
    theme_classic() +
    labs(y = var.name, x = "Ice-Off Anomaly") +
    theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 16))
  
  return(fig)
}

cyclo <- plot.zoop.anomaly("CYCLO.THOUS.M3")
cyclo
cala <- plot.zoop.anomaly("CALA.THOUS.M3")
cala
naup <- plot.zoop.anomaly("NAUP.THOUS.M3")
naup
rotif <- plot.zoop.anomaly("ROTIF.THOUS.M3")
rotif
daph <- plot.zoop.anomaly("DAPH.THOUS.M3")
daph
bosm <- plot.zoop.anomaly("BOSM.THOUS.M3")
ceriod <- plot.zoop.anomaly("CERIOD.THOUS.M3")
diaphan <- plot.zoop.anomaly("DIAPHAN.THOUS.M3")
lepto <- plot.zoop.anomaly("LEPTO.THOUS.M3")

cowplot::plot_grid(cyclo, cala, naup, rotif, daph, 
                   bosm, ceriod, diaphan, 
                   labels = "AUTO")
#ggsave("./figures/all_zoop.jpeg", height = 10, width = 15)

Can I do it in one model

long <- temp %>% 
  pivot_longer(cols = CYCLO.THOUS.M3:DIAPHAN.THOUS.M3,
               values_to = "values",
               names_to = "taxa") %>%
  mutate(taxa = as.factor(taxa)
         )

zoop.gam <- gam(values ~ 
                   te(JULIANDAY, ice_off_anomaly, m = 2, bs = "tp") + 
                   t2(JULIANDAY, ice_off_anomaly, taxa, 
                      m = 2, bs = c("tp", "tp", "re")) +
                   s(taxa, DNRID, bs = "re") + s(taxa, year_f, bs = "re"), 
                 method = "fREML", select = T,
                 data = long, family = tw(link = "log"))
summary(zoop.gam)
appraise(zoop.gam)
draw(zoop.gam, select = 2)
#write_rds(zoop.gam, "./models/multispecies.zoop.model.rds")
#zoop.gam <- readRDS("./models/multispecies.zoop.model.rds")

Alternative plotting

groups <- unique(long$taxa)
n = length(groups)
pred.df <- data.frame(ice_off_anomaly = 
                        c(rep(early.anomaly, 186*n), rep(0, 186*n), rep(late.anomaly, 186*n)),
                      JULIANDAY = seq(90, 275, by = 1), 
                      taxa = rep(groups, each = 186),
                      DNRID = "62001300", year_f = "2010") 
  
fit <- predict.gam(zoop.gam, pred.df,
                     unconditional = T, # Gives simultaneous
                     type = "response",
                     exclude = c('s(DNRID)', 
                                's(year_f)'),
                     se.fit = T)

pred.df$predictions = fit$fit
pred.df$se = fit$se.fit

pred.df %>%
      mutate(ice_off_anomaly = as.factor(ice_off_anomaly),
             ice_off_descriptor = 
               ifelse(ice_off_anomaly == early.anomaly, "Early", "Normal"),
             ice_off_descriptor = 
               ifelse(ice_off_anomaly == late.anomaly, "Late", ice_off_descriptor)) %>%
      ggplot(aes(y = predictions, x = as.Date(JULIANDAY, origin = as.Date("2018-01-01")), 
                 color = ice_off_anomaly)) +
      #geom_point() +
      geom_line(aes(y = predictions, x = as.Date(JULIANDAY, origin = as.Date("2018-01-01")), 
                    color = ice_off_descriptor), lwd = 2) +
      #geom_ribbon(aes(ymin = predictions - se, ymax = predictions + se, 
      #                fill = ice_off_descriptor, color = ice_off_descriptor), 
      #            lwd = 0.5, alpha = 0.05) +
      facet_wrap(vars(taxa), scales = "free") +
      theme_classic() +
      scale_x_date(date_breaks = "months" , date_labels = "%b") + 
      labs(y = paste0("Zooplankton density"), x = "Julian Day", 
           color = "Ice-Off", fill = "Ice-Off") +
      theme(axis.text = element_text(size = 14),
        axis.title = element_text(size = 18),
        legend.title = element_text(size = 16), 
        legend.text = element_text(size = 16))
ggsave("./multispecies.jpeg", width = 10, height = 8)