library(ggplot2)
library(tidyr)
library(dplyr) # mutate function
library(ggeffects) # ggpredict
library(ggpp) # offset scatter plot on box plot
library(car) # type 2 ANOVA
library(emmeans) # stats
library(lme4) # for lmer
library(lmerTest)
library(readxl) # read excel
library(MuMIn) # dredge()
library(patchwork) # combine graphs
Load and review data
oilpalm <- read.csv("C:/Users/charl/OneDrive - Nanyang Technological University/ES Mods/ES3307 Experimental Design/Assignment 4/Assignment 4 data/subsistence.csv")
# Check data structure and summary
str(oilpalm)
## 'data.frame': 87 obs. of 10 variables:
## $ name : chr "control" "control" "control" "control" ...
## $ kill : int 0 0 0 0 0 0 0 0 0 0 ...
## $ habitat : chr "subsistence" "subsistence" "subsistence" "subsistence" ...
## $ water : int 40 30 30 25 20 30 10 20 5 10 ...
## $ tree : int 20 10 10 15 20 20 25 10 5 10 ...
## $ shrub : int 20 15 5 10 15 15 20 25 30 15 ...
## $ human : int 60 30 140 190 180 120 120 20 310 50 ...
## $ fruit : int 10 20 20 10 10 10 10 5 20 30 ...
## $ cable : logi NA NA NA NA NA NA ...
## $ transectID: int 0 0 0 0 0 0 0 0 0 0 ...
summary(oilpalm)
## name kill habitat water
## Length:87 Min. :0.0000 Length:87 Min. : 5.00
## Class :character 1st Qu.:0.0000 Class :character 1st Qu.: 5.00
## Mode :character Median :1.0000 Mode :character Median :10.00
## Mean :0.5402 Mean :14.25
## 3rd Qu.:1.0000 3rd Qu.:20.00
## Max. :1.0000 Max. :50.00
## tree shrub human fruit
## Min. : 5.00 Min. : 5.00 Min. : 10.0 Min. : 5.000
## 1st Qu.: 5.00 1st Qu.: 5.00 1st Qu.: 50.0 1st Qu.: 5.000
## Median :10.00 Median :10.00 Median :100.0 Median :10.000
## Mean :11.44 Mean :11.67 Mean :130.1 Mean : 9.828
## 3rd Qu.:15.00 3rd Qu.:15.00 3rd Qu.:157.5 3rd Qu.:10.000
## Max. :30.00 Max. :30.00 Max. :580.0 Max. :50.000
## cable transectID
## Mode:logical Min. :0.000
## NA's:87 1st Qu.:0.000
## Median :3.000
## Mean :1.586
## 3rd Qu.:3.000
## Max. :3.000
Which model(s) have you included in the final candidate set?
# Check for multicollinearity
# Fit full model with all terms
full <- glm(kill ~ water + tree + shrub + human + fruit,
family = binomial, data = oilpalm)
# Check VIF values
vif(full)
## water tree shrub human fruit
## 1.053694 1.039607 1.044163 1.179552 1.165295
As all our variables have a VIF value of less than 3, we can proceed to determine our candidate set of models.
# Fit final model
final <- glm(kill ~ water + tree + shrub + human + fruit,
family = binomial, data = oilpalm,
na.action = "na.fail") # include this to prevent errors when running dredge
# Dredge final model
candidates <- dredge(final)
print(candidates)
## Global model call: glm(formula = kill ~ water + tree + shrub + human + fruit, family = binomial,
## data = oilpalm, na.action = "na.fail")
## ---
## Model selection table
## (Intrc) fruit human shrub tree water df logLik AICc delta
## 16 3.9640 -0.2340 0.007149 -0.09202 -0.1346 5 -40.864 92.5 0.00
## 32 4.2160 -0.2255 0.007210 -0.09276 -0.1361 -0.02289 6 -40.679 94.4 1.94
## 12 2.9620 -0.2309 0.006524 -0.1379 4 -43.052 94.6 2.12
## 28 3.1790 -0.2237 0.006506 -0.1379 -0.02035 5 -42.879 96.5 4.03
## 8 2.6430 -0.2444 0.006595 -0.09817 4 -45.053 98.6 6.13
## 14 4.0530 -0.1931 -0.07290 -0.1099 4 -45.656 99.8 7.33
## 24 2.7710 -0.2371 0.006649 -0.09732 -0.01528 5 -44.957 100.7 8.19
## 10 3.1990 -0.1914 -0.1123 3 -47.190 100.7 8.20
## 4 1.4880 -0.2299 0.005798 3 -47.624 101.5 9.07
## 30 4.2360 -0.1838 -0.07157 -0.1114 -0.01930 5 -45.504 101.7 9.28
## 26 3.4190 -0.1821 -0.1132 -0.02113 4 -46.981 102.5 9.98
## 20 1.6710 -0.2229 0.005847 -0.01834 4 -47.459 103.4 10.94
## 6 2.9090 -0.2040 -0.07782 3 -49.071 104.4 11.96
## 2 1.9410 -0.1965 2 -50.851 105.8 13.38
## 22 2.9980 -0.1970 -0.07652 -0.01206 4 -49.002 106.5 14.02
## 31 2.7880 0.005995 -0.09172 -0.1324 -0.05648 5 -48.006 106.8 14.28
## 18 2.0950 -0.1881 -0.01660 3 -50.706 107.7 15.23
## 15 1.9900 0.005832 -0.08785 -0.1351 4 -49.907 108.3 15.83
## 27 1.7490 0.005282 -0.1293 -0.05454 4 -50.502 109.5 17.02
## 11 1.0190 0.005143 -0.1328 3 -52.421 111.1 18.66
## 29 3.0870 -0.07137 -0.1148 -0.05406 4 -51.790 112.1 19.60
## 25 2.1870 -0.1116 -0.05259 3 -53.565 113.4 20.95
## 13 2.3100 -0.06960 -0.1160 3 -53.763 113.8 21.35
## 23 1.2870 0.005258 -0.08570 -0.05509 4 -53.108 114.7 22.24
## 9 1.4660 -0.1145 2 -55.541 115.2 22.76
## 7 0.4986 0.004955 -0.08192 3 -55.332 117.0 24.49
## 19 0.3807 0.004417 -0.05371 3 -55.506 117.3 24.83
## 21 1.6860 -0.06750 -0.05185 3 -56.103 118.5 26.03
## 3 -0.3582 0.004182 2 -57.765 119.7 27.20
## 17 0.8962 -0.05168 2 -57.775 119.7 27.23
## 5 0.9397 -0.06663 2 -58.280 120.7 28.23
## 1 0.1613 1 -60.022 122.1 29.62
## weight
## 16 0.500
## 32 0.189
## 12 0.173
## 28 0.067
## 8 0.023
## 14 0.013
## 24 0.008
## 10 0.008
## 4 0.005
## 30 0.005
## 26 0.003
## 20 0.002
## 6 0.001
## 2 0.001
## 22 0.000
## 31 0.000
## 18 0.000
## 15 0.000
## 27 0.000
## 11 0.000
## 29 0.000
## 25 0.000
## 13 0.000
## 23 0.000
## 9 0.000
## 7 0.000
## 19 0.000
## 21 0.000
## 3 0.000
## 17 0.000
## 5 0.000
## 1 0.000
## Models ranked by AICc(x)
# Retain retain models where the Akaike’s difference with the top-ranked model was less than 5.0
print(subset(candidates, delta <5))
## Global model call: glm(formula = kill ~ water + tree + shrub + human + fruit, family = binomial,
## data = oilpalm, na.action = "na.fail")
## ---
## Model selection table
## (Intrc) fruit human shrub tree water df logLik AICc delta
## 16 3.964 -0.2340 0.007149 -0.09202 -0.1346 5 -40.864 92.5 0.00
## 32 4.216 -0.2255 0.007210 -0.09276 -0.1361 -0.02289 6 -40.679 94.4 1.94
## 12 2.962 -0.2309 0.006524 -0.1379 4 -43.052 94.6 2.12
## 28 3.179 -0.2237 0.006506 -0.1379 -0.02035 5 -42.879 96.5 4.03
## weight
## 16 0.538
## 32 0.204
## 12 0.186
## 28 0.072
## Models ranked by AICc(x)
The models included for the final candidate set are: kill ~ fruit + human + shrub + tree kill ~ fruit + human + shrub + tree + water kill ~ fruit + human + tree kill ~ fruit + human + tree + water
Using the best model from your candidate set, obtain your model predicted lines and confidence intervals using ggpredict() and plot them with your original data. (Hint: Think of the most suitable way to show how the probability of roadkills changes with each variable.)
# Fit the best model from candidate set
best <- glm(kill ~ fruit + human + shrub + tree,
family = binomial, data = oilpalm)
summary(best)
##
## Call:
## glm(formula = kill ~ fruit + human + shrub + tree, family = binomial,
## data = oilpalm)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 3.964419 1.030820 3.846 0.000120 ***
## fruit -0.234031 0.069852 -3.350 0.000807 ***
## human 0.007149 0.002613 2.736 0.006217 **
## shrub -0.092020 0.046696 -1.971 0.048771 *
## tree -0.134638 0.051944 -2.592 0.009542 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 120.044 on 86 degrees of freedom
## Residual deviance: 81.727 on 82 degrees of freedom
## AIC: 91.727
##
## Number of Fisher Scoring iterations: 5
# Diagnostic plots, AI has been used to generate this bit of code
library(arm)
binnedplot(fitted(best), residuals(best, type = "response"))
Fig 1Ba. Diagnostic plot of best model from candidate set.
# Obtain model predicted lines
best_fruit <- ggpredict(best, terms = c("fruit"))
best_human <- ggpredict(best, terms = c("human"))
best_tree <- ggpredict(best, terms = c("tree"))
best_shrub <- ggpredict(best, terms = c("shrub"))
# Print predicted and CI values of each variable
best_fruit
## # Predicted probabilities of kill
##
## fruit | Predicted | 95% CI
## ------------------------------
## 5 | 0.78 | 0.62, 0.88
## 10 | 0.52 | 0.37, 0.67
## 15 | 0.25 | 0.10, 0.50
## 20 | 0.09 | 0.02, 0.36
## 30 | 0.01 | 0.00, 0.17
## 40 | 0.00 | 0.00, 0.07
## 50 | 0.00 | 0.00, 0.03
##
## Adjusted for:
## * human = 100.00
## * shrub = 10.00
## * tree = 10.00
best_human
## # Predicted probabilities of kill
##
## human | Predicted | 95% CI
## ------------------------------
## 10 | 0.36 | 0.19, 0.57
## 80 | 0.48 | 0.33, 0.64
## 155 | 0.61 | 0.46, 0.75
## 225 | 0.72 | 0.55, 0.85
## 295 | 0.81 | 0.60, 0.93
## 365 | 0.88 | 0.65, 0.97
## 435 | 0.92 | 0.69, 0.98
## 580 | 0.97 | 0.75, 1.00
##
## Adjusted for:
## * fruit = 10.00
## * shrub = 10.00
## * tree = 10.00
best_tree
## # Predicted probabilities of kill
##
## tree | Predicted | 95% CI
## -----------------------------
## 5 | 0.68 | 0.47, 0.83
## 10 | 0.52 | 0.37, 0.67
## 15 | 0.35 | 0.21, 0.54
## 20 | 0.22 | 0.08, 0.46
## 25 | 0.12 | 0.03, 0.41
## 30 | 0.07 | 0.01, 0.36
##
## Adjusted for:
## * fruit = 10.00
## * human = 100.00
## * shrub = 10.00
best_shrub
## # Predicted probabilities of kill
##
## shrub | Predicted | 95% CI
## ------------------------------
## 5 | 0.63 | 0.43, 0.80
## 10 | 0.52 | 0.37, 0.67
## 15 | 0.40 | 0.25, 0.58
## 20 | 0.30 | 0.13, 0.54
## 25 | 0.21 | 0.06, 0.52
## 30 | 0.15 | 0.03, 0.51
##
## Adjusted for:
## * fruit = 10.00
## * human = 100.00
## * tree = 10.00
# Plot the model predicted lines
best_fruit_graph <-
ggplot() +
geom_point(aes(x = fruit, y = kill),
data = oilpalm,
size = 0.5) +
geom_line(aes(x = x, y = predicted),
data = best_fruit) +
geom_ribbon(aes(x = x, ymin = conf.low, ymax = conf.high),
data = best_fruit,
alpha = 0.3,
fill = "violet") +
labs(x = "Distance to nearest fruit tree (m)",
y = "Estimated probability of roadkills") +
theme_minimal()
best_human_graph <-
ggplot() +
geom_point(aes(x = human, y = kill),
data = oilpalm,
size = 0.5) +
geom_line(aes(x = x, y = predicted),
data = best_human) +
geom_ribbon(aes(x = x, ymin = conf.low, ymax = conf.high),
data = best_human,
alpha = 0.3,
fill = "lightgreen") +
labs(x = "Distance to nearest human dwelling (m)",
y = "Estimated probability of roadkills") +
theme_minimal()
best_tree_graph <-
ggplot() +
geom_point(aes(x = tree, y = kill),
data = oilpalm,
size = 0.5) +
geom_line(aes(x = x, y = predicted),
data = best_tree) +
geom_ribbon(aes(x = x, ymin = conf.low, ymax = conf.high),
data = best_tree,
alpha = 0.3,
fill = "lightpink") +
labs(x = "Distance to nearest non-fruiting tree (m)",
y = "Estimated probability of roadkills") +
theme_minimal()
best_shrub_graph <-
ggplot() +
geom_point(aes(x = shrub, y = kill),
data = oilpalm,
size = 0.5) +
geom_line(aes(x = x, y = predicted),
data = best_shrub) +
geom_ribbon(aes(x = x, ymin = conf.low, ymax = conf.high),
data = best_shrub,
alpha = 0.3,
fill = "lightblue") +
labs(x = "Distance to nearest shrub (m)",
y = "Estimated probability of roadkills") +
theme_minimal()
# Plot all graphs together
(best_fruit_graph + best_human_graph) / (best_tree_graph + best_shrub_graph) +
plot_annotation(tag_levels = "A", title = "Predicted probability of kill") &
theme(axis.title.x = element_text(size = 8),
axis.title.y = element_text(size = 8))
Fig 1Bb. Probability of roadkills for each variable with 95% confidence intervals
Using the best model from your candidate set, report and interpret your results.
# Use type 2 anova
Anova(best, type = "II")
## Analysis of Deviance Table (Type II tests)
##
## Response: kill
## LR Chisq Df Pr(>Chisq)
## fruit 18.0877 1 2.11e-05 ***
## human 9.5854 1 0.001961 **
## shrub 4.3765 1 0.036437 *
## tree 8.3797 1 0.003794 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
From type II Anova results, we can see that distance from nearest fruiting tree (χ² = 18.1, df = 1, p-value = <0.01), human dwelling (χ² = 9.58, df = 1, p-value = 0.0019), shrub (χ² = 4.38, df = 1, p-value = 0.0364) and non-fruiting tree (χ² = 8.38, df = 1, p-value = 0.00379) has a statistically significant effect on the probability of roadkill as their p-value is less than 0.05.
Load and review data
trees <- read_excel("C:/Users/charl/OneDrive - Nanyang Technological University/ES Mods/ES3307 Experimental Design/Assignment 1/Assignment 1 data.xls", sheet = "Dioecious trees")
# Check data structure and summary
str(trees)
## tibble [50 × 3] (S3: tbl_df/tbl/data.frame)
## $ SEX : num [1:50] 2 1 2 1 2 1 2 2 1 1 ...
## $ DBH : num [1:50] 174 171 212 187 227 274 86 161 285 182 ...
## $ FLOWERS: num [1:50] 234 110 512 133 1107 ...
summary(trees)
## SEX DBH FLOWERS
## Min. :1.0 Min. : 68.0 Min. : 1.0
## 1st Qu.:1.0 1st Qu.:140.5 1st Qu.: 115.8
## Median :2.0 Median :188.0 Median : 253.0
## Mean :1.6 Mean :191.6 Mean : 383.5
## 3rd Qu.:2.0 3rd Qu.:242.0 3rd Qu.: 533.0
## Max. :2.0 Max. :316.0 Max. :1482.0
# Convert SEX to factor variable
trees$SEX <- as.factor(trees$SEX)
str(trees)
## tibble [50 × 3] (S3: tbl_df/tbl/data.frame)
## $ SEX : Factor w/ 2 levels "1","2": 2 1 2 1 2 1 2 2 1 1 ...
## $ DBH : num [1:50] 174 171 212 187 227 274 86 161 285 182 ...
## $ FLOWERS: num [1:50] 234 110 512 133 1107 ...
Plot the raw data - Plot the data to show the effect of DBH and SEX on the number of flowers in dioecious trees. - What does the plot tell you about the relationship between DBH and SEX? ### Plot the effect of DBH and SEX on the number of flowers
trees %>%
ggplot(aes(x = DBH, y = FLOWERS, colour = SEX)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) + # draw best fit line
labs(x = "DBH", y = "Number of Flowers", colour = "Sex") +
scale_colour_manual(name = "Sex",
values = c("lightblue", "lightpink"),
labels = c("1" = "Male", "2" = "Female")) +
theme_minimal()
Fig 2A. Scatterplot of how SEX and DBH affects the number of flowers
For male and female trees, as DBH increases, number of flowers increases as well. However, the number of flowers produced by female trees increases at a much faster rate than male ones as DBH increases.
Fit linear model - Based on the plot in Question 2A, fit an appropriate linear model to determine the effect of DBH and SEX on the number of flowers. - Run diagnostic plots to check your model fit. - Report and interpret your results.
# Fit a linear model with both explanatory variables
trees_m1 <- lm(FLOWERS ~ SEX * DBH, data = trees)
# Check model fit
anova(trees_m1)
## Analysis of Variance Table
##
## Response: FLOWERS
## Df Sum Sq Mean Sq F value Pr(>F)
## SEX 1 171841 171841 8.1186 0.006534 **
## DBH 1 5868927 5868927 277.2748 < 2.2e-16 ***
## SEX:DBH 1 174671 174671 8.2523 0.006138 **
## Residuals 46 973657 21166
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Plot diagnostics
par(mfrow = c(2, 2))
plot(trees_m1)
Fig 2B. Diagnostic plots of the linear model: FLOWERS ~ DHB * SEX
From the ANOVA table, interaction between SEX and DBH is statistically significant (F1,46 = 8.25, p = 0.00614) as p-value falls below 0.05. However, from the diagnostic plots, the residuals vs fitted graph shows that the linear model may not be the best fit for the data as the points are not randomly distributed about the y = 0 line and shows funnelling. The residuals mostly follow the QQ line but deviates near the upper tail suggesting non-normal residuals. The scale-location graph shows that the residuals have slight heteroscedasticity as the points increase near the right end.
Transform your data - Based on the diagnostic plots in Question 1B, transform your y variable using an appropriate transformation and fit the linear model again. - Explain why you have chosen this transformation. - Plot the model predicted lines and confidence intervals with your original data. (Hint: Plot your predicted lines and confidence interval on the same scale as your original data.) - Run diagnostic plots to check your model fit. - Report and interpret your results.
# Transform the data
# Raw y
t1 <- trees %>%
ggplot(aes(x = FLOWERS, fill = SEX)) +
geom_histogram(binwidth = 40) +
labs(x = "Number of Flowers", y = "Count") +
scale_fill_manual(
name = "Sex",
values = c("lightblue", "lightpink"),
labels = c("1" = "Male", "2" = "Female")
)
# sqrt(y)
t2 <- trees %>%
ggplot(aes(x = sqrt(FLOWERS), fill = SEX)) +
geom_histogram(binwidth = 1) +
labs(x = "sqrt(Number of Flowers)", y = "Count") +
scale_fill_manual(
name = "Sex",
values = c("lightblue", "lightpink"),
labels = c("1" = "Male", "2" = "Female")
)
# log(y)
t3 <- trees %>%
ggplot(aes(x = log(FLOWERS), fill = SEX)) +
geom_histogram(binwidth = 0.2) +
labs(x = "log(Number of Flowers)", y = "Count") +
scale_fill_manual(
name = "Sex",
values = c("lightblue", "lightpink"),
labels = c("1" = "Male", "2" = "Female")
)
# Combine the graphs
t1 + t2 + t3 +
plot_layout(guides = "collect") +
plot_annotation(tag_levels = "A")
Fig 2Ca. Histogram of different y transformation data
A square root transformation looks the most normally distributed, while the log-transformed looks left-skewed hence the square root transformation will be selected for subsequent tests.
# Fit a linear model with both explanatory variables and sqrt transformed y
trees_m2 <- lm(sqrt(FLOWERS) ~ DBH * SEX, data = trees)
# Check model fit
anova(trees_m2)
## Analysis of Variance Table
##
## Response: sqrt(FLOWERS)
## Df Sum Sq Mean Sq F value Pr(>F)
## DBH 1 4320.2 4320.2 782.7191 < 2.2e-16 ***
## SEX 1 387.2 387.2 70.1451 8.324e-11 ***
## DBH:SEX 1 34.8 34.8 6.3002 0.01565 *
## Residuals 46 253.9 5.5
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Model predicted lines and CI
pred_m2 <- ggpredict(trees_m2, terms = c("DBH", "SEX"))
# Plot model predicted lines and CI
ggplot() +
geom_point(aes(x = DBH, y = FLOWERS, colour = SEX),
data = trees) + # original
geom_line(aes(x = x, y = predicted, colour = group),
data = pred_m2) + # predicted lines
geom_ribbon(aes(x = x, ymin = conf.low, ymax = conf.high, fill = group),
alpha = 0.6, # 95% CI
data = pred_m2) +
labs(x = "DBH", y = "Number of Flowers", colour = "Sex") +
guides(fill = FALSE) +
scale_colour_manual(values = c("1" = "blue", "2" = "hotpink"),
labels = c("1" = "Male", "2" = "Female")) +
scale_fill_manual(values = c("1" = "lightblue", "2" = "lightpink"))
Fig 2Cb. Model predicted lines and 95% CI, sqrt transformed y LM
par(mfrow = c(2, 2))
plot(trees_m2)
Fig 2Cc. Diagnostic plots for transformed data with model
Based on the ANOVA table, interaction between DBH and SEX is statistically significant (F1,46 = 6.30, p = 0.0157). The transformed square root y data with its predicted lines look a much better fit (Fig 2Cb) than the linear model (Fig 2A) where the shortest distance between points and the best fit line is reduced. After transformation, the residuals look more normally distributed about the y = 0 line (Fig 2Cc).
Fit a generalised linear model (GLM) - Poisson - As the number of flowers is count data, we can instead fit a GLM to the data. Now, fit a GLM using a Poisson distribution to determine the effect of DBH and SEX on the number of flowers. - Plot the model predicted lines and confidence intervals with your original data. (Hint: Plot your predicted lines and confidence interval on the same scale as your original data.) - Run diagnostic plots to check your model fit. - Report and interpret your results. - What do you notice about the dispersion parameter in the summary() output?
# GLM Poisson
trees_m3 <- glm(FLOWERS ~ DBH * SEX, family = poisson, data = trees)
# Model predicted lines and CI
pred_m3 <- ggpredict(trees_m3, terms = c("DBH", "SEX"))
# Plot model predicted lines and CI
ggplot() +
geom_point(aes(x = DBH, y = FLOWERS, colour = SEX),
data = trees) + # original
geom_line(aes(x = x, y = predicted, colour = group),
data = pred_m3) + # predicted lines
geom_ribbon(aes(x = x, ymin = conf.low, ymax = conf.high, fill = group),
alpha = 0.6, # 95% CI
data = pred_m3) +
labs(x = "DBH", y = "Number of Flowers", colour = "Sex") +
guides(fill = FALSE) +
scale_colour_manual(values = c("1" = "blue", "2" = "hotpink"),
labels = c("1" = "Male", "2" = "Female")) +
scale_fill_manual(values = c("1" = "lightblue", "2" = "lightpink"))
Fig 2Da. Model predicted lines and 95% CI, raw y Poisson GLM
# Diagnostic plots
par(mfrow = c(2, 2))
plot(trees_m3)
Fig 2Db. Diagnostic plots
# ANOVA, summary, dispersion parameter
anova(trees_m3) # deviance for GLM
## Analysis of Deviance Table
##
## Model: poisson, link: log
##
## Response: FLOWERS
##
## Terms added sequentially (first to last)
##
##
## Df Deviance Resid. Df Resid. Dev Pr(>Chi)
## NULL 49 17921.6
## DBH 1 14343.9 48 3577.6 < 2.2e-16 ***
## SEX 1 1090.9 47 2486.7 < 2.2e-16 ***
## DBH:SEX 1 28.1 46 2458.7 1.168e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(trees_m3)
##
## Call:
## glm(formula = FLOWERS ~ DBH * SEX, family = poisson, data = trees)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 2.2567217 0.0798972 28.245 < 2e-16 ***
## DBH 0.0150589 0.0003198 47.087 < 2e-16 ***
## SEX2 0.9453136 0.0869851 10.868 < 2e-16 ***
## DBH:SEX2 -0.0018137 0.0003460 -5.242 1.59e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for poisson family taken to be 1)
##
## Null deviance: 17921.6 on 49 degrees of freedom
## Residual deviance: 2458.7 on 46 degrees of freedom
## AIC: 2811.6
##
## Number of Fisher Scoring iterations: 5
The 95% confidence interval for the Poisson distribution GLM is much smaller than that of an LM (Fig 2Da). The residuals vs fitted line is not as horizontal and points are not randomly distributed about the line as well compared to an LM (Fig 2Db). Additionally, there are a few points that line outside of Cook’s distance line hence considered as outliers, likely due to the smaller 95% CI. From ANOVA, the interaction between DBH and SEX is still statistically significant (χ²1,46 = 28.1, p = <0.01).
From summary, the ratio of residual deviance to df is 2458.7/46 = 53.45, suggesting overdispersion as this ratio is much higher than 1.
Fit a generalised linear model (GLM) - Quasipoisson - Now, fit a GLM with a Quasipoisson distribution. What has changed in your Quasipoisson model results? - Plot the model predicted lines and confidence intervals with your original data. (Hint: Plot your predicted lines and confidence interval on the same scale as your original data.) - Run diagnostic plots to check your model fit. - Report and interpret your results.
# Quasipoisson model
trees_m4 <- glm(FLOWERS ~ DBH * SEX, family = quasipoisson, data = trees)
# Check for significant interaction, F test for quasi
anova(trees_m4, test = "F")
## Analysis of Deviance Table
##
## Model: quasipoisson, link: log
##
## Response: FLOWERS
##
## Terms added sequentially (first to last)
##
##
## Df Deviance Resid. Df Resid. Dev F Pr(>F)
## NULL 49 17921.6
## DBH 1 14343.9 48 3577.6 282.4870 < 2.2e-16 ***
## SEX 1 1090.9 47 2486.7 21.4837 2.958e-05 ***
## DBH:SEX 1 28.1 46 2458.7 0.5529 0.4609
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(trees_m4)
##
## Call:
## glm(formula = FLOWERS ~ DBH * SEX, family = quasipoisson, data = trees)
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.256722 0.569334 3.964 0.000255 ***
## DBH 0.015059 0.002279 6.608 3.54e-08 ***
## SEX2 0.945314 0.619840 1.525 0.134083
## DBH:SEX2 -0.001814 0.002466 -0.736 0.465714
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for quasipoisson family taken to be 50.77736)
##
## Null deviance: 17921.6 on 49 degrees of freedom
## Residual deviance: 2458.7 on 46 degrees of freedom
## AIC: NA
##
## Number of Fisher Scoring iterations: 5
Since interaction between DBH and SEX is not statistically significant (F1,46 = 0.553, p = 0.461), the interaction term can be removed before calculating predicted means and confidence intervals.
# Quasipoisson model with no interaction
trees_m5 <- glm(FLOWERS ~ DBH + SEX, family = quasipoisson, data = trees)
anova(trees_m5, test = "F")
## Analysis of Deviance Table
##
## Model: quasipoisson, link: log
##
## Response: FLOWERS
##
## Terms added sequentially (first to last)
##
##
## Df Deviance Resid. Df Resid. Dev F Pr(>F)
## NULL 49 17921.6
## DBH 1 14343.9 48 3577.6 284.572 < 2.2e-16 ***
## SEX 1 1090.9 47 2486.7 21.642 2.698e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
summary(trees_m5)
##
## Call:
## glm(formula = FLOWERS ~ DBH + SEX, family = quasipoisson, data = trees)
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.6343031 0.2301817 11.444 3.45e-15 ***
## DBH 0.0135204 0.0008678 15.579 < 2e-16 ***
## SEX2 0.4982782 0.1097203 4.541 3.89e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for quasipoisson family taken to be 50.4053)
##
## Null deviance: 17921.6 on 49 degrees of freedom
## Residual deviance: 2486.7 on 47 degrees of freedom
## AIC: NA
##
## Number of Fisher Scoring iterations: 5
# Calculate model predicted lines and CI
pred_m5 <- ggpredict(trees_m5, terms = c("DBH", "SEX"))
# Plot model predicted lines and CI
ggplot() +
geom_point(aes(x = DBH, y = FLOWERS, colour = SEX),
data = trees) + # original
geom_line(aes(x = x, y = predicted, colour = group),
data = pred_m5) + # predicted lines
geom_ribbon(aes(x = x, ymin = conf.low, ymax = conf.high, fill = group),
alpha = 0.6, # 95% CI
data = pred_m5) +
labs(x = "DBH", y = "Number of Flowers", colour = "Sex") +
guides(fill = FALSE) +
scale_colour_manual(values = c("1" = "blue", "2" = "hotpink"),
labels = c("1" = "Male", "2" = "Female")) +
scale_fill_manual(values = c("1" = "lightblue", "2" = "lightpink"))
Fig 2Ea. Model predicted lines and 95% CI, quasipoisson model
# Diagnostic plots
par(mfrow = c(2, 2))
plot(trees_m5)
Fig 2Eb. Diagnostic plots
For the quasipoisson GLM, the outliers flagged out in Poisson GLM have been addressed and there are no more outliers in this new model. The residuals are generally normally distributed from the QQ plot with slight deviation at the upper tail, but this suggests that the model is a good fit, in addition to the points randomly scattered around the residuals v fitted line (Fig 2Eb). DBH and SEX have a statistically significant effect on number of flowers as their p-values are less than 0.05 (DBH: F1,48 = 285, p = <0.01; SEX: F1,47 = 21.6, p = <0.01).
From the above models, which model would you select as the final model? Explain your reasoning. The quasipoisson model as it can handle non-normal data like the number of flowers which is a count data as it is bounded by 0, unlike the LM which involved square root transform of y to get a better fit. It is able to plot the data on the original data without needing to transform the response to fit the model, and it can handle outliers better than Poisson after adjusting for overdispersion.
What are the advantages and disadvantages associated with running a linear model or a generalised linear model? Linear models are easier to interpret and simpler but have stricter assumptions like linearity, normally distributed residuals, constant variance, and independent observations. Count, proportion and binomial data rarely fulfill these assumptions as they are bounded by 0 and or 1 thus linear models are less suited for these data and generalised linear models can handle them better. However, GLMs often are harder to interpret as coefficients are on the link scale thus some manipulation is needed to obtain comparable values.
Load and review data
dung <- read.csv("C:/Users/charl/OneDrive - Nanyang Technological University/ES Mods/ES3307 Experimental Design/Assignment 4/Assignment 4 data/dungbeetletraptype.csv")
# Check data structure and summary
str(dung)
## 'data.frame': 304 obs. of 46 variables:
## $ trapID : chr "2005_FITs-BRL.2.1" "2005_FITs-BRL.2.2" "2005_FITs-BRL.2.3" "2005_FITs-BRL.2.4" ...
## $ year : int 2005 2005 2005 2005 2005 2005 2005 2005 2005 2005 ...
## $ trap : chr "FIT" "FIT" "FIT" "FIT" ...
## $ habitat : chr "Old-growth Forest" "Old-growth Forest" "Old-growth Forest" "Old-growth Forest" ...
## $ Catharsius.dayacus : int 0 0 1 0 0 0 0 0 0 0 ...
## $ Catharsius.renaudpauliani : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Copris.agnus : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Copris.sinicus : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Microcopris.doriae : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Microcopris.hidakai : int 0 0 0 0 1 0 1 0 0 0 ...
## $ Ochicanthon.worroae : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Oniticellus.tessellatus : int 2 3 2 5 4 6 2 4 0 2 ...
## $ Onthophagus..Indachorius..aff.aereopictus: int 3 6 2 2 2 0 2 0 3 0 ...
## $ Onthophagus..Indachorius..aff.cheyi : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus..Indachorius..aff.danumensis : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus..Indachorius..aff.woroae : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.aff.pacificus : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.aff.phanaeides : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.aff.rutilans : int 0 0 0 0 1 0 0 2 0 0 ...
## $ Onthophagus.aphodiodes : int 0 0 0 1 0 0 0 0 0 0 ...
## $ Onthophagus.borneensis : int 0 0 0 0 0 0 3 0 1 1 ...
## $ Onthophagus.cervicapra : int 0 3 2 2 1 2 1 2 2 1 ...
## $ Onthophagus.deflexicollis : int 1 1 2 0 0 0 1 1 0 0 ...
## $ Onthophagus.hirsutulus : int 1 2 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.incisus : int 0 0 0 1 0 0 2 0 1 1 ...
## $ Onthophagus.johkii : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.mulleri : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.obscurior : int 0 0 0 0 0 0 0 0 0 1 ...
## $ Onthophagus.pacificus : int 0 0 3 2 0 1 2 0 2 2 ...
## $ Onthophagus.parviobscurior : int 0 1 0 0 0 0 0 2 0 0 ...
## $ Onthophagus.pavidus : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.rorarius : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.rudis : int 5 7 9 6 8 4 4 2 2 5 ...
## $ Onthophagus.rugicollis : int 5 3 3 2 2 8 5 7 6 7 ...
## $ Onthophagus.sarawacus : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.semiaureus : int 2 4 5 2 3 6 5 6 7 6 ...
## $ Onthophagus.semicupreus : int 1 3 2 1 2 2 2 3 4 0 ...
## $ Onthophagus.sp.08 : int 3 0 4 1 0 2 2 4 3 2 ...
## $ Onthophagus.sp.20 : int 0 0 0 0 0 0 0 0 0 0 ...
## $ Onthophagus.vethi : int 0 0 0 1 0 0 0 1 0 0 ...
## $ Onthophagus.vulpes : int 0 2 2 1 3 2 2 3 1 4 ...
## $ Paragymnopleurus.maurus : int 0 0 0 0 0 1 0 0 0 0 ...
## $ Paragymnopleurus.sparsus : int 2 3 1 2 1 0 0 0 0 5 ...
## $ Paragymnopleurus.striatus : int 0 0 0 0 1 0 0 0 0 0 ...
## $ Proagoderus.watanabei : int 0 0 0 0 0 0 0 0 1 0 ...
## $ Sisyphus.thoracicus : int 4 6 5 3 6 7 9 5 8 6 ...
summary(dung)
## trapID year trap habitat
## Length:304 Min. :2005 Length:304 Length:304
## Class :character 1st Qu.:2005 Class :character Class :character
## Mode :character Median :2012 Mode :character Mode :character
## Mean :2009
## 3rd Qu.:2012
## Max. :2012
## Catharsius.dayacus Catharsius.renaudpauliani Copris.agnus
## Min. : 0.000 Min. : 0.000 Min. : 0.0000
## 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.0000
## Median : 0.000 Median : 0.000 Median : 0.0000
## Mean : 1.217 Mean : 3.191 Mean : 0.2368
## 3rd Qu.: 0.000 3rd Qu.: 2.000 3rd Qu.: 0.0000
## Max. :23.000 Max. :32.000 Max. :13.0000
## Copris.sinicus Microcopris.doriae Microcopris.hidakai Ochicanthon.worroae
## Min. : 0.0000 Min. : 0.0000 Min. : 0.0000 Min. :0.0000
## 1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.:0.0000
## Median : 0.0000 Median : 0.0000 Median : 0.0000 Median :0.0000
## Mean : 0.2928 Mean : 0.4375 Mean : 0.5757 Mean :0.2138
## 3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.:0.0000
## Max. :20.0000 Max. :36.0000 Max. :33.0000 Max. :4.0000
## Oniticellus.tessellatus Onthophagus..Indachorius..aff.aereopictus
## Min. : 0.000 Min. : 0.0000
## 1st Qu.: 0.000 1st Qu.: 0.0000
## Median : 0.000 Median : 0.0000
## Mean : 1.062 Mean : 0.4868
## 3rd Qu.: 2.000 3rd Qu.: 0.0000
## Max. :14.000 Max. :12.0000
## Onthophagus..Indachorius..aff.cheyi Onthophagus..Indachorius..aff.danumensis
## Min. : 0.0000 Min. : 0.0000
## 1st Qu.: 0.0000 1st Qu.: 0.0000
## Median : 0.0000 Median : 0.0000
## Mean : 0.4737 Mean : 0.3026
## 3rd Qu.: 0.0000 3rd Qu.: 0.0000
## Max. :14.0000 Max. :13.0000
## Onthophagus..Indachorius..aff.woroae Onthophagus.aff.pacificus
## Min. : 0.0000 Min. :0.0000
## 1st Qu.: 0.0000 1st Qu.:0.0000
## Median : 0.0000 Median :0.0000
## Mean : 0.4178 Mean :0.3059
## 3rd Qu.: 0.0000 3rd Qu.:0.0000
## Max. :37.0000 Max. :7.0000
## Onthophagus.aff.phanaeides Onthophagus.aff.rutilans Onthophagus.aphodiodes
## Min. :0.00000 Min. :0.0000 Min. :0.0000
## 1st Qu.:0.00000 1st Qu.:0.0000 1st Qu.:0.0000
## Median :0.00000 Median :0.0000 Median :0.0000
## Mean :0.09868 Mean :0.1776 Mean :0.1974
## 3rd Qu.:0.00000 3rd Qu.:0.0000 3rd Qu.:0.0000
## Max. :6.00000 Max. :4.0000 Max. :5.0000
## Onthophagus.borneensis Onthophagus.cervicapra Onthophagus.deflexicollis
## Min. : 0.0000 Min. : 0.000 Min. : 0.0000
## 1st Qu.: 0.0000 1st Qu.: 1.000 1st Qu.: 0.0000
## Median : 0.0000 Median : 3.000 Median : 0.0000
## Mean : 0.7303 Mean : 8.599 Mean : 0.7763
## 3rd Qu.: 0.0000 3rd Qu.: 8.000 3rd Qu.: 1.0000
## Max. :14.0000 Max. :125.000 Max. :18.0000
## Onthophagus.hirsutulus Onthophagus.incisus Onthophagus.johkii
## Min. :0.0000 Min. : 0.000 Min. : 0.0000
## 1st Qu.:0.0000 1st Qu.: 0.000 1st Qu.: 0.0000
## Median :0.0000 Median : 2.000 Median : 0.0000
## Mean :0.3355 Mean : 3.796 Mean : 0.4342
## 3rd Qu.:0.0000 3rd Qu.: 5.000 3rd Qu.: 0.0000
## Max. :5.0000 Max. :55.000 Max. :17.0000
## Onthophagus.mulleri Onthophagus.obscurior Onthophagus.pacificus
## Min. : 0.0000 Min. : 0.00 Min. : 0.000
## 1st Qu.: 0.0000 1st Qu.: 0.00 1st Qu.: 0.000
## Median : 0.0000 Median : 0.00 Median : 0.000
## Mean : 0.8092 Mean : 2.26 Mean : 0.875
## 3rd Qu.: 1.0000 3rd Qu.: 3.00 3rd Qu.: 1.000
## Max. :11.0000 Max. :34.00 Max. :10.000
## Onthophagus.parviobscurior Onthophagus.pavidus Onthophagus.rorarius
## Min. : 0.000 Min. : 0.0000 Min. :0.0000
## 1st Qu.: 0.000 1st Qu.: 0.0000 1st Qu.:0.0000
## Median : 0.000 Median : 0.0000 Median :0.0000
## Mean : 2.023 Mean : 0.3651 Mean :0.2697
## 3rd Qu.: 0.000 3rd Qu.: 0.0000 3rd Qu.:0.0000
## Max. :56.000 Max. :32.0000 Max. :5.0000
## Onthophagus.rudis Onthophagus.rugicollis Onthophagus.sarawacus
## Min. :0.000 Min. : 0.000 Min. :0.0000
## 1st Qu.:0.000 1st Qu.: 0.000 1st Qu.:0.0000
## Median :0.000 Median : 1.000 Median :0.0000
## Mean :1.533 Mean : 2.993 Mean :0.3257
## 3rd Qu.:2.000 3rd Qu.: 3.000 3rd Qu.:0.0000
## Max. :9.000 Max. :55.000 Max. :8.0000
## Onthophagus.semiaureus Onthophagus.semicupreus Onthophagus.sp.08
## Min. : 0.000 Min. : 0.0000 Min. :0.000
## 1st Qu.: 0.000 1st Qu.: 0.0000 1st Qu.:0.000
## Median : 1.000 Median : 0.0000 Median :0.000
## Mean : 1.618 Mean : 0.8289 Mean :0.273
## 3rd Qu.: 2.250 3rd Qu.: 1.0000 3rd Qu.:0.000
## Max. :10.000 Max. :13.0000 Max. :5.000
## Onthophagus.sp.20 Onthophagus.vethi Onthophagus.vulpes Paragymnopleurus.maurus
## Min. : 0.0000 Min. :0.0000 Min. : 0.000 Min. : 0.000
## 1st Qu.: 0.0000 1st Qu.:0.0000 1st Qu.: 0.000 1st Qu.: 0.000
## Median : 0.0000 Median :0.0000 Median : 1.000 Median : 0.000
## Mean : 0.6151 Mean :0.1842 Mean : 3.286 Mean : 2.691
## 3rd Qu.: 0.0000 3rd Qu.:0.0000 3rd Qu.: 3.000 3rd Qu.: 1.000
## Max. :14.0000 Max. :4.0000 Max. :82.000 Max. :93.000
## Paragymnopleurus.sparsus Paragymnopleurus.striatus Proagoderus.watanabei
## Min. : 0.000 Min. : 0.000 Min. : 0.000
## 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.000
## Median : 2.000 Median : 0.000 Median : 1.000
## Mean : 4.826 Mean : 1.128 Mean : 4.855
## 3rd Qu.: 5.000 3rd Qu.: 0.000 3rd Qu.: 5.000
## Max. :71.000 Max. :39.000 Max. :62.000
## Sisyphus.thoracicus
## Min. : 0.000
## 1st Qu.: 0.000
## Median : 2.000
## Mean : 5.523
## 3rd Qu.: 6.000
## Max. :92.000
# Filter only relevant columns
env <- dung[,1:4]
species <- dung[,5:46]
To visualise and determine the community composition of dung beetles NMDS will be used as it is good to simplify complex multivariate data into just 2D in this case. This will help with visualising whether there is overlap of community composition of dung beetles between the 2 traps or not.
Based on the analysis you have selected, plot and interpret your ordination plot.
# AI has helped in generating this portion of code, comments have been self-written
library(vegan)
# Large count range of beetles, helps to reduce skewness of count
species_sqrt <- sqrt(species)
# Prevent plot from changing configuration, keeps stress value constant
set.seed(123)
nmds <- metaMDS(species_sqrt, distance = "bray", # bray curtis dissimilarity for sp counts
k = 3, # returns higher linear R^2 fit than 2
trymax = 100,
autotransform = FALSE)
## Run 0 stress 0.1500979
## Run 1 stress 0.1516075
## Run 2 stress 0.1518805
## Run 3 stress 0.1519982
## Run 4 stress 0.1521327
## Run 5 stress 0.1510832
## Run 6 stress 0.1515884
## Run 7 stress 0.1506867
## Run 8 stress 0.1510136
## Run 9 stress 0.1503572
## ... Procrustes: rmse 0.004623916 max resid 0.06803683
## Run 10 stress 0.1528186
## Run 11 stress 0.1501245
## ... Procrustes: rmse 0.001803074 max resid 0.0252112
## Run 12 stress 0.150426
## ... Procrustes: rmse 0.005147621 max resid 0.0678934
## Run 13 stress 0.1506639
## Run 14 stress 0.1513501
## Run 15 stress 0.1503501
## ... Procrustes: rmse 0.004200838 max resid 0.0678212
## Run 16 stress 0.1506543
## Run 17 stress 0.1566428
## Run 18 stress 0.1502198
## ... Procrustes: rmse 0.003505473 max resid 0.03804821
## Run 19 stress 0.1503806
## ... Procrustes: rmse 0.004550322 max resid 0.06795373
## Run 20 stress 0.1518811
## Run 21 stress 0.1523813
## Run 22 stress 0.1524573
## Run 23 stress 0.1503507
## ... Procrustes: rmse 0.004208162 max resid 0.06785644
## Run 24 stress 0.1520601
## Run 25 stress 0.1510834
## Run 26 stress 0.1500982
## ... Procrustes: rmse 0.0003498857 max resid 0.003893322
## ... Similar to previous best
## *** Best solution repeated 1 times
# Plot NMDS stress plot
nmds$stress
## [1] 0.1500979
stressplot(nmds)
Fig 3Aa. NMDS stress plot. Fig 3Ab. NMDS plot of dung beetle composition separated by trap type.
# Separate coordinates by trap type
nmds_df <- as.data.frame(scores(nmds, display = "sites"))
nmds_df$trap <- env$trap
ggplot(nmds_df, aes(NMDS1, NMDS2, colour = trap, fill = trap)) +
geom_point(alpha = 0.6) +
stat_ellipse(geom = "polygon", level = 0.95, alpha = 0.2) + # colour ellipse
labs(colour = "Trap type", fill = "Trap type") +
theme_minimal()
Fig 3Aa. NMDS stress plot. Fig 3Ab. NMDS plot of dung beetle composition separated by trap type.
Stress = 0.150
Using appropriate statistical tests, report and interpret your results.
# AI has helped in generating this portion of code, comments have been self-written
dist_bc <- vegdist(species_sqrt, method = "bray")
# Check if community centroids differ between trap types using PERMANOVA
adonis2(dist_bc ~ trap, data = env, permutations = 999)
## Permutation test for adonis under reduced model
## Permutation: free
## Number of permutations: 999
##
## adonis2(formula = dist_bc ~ trap, data = env, permutations = 999)
## Df SumOfSqs R2 F Pr(>F)
## Model 1 13.966 0.19929 75.164 0.001 ***
## Residual 302 56.113 0.80071
## Total 303 70.078 1.00000
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Check if the spread within each group the same using PERMDISP
bd <- betadisper(dist_bc, env$trap)
anova(bd)
## Analysis of Variance Table
##
## Response: Distances
## Df Sum Sq Mean Sq F value Pr(>F)
## Groups 1 0.0006 0.0005802 0.0482 0.8265
## Residuals 302 3.6388 0.0120491
permutest(bd, permutations = 999)
##
## Permutation test for homogeneity of multivariate dispersions
## Permutation: free
## Number of permutations: 999
##
## Response: Distances
## Df Sum Sq Mean Sq F N.Perm Pr(>F)
## Groups 1 0.0006 0.0005802 0.0482 999 0.832
## Residuals 302 3.6388 0.0120491
boxplot(bd)
Fig 3B. Boxplot of distance to centroid for both trap types.
Dung beetle community composition differed significantly between pitfall and flight interception traps (PERMANOVA: pseudo-F1,302 = 75.16, R² = 0.20, p = 0.001), with trap type explaining about 20% of the variation in community composition. Multivariate dispersion did not differ between trap types (PERMDISP: F1,302 = 0.048, p = 0.83), indicating that the difference reflects a shift in community composition and not differences in within-group variability. This is consistent with the NMDS ordination where the two trap types formed distinct clusters with similar spread. From the box plot (Fig 3B), the medians for BPT and FIT are similar and the box sizes overlap almost completely, agreeing with PERMDISP where the two trap types have similar spread.