Wildlife Roadkill Mortality Estimates on a Highway in the Brazilian Semiarid Region: Effects of Pavement and Sampling Biases

Author

Marina de Souza

GenEst workflow for estimation of wildlife road mortality

This script provides the workflow and analytical steps used in “Wildlife road mortality on paved and unpaved roads in the Cerrado–Caatinga ecotone: accounting for biases in carcass persistence and searcher efficiency”. All analyses were conducted using the GenEst software. This study aimed to estimate wildlife road mortality from vehicle collisions in paved and unpaved roads in the Cerrado–Caatinga ecotone, accounting carcass persistence ans searcher efficiency biases using the GenEst framework, and to compare mortality patterns between road types (paved x unpaved). To account for potential differences in road characteristics, analyses were conducted separately for paved and unpaved roads.

GenEst

GenEst is an R-based software package designed to estimate wildlife mortality while accounting for imperfect carcass detection and carcass persistence. It integrates information from carcass persistence trials, searcher efficiency trials, search schedules, and carcass observations to correct for biases associated with carcass removal and missed detections. GenEst provides statistically robust estimates of mortality and associated uncertainty and has been widely used in studies assessing wildlife mortality related to anthropogenic structures and activities, including roads, wind energy facilities, and power lines.

Additional information on GenEst, including the user guide, software documentation, and example datasets, is available at the USGS website: GenEst – A Generalized Estimator of Mortality

Load required packages

library(GenEst)
Warning: pacote 'GenEst' foi compilado no R versão 4.4.3
library(dplyr)

Anexando pacote: 'dplyr'
O seguinte objeto é mascarado por 'package:GenEst':

    desc
Os seguintes objetos são mascarados por 'package:stats':

    filter, lag
Os seguintes objetos são mascarados por 'package:base':

    intersect, setdiff, setequal, union
library(ggplot2)
library(tidyr)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ forcats   1.0.0     ✔ readr     2.1.5
✔ lubridate 1.9.4     ✔ stringr   1.5.1
✔ purrr     1.0.2     ✔ tibble    3.2.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::desc()   masks GenEst::desc()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(MASS)

Anexando pacote: 'MASS'

O seguinte objeto é mascarado por 'package:dplyr':

    select
library(patchwork)

Anexando pacote: 'patchwork'

O seguinte objeto é mascarado por 'package:MASS':

    area

Loading input data - paved

1) Search Efficiency

SE_p <- read.csv("V1_01_SE_p.txt")

2) Carcass persistence

CP_p <- read.csv("V1_02_CP_p.txt")

3) Search Schedule

SS_p <- read.csv("V1_03_SS_p.txt")

4) Density-Weighted Proportion

DWP_p <- read.csv("V1_04_DWP_p.txt")

5) Carcass Observation

CO_p <- read.csv("V1_05_CO_p.txt")

SS, DWP and CO verification

Ensure that dates are correctly interpreted by R

all(CO_p$DateFound %in% SS_p$DateSearched)
[1] TRUE

Ensure that dates are in ascending order

SS_p <- SS_p[order(SS_p$DateSearched), ]
CO_p <- CO_p[order(CO_p$DateFound), ]
CO_p$DateFound <- as.Date(CO_p$DateFound, format = "%Y-%m-%d")

Alignment between the DWP categories and the CO dataset

setdiff(unique(CO_p$group), names(DWP_p))
character(0)

Let’s make sure the data is interpreted correctly

CO_p$group <- factor(CO_p$group)
str(SE_p)
'data.frame':   119 obs. of  2 variables:
 $ SEid  : int  1 2 3 4 5 6 7 8 9 10 ...
 $ search: int  1 0 0 0 0 0 0 0 0 1 ...
str(CP_p)
'data.frame':   420 obs. of  3 variables:
 $ CPid       : int  1 2 3 4 5 6 7 8 9 10 ...
 $ LastPresent: int  0 3 1 0 0 1 1 0 0 0 ...
 $ FirstAbsent: int  1 NA 2 1 1 NA 2 1 1 1 ...
str(SS_p)
'data.frame':   108 obs. of  2 variables:
 $ DateSearched: chr  "2017-03-24" "2017-04-27" "2017-05-23" "2017-05-24" ...
 $ paved       : int  1 1 1 1 1 1 1 1 1 1 ...
str(DWP_p)
'data.frame':   1 obs. of  2 variables:
 $ Unit      : chr "paved"
 $ vertebrate: num 0.6
str(CO_p)
'data.frame':   2606 obs. of  4 variables:
 $ carcID   : chr  "x3" "x4" "x5" "x6" ...
 $ DateFound: Date, format: "2017-03-24" "2017-03-24" ...
 $ Unit     : chr  "paved" "paved" "paved" "paved" ...
 $ group    : Factor w/ 1 level "vertebrate": 1 1 1 1 1 1 1 1 1 1 ...

Analyses

Searcher Efficiency

model_SE_p <- pkm(formula_p = p ~1,
  data = SE_p,
  obsCol = "search",
  kFixed = 0.5,
  CL=0.9)
model_SE_p$cell_pk
  cell   n p_median    p_lwr    p_upr k_median k_lwr k_upr
1  all 119 0.159664 0.111811 0.222859      0.5   0.5   0.5
plot(model_SE_p)

Carcass Persistence

model_CP_p <- cpm(
  formula_l = l ~ 1, 
  formula_s = s ~ 1,
  data = CP_p,
  dist = "lognormal",
  left = "LastPresent",
  right = "FirstAbsent",
  CL = 0.9)
model_CP_p$cell_desc
  cell medianCP        r1        r3        r7       r14       r28
1  all 0.955042 0.6155207 0.4780645 0.3735012 0.2944409 0.2244469
plot(model_CP_p)

Estimated mortality

eM_p <- estM(
  nsim = 1000,
  data_CO = CO_p,
  data_SS = SS_p,
  data_DWP = DWP_p,
  frac = 1,
  model_SE = model_SE_p,
  model_CP = model_CP_p,
  COdate = "DateFound",
  SSdate = "DateSearched")
plot(eM_p)

Paved - Bias-corrected estimates accounting for observer detection efficiency and carcass persistence time

The total sampling effort on the paved road section was calculated based on two survey schemes. First, 335 km were surveyed during 43 field expeditions, with each expedition covering the entire transect twice, resulting in a total effort of 335 × 2 × 43 = 28,810 km. Second, an additional 17 field expeditions covered the same 335 km transect four times per expedition, resulting in an additional effort of 335 × 4 × 17 = 22,780 km. Combined, the total distance surveyed on paved roads was 51,590 km.

Ext_p <- 51590

based on the data obtained from the paved plot:

mort_p <- colSums(eM_p$Mhat)

med_p <- median(mort_p)
CI_p  <- quantile(mort_p, probs = c(0.05, 0.95))

tx_p  <- med_p / Ext_p
CIi_p <- CI_p[1] / Ext_p
CIs_p <- CI_p[2] / Ext_p

cat(sprintf(
  "Mortality rate: %.4f ind/km/day (90%% CI: %.4f – %.4f)\n",
  tx_p, CIi_p, CIs_p
))
Mortality rate: 0.9906 ind/km/day (90% CI: 0.6671 – 1.4598)

Estimates based only on observed carcasses

We will calculate the wildlife road mortality rate for each field expedition. After organizing the sampling months according to the survey scheme used, the number of carcasses recorded during each expedition will be divided by the corresponding distance surveyed. For the first survey scheme, the entire 335-km transect was surveyed twice per expedition, resulting in a sampling effort of 670 km per expedition (335 × 2). For the second survey scheme, the transect was surveyed four times per expedition, resulting in a sampling effort of 1,340 km per expedition (335 × 4).

monthly_roadkill_p <- CO_p %>%
  mutate(DateFound = as.Date(DateFound)) %>%
  group_by(
    year = as.numeric(format(DateFound, "%Y")),
    month = as.numeric(format(DateFound, "%m"))
  ) %>%
  summarise(n_roadkills = n(), .groups = "drop") %>%
  arrange(year, month) %>%
  mutate(month_seq = row_number())

monthly_roadkill_p
# A tibble: 60 × 4
    year month n_roadkills month_seq
   <dbl> <dbl>       <int>     <int>
 1  2017     3          25         1
 2  2017     4          24         2
 3  2017     5          19         3
 4  2017     6           9         4
 5  2017     7          11         5
 6  2017     8           5         6
 7  2017     9           4         7
 8  2017    10           4         8
 9  2017    11          52         9
10  2017    12          13        10
# ℹ 50 more rows
specific_months_p <- monthly_roadkill_p %>%
  mutate(
    month_group = case_when(
      (year == 2017 & month %in% c(5, 8, 11)) |
      (year == 2018 & month %in% c(2, 6)) |
      (year == 2019 & month %in% c(5, 8, 11)) |
      (year == 2020 & month %in% c(2, 5, 8, 11)) |
      (year == 2021 & month %in% c(2, 5, 8, 11)) ~ "month_group_2",
      TRUE ~ "month_group_1"
    )
  )

specific_months_p
# A tibble: 60 × 5
    year month n_roadkills month_seq month_group  
   <dbl> <dbl>       <int>     <int> <chr>        
 1  2017     3          25         1 month_group_1
 2  2017     4          24         2 month_group_1
 3  2017     5          19         3 month_group_2
 4  2017     6           9         4 month_group_1
 5  2017     7          11         5 month_group_1
 6  2017     8           5         6 month_group_2
 7  2017     9           4         7 month_group_1
 8  2017    10           4         8 month_group_1
 9  2017    11          52         9 month_group_2
10  2017    12          13        10 month_group_1
# ℹ 50 more rows
monthly_rate_p <- specific_months_p%>%
  mutate(
    roadkills_per_km = ifelse(
      month_group == "month_group_1",
      n_roadkills / 670,
      n_roadkills / 1340
    )
  )
monthly_rate_p
# A tibble: 60 × 6
    year month n_roadkills month_seq month_group   roadkills_per_km
   <dbl> <dbl>       <int>     <int> <chr>                    <dbl>
 1  2017     3          25         1 month_group_1          0.0373 
 2  2017     4          24         2 month_group_1          0.0358 
 3  2017     5          19         3 month_group_2          0.0142 
 4  2017     6           9         4 month_group_1          0.0134 
 5  2017     7          11         5 month_group_1          0.0164 
 6  2017     8           5         6 month_group_2          0.00373
 7  2017     9           4         7 month_group_1          0.00597
 8  2017    10           4         8 month_group_1          0.00597
 9  2017    11          52         9 month_group_2          0.0388 
10  2017    12          13        10 month_group_1          0.0194 
# ℹ 50 more rows
median_rate_p <- median(monthly_rate_p$roadkills_per_km)

median_rate_p
[1] 0.04402985
set.seed(123)  

boot_median_p <- replicate(
  10000,
  median(sample(monthly_rate_p$roadkills_per_km, replace = TRUE))
)

IC_p <- quantile(boot_median_p, probs = c(0.05, 0.95))

IC_p
        5%        95% 
0.03731343 0.05522388 
result_p <- data.frame(
  median_roadkill_rate = median(monthly_rate_p$roadkills_per_km),
  CI_lower = IC_p[1],
  CI_upper = IC_p[2]
)

result_p
   median_roadkill_rate   CI_lower   CI_upper
5%           0.04402985 0.03731343 0.05522388

Loading input data - unpaved

1) Search Efficiency

SE_un <- read.csv("V1_01_SE_un.txt")

2) Carcass persistence

CP_un <- read.csv("V1_02_CP_un.txt")

3) Search Schedule

SS_un <- read.csv("V1_03_SS_un.txt")

4) Density-Weighted Proportion

DWP_un <- read.csv("V1_04_DWP_un.txt")

5) Carcass Observation

CO_un <- read.csv("V1_05_CO_un.txt")

SS, DWP and CO verification

all(CO_un$DateFound %in% SS_un$DateSearched)
[1] TRUE

the dates need to be in ascending order

SS_un <- SS_un[order(SS_un$DateSearched), ]
CO_un <- CO_un[order(CO_un$DateFound), ]

Ensure that dates are correctly interpreted by R

CO_un$DateFound <- as.Date(CO_un$DateFound, format = "%Y-%m-%d")

Alignment between the DWP categories and the CO dataset

setdiff(unique(CO_un$group), names(DWP_un))
character(0)
str(SE_un)
'data.frame':   15 obs. of  2 variables:
 $ SEid  : int  71 72 73 74 124 125 126 127 128 129 ...
 $ search: int  0 0 0 0 1 0 0 0 0 0 ...
str(CP_un)
'data.frame':   16 obs. of  3 variables:
 $ CPid       : int  12 13 21 35 49 57 71 79 107 127 ...
 $ LastPresent: int  0 2 2 0 0 0 0 2 3 0 ...
 $ FirstAbsent: int  1 NA NA 1 1 1 1 NA NA 1 ...
str(SS_un)
'data.frame':   108 obs. of  2 variables:
 $ DateSearched: chr  "2017-03-24" "2017-04-27" "2017-05-23" "2017-05-24" ...
 $ unpaved     : int  1 1 1 1 1 1 1 1 1 1 ...
str(DWP_un)
'data.frame':   1 obs. of  2 variables:
 $ Unit      : chr "unpaved"
 $ vertebrate: num 0.6
str(CO_un)
'data.frame':   85 obs. of  4 variables:
 $ carcID   : chr  "x1" "x2" "x11" "x21" ...
 $ DateFound: Date, format: "2017-03-24" "2017-03-24" ...
 $ Unit     : chr  "unpaved" "unpaved" "unpaved" "unpaved" ...
 $ group    : chr  "vertebrate" "vertebrate" "vertebrate" "vertebrate" ...

Let’s make sure the data is interpreted correctly

CO_un$Unit <- factor(CO_un$Unit)
CO_un$group <- factor(CO_un$group)
str(SE_un)
'data.frame':   15 obs. of  2 variables:
 $ SEid  : int  71 72 73 74 124 125 126 127 128 129 ...
 $ search: int  0 0 0 0 1 0 0 0 0 0 ...
str(CP_un)
'data.frame':   16 obs. of  3 variables:
 $ CPid       : int  12 13 21 35 49 57 71 79 107 127 ...
 $ LastPresent: int  0 2 2 0 0 0 0 2 3 0 ...
 $ FirstAbsent: int  1 NA NA 1 1 1 1 NA NA 1 ...
str(SS_un)
'data.frame':   108 obs. of  2 variables:
 $ DateSearched: chr  "2017-03-24" "2017-04-27" "2017-05-23" "2017-05-24" ...
 $ unpaved     : int  1 1 1 1 1 1 1 1 1 1 ...
str(DWP_un)
'data.frame':   1 obs. of  2 variables:
 $ Unit      : chr "unpaved"
 $ vertebrate: num 0.6
str(CO_un)
'data.frame':   85 obs. of  4 variables:
 $ carcID   : chr  "x1" "x2" "x11" "x21" ...
 $ DateFound: Date, format: "2017-03-24" "2017-03-24" ...
 $ Unit     : Factor w/ 1 level "unpaved": 1 1 1 1 1 1 1 1 1 1 ...
 $ group    : Factor w/ 1 level "vertebrate": 1 1 1 1 1 1 1 1 1 1 ...

Analyses

Searcher Efficiency

model_SE_un <- pkm(formula_p = p ~1,
  data = SE_un,
  obsCol = "search",
  kFixed = 0.5)
model_SE_un$cell_pk
  cell  n p_median    p_lwr    p_upr k_median k_lwr k_upr
1  all 15 0.066668 0.012848 0.281614      0.5   0.5   0.5

Carcass Persistence

model_CP_un <- cpm(
  formula_l = l ~ 1, 
  formula_s = s ~ 1,
  data = CP_un,
  dist = "lognormal",
  left = "LastPresent",
  right = "FirstAbsent",
  CL = 0.9)
model_CP_un$cell_desc
  cell  medianCP        r1        r3        r7       r14       r28
1  all 0.4278426 0.5151918 0.3817552 0.2874358 0.2198935 0.1627682
plot(model_CP_un)

Estimated mortality

eM_un <- estM(
  nsim = 1000,
  data_CO = CO_un,
  data_SS = SS_un,
  data_DWP = DWP_un,
  frac = 1,
  model_SE = model_SE_un,
  model_CP = model_CP_un,
  COdate = "DateFound",
  SSdate = "DateSearched")
plot(eM_un)

Unpaved - Bias-corrected estimates accounting for observer detection efficiency and carcass persistence time

The total sampling effort on the unpaved road section was calculated based on two survey schemes. First, 45 km were surveyed during 43 field expeditions, with each expedition covering the entire transect twice, resulting in a total effort of 45 × 2 × 43 = 3,870 km. Second, an additional 17 field expeditions covered the same 45 km transect four times per expedition, resulting in an additional effort of 45 × 4 × 17 = 3,060 km. Combined, the total distance surveyed on unpaved roads was 6,930 km.

Ext_un<- 6930

based on the data obtained from the unpaved plot:

mort_un <- colSums(eM_un$Mhat)

med_un <- median(mort_un)
CI_un  <- quantile(mort_un, probs = c(0.05, 0.95))

tx_un  <- med_un / Ext_un
CIi_un <- CI_un[1] / Ext_un
CIs_un <- CI_un[2] / Ext_un

cat(sprintf(
  "Mortality rate: %.4f ind/km/day (90%% CI: %.4f – %.4f)\n",
  tx_un, CIi_un, CIs_un
))
Mortality rate: 0.7050 ind/km/day (90% CI: 0.1224 – 6.2046)

Estimates based only on observed carcasses

We will calculate the carcass rate for each field expedition. After organizing the sampling months according to the survey scheme used, the number of carcasses recorded during each expedition will be divided by the corresponding distance surveyed. For the first survey scheme, the entire 45-km unpaved transect was surveyed twice per expedition, resulting in a sampling effort of 90 km per expedition (45 × 2). For the second survey scheme, the transect was surveyed four times per expedition, resulting in a sampling effort of 180 km per expedition (45 × 4).

monthly_roadkill_un <- CO_un %>%
  mutate(DateFound = as.Date(DateFound)) %>%
  group_by(
    year = as.numeric(format(DateFound, "%Y")),
    month = as.numeric(format(DateFound, "%m"))
  ) %>%
  summarise(n_roadkills = n(), .groups = "drop") %>%
  arrange(year, month) %>%
  mutate(month_seq = row_number())

monthly_roadkill_un
# A tibble: 38 × 4
    year month n_roadkills month_seq
   <dbl> <dbl>       <int>     <int>
 1  2017     3           5         1
 2  2017     6           1         2
 3  2017    11           3         3
 4  2018     1           1         4
 5  2018     2           1         5
 6  2018     5           1         6
 7  2018     6           4         7
 8  2018     7           1         8
 9  2018     8           1         9
10  2018    10           1        10
# ℹ 28 more rows
specific_months_un <- monthly_roadkill_un %>%
  mutate(
    month_group = case_when(
      (year == 2017 & month %in% c(5, 8, 11)) |
      (year == 2018 & month %in% c(2, 6)) |
      (year == 2019 & month %in% c(5, 8, 11)) |
      (year == 2020 & month %in% c(2, 5, 8, 11)) |
      (year == 2021 & month %in% c(2, 5, 8, 11)) ~ "month_group_2",
      TRUE ~ "month_group_1"
    )
  )

specific_months_un
# A tibble: 38 × 5
    year month n_roadkills month_seq month_group  
   <dbl> <dbl>       <int>     <int> <chr>        
 1  2017     3           5         1 month_group_1
 2  2017     6           1         2 month_group_1
 3  2017    11           3         3 month_group_2
 4  2018     1           1         4 month_group_1
 5  2018     2           1         5 month_group_2
 6  2018     5           1         6 month_group_1
 7  2018     6           4         7 month_group_2
 8  2018     7           1         8 month_group_1
 9  2018     8           1         9 month_group_1
10  2018    10           1        10 month_group_1
# ℹ 28 more rows
monthly_rate_un <- specific_months_un%>%
  mutate(
    roadkills_per_km = ifelse(
      month_group == "month_group_1",
      n_roadkills / 90,
      n_roadkills / 180
    )
  )
monthly_rate_un
# A tibble: 38 × 6
    year month n_roadkills month_seq month_group   roadkills_per_km
   <dbl> <dbl>       <int>     <int> <chr>                    <dbl>
 1  2017     3           5         1 month_group_1          0.0556 
 2  2017     6           1         2 month_group_1          0.0111 
 3  2017    11           3         3 month_group_2          0.0167 
 4  2018     1           1         4 month_group_1          0.0111 
 5  2018     2           1         5 month_group_2          0.00556
 6  2018     5           1         6 month_group_1          0.0111 
 7  2018     6           4         7 month_group_2          0.0222 
 8  2018     7           1         8 month_group_1          0.0111 
 9  2018     8           1         9 month_group_1          0.0111 
10  2018    10           1        10 month_group_1          0.0111 
# ℹ 28 more rows
set.seed(123)  

boot_median_un <- replicate(
  10000,
  median(sample(monthly_rate_un$roadkills_per_km, replace = TRUE))
)

IC_un <- quantile(boot_median_un, probs = c(0.05, 0.95))

IC_un
        5%        95% 
0.01111111 0.02222222 
result_un <- data.frame(
  median_roadkill_rate = median(monthly_rate_un$roadkills_per_km),
  CI_lower = IC_un[1],
  CI_upper = IC_un[2]
)

result_un
   median_roadkill_rate   CI_lower   CI_upper
5%           0.01388889 0.01111111 0.02222222

Graphical

SE data

se_df <- bind_rows(
  model_SE_p$cell_pk %>%
    mutate(road_surface = "Paved"),
  
  model_SE_un$cell_pk %>%
    mutate(road_surface = "Unpaved")
)

surface_colors <- c(
  "Paved" = "grey40",
  "Unpaved" = "orange3"
)

surface_fills <- c(
  "Paved" = "grey70",
  "Unpaved" = "orange"
)

CP data

generate_cp_curve <- function(model, road_surface,
                              time_max = 10,
                              by = 0.05,
                              nsim = 5000){
  
  time <- seq(0, time_max, by = by)
  
  mu <- unname(model$betahat_l)
  sigma <- unname(model$betahat_s)
  
  beta <- c(mu, log(sigma))
  
  sims <- MASS::mvrnorm(
    n = nsim,
    mu = beta,
    Sigma = model$varbeta
  )
  
  curves <- sapply(1:nsim, function(i){
    
    mu_i <- sims[i, 1]
    sigma_i <- exp(sims[i, 2])
    
    1 - plnorm(
      time,
      meanlog = mu_i,
      sdlog = sigma_i
    )
  })
  
  data.frame(
    time = time,
    median = apply(curves, 1, median),
    lower_ci = apply(curves, 1, quantile, probs = 0.05),
    upper_ci = apply(curves, 1, quantile, probs = 0.95),
    road_surface = road_surface
  )
}

cp_df <- bind_rows(
  generate_cp_curve(model_CP_p, "Paved"),
  generate_cp_curve(model_CP_un, "Unpaved")
)

EM data

mortality_df <- rbind(
  data.frame(
    road_surface = "Paved",
    estimate_type = "Observed",
    rate = result_p$median_roadkill_rate,
    lower_ci = result_p$CI_lower,
    upper_ci = result_p$CI_upper
  ),
  
  data.frame(
    road_surface = "Unpaved",
    estimate_type = "Observed",
    rate = result_un$median_roadkill_rate,
    lower_ci = result_un$CI_lower,
    upper_ci = result_un$CI_upper
  ),
  
  data.frame(
    road_surface = "Paved",
    estimate_type = "Bias-corrected",
    rate = tx_p,
    lower_ci = CIi_p,
    upper_ci = CIs_p
  ),
  
  data.frame(
    road_surface = "Unpaved",
    estimate_type = "Bias-corrected",
    rate = tx_un,
    lower_ci = CIi_un,
    upper_ci = CIs_un
  )
)

mortality_df$road_surface <- factor(
  mortality_df$road_surface,
  levels = c("Paved", "Unpaved")
)

mortality_df$estimate_type <- factor(
  mortality_df$estimate_type,
  levels = c("Observed", "Bias-corrected")
)

Sigle Plots

base_theme <- theme_classic(base_size = 13) +
  theme(
    axis.title = element_text(),
    axis.text = element_text(color = "black"),
    legend.title = element_text(),
    legend.position = "bottom"
  )

SE

SE_p_un <- ggplot(
  se_df,
  aes(x = road_surface,
      y = p_median)
) +
  geom_point(size = 2.5) +
  geom_errorbar(
    aes(ymin = p_lwr,
        ymax = p_upr),
    width = 0.12,
    linewidth = 0.5
  ) +
  scale_color_manual(values = surface_colors, guide="none") +
  coord_cartesian(ylim = c(0, 0.35)) +
  labs(
    x = NULL,
    y = expression(atop("Searcher efficiency",
    "(median and 90% CI)"))
  ) +
  base_theme +
  theme(legend.position = "none")

SE_p_un

CP

CP_p_un <- ggplot(
  cp_df,
  aes(
    x = time,
    y = median
  )
) +
  geom_ribbon(
    aes(
      ymin = lower_ci,
      ymax = upper_ci,
      fill = road_surface
    ),
    alpha = 0.25,
    colour = NA,
    show.legend = FALSE
  ) +
  geom_line(
    aes(color = road_surface),
    linewidth = 0.8
  ) +
  geom_vline(
    aes(
      xintercept = model_CP_p$cell_desc$medianCP,
      linetype = "Median Paved"
    ),
    color = "grey40",
    linewidth = 0.8
  ) +
  geom_vline(
    aes(
      xintercept = model_CP_un$cell_desc$medianCP,
      linetype = "Median Unpaved"
    ),
    color = "orange3",
    linewidth = 0.8
  ) +
  scale_linetype_manual(
    name = NULL,
    values = c(
      "Median Paved" = "dashed",
      "Median Unpaved" = "dashed"
    )
  ) +
  scale_fill_manual(
    name = NULL,
    values = surface_fills
  ) +
  scale_color_manual(
    name = NULL,
    values = surface_colors
  ) +
  scale_x_continuous(
    breaks = seq(0, 10, by = 1)
  ) +
  scale_y_continuous(
    breaks = seq(0, 1, by = 0.2)
  ) +
  labs(
    x = "Carcass persistence time (days)",
    y = "Persistence probability"
  ) +
  base_theme

CP_p_un

EM

EM_p_un <- ggplot(
  mortality_df,
  aes(x = road_surface,
      y = rate,
      shape = estimate_type)
) +
  geom_point(
    size = 2.5,
    position = position_dodge(width = 0.35)
  ) +
  geom_errorbar(
    aes(ymin = lower_ci,
        ymax = upper_ci),
    width = 0.12,
    position = position_dodge(width = 0.35)
  ) +
  scale_y_log10() +
  scale_color_manual(
    name = NULL,
    values = surface_colors
  ) +
  scale_shape_manual(
    name = NULL,
    values = c(
      "Observed" = 17,
      "Bias-corrected" = 15
    )
  ) +
  labs(
    color=NULL,
    x= NULL,
  y = expression(atop(
  "Wildlife fatality rate",
  "(ind/km/day, log"[10]*")"
))
) +
  base_theme

EM_p_un
Warning: No shared levels found between `names(values)` of the manual scale and the
data's colour values.

Final panel

final_panel <- 
  (SE_p_un | CP_p_un) /
  EM_p_un +
  plot_layout(
    guides = "collect"
  ) +
  plot_annotation(tag_levels = "A") &
  theme(
    legend.position = "bottom",
    legend.title = element_blank()
  )

final_panel
Warning: No shared levels found between `names(values)` of the manual scale and the
data's colour values.