The U.S National Science Foundation’s National Ecological Observatory Network is a continental-scale observation facility designed to collect long-term ecological data. The data product being used contains the quality-controlled, native sampling resolution data from NEON’s breeding landbird sampling.
Protocols: https://data.neonscience.org/documents/10179/1883155/NEON.DOC.014041vL/2a13bc2b-84db-e4e5-1d33-af8fac1f9e24 Data Product: https://data.neonscience.org/data-products/DP1.10003.001
Occupancy models are used to help estimate true occupancy of a species and can help account for imperfect detection of organisms in a study.
Occupancy (psi) = the probability that a species occurs at a site. Detection (p) = the probability that a species is detected at a site, given occupancy
library(neonUtilities) #package for downloading NEON data
## Warning: package 'neonUtilities' was built under R version 4.3.3
library(RPresence) # library for doing occupancy modelling
## Warning: package 'RPresence' was built under R version 4.4.1
## RPresence_2.15.9
## - new: static staggered-entry model (type="so.se") added
## - new: integrated habitat model (type="do.ih") added
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(tidyr)
## Warning: package 'tidyr' was built under R version 4.3.3
library(stringr)
library(ggplot2) # library for visualization
## Warning: package 'ggplot2' was built under R version 4.3.3
large_sites <- c(
"BARR", "BART", "BONA", "CLBJ", "CPER", "DEJU", "DSNY", "GRSM",
"GUAN", "HARV", "HEAL", "JERC", "JORN", "KONZ", "MLBS", "MOAB",
"NIWO", "OAES", "ONAQ", "ORNL", "OSBS", "RMNP", "SJER", "SRER",
"STEI", "TALL", "TEAK", "UKFS", "UNDE", "WOOD", "WREF", "YELL"
)
small_sites <- c(
"DELA", "LENO", "TOOL", "SOAP", "STER", "PUUM", "KONA",
"SERC", "DCFS", "NOGP", "LAJA", "BLAN", "SCBI", "ABBY", "TREE"
)
sites <- c(large_sites, small_sites)
if (!dir.exists("data")) {
dir.create("data")
}
if (!dir.exists("outputs")) {
dir.create("outputs")
}
if (file.exists("data/bird_counts.RData")) {
load("data/bird_counts.RData")
} else {
###todo: add NEON token
bird.counts <- loadByProduct(dpID="DP1.10003.001",
site=sites,
startdate="2018-01",
enddate="2023-12",
check.size = F)
save(bird.counts, file = "data/bird_counts.RData")
}
Describe how the data are set up (i.e., there are two tables, with what data in them?)
CountData:
# Time distribution (number of observations per year)
bird.counts$brd_perpoint %>%
mutate(year = lubridate::year(startDate)) %>%
ggplot(aes(x = factor(year))) +
geom_bar(fill = "forestgreen") +
ylim(0, 4000) +
labs(
title = "Distribution of Observations Over Time",
x = "Year",
y = "Number of Observations"
) +
theme_minimal()
# % missing for each column
missing_pct <- sapply(bird.counts$brd_perpoint, function(x) {
round(sum(is.na(x)) / length(x) * 100, 2)
})
missing_df <- data.frame(
column = names(missing_pct),
missing_percent = missing_pct
) %>%
filter(missing_percent > 0) # show only columns with missing values
ggplot(missing_df, aes(x = reorder(column, -missing_percent), y = missing_percent)) +
geom_bar(stat = "identity", fill = "forestgreen") +
labs(
title = "Percentage of Missing Data by Column",
x = "Column",
y = "Missing Data (%)"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
# Distribution by site
ggplot(bird.counts$brd_perpoint, aes(x = siteID)) +
geom_bar(fill = "forestgreen") +
labs(title = "Distribution of Observations by Site", x = "Site ID", y = "Count") +
theme(axis.text.x = element_text(angle = 90))
# Species by site
grouped_data <- bird.counts$brd_countdata %>%
group_by(siteID)
summarized_data <- grouped_data %>%
summarize(num_species = n_distinct(scientificName))
ggplot(summarized_data, aes(x = siteID, y = num_species)) +
geom_bar(stat = "identity", fill = "forestgreen") +
labs(title = "Number of Species Observed by Site", x = "Site ID", y = "Number of Species") +
theme(axis.text.x = element_text(angle = 90))
PerPoint
# Time distribution (number of observations per year)
bird.counts$brd_countdata %>%
mutate(year = lubridate::year(startDate)) %>%
ggplot(aes(x = factor(year))) +
geom_bar(fill = "steelblue") +
labs(
title = "Distribution of Observations Over Time",
x = "Year",
y = "Number of Observations"
) +
theme_minimal()
# % missing for each column
missing_pct <- sapply(bird.counts$brd_countdata, function(x) {
round(sum(is.na(x)) / length(x) * 100, 2)
})
missing_df <- data.frame(
column = names(missing_pct),
missing_percent = missing_pct
) %>%
filter(missing_percent > 0)
ggplot(missing_df, aes(x = reorder(column, -missing_percent), y = missing_percent)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(
title = "Percentage of Missing Data by Column",
x = "Column",
y = "Missing Data (%)"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
# Observations by site
ggplot(bird.counts$brd_countdata, aes(x = siteID)) +
geom_bar(fill = "steelblue") +
labs(title = "Distribution of Observations by Site", x = "Site ID", y = "Count") +
theme(axis.text.x = element_text(angle = 90))
# Species by site
grouped_data <- bird.counts$brd_countdata %>%
group_by(siteID)
summarized_data <- grouped_data %>%
summarize(num_species = n_distinct(scientificName))
ggplot(summarized_data, aes(x = siteID, y = num_species)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(title = "Number of Species Observed by Site", x = "Site ID", y = "Number of Species") +
theme(axis.text.x = element_text(angle = 90))
First, we need to do some generic data cleanup.
brd_perpoint_clean <- bird.counts$brd_perpoint %>%
mutate(year = str_extract(eventID, "\\d{4}"))%>%
mutate(pointSurveyID = paste(plotID, "point", pointID, year, "bout", boutNumber, sep = "_")) %>%
mutate(locationID = paste(plotID, "point", pointID, sep = "_")) %>%
mutate(timeID = paste(year, "bout", boutNumber, sep = "_"))
brd_countdata_clean <- bird.counts$brd_countdata %>%
mutate(year = str_extract(eventID, "\\d{4}"))%>%
mutate(pointSurveyID = paste(plotID, "point", pointID, year, "bout", boutNumber, sep = "_")) %>%
mutate(locationID = paste(plotID, "point", pointID, sep = "_")) %>%
mutate(timeID = paste(year, "bout", boutNumber, sep = "_"))
Various data issues.
A large site should have points 1-9 for each plot. If it doesn’t, something is wrong. Remove the whole plot survey if missing a point survey
Note: this isn’t a huge deal for these analyses but this is a problem. Needs to be above any survey removal if using.
brd_perpoint_clean <- brd_perpoint_clean %>%
filter(samplingImpractical == "OK")
brd_countdata_clean <- brd_countdata_clean %>%
filter(taxonRank == "species")
Sometimes there are duplicate pointSurveyIDs and there shouldn’t be - likely errors in data input.
duplicate_pointSurveyID <- brd_perpoint_clean %>%
count(pointSurveyID) %>%
filter(n > 1) %>%
pull(pointSurveyID)
duplicate_pointSurveyID
## [1] "HARV_004_point_7_2019_bout_1" "HEAL_006_point_1_2019_bout_1"
## [3] "HEAL_006_point_2_2019_bout_1" "HEAL_006_point_3_2019_bout_1"
## [5] "HEAL_006_point_4_2019_bout_1" "HEAL_006_point_5_2019_bout_1"
## [7] "HEAL_006_point_6_2019_bout_1" "HEAL_006_point_7_2019_bout_1"
## [9] "HEAL_006_point_8_2019_bout_1" "HEAL_006_point_9_2019_bout_1"
## [11] "JERC_011_point_6_2019_bout_1" "SJER_033_point_1_2022_bout_1"
## [13] "SJER_033_point_2_2022_bout_1" "SJER_033_point_3_2022_bout_1"
## [15] "SJER_033_point_4_2022_bout_1" "SJER_033_point_5_2022_bout_1"
## [17] "SJER_033_point_6_2022_bout_1" "SJER_033_point_7_2022_bout_1"
## [19] "SJER_033_point_8_2022_bout_1" "SJER_033_point_9_2022_bout_1"
## [21] "TEAK_031_point_1_2019_bout_1" "TEAK_031_point_2_2019_bout_1"
## [23] "TEAK_031_point_3_2019_bout_1" "TEAK_031_point_4_2019_bout_1"
## [25] "TEAK_031_point_5_2019_bout_1" "TEAK_031_point_6_2019_bout_1"
## [27] "TEAK_031_point_7_2019_bout_1" "TEAK_031_point_8_2019_bout_1"
## [29] "TEAK_031_point_9_2019_bout_1"
#remove both of the duplicate surveys from each table
brd_perpoint_clean <- brd_perpoint_clean %>%
filter(!pointSurveyID %in% duplicate_pointSurveyID)
brd_countdata_clean <- brd_countdata_clean %>%
filter(!pointSurveyID %in% duplicate_pointSurveyID)
Do the join
brd_joineddata_clean <- inner_join(
brd_countdata_clean,
brd_perpoint_clean,
by = "pointSurveyID",
suffix = c(".count", ".perpoint")
)
#Remove duplicate columns between tables
brd_joineddata_clean <- brd_joineddata_clean %>%
select(-matches("\\.perpoint$"))
names(brd_joineddata_clean) <- gsub("\\.count$", "", names(brd_joineddata_clean))
Because there is inconsistent sampling across sites (i.e., some sites are larger than others and not all plots within a site were continuously sampled over time), in order to do analyses across sites, we need to make sure that the data are standardized with respect to amount of sampling time.
Actually - a way to get around this might be to just add a detection covariate for time or number of surveys to account for this.
Great, now that our data are cleaned and standardized, we can start doing some basic analyses. First, lets take a look at a single species within a site in terms of its detection and occupancy.
Melanerpes carolinus (Red-bellied Woodpecker) across all sites
# Filter table by species
bird_species <- "Melanerpes carolinus"
brd_joineddata_model1 <- brd_joineddata_clean %>%
filter(siteID %in% sites) %>%
filter(scientificName == bird_species)
# Get all valid surveys years across sites (so we can fill in NAs)
valid_site_years <- brd_perpoint_clean %>%
distinct(siteID, year)
# Get detections — only species *actually observed at a site* (so we can fill in 0s)
detections <- brd_joineddata_model1 %>%
group_by(siteID, year) %>%
summarize(present = 1, .groups = "drop")
# Join and fill
site_by_year <- valid_site_years %>%
left_join(detections, by = c("siteID", "year")) %>%
mutate(present = replace_na(present, 0)) %>%
pivot_wider(
names_from = year,
values_from = present
)
head(site_by_year, n=10)
birds_pao_model1 <- createPao(
data = site_by_year[, c("2018", "2019", "2020", "2021", "2022", "2023")],
unitnames = site_by_year$siteID,
title = "Red-bellied Woodpecker"
)
birds_occupancy_model1 <- occMod(
data = birds_pao_model1,
model = list(psi ~ 1, p ~ 1),
type = 'so'
)
summary(birds_occupancy_model1)
## Model name=psi(1)p(1)
## AIC=142.8966
## -2*log-likelihood=138.8966
## num. par=2
## Warning: Numerical convergence may not have been reached. Parameter estimates converged to approximately 7 signifcant digits.
This shows that Melanerpes carolinus has an occupancy probability (psi) of 45% (i.e., if you go to any of these sites, there is a 45% chance of this bird occupying the site) and a detection probability (p) of 91% (i.e., if you visit a site and the bird occupies it, there is a 91% chance you will detect it)
#print_one_site_estimates(mod = birds_occupancy_model1, site = 1)
#fitted(object = birds_occupancy_model1, param = 'psi', prob = 0.95)
#fitted(object = birds_occupancy_model1, param = 'p', prob = 0.95)
ests <- as.data.frame(print_one_site_estimates(mod = birds_occupancy_model1))
ests <- ests[1:2,] %>%
mutate(parm = c("psi", "p")) %>%
select(parm, est, se, lower, upper) %>%
rename(.,
c(
Parameter = parm,
Estimate = est,
SE = se,
L95 = lower,
U95 = upper
)
) %>%
`rownames<-`(seq_len(nrow(ests[1:2,])))
ests
ggplot(ests, aes(x = Parameter, y = Estimate)) +
labs(x = "Parameter", y = "Estimate") +
geom_bar(stat = 'identity') +
geom_errorbar(aes(ymin = L95, ymax = U95), width = 0.2) +
theme_bw()
todo: change the vars to be just in scope for the function
run_occ_for_species_list <- function(species_df, brd_joineddata_clean, brd_perpoint_clean, sites, years = c("2018", "2019", "2020", "2021", "2022", "2023")) {
results <- list()
detection_histories <- list()
for (i in seq_len(nrow(species_df))) {
sci_name <- species_df$scientificName[i]
common_name <- species_df$vernacularName[i]
# Filter detection data to this species and the sites
brd_species <- brd_joineddata_clean %>%
filter(siteID %in% sites, scientificName == sci_name)
# Get valid surveys (site-year combos with effort)
valid_site_years <- brd_perpoint_clean %>%
filter(siteID %in% sites) %>%
distinct(siteID, year)
# Get detections for this species
detections <- brd_species %>%
group_by(siteID, year) %>%
summarize(present = 1, .groups = "drop")
# Build site-year detection matrix
site_by_year <- valid_site_years %>%
left_join(detections, by = c("siteID", "year")) %>%
mutate(present = replace_na(present, 0)) %>%
pivot_wider(
names_from = year,
values_from = present
)
# Create Pao object
birds_pao <- createPao(
data = site_by_year[, years],
unitnames = site_by_year$siteID,
title = common_name
)
# Run occupancy model
model <- occMod(
data = birds_pao,
model = list(psi ~ 1, p ~ 1),
type = "so"
)
# Extract estimates
ests <- print_one_site_estimates(mod = model) %>%
as.data.frame() %>%
slice(1:2)
rownames(ests) <- NULL
named_est <- list(
vernacularName = common_name,
psi_Estimate = ests$est[1],
psi_SE = ests$se[1],
psi_L95 = ests$lower[1],
psi_U95 = ests$upper[1],
p_Estimate = ests$est[2],
p_SE = ests$se[2],
p_L95 = ests$lower[2],
p_U95 = ests$upper[2]
)
detection_histories[[sci_name]] <- site_by_year
results[[sci_name]] <- named_est
}
return(list(
estimates = results,
detection_histories = detection_histories
))
}
Run the function across all species
species_input <- brd_joineddata_clean %>%
distinct(scientificName, vernacularName) %>%
arrange(scientificName)
results_df <- run_occ_for_species_list(
species_df = species_input,
brd_joineddata_clean = brd_joineddata_clean,
brd_perpoint_clean = brd_perpoint_clean,
sites = sites
)
Plot psi (occupancy)
psi_df <- bind_rows(lapply(names(results_df$estimates), function(name) {
row <- results_df$estimates[[name]]
tibble(
scientificName = name,
vernacularName = row$vernacularName,
psi_Estimate = row$psi_Estimate,
psi_SE = row$psi_SE,
psi_L95 = row$psi_L95,
psi_U95 = row$psi_U95
)
})) %>%
arrange(psi_Estimate)
psi_df <- psi_df %>%
mutate(label = paste0(scientificName))
#mutate(label = paste0(scientificName, "\n", vernacularName))
ggplot(psi_df, aes(x = reorder(label, psi_Estimate), y = psi_Estimate)) +
geom_col(fill = "grey") +
geom_errorbar(aes(ymin = psi_L95, ymax = psi_U95), width = 0.2) +
labs(
title = "Species Occupancy (ψ) Estimates",
x = "Species",
y = "ψ Estimate"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Plot p (detection)
p_df <- bind_rows(lapply(names(results_df$estimates), function(name) {
row <- results_df$estimates[[name]]
tibble(
scientificName = name,
vernacularName = row$vernacularName,
p_Estimate = row$p_Estimate,
p_SE = row$p_SE,
p_L95 = row$p_L95,
p_U95 = row$p_U95
)
})) %>%
arrange(p_Estimate)
p_df <- p_df %>%
mutate(label = paste0(scientificName))
#mutate(label = paste0(scientificName, "\n", vernacularName))
ggplot(p_df, aes(x = reorder(label, p_Estimate), y = p_Estimate)) +
geom_col(fill = "grey") +
geom_errorbar(aes(ymin = p_L95, ymax = p_U95), width = 0.2) +
labs(
title = "Species Detection Probability (p) Estimates",
x = "Species",
y = "p Estimate"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Take a look at the data for any of the species - we probably aren’t
interested in species with a 1 for detection, 1 for occupancy (there
likely aren’t any species that have all 1s)
Curious about species with higher occupancy but lower detection
For a given occupancy range, get species and related detections. It will be interesting to see the range.
estimates_df <- bind_rows(lapply(names(results_df$estimates), function(name) {
row <- results_df$estimates[[name]]
tibble(
scientificName = name,
vernacularName = row$vernacularName,
psi_Estimate = row$psi_Estimate,
psi_L95 = row$psi_L95,
psi_U95 = row$psi_U95,
p_Estimate = row$p_Estimate,
p_L95 = row$p_L95,
p_U95 = row$p_U95
)
}))
psi_range_species <- estimates_df %>%
filter(psi_Estimate >= 0.5, psi_Estimate <= 0.6) %>%
select(scientificName, vernacularName, psi_Estimate, p_Estimate, p_L95, p_U95) %>%
arrange(desc(p_Estimate))
psi_range_species
Blue Jays and Great Horned Owls have similar occupancies (.51 and .52) but wildly different detections (.91 and .23).
Detectability and occupancy of a species can be influenced by many different random effects, such as:
Survey characteristics:
The observer: some observers may be better at spotting individuals of a species that are particularly camouflaged to their environments
The weather: adverse weather conditions such as rain or snow can impact visibility during a study, and therefore impact detection probability
Site characteristics: sites with heavy cover or dense forests may decrease detection probability and make it harder for observers to identify the species
We can include data about these characteristics in our model to either a) describe previously known variation and thereby improve the model fit or b) test to see whether occupancy/detection of a site is a function of a covariate.
These are covariates that influence occupancy of a species. Occupancy probability can only be influenced by site characteristics.
For example, shorebird occupancy would benefit from data on the extent of tidal flats, seabirds distributions are often influenced by ocean depth, and in many regions elevation plays a critical role in determining site occupancy.
Melanerpes carolinus (Red-bellied Woodpecker) across all sites
Lets add elevation as a site covariate and see how it improves the model fit.
We use the same occupancy table as above, but we need to create a table with the site covariates (aka nlcdClass).
#categorical
#site_cov <- brd_perpoint_clean %>%
# select(siteID, nlcdClass) %>%
# distinct(siteID, .keep_all = TRUE) %>%
# mutate(nlcdClass = as.factor(nlcdClass)) %>%
# tibble::column_to_rownames("siteID")
#non-categorical
site_cov <- brd_perpoint_clean %>%
select(siteID, elevation) %>%
distinct(siteID, .keep_all = TRUE) %>%
tibble::column_to_rownames("siteID")
todo: Plot the elevation for each site.
birds_pao_model2 <- createPao(
data = site_by_year[, c("2018", "2019", "2020", "2021", "2022", "2023")],
unitnames = site_by_year$siteID,
unitcov = site_cov,
title = "Red-bellied Woodpecker"
)
birds_occupancy_model2 <- occMod(
data = birds_pao_model2,
model = list(psi ~ elevation, p ~ 1),
type = 'so'
)
mod.list <- list(
birds_occupancy_model1,
birds_occupancy_model2
)
aictable <- createAicTable(mod.list = mod.list)
aictable$table
Looking at the two models, we can briefly look at the AIC values. The AIC for the model with elevation is lower (better), so the principle of parsimony suggests this is the better model: it is the more parsimonous model when considering both fit and number of parameters.
We see that compared to the original model that did not account for elevation there are 3 parameters vs 2. We can calculate a p value to quantify how much better of a fit this model is.
#get the difference in the neg2loglike between the two models
diff <- abs(birds_occupancy_model1$neg2loglike - birds_occupancy_model2$neg2loglike)
#look up our observed difference on a chi-square distribution with 1 degree of freedom to get p value
pchisq(diff, df = 1, lower.tail = FALSE)
## [1] 2.356024e-05
A p value of 2.356024e-05 is low so we can confidently reject the null hypothesis.
coef(birds_occupancy_model2, param = 'psi', prob = 0.95)
data <- cbind(site_cov, birds_occupancy_model2$real$psi) %>%
distinct(.keep_all = TRUE) %>%
arrange(elevation)
ggplot(data, aes(x = elevation, y = est)) +
geom_point() +
labs(x = "Elevation",
y = "Predicted Occupancy") +
lims(y = c(0, 1))
Can also be unique for each site as well as the survey
It is important to recognize that detection probability can be influenced by either survey characteristics or site characteristics.
We are going to see whether cloud cover of the survey affects detection of Red-bellied Woodpecker.
# Filter table by species
bird_species <- "Melanerpes carolinus"
brd_joineddata_model3 <- brd_joineddata_clean %>%
filter(siteID %in% large_sites) %>%
filter(scientificName == bird_species)
# Get all valid surveys years across sites (so we can fill in NAs)
valid_plot_years <- brd_perpoint_clean %>%
filter(siteID %in% large_sites) %>%
distinct(plotID, year)
# Get detections — only species *actually observed at a site* (so we can fill in 0s)
detections <- brd_joineddata_model3 %>%
filter(siteID %in% large_sites) %>%
group_by(plotID, year
) %>%
summarize(present = 1, .groups = "drop")
# Join and fill
plot_by_year <- valid_plot_years %>%
left_join(detections, by = c("plotID","year")) %>%
mutate(present = replace_na(present, 0)) %>%
pivot_wider(
names_from = year,
values_from = present
) %>%
select(1, sort(names(.)[-1]))
# # Create the detection covariate table: average cloud cover per plot per year
# cloud_cover_by_plot_year <- brd_perpoint_clean %>%
# filter(siteID %in% large_sites) %>%
# group_by(plotID, year) %>%
# summarize(
# cloudCover = mean((startCloudCoverPercentage + endCloudCoverPercentage) / 2),
# .groups = "drop"
# ) %>%
# pivot_wider(
# names_from = year,
# values_from = cloudCover
# ) %>%
# tibble::column_to_rownames('plotID') %>%
# select(sort(names(.)))
# View result
#head(cloud_cover_by_plot_year)
get_mode <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
detection_method_all_detections <- brd_joineddata_model3 %>%
group_by(plotID, year) %>%
summarise(detectionMethod = get_mode(detectionMethod), .groups = "drop")
det_method_matrix <- valid_plot_years %>%
left_join(detection_method_all_detections, by = c("plotID","year")) %>%
mutate(detectionMethod = replace_na(detectionMethod, 'not_detected')) %>%
pivot_wider(
names_from = year,
values_from = detectionMethod
) %>%
select(1, sort(names(.)[-1]))
det_method_matrix <- as.data.frame(det_method_matrix)
rownames(det_method_matrix) <- det_method_matrix$plotID
det_method_matrix <- det_method_matrix[ , -1]
#det_method_numeric <- det_method_matrix %>%
# mutate(across(everything(), ~ as.numeric(as.factor(.))))
We use the same occupancy table as above, but we need to create a table with the survey covariates.
surveycov <- data.frame(
detMethod = unlist(det_method_matrix)
)
row.names(surveycov) <- NULL
birds_pao_model3 <- createPao(
data = plot_by_year[, c("2018", "2019", "2020", "2021", "2022", "2023")],
unitnames = plot_by_year$plotID,
survcov = surveycov,
title = "Red-Bellied Woodpecker"
)
birds_occupancy_model3 <- occMod(
data = birds_pao_model3,
model = list(psi ~ 1, p ~ detMethod),
type = 'so'
)
mod.list <- list(
birds_occupancy_model1,
birds_occupancy_model3
)
aictable <- createAicTable(mod.list = mod.list)
aictable$table
Looking at the two models, we can briefly look at the AIC values. The AIC for the model with elevation is lower (better), so the principle of parsimony suggests this is the better model: it is the more parsimonous model when considering both fit and number of parameters.
results <- print_one_site_estimates(
mod = birds_occupancy_model3,
site = 1)
ggplot(data = data.frame(results), aes(x = rownames(results), y = est)) +
geom_bar(stat = 'identity') +
geom_errorbar(aes(ymin = lower, ymax = upper, width = 0.2)) +
lims(y = c(0, 1)) +
labs(y = "Maximum likelihood estimate",
x = "Parameter")
data <- data.frame(
detMethod = birds_pao_model3$survcov$detMethod
) %>%
filter(!is.na(detMethod), detMethod != "not_detected")
ggplot(data, aes(x = detMethod)) +
geom_bar(stat = 'count') +
labs(x = "Detection Method", y = "Number of Surveys Conducted") +
theme(axis.text.x = element_text(angle = 45, margin = margin(t = 30)))