# QCBS R Workshop Series
# Linear Mixed Models
#Designed by Jacob Ziegler, Dalal Hanna and Catherine Baltazar
################Section 1#########################
# Remove prior commands in R
rm(list=ls())
# Place all workshop material in one folder on your computer
# Run the following line of code and use the browsing window to choose the QCBS_W6_Data.csv
# file in the folder that contains the workshop material
file.choose()
# Set the working directoy to the folder which contains the lab material by copy and pasting
# all but the R file name from the output of the file.choose() command into the set working
# directory command.
# For example paste "/Users/ziegljac/Documents/QCBS_R/" -> include the quotations
# NOT "/Users/ziegljac/Documents/QCBS_R/Get_Data_Func.R" -> include the quotations
setwd()
# Load useful libraries and data
#Note: if you have never loaded these libraries before you will have to use
#the "install.packages" function before the "library" function
library(ggplot2)
library(lme4)
library(arm)
library(AICcmodavg)
library(beepr)
#AICc is for small sample size corrected
#Always use this because the equation is set that the bigger the sample size gets the more it's just like AIC
data <- read.csv('qcbs_w6_data.csv')
# Used to strip down figures to make them simpler
fig <- theme_bw() + theme(panel.grid.minor=element_blank(), panel.grid.major=element_blank(), panel.background=element_blank()) +
theme(strip.background=element_blank(), strip.text.y = element_text()) + theme(legend.background=element_blank()) +
theme(legend.key=element_blank()) + theme(panel.border = element_rect(colour="black", fill=NA))
# Make the followoing three plots to explore the data
plot <- ggplot(aes(Fish_Length,Trophic_Pos),data=data)
# Plot 1 - All Data
plot + geom_point() + xlab("Length (mm)") + ylab("Trophic Position") + labs(title="All Data") + fig
# Plot 2 - By Speceis - BG = Bluegill, WY = Walleye, and YP = Yellow Perch
plot + geom_point() + facet_wrap(~ Fish_Species) + xlab("Length (mm)") + ylab("Trophic Position") + labs(title="By Species") + fig
# Plot 3 - By Lake
plot + geom_point() + facet_wrap(~ Lake) + xlab("Length (mm)") + ylab("Trophic Position") + labs(title="By Lake") + fig
################Section 2#########################
#Running a mixed model in R
#Four Step Process to build a mixed model in R
#1) A priori model bulding and data exploration####
#i) Map out the model based on a priori knowledge
#We know that we want to build a model that evaluates the relationship
#bewteen trophic position and length while accounting for lake and species varition
#Trophic Position ~ Length + Species + Lake
#ii)Housekeeping and data exploration
#Ensure that the structure of your data is correct
str(data)
#Look at sample distribution across factors to check
#if there are any major unequal distributions
table(data$Lake)
table(data$Fish_Species)
#Look at distribution of continuous variables
#Transform if necessary (will avoid future problems with homogeneity of model residuals)
hist(data$Fish_Length)
hist(data$Trophic_Pos)
#Check for colinearity between variables
plot(data)
cor(data$Fish_Length, data$Trophic_Pos)
#Note that in this data set there are not mulitple continuous variables
#between which correlations might cause problems
#but if for example we have length and mass data
#we would not want to use both, as they would likely be highly correlated
#Consider the scales of your variables
#Note: when 2 variables have very different ranges of scale the criteria mixed models use to come up
#with parameter estimates are likely to return 'convergance errors'
#Z correcting adjusts for this scaling problem:
#What is a z correction?: (z = (x - mean(x))/sd(x))
#Z-correct Length
data$Z_Length<-(data$Fish_Length-mean(data$Fish_Length))/sd(data$Fish_Length)
#Z-correct Trophic Position
data$Z_TP<-(data$Trophic_Pos-mean(data$Trophic_Pos))/sd(data$Trophic_Pos)
#Find out if it is important to account for variation in "random effects"
#by comparing the residuals of a linear model without the random effects with
#the potential random effects
lm.test<-lm(Z_TP~Z_Length, data=data)
lm.test.resid<-rstandard(lm.test)
#Species Effect
plot(lm.test.resid~ data$Fish_Species, xlab = "Species", ylab="Standardized residuals")
abline(0,0, lty=2)
#Lake Effect
plot(lm.test.resid~ data$Lake, xlab = "Lake", ylab="Standardized residuals")
abline(0,0, lty=2)
#2) Coding potential models and model selection####
#i) Coding all potential models
#List of all Potential models-->
#Note: you can chose to not code ones that do not make biological sense.
#Linear model with no random effects NOTE if you want to compare this model with the other models you must
#Change REML = FALSE for all lmer models
M0<-lm(Z_TP~Z_Length,data=data)
#Full model with varying intercepts
M1<-lmer(Z_TP~Z_Length + (1|Fish_Species) + (1|Lake), data=data, REML=TRUE)
#Full model with varying intercepts and slopes
M2<-lmer(Z_TP~Z_Length + (1+Z_Length|Fish_Species) + (1+Z_Length|Lake), data=data, REML=TRUE)
#No Lake, varying intercepts only
M3<-lmer(Z_TP~Z_Length + (1|Fish_Species), data=data, REML=TRUE)
#No Species, varying intercepts only
M4<-lmer(Z_TP~Z_Length + (1|Lake), data=data, REML=TRUE)
#No Lake, varying intercepts and slopes
M5<-lmer(Z_TP~Z_Length + (1+Z_Length|Fish_Species), data=data, REML=TRUE)
#No Species, varying intercepts and slopes
M6<-lmer(Z_TP~Z_Length + (1+Z_Length|Lake), data=data, REML=TRUE)
#Full model with varying intercepts and slopes only varying by lake
M7<-lmer(Z_TP~Z_Length + (1|Fish_Species) + (1+Z_Length|Lake), data=data, REML=TRUE)
#Full model with varying intercepts and slopes only varying by species
M8<-lmer(Z_TP~Z_Length + (1+Z_Length|Fish_Species) + (1|Lake), data=data, REML=TRUE)
#ii) Compare models using AICc values
#Compute AICc values for each model
AICc<-c(AICc(M1), AICc(M2), AICc(M3), AICc(M4), AICc(M5), AICc(M6), AICc(M7), AICc(M8))
#Put values into one table for easy comparision
Model<-c("M1", "M2", "M3", "M4", "M5", "M6", "M7", "M8")
AICtable<-data.frame(Model=Model, AICc=AICc)
AICtable
#M8 has the lowest AICc value so it has the most predictive power
#M2 is also a good fit, but all other models are not nearly as good.
#Note when you compare models with different fixed effects they must be fit by
#Maximum Likelihood (ML) and not by Restricted Maximum Likelihood (REML)
#3) Checking model assumptions####
#Checking for M8
#A. Look at independence: plot fitted values vs residuals
E1 <- resid(M8)
F1<-fitted(M8)
plot(x = F1,
y = E1,
xlab = "Fitted Values",
ylab = "Normalized residuals")
abline(h = 0, lty = 2)
#B. Look at homogeneity:
# i. plot residuals vs each covariate in the model
#Fish_Length
plot(x = data$Z_Length,
y = E1,
xlab = "Z Length",
ylab = "Normalized residuals")
abline(h = 0, lty = 2)
#Note: observed groupings are created by the nature of the data because in the data set
#we only measured individuals from 5 categories of lengths (big, small, and three groups in between)
#Species
boxplot(E1 ~ Fish_Species,
ylab = "Normalized residuals",
data = data, xlab = "Species")
abline(h = 0, lty = 2)
#Lake
boxplot(E1 ~ Lake,
ylab = "Normalized residuals",
data = data, xlab = "Lake")
abline(h = 0, lty = 2)
# ii. plot residuals vs each covariate not in the model
#NA in the case of this data set
#D. Look at normality: histogram
hist(E1)
#4) Interpreting results and visuzaling the model####
#Re-fit model by REML if you set REML = FALSE for model comparisions
#REML is a more conservative estimate, so the rule is to use this estimation method when
#obtaining model coefficients
M8<-lmer(Z_TP~Z_Length + (1+Z_Length|Fish_Species) + (1|Lake), data=data, REML=TRUE)
#Look at model summary
#This allows you to get an idea of the variance explained by the different components
#of the model and the "significance" of fixed effects
summary(M8)
#Visualizing model results####
#There are several ways of visualizing the results of a mixed model, all of which
#involve using the coefficients generated by the model.
#So the first step is get the model coefficients to be able to add them to the figures
coef(M8)
#Now put the coefs into dataframes to make them more easy to manipulate
Lake.coef <- as.data.frame(coef(M8)$Lake)
colnames(Lake.coef) <- c("Intercept","Slope")
Species.coef <- as.data.frame(coef(M8)$Fish_Species)
colnames(Species.coef) <- c("Intercept","Slope")
# Plot 1 - All Data
#Make a plot that includes all the data
plot <- ggplot(aes(Z_Length,Z_TP),data=data)
Plot_AllData <- plot + geom_point() + xlab("Length (mm)") + ylab("Trophic Position") + labs(title="All Data") + fig
#Add a layer that has an abline with the intercept and slope of the relationship between length and trophic position
#Note that you can obtain the intercept and slope of the fixed factor directly from the model summary
summary(M8)
Plot_AllData + geom_abline(intercept = -0.0009059, slope =0.4222697)
# Plot 2 - By Speceis
#Plot the data color coded by Species
Plot_BySpecies<-plot + geom_point(aes(colour = factor(Fish_Species)), size = 4) + xlab("Length (mm)") + ylab("Trophic Position") + labs(title="By Species") + fig
#Add the regression line with the intercepts and slopes specific to each species
Plot_BySpecies + geom_abline(intercept = Species.coef[1,1], slope =Species.coef[1,2], colour="coral2") + geom_abline(intercept = Species.coef[2,1], slope =Species.coef[2,2], colour = "green4") + geom_abline(intercept = Species.coef[3,1], slope =Species.coef[3,2], colour="blue1")
# Plot 3 - By Lake
#Plot the data color coded by lake
Plot_ByLake<-plot + geom_point(aes(colour = factor(Lake)), size = 4) + xlab("Length (mm)") + ylab("Trophic Position") + labs(title="By Lake") + fig
#Add in regression lines with the intercepts specific to each lake
Plot_ByLake + geom_abline(intercept = Lake.coef[1,1], slope =Lake.coef[1,2], colour="coral2") + geom_abline(intercept = Lake.coef[2,1], slope =Lake.coef[2,2], colour="khaki4") + geom_abline(intercept = Lake.coef[3,1], slope =Lake.coef[3,2], colour="green4") + geom_abline(intercept = Lake.coef[4,1], slope =Lake.coef[4,2], colour="darkgoldenrod") + geom_abline(intercept = Lake.coef[5,1], slope =Lake.coef[5,2], colour="royalblue1") + geom_abline(intercept = Lake.coef[6,1], slope =Lake.coef[6,2], colour="magenta3")
beep(sound = 4)
beep(sound = 3)
#Thanks for attending the workshop and/or checking out this code!
#We hope that this has been helpful to you!