6  ModelTemp: EcoD, simple

Purpose: fit hierarchical stream temp model to all EcoDrought data. Estimate random betas independently, then inspect correlations with catchment covariates post-hoc.

This model uses air temperature and flow data standardized by site. But does this make sense? Especially for flow, as spatial variation in streamflow (among streams) may underlie important spatial variation in temperature

Notes:

6.1 Load data

Code
dat <- read_csv("data/EcoDrought_FlowTempData_formatted.csv") #%>% 
  # filter(!is.na(tempc_mean)) %>%
  # mutate(rowNum = 1:nrow(.),
  #        # site_code = as.numeric(as.factor(site_name)),
  #        year_code = year - min(year) + 1)

# redo basin and site codes
# dat <- dat %>% arrange(basin, site_name, year, yday)
# sitecodes <- tibble(site_name = unique(dat$site_name), site_code = 1:length(unique(dat$site_name)))
# basincodes <- tibble(basin = unique(dat$basin), basin_code = 1:length(unique(dat$basin)))

# dat <- dat %>%
#   select(-c(site_code, basin_code)) %>% 
#   left_join(sitecodes) %>%
#   # left_join(basincodes) %>%
#   mutate(rowNum = 1:nrow(.))

dat
# A tibble: 56,051 × 41
   site_name basin   lat  long elev_ft area_sqmi date        yday  year siteYear
   <chr>     <chr> <dbl> <dbl>   <dbl>     <dbl> <date>     <dbl> <dbl> <chr>   
 1 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-19    19  2011 Donner …
 2 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-20    20  2011 Donner …
 3 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-21    21  2011 Donner …
 4 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-22    22  2011 Donner …
 5 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-23    23  2011 Donner …
 6 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-24    24  2011 Donner …
 7 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-25    25  2011 Donner …
 8 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-26    26  2011 Donner …
 9 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-27    27  2011 Donner …
10 Donner B… Donn…  42.8 -119.   4263.      205. 2011-01-28    28  2011 Donner …
# ℹ 56,041 more rows
# ℹ 31 more variables: tempc_mean <dbl>, tempc_min <dbl>, tempc_max <dbl>,
#   flow_mean <dbl>, Yield_mm <dbl>, air_temp_mean <dbl>, precip_mmday <dbl>,
#   swe_kgm2 <dbl>, daylength_sec <dbl>, shortrad_wm2 <dbl>,
#   Yield_mm_log <dbl>, flow_mean_log <dbl>, z_Yield_mm_log <dbl>,
#   z_air_temp_mean <dbl>, air_temp_mean_lag1 <dbl>, air_temp_mean_lag2 <dbl>,
#   z_air_temp_mean_lag1 <dbl>, z_air_temp_mean_lag2 <dbl>, …

Drop sites without any flow or water temp observations and re-do site and year codes

Code
# dropsites <- unlist(dat %>% 
#   group_by(site_name) %>% 
#   summarize(numt = sum(!is.na(tempc_mean)), 
#             numf = sum(!is.na(z_Yield_mm_log))) %>% 
#   ungroup() %>%
#   filter(numt == 0 | numf == 0) %>%
#   select(site_name))
# 
# dat <- dat %>% 
#   filter(!site_name %in% dropsites) %>%
#   mutate(rowNum = 1:nrow(.),
#          site_code = as.numeric(as.factor(site_name)),
#          year_code = year - min(year) + 1)

View basins and sites

Code
DT::datatable(dat %>% group_by(basin, site_name, site_code) %>% summarise(n = n()) )

How much missing data? Need to put a prior on flow

Code
sum(is.na(dat$z_air_temp_mean))
[1] 0
Code
sum(is.na(dat$z_air_temp_mean_lag1))
[1] 0
Code
sum(is.na(dat$z_air_temp_mean_lag2))
[1] 0
Code
sum(is.na(dat$z_Yield_mm_log))
[1] 4290

Set covariate NAs to 0. Probably should change this to latent variable in model, especially when expanding to sites where flow data is more rare

Code
# dat <- dat %>% mutate(z_air_temp_mean = ifelse(is.na(z_air_temp_mean), 0, z_air_temp_mean),
#                       z_air_temp_mean_lag1 = ifelse(is.na(z_air_temp_mean_lag1), 0, z_air_temp_mean_lag1),
#                       z_air_temp_mean_lag2 = ifelse(is.na(z_air_temp_mean_lag2), 0, z_air_temp_mean_lag2),
#                       z_Yield_mm_log = ifelse(is.na(z_Yield_mm_log), 0, z_Yield_mm_log))

View data

Code
ggpubr::ggarrange(dat %>% ggplot(aes(x = air_temp_mean, color = site_name)) + geom_density() + theme_bw(),
                  dat %>% ggplot(aes(x = flow_mean_log, color = site_name)) + geom_density() + theme_bw(),
                  dat %>% ggplot(aes(x = Yield_mm_log, color = site_name)) + geom_density() + theme_bw(),
                  common.legend = TRUE, legend = "right", ncol = 1)
Code
ggplot(dat, aes(yday, z_air_temp_mean))+
  geom_line(aes(color = factor(year))) +
  facet_grid(year ~ site_name)
Code
ggplot(dat, aes(yday, tempc_mean))+
  geom_line(aes(color = factor(year)))+
  facet_grid(year ~ site_name)
Code
ggplot(dat, aes(yday, z_Yield_mm_log))+
  geom_line(aes(color = factor(year)))+
  facet_grid(year ~ site_name)
Code
dat %>% ggplot(aes(x = z_air_temp_mean, y = tempc_mean, color = z_Yield_mm_log)) + geom_point(size = 0.2) + facet_wrap(~site_name) + theme_bw()
Code
dat %>% ggplot(aes(x = z_air_temp_mean, y = z_Yield_mm_log, colour = tempc_mean)) + geom_point(size = 0.2) + facet_wrap(~site_name) + theme_bw() + ggpubr::stat_cor()
Code
dat %>% ggplot(aes(x = z_yday, y = z_air_temp_mean, colour = tempc_mean)) + geom_point(size = 0.2) + facet_wrap(~site_name) + theme_bw() + ggpubr::stat_cor()
Code
dat %>% filter(z_Yield_mm_log != 0) %>% ggplot(aes(x = z_yday, y = z_Yield_mm_log, colour = tempc_mean)) + geom_point(size = 0.2) + facet_wrap(~site_name) + theme_bw() + ggpubr::stat_cor()
Code
ggpairs(dat %>% select(yday, z_air_temp_mean, z_Yield_mm_log))

6.2 Specify JAGS model

Specify hierarchical model following Letcher et al. (2016). MODIFIED:

  • add prior on covariates to deal with missing values
  • calculate Bayesian R^2 and RMSE
  • remove the year trends (same as for Hocking et al 2018)
  • add catchment covariate effects on random effects parameters
Code
cat("model {

    ###----------------- LIKELIHOOD -----------------###
    
    # Days without an observation on the previous day (first observation in a series)
    # No autoregressive term
    
    for (i in 1:nFirstObsRows){
      temp[firstObsRows[i]] ~ dnorm(stream.mu[firstObsRows[i]], pow(sigma, -2)) 
      stream.mu[firstObsRows[i]] <- trend[firstObsRows[i]]
      trend[firstObsRows[i]] <- #inprod(B.0[], X.0[firstObsRows[i], ]) + 
                                inprod(B.site[site[firstObsRows[i]], ], X.site[firstObsRows[i], ]) #+ 
                                #inprod(B.year[year[firstObsRows[i]], ], X.year[firstObsRows[i], ])
      }
    
    # Days with an observation on the previous dat (all days following the first day)
    # Includes autoregressive term (ar1)
    
    for (i in 1:nEvalRows){ 
      temp[evalRows[i]] ~ dnorm(stream.mu[evalRows[i]], pow(sigma, -2))
      stream.mu[evalRows[i]] <- trend[evalRows[i]] + ar1[site[evalRows[i]]] * (temp[evalRows[i]-1] - trend[ evalRows[i]-1 ])
      trend[evalRows[i]] <- #inprod(B.0[], X.0[evalRows[i], ]) + 
                            inprod(B.site[site[evalRows[i]], ], X.site[evalRows[i], ]) #+ 
                            #inprod(B.year[year[evalRows[i]], ], X.year[evalRows[i], ])
      }
    
    
    # Deal with missing data
    for (i in 1:n){
      for (k in 1:Krandom) {
        X.site[i,k] ~ dnorm(0, pow(1, -2))
      }
    }
    
    # Save log-likelihoods for LOO-CV
    for (i in 1:n) {
      loglik[i] <- logdensity.norm(temp[i], stream.mu[i], pow(sigma, -2))
    }
    
    ###----------------- PRIORS ---------------------###
    
    # ar1, hierarchical by site
    for (i in 1:nsites){
      ar1[i] ~ dnorm(ar1Mean, pow(ar1SD, -2)) T(-1,1)       
    }
    ar1Mean ~ dunif(-1, 1) 
    ar1SD ~ dunif(0, 2)

    # model variance
    sigma ~ dunif(0, 100)
    
    
    # fixed effect coefficients
    # for (k in 1:Kfixed) {
    #   B.0[k] ~ dnorm(0, 0.001)
    #   }
      
    # random effect coefficients (by site)
    for (k in 1:Krandom) {
      sigma.B.site[k] ~ dunif(0, 100)
      #alpha.0[k] ~ dnorm(0, pow(10, -2))
      #alpha.1[k] ~ dnorm(0, pow(10, -2))
      for (i in 1:nsites) {
        B.site[i,k] ~ dnorm(0, pow(sigma.B.site[k], -2))
        #mu.B.site[i,k] <- alpha.0[k] + alpha.1[k] * area[i]
      }
    }
    
    # YEAR EFFECTS
    # for(l in 1:L) {
    #   sigma.b.year[l] ~ dunif(0, 100)
    #   for(t in 1:Ti){
    #     B.year[t, l] ~ dnorm(0, pow(sigma.b.year[l], -2))
    #     }
    #   }
    
    # # YEAR EFFECTS
    # # Priors for random effects of year
    # for (t in 1:Ti) { # Ti years
    #   B.year[t, 1:L] ~ dmnorm(mu.year[ ], tau.B.year[ , ])
    #   }
    #   
    # mu.year[1] <- 0
    # 
    # for (l in 2:L) {
    #   mu.year[l] ~ dnorm(0, 0.0001)
    #   }
    #   
    # # Prior on multivariate normal std deviation
    # tau.B.year[1:L, 1:L] ~ dwish(W.year[ , ], df.year)
    # df.year <- L + 1
    # sigma.B.year[1:L, 1:L] <- inverse(tau.B.year[ , ])
    # for (l in 1:L) {
    #   for (l.prime in 1:L) {
    #     rho.B.year[l, l.prime] <- sigma.B.year[l, l.prime]/sqrt(sigma.B.year[l, l]*sigma.B.year[l.prime, l.prime])
    #     }
    #   sigma.b.year[l] <- sqrt(sigma.B.year[l, l])
    # }
    
    
    ###----------------- DERIVED VALUES -------------###
    #residuals[1] <- 0 # hold the place. Not sure if this is necessary...
    for (i in 1:n) {
      residuals[i] <- temp[i] - stream.mu[i]
    }
    
    # variance of model predictions (fixed + random effects)
    var_fit <- (sd(stream.mu))^2

    # residual variance
    var_res <- (sd(residuals))^2

    # calculate Bayesian R^2
    R2 <- var_fit / (var_fit + var_res)

    # Root mean squared error
    rmse <- sqrt(mean(residuals[]^2))
    
    }", file = "JAGS models/DailyTempModelJAGS_Letcher_hierarchical_modified.txt")

6.3 Organize objects

Get first observation indices and check that nFirstRowObs equals the number of unique site-years: must be TRUE!

Code
# row indices for first observation in each site-year
firstObsRows <- unlist(dat %>% 
  group_by(siteYear) %>%
  summarize(index = rowNum[min(which(!is.na(tempc_mean)))]) %>%
  ungroup() %>% 
  select(index))
nFirstObsRows <- length(firstObsRows)

# does the number of first observations match the number of site years?
nFirstObsRows == length(unique(dat$siteYear))
[1] TRUE

Get row indices for all other observations

Code
evalRows <- unlist(dat %>% filter(!rowNum %in% firstObsRows) %>% select(rowNum))
nEvalRows <- length(evalRows)

Fixed and random effect data

Code
data.fixed <- data.frame(airTempLag1 = dat$z_air_temp_mean_lag1,
                         airTempLag2 = dat$z_air_temp_mean_lag2) 

data.random <- data.frame(intercept = 1,
                          airTemp = dat$z_air_temp_mean, 
                          flow =  dat$z_Yield_mm_log,
                          airFlow = dat$z_air_temp_mean * dat$z_Yield_mm_log,
                          airTempLag1 = dat$z_air_temp_mean_lag1,
                          airTempLag2 = dat$z_air_temp_mean_lag2
                          # doy = dat$z_yday,
                          # doyFlow = dat$z_yday * dat$z_Yield_mm_log
                          )

data.random.years <- data.frame(intercept.year = rep(1, length.out = dim(data.fixed)[1])#, 
                                #dOY  = dat$yday, 
                                #dOY2 = dat$yday^2,
                                #dOY3 = dat$yday^3
                                )

data.random.covs <- log(dat %>% group_by(site_code) %>% summarize(area_sqmi = unique(area_sqmi)) %>% ungroup() %>% arrange(site_code) %>% pull(area_sqmi))

any(is.na(data.random))
[1] TRUE
Code
any(is.na(data.fixed))
[1] FALSE
Code
any(is.na(data.random.years))
[1] FALSE
Code
any(is.na(data.random.covs))
[1] FALSE

Misc. objects

Code
Ti <- length(unique(dat$year))
L <- dim(data.random.years)[2]
W.year <- diag(L)

Combine data in list

Code
# combine data in a list
jags.data <- list("temp" = dat$tempc_mean,
                  "nFirstObsRows" = nFirstObsRows,
                  "firstObsRows" = firstObsRows,
                  "nEvalRows" = nEvalRows,
                  "evalRows" = evalRows,
                  "X.0" = as.matrix(data.fixed),
                  "X.site" = as.matrix(data.random),
                  "X.year" = as.matrix(data.random.years),
                  "Kfixed" = dim(data.fixed)[2],
                  "Krandom" = dim(data.random)[2],
                  "nsites" = length(unique(dat$site_name)),
                  "Ti" = Ti,
                  "L" = L,
                  "W.year" = W.year,
                  "n" = dim(dat)[1],
                  "year" = dat$year_code,
                  "site" = dat$site_code,
                  "area" = data.random.covs
                  )

Parameters to monitor

Code
jags.params <- c("residuals",
                 "deviance",
                 "sigma",
                 "B.0",
                 "B.site",
                 "B.year",
                 "rho.B.year",
                 "mu.year",
                 "sigma.b.year",
                 "stream.mu",
                 "ar1" ,
                 "ar1Mean",
                 "ar1SD",
                 "temp",
                 "sigma.B.site",
                 "R2",
                 "rmse",
                 "alpha.0",
                 "alpha.1",
                 "loglik"
                 )

6.4 Fit model

~1 hour to run: n.chains = 15, n.thin = 20, n.burnin = 5000, n.iter = 9000

Code
st <- Sys.time()
fit_ed <- jags.parallel(data = jags.data, inits = NULL, parameters.to.save = jags.params, 
                        model.file = "JAGS models/DailyTempModelJAGS_Letcher_hierarchical_modified.txt",
                        n.chains = 15, n.thin = 20, n.burnin = 1000, n.iter = 5000, DIC = TRUE)
Sys.time() - st

6.4.0.1 Save model ouput

Save to file

Code
saveRDS(fit_ed, "Model objects/LetcherTempModel_EcoDrought_hierarchical_fullish.RDS")

Read in fitted model object

Code
fit_ed <- readRDS("Model objects/LetcherTempModel_EcoDrought_hierarchical_fullish.RDS")

Get MCMC samples and summary

Code
top_mod <- fit_ed
# generate MCMC samples and store as an array
modelout <- top_mod$BUGSoutput
McmcList <- vector("list", length = dim(modelout$sims.array)[2])
for(i in 1:length(McmcList)) { McmcList[[i]] = coda::as.mcmc(modelout$sims.array[,i,]) }
# rbind MCMC samples from 10 chains 
Mcmcdat <- rbind(McmcList[[1]], McmcList[[2]], McmcList[[3]], McmcList[[4]], McmcList[[5]], McmcList[[6]], McmcList[[7]], McmcList[[8]], McmcList[[9]], McmcList[[10]], McmcList[[11]], McmcList[[12]], McmcList[[13]], McmcList[[14]], McmcList[[15]])
param.summary <- modelout$summary
head(param.summary)
                 mean        sd      2.5%       25%       50%       75%
B.site[1,1]  6.952124 0.1543487  6.647068  6.848740  6.955053  7.057282
B.site[2,1] 13.837811 0.3256225 13.226042 13.621507 13.827481 14.049675
B.site[3,1] 14.279375 0.2891955 13.697118 14.088255 14.280658 14.475275
B.site[4,1] 12.620043 0.2821574 12.059443 12.435082 12.618156 12.807351
B.site[5,1] 12.595488 0.2852095 12.043208 12.402728 12.595225 12.783190
B.site[6,1]  6.483162 0.4411300  5.657221  6.179213  6.482861  6.785812
                97.5%      Rhat n.eff
B.site[1,1]  7.257362 1.0000398  3000
B.site[2,1] 14.502177 1.0031129  1700
B.site[3,1] 14.837742 1.0016324  2600
B.site[4,1] 13.165442 1.0005697  3000
B.site[5,1] 13.166335 0.9999335  3000
B.site[6,1]  7.327864 1.0003679  3000
Code
rm(fit_ed)

6.4.0.2 Check convergence

Any problematic R-hat values (>1.05)?

Code
top_mod$BUGSoutput$summary[,8][top_mod$BUGSoutput$summary[,8] > 1.05]
loglik[47177] loglik[47178] loglik[47213] 
     1.142570      1.143841      1.094323 

View traceplots

Code
MCMCtrace(top_mod, ind = TRUE, 
          params = c(#"B.0", 
                     "B.site", 
                     # "B.year", 
                     # "ar1", 
                     # "alpha.0", 
                     # "alpha.1",
                     "sigma"), pdf = FALSE)

Convert to ggs object

Code
ggfit <- ggs(coda::as.mcmc(top_mod), keep_original_order = TRUE)
head(ggfit)
# A tibble: 6 × 4
  Iteration Chain Parameter value
      <int> <int> <fct>     <dbl>
1         1     1 ar1[1]    0.991
2         2     1 ar1[1]    0.993
3         3     1 ar1[1]    0.995
4         4     1 ar1[1]    0.996
5         5     1 ar1[1]    0.992
6         6     1 ar1[1]    0.993

6.4.1 LOO-CV

LOO diagnostics indicates that the model fits the data well.

Code
loglik <- top_mod$BUGSoutput$sims.list$loglik # extract the log-likelihood estimates for each MCMC sample
rf <- relative_eff(exp(loglik), chain_id = rep(1:15, each = 200))
my_loo <- loo(loglik, r_eff = rf)
my_loo

Computed from 3000 by 56051 log-likelihood matrix.

         Estimate    SE
elpd_loo -43446.0 409.3
p_loo      1811.2 105.5
looic     86891.9 818.5
------
MCSE of elpd_loo is NA.
MCSE and ESS estimates assume MCMC draws (r_eff in [0.7, 1.2]).

Pareto k diagnostic values:
                         Count Pct.    Min. ESS
(-Inf, 0.7]   (good)     55881 99.7%   58      
   (0.7, 1]   (bad)        107  0.2%   <NA>    
   (1, Inf)   (very bad)    63  0.1%   <NA>    
See help('pareto-k-diagnostic') for details.
Code
plot(my_loo)

Code
# badpts <- which(my_loo$diagnostics$pareto_k > 0.7)

6.4.2 Goodness of fit

Format observed and predicted values

Code
Mcmcdat <- as_tibble(Mcmcdat)

# subset expected and observed MCMC samples
ppdat_exp <- as.matrix(Mcmcdat[,startsWith(names(Mcmcdat), "stream.mu[")])
ppdat_obs <- as.matrix(Mcmcdat[,startsWith(names(Mcmcdat), "temp[")])

Bayesian p-value

Code
sum(ppdat_exp > ppdat_obs) / (dim(ppdat_obs)[1]*dim(ppdat_obs)[2])
[1] 0.4753151

PP-check, global

Code
ppdat_obs_mean <- apply(ppdat_obs, 2, mean)
ppdat_exp_mean <- apply(ppdat_exp, 2, mean)
tibble(obs = ppdat_obs_mean, exp = ppdat_exp_mean) %>% 
  ggplot(aes(x = obs, y = exp)) + 
  geom_point(alpha = 0.1) + 
  # geom_smooth(method = "lm") +
  geom_abline(intercept = 0, slope = 1, color = "red") + 
  theme_bw() + theme(panel.grid = element_blank()) +
  xlab("Observed") + ylab("Predicted (mean)")

PP-check by river and year

Code
tibble(obs = ppdat_obs_mean, exp = ppdat_exp_mean, site = dat$site_name, year = dat$year) %>% 
  ggplot(aes(x = obs, y = exp)) + 
  geom_point(alpha = 0.1) + 
  # geom_smooth(method = "lm") +
  geom_abline(intercept = 0, slope = 1, color = "red") + 
  theme_bw() + theme(panel.grid = element_blank()) +
  xlab("Observed") + ylab("Predicted (mean)") +
  facet_grid(year ~ site)

RMSE

Code
mean(unlist(ggfit %>% filter(Parameter == "rmse") %>% select(value)))
[1] 0.5155691
Code
ggs_density(ggfit, "rmse") + theme_bw() + theme(panel.grid = element_blank())

R-squared

Code
mean(unlist(ggfit %>% filter(Parameter == "R2") %>% select(value)))
[1] 0.9885078
Code
ggs_density(ggfit, "R2") + theme_bw() + theme(panel.grid = element_blank())

Histogram of residuals

Code
ggplot() + 
  geom_histogram(aes(x = c(ppdat_obs_mean - ppdat_exp_mean)), color = "black", fill = "grey") + 
  xlab("Residuals") + theme_bw() + theme(panel.grid = element_blank()) + 
  geom_vline(xintercept = 0, color = "red") + xlim(-4,4)

Residuals by time (day of year): are we missing something important? Like some time dependent variable (e.g., water source to flow ~~ the transition from snowmelt to groundwater).

Code
tibble(site_name = dat$site_name, year = dat$year, yday = dat$yday, resid = c(ppdat_obs_mean - ppdat_exp_mean)) %>%
  ggplot(aes(x = yday, y = resid)) +
  geom_point(alpha = 0.1) +
  geom_hline(yintercept = c(0), color = "red") +
  geom_smooth(se = FALSE, linewidth = 1.5) +
  theme_bw() + #theme(panel.grid = element_blank()) +
  xlab("Day of year") + ylab("Residual") +
  #facet_wrap(~site_name) + 
  coord_cartesian(ylim = c(-3,3)) #+ facet_wrap(~year)

6.5 Plot model output

Code
# myparams <- unique(ggfit$Parameter)
mybasins <- c(dat %>% group_by(site_name) %>% summarize(basin = unique(basin)) %>% arrange(basin) %>% ungroup() %>% pull(basin))
mysites <- c(dat %>% group_by(site_name) %>% summarize(site_code = unique(site_code)) %>% arrange(site_code) %>% ungroup() %>% pull(site_name))

6.5.1 Dot plots

Code
ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",5]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",5]", sep = ""))) %>%
  ggs_caterpillar(sort = FALSE) +
  aes(color = mybasins) +
  scale_y_discrete(labels = rev(mysites), limits = rev) + 
  xlab("Air temp lag 1") +
  theme_bw()

Code
ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",6]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",6]", sep = ""))) %>%
  ggs_caterpillar(sort = FALSE) +
  aes(color = mybasins) +
  scale_y_discrete(labels = rev(mysites), limits = rev) + 
  xlab("Air temp lag 2") +
  theme_bw()

Code
tibble(site_name = mysites,
       lag1_mean = ggfit %>%
         filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",5]", sep = "")) %>%
         mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",5]", sep = ""))) %>%
         group_by(Parameter) %>% summarize(lag1_mean = mean(value)) %>% ungroup() %>% pull(lag1_mean),
       lag2_mean = ggfit %>%
         filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",6]", sep = "")) %>%
         mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",6]", sep = ""))) %>%
         group_by(Parameter) %>% summarize(lag2_mean = mean(value)) %>% ungroup() %>% pull(lag2_mean),
       basin = mybasins) %>%
  ggplot(aes(x = lag1_mean, y = lag2_mean, color = basin)) + geom_point() + theme_bw()

Code
ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",1]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",1]", sep = ""))) %>%
  ggs_caterpillar(sort = FALSE) +
  aes(color = mybasins) +
  scale_y_discrete(labels = rev(mysites), limits = rev) + 
  xlab("Intercepts") +
  theme_bw()

Code
ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",2]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",2]", sep = ""))) %>%
  ggs_caterpillar(sort = FALSE) +
  aes(color = mybasins) +
  scale_y_discrete(labels = rev(mysites), limits = rev) + 
  xlab("Slopes, temperature effect") +
  theme_bw()

Code
ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",3]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",3]", sep = ""))) %>%
  ggs_caterpillar(sort = FALSE) +
  aes(color = mybasins) +
  scale_y_discrete(labels = rev(mysites), limits = rev) + 
  xlab("Slopes, flow effect") +
  theme_bw() + geom_vline(xintercept = 0, linetype = "dashed")

Code
ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",4]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",4]", sep = ""))) %>%
  ggs_caterpillar(sort = FALSE) +
  aes(color = mybasins) +
  scale_y_discrete(labels = rev(mysites), limits = rev) + 
  xlab("Slopes, temp-flow interaction") +
  theme_bw() + geom_vline(xintercept = 0, linetype = "dashed")

Code
ggs_caterpillar(ggfit %>% filter(Parameter %in% paste("ar1[", c(1:jags.data$nsites), "]", sep = "")) %>%
                  mutate(Parameter = factor(Parameter, levels = paste("ar1[", c(1:jags.data$nsites), "]", sep = ""))),
                sort = FALSE) + scale_y_discrete(labels = rev(mysites), limits = rev) + theme_bw() + aes(color = mybasins) + xlab("Autoregressive parameter")

Code
ggfit %>%
  filter(Parameter %in% c("B.year[1,1]", "B.year[2,1]", "B.year[3,1]", "B.year[4,1]", "B.year[5,1]",
                          "B.year[6,1]", "B.year[7,1]", "B.year[8,1]", "B.year[9,1]", "B.year[10,1]", 
                          "B.year[11,1]", "B.year[12,1]", "B.year[13,1]", "B.year[14,1]")) %>%
  mutate(Parameter = factor(Parameter, levels = c("B.year[1,1]", "B.year[2,1]", "B.year[3,1]", "B.year[4,1]", "B.year[5,1]",
                          "B.year[6,1]", "B.year[7,1]", "B.year[8,1]", "B.year[9,1]", "B.year[10,1]", 
                          "B.year[11,1]", "B.year[12,1]", "B.year[13,1]", "B.year[14,1]"))) %>%
  ggs_caterpillar(sort = FALSE) +
  scale_y_discrete(labels = rev(sort(unique(dat$year))), limits = rev) + 
  ylab("Year effects, offsets to intercepts") +
  theme_bw() + geom_vline(xintercept = 0, linetype = "dashed")
Code
ggs_caterpillar(ggfit, family = "alpha.1", sort = FALSE) +
  scale_y_discrete(labels = rev(c("Intercepts", "Temp eff.", "Flow eff.", "Temp-Flow int.")), limits = rev) + 
  ylab("Catchment size effects") +
  theme_bw() + geom_vline(xintercept = 0, linetype = "dashed")

6.5.2 Post-hoc catchment effects

Bring in complete daymet data and summarize catchment covariates

Code
climdf <- read_csv("data/Daymet_daily.csv")

basinvars <- climdf %>% 
  filter(site_name %in% mysites) %>%
  group_by(site_name) %>%
  summarize(swe = mean(swe_kgm2)) %>%
  ungroup() %>%
  left_join(dat %>% group_by(site_name) %>% summarize(basin = unique(basin), area_sqmi = unique(area_sqmi), elev_ft = unique(elev_ft)) %>% ungroup())

# ggpubr::ggarrange(climdf %>% filter(site_name == "BigCreekLower") %>% ggplot(aes(x = date, y = swe_kgm2)) + geom_line(),
#                   climdf %>% filter(site_name == "CoalCreekLower") %>% ggplot(aes(x = date, y = swe_kgm2)) + geom_line(),
#                   climdf %>% filter(site_name == "NicolaCreek") %>% ggplot(aes(x = date, y = swe_kgm2)) + geom_line(),
#                   climdf %>% filter(site_name == "McGeeCreekTrib") %>% ggplot(aes(x = date, y = swe_kgm2)) + geom_line(),
#                   ncol = 1)
# 
# climdf %>% filter(site_name == "Avery Brook", year(date) >= 2020) %>% ggplot(aes(x = date, y = daylength_sec)) + geom_line()

Pairs plot: raw variables

Code
ggpairs(basinvars %>% 
          mutate(logarea = log(area_sqmi)) %>% 
          left_join(dat %>% 
                      mutate(month = month(date)) %>% 
                      filter(month %in% c(8)) %>% 
                      group_by(site_name) %>% 
                      summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
          select(basin, swe, logarea, elev_ft, meanSummYield), 
        mapping = aes(color = basin))

Pairs plot: variables standardized by basin

Code
ggpairs(basinvars %>% 
          mutate(logarea = log(area_sqmi)) %>% 
          left_join(dat %>% 
                      mutate(month = month(date)) %>% 
                      filter(month %in% c(8)) %>% 
                      group_by(site_name) %>% 
                      summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
          group_by(basin) %>%
          mutate(z_logarea = c(scale(logarea)), 
                 z_elev_ft = c(scale(elev_ft)), 
                 z_swe = c(scale(swe)),
                 z_yield = c(scale(meanSummYield))) %>%
          ungroup() %>%
          select(basin, z_swe, z_logarea, z_elev_ft, z_yield), 
        mapping = aes(color = basin))

6.5.2.1 PCA

Use dimension reduction to derive variables that describe multiple correlated aspects of catchments

PCA on raw variables

Run PCA

Code
testdata <- basinvars %>% 
          mutate(logarea = log(area_sqmi)) %>% 
          left_join(dat %>% 
                      mutate(month = month(date)) %>% 
                      filter(month %in% c(8)) %>% 
                      group_by(site_name) %>% 
                      summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
          select(basin, site_name, swe, logarea, elev_ft, meanSummYield)

mypca <- prcomp(testdata[,-c(1:2)], scale. = TRUE)
mypca
Standard deviations (1, .., p=4):
[1] 1.4876752 1.0335962 0.7006974 0.4769952

Rotation (n x k) = (4 x 4):
                     PC1        PC2        PC3          PC4
swe            0.4372554 -0.6807160  0.1107323 -0.577210305
logarea        0.4832652  0.4301354  0.7625040  0.005100505
elev_ft        0.5926503 -0.2701012 -0.2280887  0.723730989
meanSummYield -0.4733161 -0.5278774  0.5952324  0.378174172

Scree plot

Code
# scree plot
fviz_eig(mypca)

Variable contribution

Code
fviz_contrib(mypca, choice = "var", axes = 1, sort.val = "none")

Code
fviz_contrib(mypca, choice = "var", axes = 2, sort.val = "none")

Code
fviz_contrib(mypca, choice = "var", axes = 3, sort.val = "none")

Code
fviz_contrib(mypca, choice = "var", axes = 4, sort.val = "none")

Bi-plot

Code
#bi-plot
fviz_pca_biplot(mypca, repel = TRUE,
                col.var = "black", # Variables color
                col.ind = testdata$basin,  # Individuals color
                addEllipses = TRUE,
                #ellipse.type = "confidence",
                legend.title = "Basins"
                )

PCA on raw variables mostly distinguishes basin and regional groupings: higher elevation, snowmelt-dominated catchments of the Rocky Mountains (catchments which also happen to happen to be larger, by virtue of site selection) versus lower elevation, rain-dominated catchments of the Eastern US (which happen to be smaller).

PCA on std variables

Run PCA: variables standardized by basin

Code
testdata <- basinvars %>% 
          mutate(logarea = log(area_sqmi)) %>% 
          left_join(dat %>% 
                      mutate(month = month(date)) %>% 
                      filter(month %in% c(8)) %>% 
                      group_by(site_name) %>% 
                      summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
          group_by(basin) %>%
          mutate(z_logarea = c(scale(logarea)), 
                 z_elev_ft = c(scale(elev_ft)), 
                 z_swe = c(scale(swe)),
                 z_yield = c(scale(meanSummYield))) %>%
          ungroup() %>%
          select(basin, site_name, z_swe, z_logarea, z_elev_ft, z_yield)

mypca_std <- prcomp(testdata[,-c(1:2)])
mypca_std
Standard deviations (1, .., p=4):
[1] 1.4385124 0.8594391 0.8183971 0.3837612

Rotation (n x k) = (4 x 4):
                 PC1         PC2       PC3         PC4
z_swe     -0.5741024  0.36060317 0.3049379 -0.66886817
z_logarea  0.4150813  0.08309891 0.9005462  0.09908873
z_elev_ft -0.6061326  0.24919145 0.1755520  0.73463487
z_yield   -0.3615484 -0.89496567 0.2553641 -0.05575308

Scree plot

Code
# scree plot
fviz_eig(mypca_std)

Variable contribution

Code
fviz_contrib(mypca_std, choice = "var", axes = 1, sort.val = "none")

Code
fviz_contrib(mypca_std, choice = "var", axes = 2, sort.val = "none")

Code
fviz_contrib(mypca_std, choice = "var", axes = 3, sort.val = "none")

Code
fviz_contrib(mypca_std, choice = "var", axes = 4, sort.val = "none")

Bi-plot

Code
#bi-plot
fviz_pca_biplot(mypca_std, repel = TRUE,
                col.var = "black", # Variables color
                col.ind = testdata$basin  # Individuals color
                )

PCA on variables standardized by basin removes the regional effects and separates sites by more relevant axes of variation. Within basins, sites mostly vary along a ~single axis of small catchment area, high elevation, and snowmelt-dominated …to… large catchment area, low elevation, and rain-dominated (less snow).

6.5.2.2 Intercepts

Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",1]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",1]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites) %>%
  left_join(basinvars) %>% 
  left_join(dat %>% mutate(month = month(date)) %>% filter(month %in% c(8)) %>% group_by(site_name) %>% summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
  group_by(basin) %>%
  mutate(z_area = scale(log(area_sqmi)), 
         z_elev = scale(elev_ft), 
         z_swe = scale(swe),
         z_yield = scale(meanSummYield)) %>%
  ungroup()

summary(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
Linear mixed model fit by REML ['lmerMod']
Formula: mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin)
   Data: mydf

REML criterion at convergence: 278.3

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.69567 -0.67589 -0.07851  0.61859  2.22605 

Random effects:
 Groups   Name           Variance Std.Dev. Corr             
 basin    (Intercept)    2.3281   1.5258                    
          z_elev         0.3463   0.5885    1.00            
          z_yield        0.2358   0.4856   -1.00 -1.00      
          z_elev:z_yield 0.2075   0.4555    1.00  1.00 -1.00
 Residual                3.5186   1.8758                    
Number of obs: 65, groups:  basin, 7

Fixed effects:
               Estimate Std. Error t value
(Intercept)     9.04826    0.63935  14.152
z_elev         -0.01052    0.34754  -0.030
z_yield        -0.97421    0.36546  -2.666
z_elev:z_yield  0.72215    0.39248   1.840

Correlation of Fixed Effects:
            (Intr) z_elev z_yild
z_elev       0.573              
z_yield     -0.374 -0.562       
z_elv:z_yld  0.218  0.358 -0.638
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
Code
ranef(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
$basin
               (Intercept)     z_elev     z_yield z_elev:z_yield
Donner Blitzen   2.0477619  0.7898203 -0.65171901     0.61128048
Flathead        -2.3032082 -0.8883457  0.73301714    -0.68753412
Paine Run        0.9194709  0.3546393 -0.29263004     0.27447263
Snake River     -0.6544679 -0.2524278  0.20829041    -0.19536619
Staunton River   0.9670684  0.3729976 -0.30777839     0.28868104
West Brook      -0.2705890 -0.1043660  0.08611744    -0.08077394
Yellowstone     -0.7060360 -0.2723176  0.22470245    -0.21075989

with conditional variances for "basin" 
Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Catchment area (log scale)") + ylab("Intercept (posterior mean)") #+ #ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Elevation (feet)") + ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("SWE (10-yr mean)") + ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Mean August yield (log scale)") + ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Catchment area (log scale)") + ylab("Intercept (posterior mean)") #+ #ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Elevation (feet)") + ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("SWE (10-yr mean)") + ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Mean August yield (log scale)") + ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1)

by PC scores
Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",1]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",1]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites,
         PC1 = mypca_std$x[,1],
         PC2 = mypca_std$x[,2],
         PC3 = mypca_std$x[,3],
         PC4 = mypca_std$x[,4]) %>%
  left_join(basinvars %>% select(basin, site_name)) 

p1 <- mydf %>%
  ggplot(aes(x = PC1, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Intercept (posterior mean)") #+ #ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = PC2, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = PC3, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = PC3, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Intercept (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

6.5.2.3 Temp effect

Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",2]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",2]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites) %>%
  left_join(basinvars) %>% 
  left_join(dat %>% mutate(month = month(date)) %>% filter(month %in% c(8)) %>% group_by(site_name) %>% summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
  group_by(basin) %>%
  mutate(z_area = scale(log(area_sqmi)), 
         z_elev = scale(elev_ft), 
         z_swe = scale(swe),
         z_yield = scale(meanSummYield)) %>%
  ungroup()

summary(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
Linear mixed model fit by REML ['lmerMod']
Formula: mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin)
   Data: mydf

REML criterion at convergence: 11.2

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.05953 -0.52975  0.01319  0.47344  2.51739 

Random effects:
 Groups   Name           Variance  Std.Dev. Corr             
 basin    (Intercept)    0.2177477 0.46663                   
          z_elev         0.0007084 0.02662   1.00            
          z_yield        0.0006844 0.02616  -1.00 -1.00      
          z_elev:z_yield 0.0012405 0.03522  -1.00 -1.00  1.00
 Residual                0.0373359 0.19322                   
Number of obs: 65, groups:  basin, 7

Fixed effects:
               Estimate Std. Error t value
(Intercept)     0.98997    0.17890   5.534
z_elev         -0.14544    0.02910  -4.997
z_yield        -0.01347    0.03419  -0.394
z_elev:z_yield  0.03060    0.03922   0.780

Correlation of Fixed Effects:
            (Intr) z_elev z_yild
z_elev       0.335              
z_yield     -0.244 -0.419       
z_elv:z_yld -0.412 -0.008 -0.408
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
Code
ranef(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
$basin
               (Intercept)       z_elev      z_yield z_elev:z_yield
Donner Blitzen -0.36680547 -0.020921823  0.020563802    0.027685724
Flathead       -0.29462678 -0.016804895  0.016517302    0.022237839
Paine Run      -0.06333643 -0.003612581  0.003550766    0.004780503
Snake River    -0.12482048 -0.007119502  0.006997675    0.009421190
Staunton River  0.24680139  0.014077039 -0.013836146   -0.018628064
West Brook      0.93134140  0.053121778 -0.052212720   -0.070295752
Yellowstone    -0.32855363 -0.018740016  0.018419321    0.024798560

with conditional variances for "basin" 
Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Catchment area (log scale)") + ylab("Temp effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Elevation (feet)") + ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("SWE (10-yr mean)") + ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Mean August yield (log scale)") + ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Catchment area (log scale)") + ylab("Temp effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Elevation (feet)") + ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("SWE (10-yr mean)") + ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Mean August yield (log scale)") + ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1)

by PC scores
Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",2]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",2]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites,
         PC1 = mypca_std$x[,1],
         PC2 = mypca_std$x[,2],
         PC3 = mypca_std$x[,3],
         PC4 = mypca_std$x[,4]) %>%
  left_join(basinvars %>% select(basin, site_name)) 

p1 <- mydf %>%
  ggplot(aes(x = PC1, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Temp effect (posterior mean)") #+ #ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = PC2, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = PC3, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = PC3, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Temp effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

6.5.2.4 Flow effect

Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",3]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",3]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites) %>%
  left_join(basinvars) %>% 
  left_join(dat %>% mutate(month = month(date)) %>% filter(month %in% c(8)) %>% group_by(site_name) %>% summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
  group_by(basin) %>%
  mutate(z_area = scale(log(area_sqmi)), 
         z_elev = scale(elev_ft), 
         z_swe = scale(swe),
         z_yield = scale(meanSummYield)) %>%
  ungroup()

summary(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
Linear mixed model fit by REML ['lmerMod']
Formula: mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin)
   Data: mydf

REML criterion at convergence: 40.9

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-3.9418 -0.2748  0.1203  0.4650  1.7761 

Random effects:
 Groups   Name           Variance Std.Dev. Corr             
 basin    (Intercept)    0.030808 0.17552                   
          z_elev         0.001602 0.04003  -0.61            
          z_yield        0.003094 0.05562   0.01 -0.79      
          z_elev:z_yield 0.002394 0.04893   0.37  0.51 -0.93
 Residual                0.074597 0.27313                   
Number of obs: 65, groups:  basin, 7

Fixed effects:
               Estimate Std. Error t value
(Intercept)    0.069251   0.077873   0.889
z_elev         0.053043   0.042583   1.246
z_yield        0.031792   0.053821   0.591
z_elev:z_yield 0.005071   0.054940   0.092

Correlation of Fixed Effects:
            (Intr) z_elev z_yild
z_elev      -0.247              
z_yield      0.145 -0.467       
z_elv:z_yld -0.123  0.231 -0.630
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
Code
ranef(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
$basin
               (Intercept)       z_elev      z_yield z_elev:z_yield
Donner Blitzen -0.01038276  0.018979550 -0.030849236    0.024181665
Flathead       -0.16490360  0.001148689  0.038180225   -0.048342310
Paine Run      -0.10196005  0.009304089  0.008492392   -0.017515392
Snake River     0.03944288  0.005288636 -0.018916865    0.019573866
Staunton River  0.05628131 -0.005194433 -0.004584624    0.009583957
West Brook      0.29687557 -0.048566120  0.013043027    0.020075644
Yellowstone    -0.11535335  0.019039590 -0.005364919   -0.007557430

with conditional variances for "basin" 
Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Catchment area (log scale)") + ylab("Flow effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Elevation (feet)") + ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("SWE (10-yr mean)") + ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Mean August yield (log scale)") + ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Catchment area (log scale)") + ylab("Flow effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Elevation (feet)") + ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("SWE (10-yr mean)") + ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Mean August yield (log scale)") + ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1)

by PC scores
Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",3]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",3]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites,
         PC1 = mypca_std$x[,1],
         PC2 = mypca_std$x[,2],
         PC3 = mypca_std$x[,3],
         PC4 = mypca_std$x[,4]) %>%
  left_join(basinvars %>% select(basin, site_name)) 

p1 <- mydf %>%
  ggplot(aes(x = PC1, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Flow effect (posterior mean)")

p2 <- mydf %>%
  ggplot(aes(x = PC2, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = PC3, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = PC3, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  ylab("Flow effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

6.5.2.5 Air temp lagged 1

Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",5]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",5]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites) %>%
  left_join(basinvars) %>% 
  left_join(dat %>% mutate(month = month(date)) %>% filter(month %in% c(8)) %>% group_by(site_name) %>% summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
  group_by(basin) %>%
  mutate(z_area = scale(log(area_sqmi)), 
         z_elev = scale(elev_ft), 
         z_swe = scale(swe),
         z_yield = scale(meanSummYield)) %>%
  ungroup()

summary(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
Linear mixed model fit by REML ['lmerMod']
Formula: mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin)
   Data: mydf

REML criterion at convergence: 19.1

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.18549 -0.51327  0.00035  0.52878  2.54530 

Random effects:
 Groups   Name           Variance Std.Dev. Corr             
 basin    (Intercept)    0.079158 0.28135                   
          z_elev         0.003817 0.06178  -0.99            
          z_yield        0.006693 0.08181   0.97 -0.98      
          z_elev:z_yield 0.010984 0.10481  -0.38  0.51 -0.56
 Residual                0.044369 0.21064                   
Number of obs: 65, groups:  basin, 7

Fixed effects:
               Estimate Std. Error t value
(Intercept)     1.02988    0.11175   9.216
z_elev         -0.10469    0.03890  -2.691
z_yield        -0.06226    0.04964  -1.254
z_elev:z_yield  0.05295    0.05823   0.909

Correlation of Fixed Effects:
            (Intr) z_elev z_yild
z_elev      -0.589              
z_yield      0.663 -0.609       
z_elv:z_yld -0.364  0.308 -0.586
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
Code
ranef(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
$basin
               (Intercept)        z_elev       z_yield z_elev:z_yield
Donner Blitzen  0.49704652 -1.021824e-01  0.1320008118   -0.016393415
Flathead       -0.11288946  2.952272e-02 -0.0453711305    0.080645809
Paine Run      -0.24501190  6.106485e-02 -0.0801118594    0.109448115
Snake River    -0.01456840  2.776056e-05  0.0005726904   -0.028344389
Staunton River  0.12783416 -2.841679e-02  0.0365808041   -0.023487283
West Brook     -0.30906002  6.242561e-02 -0.0770454263   -0.009433651
Yellowstone     0.05664908 -2.244175e-02  0.0333741099   -0.112435186

with conditional variances for "basin" 
Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Catchment area (log scale)") + ylab("AirT lag-1 effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Elevation (feet)") + ylab("AirT lag-1 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("SWE (10-yr mean)") + ylab("AirT lag-1 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Mean August yield (log scale)") + ylab("AirT lag-1 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Catchment area (log scale)") + ylab("AirT lag-1 effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Elevation (feet)") + ylab("AirT lag-1 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("SWE (10-yr mean)") + ylab("AirT lag-1 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Mean August yield (log scale)") + ylab("AirT lag-1 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1)

6.5.2.6 Air temp lagged 2

Code
mydf <- ggfit %>%
  filter(Parameter %in% paste("B.site[", c(1:jags.data$nsites), ",6]", sep = "")) %>%
  mutate(Parameter = factor(Parameter, levels = paste("B.site[", c(1:jags.data$nsites), ",6]", sep = ""))) %>%
  group_by(Parameter) %>% 
  summarize(mean = mean(value)) %>%
  ungroup() %>%
  mutate(site_name = mysites) %>%
  left_join(basinvars) %>% 
  left_join(dat %>% mutate(month = month(date)) %>% filter(month %in% c(8)) %>% group_by(site_name) %>% summarize(meanSummYield = mean(Yield_mm, na.rm = TRUE))) %>%
  group_by(basin) %>%
  mutate(z_area = scale(log(area_sqmi)), 
         z_elev = scale(elev_ft), 
         z_swe = scale(swe),
         z_yield = scale(meanSummYield)) %>%
  ungroup()

summary(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
Linear mixed model fit by REML ['lmerMod']
Formula: mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin)
   Data: mydf

REML criterion at convergence: -104

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.71150 -0.56147 -0.04064  0.53997  2.05632 

Random effects:
 Groups   Name           Variance  Std.Dev. Corr             
 basin    (Intercept)    0.0070081 0.08371                   
          z_elev         0.0006758 0.02600  -0.37            
          z_yield        0.0021957 0.04686   0.43 -0.41      
          z_elev:z_yield 0.0028800 0.05367  -0.51  0.89 -0.78
 Residual                0.0054304 0.07369                   
Number of obs: 65, groups:  basin, 7

Fixed effects:
               Estimate Std. Error t value
(Intercept)     0.09243    0.03419   2.703
z_elev          0.02398    0.01479   1.622
z_yield        -0.02396    0.02423  -0.989
z_elev:z_yield  0.01995    0.02646   0.754

Correlation of Fixed Effects:
            (Intr) z_elev z_yild
z_elev      -0.274              
z_yield      0.414 -0.420       
z_elv:z_yld -0.505  0.580 -0.745
optimizer (nloptwrap) convergence code: 0 (OK)
boundary (singular) fit: see help('isSingular')
Code
ranef(lmer(mean ~ z_elev * z_yield + (1 + z_elev * z_yield | basin), data = mydf))
$basin
                (Intercept)       z_elev      z_yield z_elev:z_yield
Donner Blitzen -0.005247194 -0.027456507  0.013925482  -0.0457186751
Flathead        0.011049710  0.032407047 -0.006698632   0.0484306827
Paine Run       0.083579747 -0.008409603  0.021377880  -0.0261029041
Snake River    -0.116451034  0.016969686 -0.071269754   0.0665964656
Staunton River  0.049748870  0.005806231  0.013379452  -0.0008616029
West Brook      0.068810646 -0.021637436 -0.003729892  -0.0301687220
Yellowstone    -0.091490744  0.002320582  0.033015464  -0.0121752442

with conditional variances for "basin" 
Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Catchment area (log scale)") + ylab("AirT lag-2 effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Elevation (feet)") + ylab("AirT lag-2 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("SWE (10-yr mean)") + ylab("AirT lag-2 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  xlab("Mean August yield (log scale)") + ylab("AirT lag-2 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1, common.legend = TRUE, legend = "top")

Code
p1 <- mydf %>%
  ggplot(aes(x = log(area_sqmi), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Catchment area (log scale)") + ylab("AirT lag-2 effect (posterior mean)") #+ ggpubr::stat_cor()

p2 <- mydf %>%
  ggplot(aes(x = elev_ft, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Elevation (feet)") + ylab("AirT lag-2 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p3 <- mydf %>%
  ggplot(aes(x = swe, y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("SWE (10-yr mean)") + ylab("AirT lag-2 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

p4 <- mydf %>%
  ggplot(aes(x = log(meanSummYield), y = mean, color = basin)) +
  geom_smooth(method = "lm") + geom_point() + theme_bw() +
  theme(legend.position = "none") +
  facet_wrap(~basin, scales = "free", ncol = 1) +
  xlab("Mean August yield (log scale)") + ylab("AirT lag-2 effect (posterior mean)") + #ggpubr::stat_cor() + 
  theme(axis.title.y = element_blank(), axis.text.y = element_blank())

ggpubr::ggarrange(p1, p2, p3, p4, nrow = 1)