## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4.1,
  dpi = 144
)
if (!"package:drmTMB" %in% search()) {
  library(drmTMB)
}

## ----personality-simulate-----------------------------------------------------
set.seed(20260715)
n_individual <- 80L
n_each <- 6L

individual_info <- data.frame(
  individual = factor(seq_len(n_individual)),
  sex = factor(
    rep(c("female", "male"), each = n_individual / 2),
    levels = c("female", "male")
  )
)

mean_by_sex <- c(female = 0.35, male = 0.70)
between_sd_by_sex <- c(female = 0.65, male = 0.40)
within_sd_by_sex <- c(female = 0.35, male = 0.60)

individual_effect <- stats::rnorm(
  n_individual,
  sd = between_sd_by_sex[individual_info$sex]
)

personality <- individual_info[rep(seq_len(n_individual), each = n_each), ]
personality$exploration_score <-
  mean_by_sex[personality$sex] +
  individual_effect[as.integer(personality$individual)] +
  stats::rnorm(
    nrow(personality),
    sd = within_sd_by_sex[personality$sex]
  )

## ----personality-fit----------------------------------------------------------
fit_personality <- drmTMB(
  bf(
    exploration_score ~ sex + (1 | individual),
    sigma ~ sex,
    sd(individual) ~ sex
  ),
  family = gaussian(),
  data = personality
)

check_drm(fit_personality)
round(coef(fit_personality, "mu"), 3)
round(coef(fit_personality, "sigma"), 3)
round(coef(fit_personality, "sd(individual)"), 3)

## ----personality-interval-targets---------------------------------------------
personality_targets <- profile_targets(fit_personality)
personality_targets[
  personality_targets$parm %in% c(
    "fixef:sigma:sexmale",
    "fixef:sd(individual):sexmale"
  ),
  c("parm", "estimate", "profile_ready", "profile_note")
]

## ----personality-component-intervals------------------------------------------
personality_ci <- confint(
  fit_personality,
  parm = c(
    "fixef:sigma:sexmale",
    "fixef:sd(individual):sexmale"
  ),
  method = "wald"
)
personality_ci

## ----personality-ratio-intervals----------------------------------------------
interval_ok <- personality_ci$conf.status == "wald" &
  stats::complete.cases(personality_ci[c("lower", "upper")])
data.frame(
  parm = personality_ci$parm[interval_ok],
  sd_ratio_lower = exp(personality_ci$lower[interval_ok]),
  sd_ratio_upper = exp(personality_ci$upper[interval_ok]),
  method = personality_ci$method[interval_ok],
  conf.status = personality_ci$conf.status[interval_ok]
)

## ----personality-data-figure, echo = FALSE, fig.width = 7, fig.height = 4.1, fig.cap = "Repeated exploration scores for females and males. Grey points are observations, blue ticks are individual means, and the vermillion line is the fitted sex-specific mean. The spread of individual means represents between-individual variation; scatter around each individual mean represents within-individual variation. Both panels use the same vertical scale.", fig.alt = "Two panels show repeated exploration scores for female and male individuals. Grey observations cluster around blue individual-mean ticks, while a vermillion horizontal line marks the fitted sex-specific mean. The female panel has wider spread among individual means, while the male panel has more scatter within individuals."----
individual_summary <- stats::aggregate(
  exploration_score ~ individual + sex,
  data = personality,
  FUN = mean
)
individual_summary <- individual_summary[
  order(individual_summary$sex, individual_summary$exploration_score),
]
individual_summary$display_id <- ave(
  individual_summary$exploration_score,
  individual_summary$sex,
  FUN = seq_along
)

plot_data <- merge(
  personality,
  individual_summary[c("individual", "sex", "display_id")],
  by = c("individual", "sex"),
  sort = FALSE
)

sex_grid <- data.frame(
  sex = factor(c("female", "male"), levels = levels(personality$sex))
)
sex_grid$fitted_mean <- predict(
  fit_personality,
  newdata = sex_grid,
  dpar = "mu"
)

ggplot2::ggplot(
  plot_data,
  ggplot2::aes(x = display_id, y = exploration_score)
) +
  ggplot2::geom_point(
    position = ggplot2::position_jitter(width = 0.08, height = 0, seed = 1),
    colour = "grey55",
    alpha = 0.42,
    size = 0.8
  ) +
  ggplot2::geom_segment(
    data = individual_summary,
    ggplot2::aes(
      x = display_id - 0.26,
      xend = display_id + 0.26,
      y = exploration_score,
      yend = exploration_score
    ),
    inherit.aes = FALSE,
    colour = "#0072B2",
    linewidth = 0.65
  ) +
  ggplot2::geom_hline(
    data = sex_grid,
    ggplot2::aes(yintercept = fitted_mean),
    inherit.aes = FALSE,
    colour = "#D55E00",
    linewidth = 0.8
  ) +
  ggplot2::geom_text(
    data = sex_grid,
    ggplot2::aes(
      x = Inf,
      y = fitted_mean,
      label = "fitted sex mean"
    ),
    inherit.aes = FALSE,
    colour = "#D55E00",
    hjust = 1.05,
    vjust = -0.6,
    size = 3
  ) +
  ggplot2::facet_wrap(
    ~sex,
    nrow = 1,
    labeller = ggplot2::as_labeller(c(female = "Female", male = "Male"))
  ) +
  ggplot2::labs(
    x = "Individuals, ordered within sex by their observed mean",
    y = "Exploration score"
  ) +
  ggplot2::theme_minimal(base_size = 11) +
  ggplot2::theme(
    panel.grid.minor = ggplot2::element_blank(),
    panel.grid.major.x = ggplot2::element_blank(),
    axis.text.x = ggplot2::element_blank(),
    axis.ticks.x = ggplot2::element_blank()
  )

## ----personality-components---------------------------------------------------
sex_grid$mean_score <- predict(
  fit_personality, newdata = sex_grid, dpar = "mu"
)
sex_grid$between_individual_sd <- predict(
  fit_personality, newdata = sex_grid, dpar = "sd(individual)"
)
sex_grid$within_individual_sd <- predict(
  fit_personality, newdata = sex_grid, dpar = "sigma"
)

sex_grid$repeatability <- with(
  sex_grid,
  between_individual_sd^2 /
    (between_individual_sd^2 + within_individual_sd^2)
)

repeatability_table <- sex_grid[c(
  "sex",
  "mean_score",
  "between_individual_sd",
  "within_individual_sd",
  "repeatability"
)]
repeatability_table[-1] <- lapply(
  repeatability_table[-1],
  round,
  digits = 3
)
repeatability_table

## ----personality-component-figure, echo = FALSE, fig.width = 7, fig.height = 2.9, fig.cap = "Model-implied values for females and males. Panels show the expected exploration score, between-individual SD from `sd(individual)`, and within-individual residual SD from `sigma`. Lines aid comparison and are not uncertainty intervals.", fig.alt = "Three small panels compare fitted female and male values. The expected score and within-individual standard deviation are higher for males, while the between-individual standard deviation is higher for females. No uncertainty intervals are shown."----
component_surface <- rbind(
  data.frame(
    sex = sex_grid$sex,
    component = "Expected score",
    estimate = sex_grid$mean_score
  ),
  data.frame(
    sex = sex_grid$sex,
    component = "Between-individual SD",
    estimate = sex_grid$between_individual_sd
  ),
  data.frame(
    sex = sex_grid$sex,
    component = "Within-individual SD",
    estimate = sex_grid$within_individual_sd
  )
)
component_surface$component <- factor(
  component_surface$component,
  levels = c(
    "Expected score",
    "Between-individual SD",
    "Within-individual SD"
  )
)

ggplot2::ggplot(
  component_surface,
  ggplot2::aes(x = sex, y = estimate, group = 1)
) +
  ggplot2::geom_line(colour = "grey65", linewidth = 0.55) +
  ggplot2::geom_point(colour = "#0072B2", size = 2.4) +
  ggplot2::geom_text(
    ggplot2::aes(label = sprintf("%.2f", estimate)),
    vjust = -0.8,
    colour = "grey20",
    size = 3.1
  ) +
  ggplot2::facet_wrap(~component, scales = "free_y", nrow = 1) +
  ggplot2::expand_limits(y = 0) +
  ggplot2::scale_y_continuous(
    expand = ggplot2::expansion(mult = c(0.02, 0.18))
  ) +
  ggplot2::scale_x_discrete(labels = c(female = "Female", male = "Male")) +
  ggplot2::labs(x = NULL, y = "Fitted value") +
  ggplot2::theme_minimal(base_size = 11) +
  ggplot2::theme(
    panel.grid.minor = ggplot2::element_blank(),
    panel.grid.major.x = ggplot2::element_blank(),
    strip.text = ggplot2::element_text(face = "bold")
  )

