Chapter 6 ANCOVA Linear Regression Practice Script This script is for practicing using a analysis of covariance (ANCOVA) from chapter 6 of the Beckerman textbook.

See https://rpubs.com/jorgelizarazo/Rdemo5_wekk5_chapter6 for lab demo

ANCOVA combines a categorical explanatory variable with a continuous explanatory variable. We are essentially combining regression and one-way ANOVA

Clear R’s brain:

rm(list = ls())

Import libraries:

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
library(ggfortify)
## Warning: package 'ggfortify' was built under R version 4.6.1

Import the dataset and call it limp then view it:

limp <- read.csv("/Users/jroweibekwe/Library/Mobile Documents/com~apple~CloudDocs/MSc/Bio 9915/Bio_9915_R_Practice/textbook_data_9915/limpet.csv")

glimpse(limp)
## Rows: 24
## Columns: 3
## $ DENSITY <int> 8, 8, 8, 8, 8, 8, 15, 15, 15, 15, 15, 15, 30, 30, 30, 30, 30, …
## $ SEASON  <chr> "spring", "spring", "spring", "summer", "summer", "summer", "s…
## $ EGGS    <dbl> 2.875, 2.625, 1.750, 2.125, 1.500, 1.875, 2.600, 1.866, 2.066,…

The data relates egg production by limpets to four density conditions in two seasons. The response variable (y) is egg production (EGGS) and the independent variables are DENSITY (continuous) and SEASON (categorical).

This analysis is trying to solve if the density dependence of egg production differs between spring and summer?

#Creating a plot: The response (y) variable is egg production, DENSITY is the continuous independent variable (x), and the values are also classified by the category SEASON. This suggests that a plot of egg production against density, distinguishing the two seasonal categories, is what we need.

ggplot(limp, aes(x = DENSITY, y = EGGS, colour = SEASON)) +
geom_point() +
scale_color_manual(values = c(spring="green", summer="red")) + #lets you choose the exact colours assigned to categories in your plot
theme_bw()

On our graph, if we were to describe the relationship between egg production and density by a straight line (ignore the season for the moment), then y is egg production, x is density, b is where the line crosses the y-axis (i.e. the egg production at 0 density—admittedly an odd concept!), and m is the slope of the egg production density relationship. This slope represents the change in egg production per unit change in density.

Overall, egg numbers decline with increasing density—that is, there is a negative slope. Second, there may be a seasonal difference(the intercept) the value of egg production at zero density, is different in each season.

Possible slopes: 1. The number of eggs produced by limpets does not vary with density or season. *Common intercept, Zero slope(s)

  1. The number of eggs produced by limpets does not vary with density but is reduced in the summer season (dashed). *Different intercepts, Zero slope(s), Parallel horizontal line

  2. The number of eggs produced declines with density, but the maximum number of eggs (intercept) and the rate (slope) do not vary with season *Same intercept, Same slope, Same lines

  3. The number of eggs produced declines with density and the number of eggs at density zero (intercept) differs between seasons but the rate (slope) does not vary with season.

  • Different intercepts, Same (negative) slope, Parallel lines
  1. The number of eggs at density zero (intercept) and the rate (slope) vary with season *Different intercepts, Different slopes - ‘Crossing’ lines

#Constructing the ANCOVA: Since we want to include an effect of DENSITY (main effect), an effect of SEASON (main effect), and the potential for the effect of DENSITY to depend on SEASON (interaction). The model that captures this best is:

limp.mod <- lm(EGGS ~ DENSITY * SEASON, data = limp)
#lm creates linear models
#the * symbol between the two explanatory variables
names(limp.mod)
##  [1] "coefficients"  "residuals"     "effects"       "rank"         
##  [5] "fitted.values" "assign"        "qr"            "df.residual"  
##  [9] "contrasts"     "xlevels"       "call"          "terms"        
## [13] "model"
#R has stored for us the coeffcients (intercept and slope estimates), the residuals, the fitted values, and a number of other values. 

#Check the Model assumptions:

autoplot(limp.mod, smooth.colour = NA)
## Warning: `fortify(<lm>)` was deprecated in ggplot2 4.0.0.
## ℹ Please use `broom::augment(<lm>)` instead.
## ℹ The deprecated feature was likely used in the ggfortify package.
##   Please report the issue at <https://github.com/sinhrks/ggfortify/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## ℹ The deprecated feature was likely used in the ggfortify package.
##   Please report the issue at <https://github.com/sinhrks/ggfortify/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Removed 24 rows containing missing values or values outside the scale range
## (`geom_line()`).
## Removed 24 rows containing missing values or values outside the scale range
## (`geom_line()`).
## Removed 24 rows containing missing values or values outside the scale range
## (`geom_line()`).

Interpreting the Model Output: anova() produces an ANOVA table listing sums of squares, mean squares, F-values, and p-values. It does not perform an ANOVA. summary() returns a table of coefficients (slopes and intercepts), standard errors, and t-values.

anova(limp.mod)
## Analysis of Variance Table
## 
## Response: EGGS
##                Df Sum Sq Mean Sq F value    Pr(>F)    
## DENSITY         1 5.0241  5.0241 30.1971 2.226e-05 ***
## SEASON          1 3.2502  3.2502 19.5350 0.0002637 ***
## DENSITY:SEASON  1 0.0118  0.0118  0.0711 0.7925333    
## Residuals      20 3.3275  0.1664                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#This table reveals that there is no additional, significant variation in egg number explained by allowing different slopes for each season (since the p-value for the DENSITY:SEASON row is 0.79). We fail to reject the null hypothesis that the slopes are the same, i.e. we have failed to find evidence for the alternative hypothesis. 
#Our result could actually be written:
#We tested the hypothesis that the effect of density on egg production in limpets depends on the season in which they are reproducing. We found no evidence for an interaction between density and season (F = 0.0711; df = 1, 20; p = 0.79), indicating that the effects of density and season are additive.
summary(limp.mod)
## 
## Call:
## lm(formula = EGGS ~ DENSITY * SEASON, data = limp)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.65468 -0.25021 -0.03318  0.28335  0.57532 
## 
## Coefficients:
##                       Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           2.664166   0.234118  11.380 3.45e-10 ***
## DENSITY              -0.033650   0.008259  -4.074 0.000591 ***
## SEASONsummer         -0.812282   0.331092  -2.453 0.023450 *  
## DENSITY:SEASONsummer  0.003114   0.011680   0.267 0.792533    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.4079 on 20 degrees of freedom
## Multiple R-squared:  0.7135, Adjusted R-squared:  0.6705 
## F-statistic:  16.6 on 3 and 20 DF,  p-value: 1.186e-05
#Provides a statement of your model by repeating your usage of lm()—the call. 
#Next it provides the range, interquartile range, and median of the residuals as a quick, additional check of the residuals.
#The coefficient table provides insight into the estimates for the lines that you have fitted—these are the coeffcients that quantify the important biological relationships.

#PUTTING THE LINES ONTO THE FIGURE: To start this process, we invoke two functions: expand.grid() and predict().

expand.grid() is a function that generates a grid of numbers — essentially it builds a factorial representation of any variables you provide to it, and returns a data frame

predict() is a function that generates fitted values from a model. It requires at least one argument, but works most effectively for us with three: a model, a set of new ‘x’ (explanatory) values at whichwewant to know ‘y’ values, generated according to our model, and another argument, interval, which provides a rapid way to get the 95% confidence interval, doing some hard work for us.

expand.grid(FIRST = c("A", "B"), SECOND = c(1, 2))
##   FIRST SECOND
## 1     A      1
## 2     B      1
## 3     A      2
## 4     B      2
predict(limp.mod)
##         1         2         3         4         5         6         7         8 
## 2.3949692 2.3949692 2.3949692 1.6075953 1.6075953 1.6075953 2.1594217 2.1594217 
##         9        10        11        12        13        14        15        16 
## 2.1594217 1.3938428 1.3938428 1.3938428 1.6546769 1.6546769 1.6546769 0.9358016 
##        17        18        19        20        21        22        23        24 
## 0.9358016 0.9358016 1.1499321 1.1499321 1.1499321 0.4777604 0.4777604 0.4777604

First, we create the ‘new x’s’ (DENSITY) at which we want values of y (EGGS). Here we use expand.grid() to generate a set of ‘new x’ values, and we take special care to label them as the column name in the original dataset, DENSITY.

We know the density varies from 8 to 45, so let’s use the function seq() to get ten numbers in between 8 and 45:

new.x <- expand.grid(DENSITY =
seq(from = 8, to = 45, length.out = 10))

to check that it worked:

head(new.x)
##    DENSITY
## 1  8.00000
## 2 12.11111
## 3 16.22222
## 4 20.33333
## 5 24.44444
## 6 28.55556
limp$SEASON <- as.factor(limp$SEASON)
class(limp$SEASON)
## [1] "factor"

Let’s add SEASON to this grid now. SEASON has two levels, spring and summer. We can add these levels to the ‘new x’s’ as follows. Note how we use the function levels(), against the dataset, to extract the information we desire:

new.x <- expand.grid(DENSITY = seq(from = 8, to = 45, length.out = 10), SEASON = levels(limp$SEASON))

To check if it worked:

head(new.x)
##    DENSITY SEASON
## 1  8.00000 spring
## 2 12.11111 spring
## 3 16.22222 spring
## 4 20.33333 spring
## 5 24.44444 spring
## 6 28.55556 spring

use predict() with three arguments: a model, a value for newdata, and confidence intervals. We assign what predict returns to an object called new.y:

new.y <- predict(limp.mod, newdata=new.x,
interval = 'confidence')

To check if it worked:

head(new.y)
##        fit      lwr      upr
## 1 2.394969 2.019285 2.770654
## 2 2.256632 1.931230 2.582034
## 3 2.118294 1.834274 2.402315
## 4 1.979957 1.724062 2.235852
## 5 1.841619 1.595998 2.087241
## 6 1.703282 1.447918 1.958646

Housekeeping step: combine the new y’s with the new x’s, so that we have a clear picture of what it is you’ve made, using the function data.frame(). Call the data frame addThese, because we are gonna add these to the plot. Rename the fit produced by predict to EGGS to match the original data using rename() - dplyr function.

addThese <- data.frame(new.x, new.y)
addThese <- rename(addThese, EGGS = fit)

To check if it worked:

head(addThese)
##    DENSITY SEASON     EGGS      lwr      upr
## 1  8.00000 spring 2.394969 2.019285 2.770654
## 2 12.11111 spring 2.256632 1.931230 2.582034
## 3 16.22222 spring 2.118294 1.834274 2.402315
## 4 20.33333 spring 1.979957 1.724062 2.235852
## 5 24.44444 spring 1.841619 1.595998 2.087241
## 6 28.55556 spring 1.703282 1.447918 1.958646

This creates a data frame that contains the grid of seasons and densities, as well as the predicted values and 95% CI at each of these combinations.

Before we finish with adding these data to the figure, we want to show how flexible this method is. Let’s modify the code so that we get the predicted number of eggs for each season at the mean limpet density:

#Making the plot:

ggplot(limp, aes(x = DENSITY, y = EGGS, colour = SEASON)) + # first add the points
geom_point(size = 5) + geom_smooth(data = addThese, #geom_smooth() adds the fits and CIs 
aes(ymin = lwr, ymax = upr, # note we don't need to specify DENSITY AND EGG
fill = SEASON), stat = 'identity') + #stat = ‘identity’ tells geom_smooth() not to try and calculate anything fancy-shmancy for us
scale_colour_manual(values = c(spring="green", summer="red")) + #now adjust the colours
scale_fill_manual(values = c(spring="green", summer="red")) +
# theme it
theme_bw()

#In Summary: 1. Enter your data in a spreadsheet program, check it, and save it as a comma-separated values file. 2. Start R, wipe its brain of all previous data — rm(list=ls())—and get your libraries sorted. 3. Import your data into R and check this has worked, for example using glimpse(). 4. Make a picture that reflects your questions and that will help you specify your model to answer your question. 5. Specify a statistical model to capture the hypothesis you are testing. 6. Assess whatever assumptions are important to the type of modelling you have decided to implement (use autoplot()). 7. Interpret the model with anova() and summary(). 8. Add fitted lines and CIs, when appropriate, to the plot; make the figure that is worth 1000 words. 9. Keep your data file and script file very safe (and backed up in a separate location). With these, you can recreate all your graphs