Analysis of stress in wild rodents

Author
Affiliation

Serge Morand

Abstract

A short tutorial to analyse stress in wild rodents.

Keywords

rodent, Maxomys surifer, cortisol, cmr (capture, mark, recapture)

Stress in rodents

Summary according to google AI: In wild rodent populations, cortisol (or corticosterone in rodents) levels vary based on several factors, including age, sex, social status (Schradin 2008), and environmental conditions (Beery 2015). Levels can fluctuate seasonally, with breeding males often exhibiting lower corticosterone levels during the breeding season, while other classes show declines in corticosterone during periods of low food abundance. Furthermore, urban populations may experience different stress hormone profiles compared to rural populations (Łopucki 2019).

Factors Influencing Cortisol Levels:

Age: Cortisol levels can vary with age, with juveniles potentially showing different levels than adults.

Sex: Studies have shown differences in cortisol levels between males and females.

Social Status: In some species, subordinate animals may experience higher cortisol levels due to factors like social instability or limited social contact.

Seasonal Changes: Corticosterone levels in some rodent species can fluctuate seasonally, with breeding females and non-breeding males and females showing a decline in levels during the non-breeding season.

Environmental Conditions: Studies have shown that urban and rural populations may exhibit different stress hormone profiles, with urban populations sometimes having narrower ranges and lower median values.

Stressors: Various stressors, such as capture, handling, and exposure to predators or competitors, can trigger acute increases in cortisol levels.

Reproductive Status: Pregnancy and other reproductive events can also influence cortisol levels.

Analysis of stress level (cortisol) in Maxomys surifer

Animals were trapped using living trap cages. Their feces were collected and prepared for estimating the level of cortisol.

Elisa technique

Read the file of OD measures for the calibration

Download cortisol data

You can downlaod the data using this link

Code
load("data_for_cortisol.Rdata")

data <- data |>
  dplyr::mutate(meanOD = (duplicate1 + duplicate2) / 2) |>
  dplyr::mutate(netOD = meanOD - dplyr::first(meanOD)) |>
  dplyr::mutate(BB0 = (netOD/dplyr::last(netOD))*100) |>
  dplyr::mutate(meanOD = round(meanOD, 3),
                netOD = round(netOD, 3),
                BB0 = round(BB0, 2))

Data calibration

Concentrations of cortisol are in picogram / mL

Code
DT::datatable(data, class = 'cell-border stripe',
              caption = 'Table 1: "Data calibration for cortisol"',
              options = list(columnDefs = 
                               list(list(className = 'dt-center', 
                                         targets = "_all"))))

Fit a four-parameter logistic (4PL) model (using the library drc)

Code
library(drc)
model <- drc::drm(netOD ~ concentration, data = data, fct = LL.4())

Summary of the model and plot the calibration cure

Code
summary(model)

Model fitted: Log-logistic (ED50 as parameter) (4 parms)

Parameter estimates:

                Estimate Std. Error t-value   p-value    
b:(Intercept)   1.333307   0.247577  5.3854  0.005748 ** 
c:(Intercept)   0.101740   0.084413  1.2053  0.294523    
d:(Intercept)   0.932846   0.025982 35.9035 3.592e-06 ***
e:(Intercept) 702.278668 146.590399  4.7908  0.008707 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error:

 0.03410461 (4 degrees of freedom)
Code
plot(model)

Lecture of samples

Code
response <- response |>
  dplyr::mutate(meanOD = (duplicate1 + duplicate2) / 2) |>
  dplyr::mutate(netOD = meanOD - dplyr::first(data$meanOD)) |>
  dplyr::mutate(BB0 = (netOD/dplyr::last(data$netOD))*100) |>
  dplyr::mutate(meanOD = round(meanOD, 3),
                netOD = round(netOD, 3),
                BB0 = round(BB0, 2))

DT::datatable(response, class = 'cell-border stripe',
              caption = 'Table 2: "Data lecture of samples"',
              options = list(columnDefs = 
                               list(list(className = 'dt-center', 
                                         targets = "_all"))))

Estimation of the cortisol concentration

Code
DOSEx<-ED(model,response$netOD,type="absolute",display=F)

plot(model)
points(y=response$netOD,x=DOSEx[,1],col="blue",pch=19,cex=2)

Code
#arrows(DOSEx[,1],response$netOD,DOSEx[,1]+DOSEx[,2]*1.96,response$netOD,length=0.1,angle=90,lwd=3,col="blue")
#arrows(DOSEx[,1],response$netOD,DOSEx[,1]-DOSEx[,2]*1.96,response$netOD,length=0.1,angle=90,lwd=3,col="blue")

Merging with data on individual rodents

Code
data_merge <- cbind(response,DOSEx) |>
  dplyr::left_join(data_rodent,
                   by = c("id_indiv","trappingDate")) |>
  dplyr::mutate(body_condition = bodyWeight / length_mm) |>
  dplyr::mutate(body_condition = round(body_condition, 2),
                length_mm = round(length_mm, 2),
                Estimate = round(Estimate,0),
                `Std. Error` = round(`Std. Error`,0)) 


DT::datatable(data_merge, class = 'cell-border stripe',
              caption = 'Table 3: "Data on individual rodents with estimated concentration of cortisol"',
              options = list(columnDefs = 
                               list(list(className = 'dt-center', 
                                         targets = "_all"))))

Estimating body mass index (BMI) by regression analysis

Code
model_bd <- lm(bodyWeight ~ length_mm,data_merge)

plot(effects::allEffects(model_bd),
     col = 2,
     ylab = "Body weight", 
     #ylim = c(-1, 1),  
     type = "response")

Code
bmi_res <- as.data.frame(resid(model_bd ))
names(bmi_res) = c("bmi_res")

data_merge <-  cbind(data_merge,bmi_res)

Counting and adding the number of days since the first trapping (day 1) of each individual

Code
data_merge <-  data_merge |>
  dplyr::mutate(trappingDate = as.Date(trappingDate)) |>
  dplyr::group_by(id_indiv) |>
  dplyr::mutate(day = trappingDate - dplyr::first(trappingDate) +1) |>
  dplyr::mutate(day = as.integer(day))

DT::datatable(data_merge, class = 'cell-border stripe',
              caption = 'Table 3: "Data on individual rodents with estimated concentration of cortisol"',
              options = list(columnDefs = 
                               list(list(className = 'dt-center', 
                                         targets = "_all"))))

General Linear Mixed Modeling (GLMM) explaining the level of fecal cortisol

random factor = individuals (id_indiv) as several measures on the same individuals explanatory variables = number of days after the first recapture (day 1) sex BMI (residuals)

Code
library(lme4)
model <- glmer(Estimate ~ sex + day  + (1|id_indiv), data = data_merge)
Code
library(sjPlot)
library(sjstats)
library(sjlabelled)
library(sjmisc)

#plot_model(model, vline.color = "red",show.values = TRUE, value.offset = .3) + 
#  theme_sjplot2() + 
#  scale_color_sjplot("simply")

tab_model(model, show.se = TRUE, show.std = TRUE, show.stat = TRUE,
          show.aic = TRUE)
  Estimate
Predictors Estimates std. Error std. Beta standardized std. Error CI standardized CI Statistic p
(Intercept) 472.43 63.58 0.38 0.19 342.20 – 602.67 -0.01 – 0.76 7.43 <0.001
sex [M] -205.26 63.63 -0.96 0.30 -335.61 – -74.91 -1.57 – -0.35 -3.23 0.003
day 29.63 11.29 0.36 0.14 6.51 – 52.74 0.08 – 0.65 2.62 0.014
Random Effects
σ2 26142.76
τ00 id_indiv 1028.42
ICC 0.04
N id_indiv 10
Observations 33
Marginal R2 / Conditional R2 0.421 / 0.443
AIC 412.019
Code
library(effects)
effects = allEffects(model)

plot(effects,
     #col = 3,
     ylab = "Cortisol level", 
     type = "response")

Level estimate of cortisol per individual and by day after first recapture (day 1)

Code
library(ggplot2)

ggplot(data_merge, aes(x = day, y = Estimate)) +
  geom_point(aes(colour = sex)) +
  geom_line(color = "blue") +
  facet_wrap( ~ id_indiv) +
  theme_classic()

Effect of location (grids)

Code
# Kruskal Wallis Test One Way Anova by Ranks
kruskal.test(Estimate ~ id_grid_line, data = data_merge |>
               dplyr::filter(day == "1"))

    Kruskal-Wallis rank sum test

data:  Estimate by id_grid_line
Kruskal-Wallis chi-squared = 3.3455, df = 2, p-value = 0.1877

Effect of body mass index (BMI)

Code
data <- data_merge |>
               dplyr::filter(day == "1")

cor.test(data$bmi_res, data$Estimate, method = "spearman")

    Spearman's rank correlation rho

data:  data$bmi_res and data$Estimate
S = 166, p-value = 1
alternative hypothesis: true rho is not equal to 0
sample estimates:
         rho 
-0.006060606 

Distribution

Code
library(leaflet)
library(leafpop)

map <-  leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data = data_merge,
                   ~long, ~lat,
                   label = ~ as.character(id_indiv),
                   popup = popupTable(data_merge |>
                                       dplyr::select(sex,Estimate,day)),
                   fillColor = "blue", weight = 0.2,
                   group = "Maxomys surifer",
                   stroke = FALSE, fillOpacity = 0.4) %>%

  addProviderTiles(providers$Esri.WorldStreetMap,group ="WSM") %>%
  addTiles(group = "OSM") %>%
  addTiles(urlTemplate = "https://mts1.google.com/vt/lyrs=s&hl=en&src=app&x={x}&y={y}&z={z}&s=G",
           attribution = 'Google',group ="Google") %>%
  addLayersControl(baseGroups = c("WSM","OSM","Google"),
                   overlayGroups = c("Maxomys surifer"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  addScaleBar(position = "bottomright",
              scaleBarOptions(maxWidth = 100, metric = TRUE, imperial = FALSE,
                              updateWhenIdle = TRUE)) 

map

Summary for each individual with time since first trapping in the grid (in days) and home range (estimated area in square meters)

Code
longevity_df <- readxl::read_excel("/Users/serge/Documents/DATA/data_muka/muka_rodents/Rodent_MUKA_database_20250731.xlsx",
                                sheet="trapRoudup") |>
  dplyr::filter(id_indiv %in% unique(data_merge$id_indiv)) |>
  dplyr::group_by(id_indiv) |>
  dplyr::summarise(
    first_seen = min(trappingDate),
    last_seen = max(trappingDate),
    longevity_days = as.numeric(difftime(last_seen, first_seen, units = "days"))) |>
  dplyr::arrange(desc(longevity_days)) |>
  dplyr::left_join(data_merge |>
                     dplyr::filter(day == "1") |>
                     dplyr::select(id_indiv,id_grid_line,sex,bodyWeight, Estimate),
                   by="id_indiv") |>
  dplyr::rename(longevity = longevity_days,
                grid = id_grid_line,
                weight = bodyWeight) |>
  dplyr::mutate(first_seen= as.Date(first_seen),
                last_seen= as.Date(last_seen),
                Estimate= round(Estimate, 1)) |>
  dplyr::left_join(read.csv("data_area_range.csv"), by = "id_indiv") |>
  dplyr::mutate(area_sq_meters = round(area_sq_meters, 0)) |>
  dplyr::rename(area = area_sq_meters,
                cortisol = Estimate) |>
  dplyr::relocate(cortisol, .after = last_col())

DT::datatable(longevity_df, class = 'cell-border stripe',
              caption = 'Table 4: "Individual longevity"',
              options = list(columnDefs = 
                               list(list(className = 'dt-center', 
                                         targets = "_all"))))

Effect of home range

no statistical effect of home range on the level of cortisol

Code
cor.test(longevity_df$area, longevity_df$cortisol, method = "spearman")

    Spearman's rank correlation rho

data:  longevity_df$area and longevity_df$cortisol
S = 64, p-value = 0.7825
alternative hypothesis: true rho is not equal to 0
sample estimates:
       rho 
-0.1428571 

References

Beery, Kaufer, AK. 2015. “Stress, Social Behavior, and Resilience: Insights from Rodents.” Neurobiol Stress 1: 116–27. https://doi.org/10.1016/j.ynstr.2014.10.004.
Łopucki, Klich, R. 2019. “Hormonal Adjustments to Urban Conditions: Stress Hormone Levels in Urban and Rural Populations of Apodemus Agrarius.” Urban Ecosyst 22: 435–42. https://doi.org/10.1007/s11252-019-0832-8.
Schradin, C. 2008. “Seasonal Changes in Testosterone and Corticosterone Levels in Four Social Classes of a Desert Dwelling Sociable Rodent.” Horm Behav 53 (4): 573–79. https://doi.org/10.1016/j.yhbeh.2008.01.003.