---
title: "Analysis"
author: "Hannah Rickman"
date: "2025-01-09"
output: html_document
editor_options: 
  markdown: 
    wrap: 72
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

## Libraries

```{r}
# Data manipulation and plotting
library(tidyverse)

# Descriptive tables
library(gtsummary)

# Bayesian models
library(brms)
library(broom)
library(broom.mixed)
library(emmeans)
library(car)  

# Spatial analysis and mapping
library(sf)
library(spdep)
library(tmap)

# Exploratory plots
library(GGally)
library(ggridges)
library(patchwork)

# Combining figures
library(gridExtra)
library(png)
library(grid)

# Blantyre data
library(mlwdata)
```

## Loading Files

```{r}
u5m <- readRDS("u5m_anon.rds") #Anonymised
cluster_sum_sf<- readRDS("cluster_sum_sf.rds") %>% # Contains shapefiles and cluster-level data
  #CNR currently per 100,000 person per year - change to per 1000 per year
  mutate(cnr_2020u_scaled = cnr_2020u/100,
  #Density is currently people per km2 - scale to per 10m x 10m
  dens_2020u_scaled = dens_2020u/10000)

#Adding cluster-level variables to u5m
cluster_data <- sf::st_drop_geometry(cluster_sum_sf)
u5m <- u5m %>% left_join(cluster_data, by = "cluster")
```

## Table 1.

```{r}
# Covariables for Table 1
table1_covs <- c(
                 "sex", 
                 "hivstatus_binary", "hiv_exp_bin" , "currentcough_ind" , "any_tbsx" , "bcg_ind" , 
         "tbrx_cat"  , "genhealth_cat" , "muacz_2", "hosp_amt_yn", "locschool_ind", "travel_yn",
         "clinic_catchment", "hh_members_n", "means_score_tertile", "poorest_third", "educ_3", "distance_to_clinic", 
         "tb_hhc_lifetime" , "qftp"
                 )

table1 <- u5m %>%
  tbl_summary(
    by = rec_location, # Stratify by "Clinic" vs "Community"
    include = c(age, all_of(table1_covs)),  # Use predefined vector of covariates
    statistic = list(
      all_continuous() ~ "{median} ({p25}, {p75})" ,# Median (IQR)
      all_categorical() ~ "{n}/{N} ({p}%)"              # Frequency (Percentage)
        ),
    missing = "no"
  ) %>%
  add_overall() %>%
  bold_labels() 

# Print Table 1
table1 %>% as.tibble()
```

## Supplementary data 2 - age-adjustedassociations between covariables and recruitment locations

```{r}
priors <- c(
  set_prior("normal(0, 2.5)", class = "b"),            # Fixed effects (age, sex)
  set_prior("normal(0, 5)", class = "Intercept")
)
```

```{r}
# Unadjusted analysis
tab1_supp_results_unadj <- vector("list", length(table1_covs))
names(tab1_supp_results_unadj) <- table1_covs

for (cov in table1_covs) {
  
  model_unadj <- brm(
    formula = as.formula(paste("as.factor(rec_location) ~", cov)),
    data = u5m,
    prior = priors,
    family = bernoulli(),
    silent = TRUE,
    refresh = 0
  )
  
  post_summary <- summary(model_unadj)$fixed
  df <- as_tibble(post_summary, rownames = "term") %>%
    filter(term != "Intercept") %>%
    mutate(
      OR = exp(Estimate),
      CI_low = exp(`l-95% CI`),
      CI_high = exp(`u-95% CI`),
      `OR (95% CrI)` = paste0(round(OR, 2), " (", round(CI_low, 2), "-", round(CI_high, 2), ")")
    ) %>%
    select(term, `OR (95% CrI)`) %>%
    rename(Covariate = term)
  
  tab1_supp_results_unadj[[cov]] <- df
}

# Age-adjusted analysis
tab1_supp_results_ageadj <- vector("list", length(table1_covs))
names(tab1_supp_results_ageadj) <- table1_covs

for (cov in table1_covs) {
  
  model_ageadj <- brm(
    formula = as.formula(paste("as.factor(rec_location) ~ age +", cov)),
    data = u5m,
    prior = priors,
    family = bernoulli(),
    silent = TRUE,
    refresh = 0
  )
  
  post_summary <- summary(model_ageadj)$fixed
  df <- as_tibble(post_summary, rownames = "term") %>%
    filter(term != "Intercept", term != "age") %>%
    mutate(
      OR = exp(Estimate),
      CI_low = exp(`l-95% CI`),
      CI_high = exp(`u-95% CI`),
      `Age-adjusted AOR (95% CrI)` = paste0(round(OR, 2), " (", round(CI_low, 2), "-", round(CI_high, 2), ")")
    ) %>%
    select(term, `Age-adjusted AOR (95% CrI)`) %>%
    rename(Covariate = term)
  
  tab1_supp_results_ageadj[[cov]] <- df
}


# Combine unadjusted results
unadj_df <- bind_rows(tab1_supp_results_unadj, .id = "Variable")  # Covariate = variable name (e.g., "sex")
# Rename coefficient name to "Term" for clarity
unadj_df <- unadj_df %>% rename(Value = Covariate)
# Combine age-adjusted results
ageadj_df <- bind_rows(tab1_supp_results_ageadj, .id = "Variable")  # Covariate = variable name (e.g., "sex")
# Rename coefficient name to "Term" for clarity
ageadj_df <- ageadj_df %>% rename(Value = Covariate)
# Now join by both variable and term
supp_tab_2 <- left_join(unadj_df, ageadj_df, by = c("Variable", "Value"))


# View results
supp_tab_2
```

## Calculating ARTI with 95% CIs for Table 1

```{r}
compute_risk_bayesian <- function(data, n_draws = 10000) {
  set.seed(123)
  n_positive <- sum(data$qftp == "Positive", na.rm = TRUE)
  n_total <- sum(!is.na(data$qftp))
  prevalence_draws <- rbeta(n_draws, n_positive + 1, n_total - n_positive + 1)

  age_values <- data$age[!is.na(data$age)]
  mean_age <- mean(age_values)
  se_age <- sd(age_values) / sqrt(length(age_values))
  age_draws <- rnorm(n_draws, mean = mean_age, sd = se_age)

  # Ensure age > 0 to avoid issues
  age_draws <- pmax(age_draws, 0.1)

  # Transform to risk
  risk_draws <- 1 - (1 - prevalence_draws)^(1 / age_draws)
  
  risk_ci <- quantile(risk_draws, probs = c(0.025, 0.975))
  risk_point <- mean(risk_draws)

  return(list(
    Risk_Estimate = risk_point,
    Risk_Draws = risk_draws,
    UI_Lower = risk_ci[1],
    UI_Upper = risk_ci[2]
  ))
}

group_results <- compute_risk_bayesian(u5m)
clinic_results <- compute_risk_bayesian(u5m %>% filter(rec_location == "Clinic"))
community_results <- compute_risk_bayesian(u5m %>% filter(rec_location == "Community"))

risk_diff_draws <- clinic_results$Risk_Draws - community_results$Risk_Draws
obs_risk_diff <- mean(risk_diff_draws)

prob_diff_gt_zero <- mean(risk_diff_draws > 0)  # Posterior probability that clinic > community
risk_diff_ci <- quantile(risk_diff_draws, probs = c(0.025, 0.975))

# Package results
risk_df <- tibble(
  Group = c("Overall", "Clinic", "Community"),
  Risk_Estimate = c(group_results$Risk_Estimate*100, clinic_results$Risk_Estimate*100, community_results$Risk_Estimate*100),
  UI_Lower = c(group_results$UI_Lower*100, clinic_results$UI_Lower*100, community_results$UI_Lower*100),
  UI_Upper = c(group_results$UI_Upper*100, clinic_results$UI_Upper*100, community_results$UI_Upper*100)
)

risk_diff_df <- tibble(
  Comparison = "Clinic vs Community",
  Observed_Risk_Difference = obs_risk_diff*100,
  UI_Lower = 100*risk_diff_ci[1],
  UI_Upper = 100*risk_diff_ci[2],
  Posterior_Prob_Clinic_Greater = prob_diff_gt_zero
)

risk_df
risk_diff_df
```

## Plotting graphically posterior distributions for ARTI from each area

```{r}
# Combine risk draws into one data frame for plotting
posterior_df <- tibble(
  Clinic = clinic_results$Risk_Draws*100,
  Household = community_results$Risk_Draws*100
) %>%
  pivot_longer(cols = everything(), names_to = "Group", values_to = "ARTI")

ggplot(posterior_df, aes(x = ARTI, fill = Group, color = Group)) +
  geom_density(alpha = 0.4) +
  geom_vline(data = risk_df %>% filter(Group!="Overall"), aes(xintercept = Risk_Estimate, color = Group),
             linetype = "dashed") +
  labs(
    title = "Posterior distributions of Annual Risk of Mtb Infection (ARTI)",
    x = "Estimated ARTI (%)",
    y = "Density"
  ) +
  theme_minimal() +
  scale_fill_brewer(palette = "Set2") +
  scale_color_brewer(palette = "Set2")
```

## Area-level COVS - covariance

```{r}
cluster_cov_labels2 <- c(
              dens_2020u = "Pop Density",
              scale_mean_pov = "Mean poverty score", 
              hiv_prev_mean = "HIV prevalence, %", 
              cnr_2020u = "CNR")
cluster_vars2<- c("dens_2020u", 
              "scale_mean_pov", 
              "hiv_prev_mean", 
              "cnr_2020u")
```

```{r, fig.width = 10, fig.height = 10}
# Function to compute and annotate R² directly on scatterplots
annotate_r2 <- function(data, mapping, ...) {
  x_var <- rlang::as_name(mapping$x)  # Extract variable names
  y_var <- rlang::as_name(mapping$y)

  model <- lm(reformulate(x_var, y_var), data = data)  # Fit model
  r2 <- summary(model)$r.squared  # Extract R²
  
  # Create scatterplot
  p <- ggplot(data, mapping) +
    geom_point(alpha = 0.6) +  # Scatterplot
    annotate("text", x = Inf, y = -Inf, label = paste0("R² = ", round(r2, 2)), 
             hjust = 1.1, vjust = -0.5, size = 4) +  # Annotate R² in bottom right
    theme_bw()
  
  return(p)
}

# Create ggpairs plot with R² annotations on scatterplots
cluster_data %>%
  ggpairs(
    columns = cluster_vars2,
    lower = list(continuous = wrap("points", alpha = 0.6)),  # Scatterplots in lower panel
    upper = list(continuous = annotate_r2),  # Annotate R² directly on scatterplots
    labeller = as_labeller(cluster_cov_labels2)
  ) +
  theme_bw()

```

## Table 2 summary statistics

```{r}
tab2_covs <- c("age", "sex",  "hiv_exp_bin", "muacz_2", "means_score_tertile", "educ_bin", "distance_to_clinic", "tb_hhc_lifetime", 
               "scale_mean_pov", "hiv_prev_mean", "cnr_2020u_scaled", "dens_2020u_scaled")
```

```{r}
table2 <- u5m %>%
  tbl_summary(
    by = qftp, # Stratify by QFTP
    include = all_of(tab2_covs), 
    statistic = list(
      all_continuous() ~ "{median} ({p25}, {p75})" ,# Median (IQR)
      all_categorical() ~ "{n}/{N} ({p}%)"              # Frequency (Percentage)
        ),
    missing = "no"
  ) %>%
  bold_labels() 

# table2 %>% as.tibble() %>% write.csv("table2.csv", na = "")
table2 %>% as.tibble()
```

## Univariable ORs for Table 2

```{r}
priors <- c(
  set_prior("normal(0, 2.5)", class = "b"),            # Fixed effects (age, sex)
  set_prior("normal(0, 5)", class = "Intercept"),      # Intercept
  set_prior("exponential(1)", class = "sd")           # Random effect (cluster variance)
)
```

```{r, eval = FALSE}
univar_models <- list()

univar_re_results <- lapply(tab2_covs, function(cov) {
  
  formula <- as.formula(
    paste("qftp ~ (1 | cluster) + ", cov)
  )
  
  model <- brm(
    formula = formula,
    data = u5m,
    family = bernoulli(link = "logit"),
    prior = priors,
    cores = 4,
    chains = 4,
    seed = 12345,
    iter = 4000,
    refresh = 0
  )
  
  univar_models[[cov]] <- model
  
  broom.mixed::tidy(
    model,
    conf.int = TRUE,
    exponentiate = TRUE
  ) %>%
    filter(term != "(Intercept)",
           term != "sd__(Intercept)") %>%
    mutate(
      AOR_CI = paste0(
        round(estimate, 2), " (",
        round(conf.low, 2), "-",
        round(conf.high, 2), ")"
      )
    ) %>%
    dplyr::select(term, AOR_CI)
})

univar_re_results_table <- bind_rows(univar_re_results)
```


## AORs for Table 2

```{r, eval = FALSE}
adj_models <- list()

adj_re_results <- lapply(tab2_covs, function(cov) {
  
  formula <- as.formula(
    paste("qftp ~ (1 | cluster) + age + sex +", cov)
  )
  
  model <- brm(
    formula = formula,
    data = u5m,
    family = bernoulli(link = "logit"),
    prior = priors,
    cores = 4,
    chains = 4,
    seed = 12345,
    iter = 4000,
    refresh = 0
  )
  
  adj_models[[cov]] <- model
  
  broom.mixed::tidy(
    model,
    conf.int = TRUE,
    exponentiate = TRUE
  ) %>%
    filter(term != "(Intercept)",
           term != "sd__(Intercept)") %>%
    mutate(
      AOR_CI = paste0(
        round(estimate, 2), " (",
        round(conf.low, 2), "-",
        round(conf.high, 2), ")"
      )
    ) %>%
    dplyr::select(term, AOR_CI)
})

adj_re_results_table <- bind_rows(adj_re_results)
```

## Sensitivity analysis additionally adjusting for recruitment location

```{r, eval = FALSE}
sens_models <- list()

sens_re_results <- lapply(tab2_covs, function(cov) {
  
  formula <- as.formula(
    paste("qftp ~ (1 | cluster) + age + sex + rec_location +", cov)
  )
  
  model <- brm(
    formula = formula,
    data = u5m,
    family = bernoulli(link = "logit"),
    prior = priors,
    cores = 4,
    chains = 4,
    seed = 12345,
    iter = 4000,
    refresh = 0
  )
  
  sens_models[[cov]] <- model
  
  broom.mixed::tidy(
    model,
    conf.int = TRUE,
    exponentiate = TRUE
  ) %>%
    filter(
      term != "(Intercept)",
      term != "sd__(Intercept)"
    ) %>%
    mutate(
      AOR_recloc_CI = paste0(
        round(estimate, 2), " (",
        round(conf.low, 2), "-",
        round(conf.high, 2), ")"
      )
    ) %>%
    dplyr::select(term, AOR_recloc_CI)
})

sens_re_results_table <- bind_rows(sens_re_results) %>%
  distinct(term, .keep_all = TRUE)
```

## ORs for poverty tertiles

```{r}
emm_univar <- emmeans(m_univar_means_score_tertile, ~ means_score_tertile)

contrast(
  emm_univar,
  method = "trt.vs.ctrl",
  ref = 3,
  type = "response"
)

emm_adj <- emmeans(m_adj_means_score_tertile, ~ means_score_tertile)

contrast(
  emm_adj,
  method = "trt.vs.ctrl",
  ref = 3,
  type = "response"
)

emm_sens <- emmeans(m_sens_means_score_tertile, ~ means_score_tertile)

contrast(
  emm_sens,
  method = "trt.vs.ctrl",
  ref = 3,
  type = "response"
)
```

## Combine for Table 2 and supp table 3

```{r}
tab2_ors <- univar_re_results_table %>% left_join(adj_re_results_table, by = c("term")) %>%
  left_join(sens_re_results_table, by = c("term"))

tab2_ors
# tab2_ors %>% write.csv("tab2_ors.csv")

```

## Correlation between cluster-level covariables, and Mtb immunoreativity prevalence

```{r, fig.width = 10, fig.height = 10}
prev_sum <- u5m %>%
  group_by(cluster) %>%
  summarise(qftp_prev = mean(qftp == "Positive") * 100, 
            cluster_n = n(), 
            cluster_pos = cluster_n * qftp_prev/100)

cluster_data <- cluster_data %>% left_join(prev_sum, by = c("cluster"))

cluster_cov_labels3 <- c(
              dens_2020u = "Pop Dens (2020)",
              scale_mean_pov = "Mean poverty score", 
              hiv_prev_mean = "HIV prevalence", 
              cnr_2020u = "CNR", 
              qftp_prev = "Prevalence QFTP+")
cluster_vars3<- c("dens_2020u", 
              "scale_mean_pov", 
              "hiv_prev_mean", 
              "cnr_2020u", 
              "qftp_prev")


# Create ggpairs plot with R² annotations on scatterplots
cluster_data %>%
  ggpairs(
    columns = cluster_vars3,
    lower = list(continuous = wrap("points", alpha = 0.6)),  # Scatterplots in lower panel
    upper = list(continuous = annotate_r2),  # Annotate R² directly on scatterplots
    labeller = as_labeller(cluster_cov_labels3)
  ) +
  theme_bw()

```

```{r}
cor_matrix <- cluster_data %>%
  dplyr::select(all_of(cluster_vars3)) %>%
  cor(use = "pairwise.complete.obs")
cor_matrix
```

```{r}
vif_values <- vif(lm(qftp_prev ~ ., data = cluster_data %>% dplyr::select(all_of(cluster_vars3))))
print(vif_values)
```

## Spatial variation in neighbourhood-level covariables (supp fig 5)

```{r}
hiv_plot<- cluster_sum_sf %>%
  ggplot(aes (fill = hiv_prev_mean)) +
  geom_sf() +
  scale_fill_gradient(
    low = "whitesmoke",    # Color for the lowest value
    high = "darkred", # Color for the highest value
    name = "HIV prevalence, %" # Optional: Label for the legend
  ) +
  guides(fill =guide_colorbar(direction = "horizontal")) +
  theme_void() +
  theme(legend.position = "bottom") 
  
dens_plot<- cluster_sum_sf %>%
  ggplot(aes (fill = dens_2020u_scaled)) +
  geom_sf() +
  scale_fill_gradient(
    low = "whitesmoke",    # Color for the lowest value
    high = "darkgreen", # Color for the highest value
    name = "Population density" # Optional: Label for the legend
  ) +
  guides(fill =guide_colorbar(direction = "horizontal")) +
  theme_void() +
  theme(legend.position = "bottom") 

pov_plot<- cluster_sum_sf %>%
  ggplot(aes (fill = scale_mean_pov)) +
  geom_sf() +
  scale_fill_gradient(
    low = "whitesmoke",    # Color for the lowest value
    high = "darkmagenta", # Color for the highest value
    name = "Median poverty score" # Optional: Label for the legend
  ) +
  guides(fill =guide_colorbar(direction = "horizontal")) +
  theme_void() +
  theme(legend.position = "bottom") 

hiv_plot + dens_plot + pov_plot +
  plot_annotation(tag_levels = "A")

```

## Model 1 - adjusted for baseline characteristics only (age + sex), no neighbourhood-level COVs

```{r, eval = FALSE}
m1 <- brm(
  qftp ~ age + sex + (1|cluster),
  data = u5m,
  prior = priors,
  family = bernoulli(link = "logit"),
  chains = 4, iter = 4000, cores = 4, 
  seed = 12345
)
```

```{r}
m1 %>% summary()
```


### Model checking
```{r}
plot(m1, ask = FALSE)
```

```{r}
pp_check(m1)  # Posterior predictive check
```

```{r}
plot(conditional_effects(m1), ask = FALSE)
```

```{r}
mcmc_plot(m1, type = "dens_overlay", prob = 0.95)
```

### ICC - looking how much variation is cluster-level

```{r}
# Extract standard deviation of cluster-level intercept
sd_cluster <- posterior_summary(m1, pars = "sd_cluster__Intercept")[, "Estimate"]

# Compute variance of the random intercept
var_cluster <- sd_cluster^2

# Residual variance for logistic regression (logit link)
var_residual <- (pi^2) / 3  # approx 3.29

# Compute ICC
icc <- var_cluster / (var_cluster + var_residual)

# Print ICC
print(paste("ICC:", round(icc, 4)))
```


```{r}
m1_ranef <- ranef(m1)$cluster

# Create a data frame with the posterior mean and SD of the random intercept for each cluster.
m1_ranef_df <- data.frame(
  cluster = rownames(m1_ranef),
  m1_re_mean = m1_ranef[, "Estimate", "Intercept"],   # posterior mean for the random intercept
  m1_re_sd   = m1_ranef[, "Est.Error", "Intercept"]     # posterior standard deviation (SD) for the random intercept
) %>%
    mutate(
    m1_re_or_mean  = exp(m1_re_mean),  
    m1_re_or_lower = exp(m1_re_mean - 1.96 * m1_re_sd),  # 95% CI lower bound
    m1_re_or_upper = exp(m1_re_mean + 1.96 * m1_re_sd),   # 95% CI upper bound
    cluster_num = as.numeric(gsub("c", "", cluster)),
  cluster_rank = case_when(cluster_num >14 & cluster_num <48 ~ cluster_num - 12, 
                             cluster_num >= 48 ~ cluster_num - 16,
                             cluster_num <=14 ~ cluster_num)
    )


m1_ranef_df %>% dplyr::select(cluster, cluster_rank, m1_re_or_mean, m1_re_or_lower, m1_re_or_upper) %>% arrange(m1_re_or_mean)

```

```{r}
m1_post <- posterior_samples(m1, pars = "r_cluster")

# Reshape to long format for plotting
m1_post_long <- m1_post %>%
  pivot_longer(cols = everything(), names_to = "cluster", values_to = "log_odds") %>%
  mutate(
    cluster_num = as.numeric(gsub("r_cluster\\[c|,Intercept\\]", "", cluster)),  # Clean cluster names
    cluster_rank = case_when(cluster_num >14 & cluster_num <48 ~ cluster_num - 12, 
                             cluster_num >= 48 ~ cluster_num - 16,
                             cluster_num <=14 ~ cluster_num),
    cluster_rank = as.factor(cluster_rank),
    odds_ratio = exp(log_odds)  
  )
m1_post_long 
```

```{r}
cluster_sum_sf <- cluster_sum_sf %>% left_join(m1_ranef_df, by = c("cluster", "cluster_num", "cluster_rank"))
```


### Density distributions for ARTI posterior distributions (supplementary data 4)

```{r}
ggplot() + 
  # Density ridgelines for full posterior distributions
  geom_density_ridges(
    data = m1_post_long, 
    aes(x = odds_ratio, y = fct_reorder(cluster_rank, odds_ratio, .fun = median
                                        ),
        ), 
    fill = "lightseagreen",
    alpha = 0.5,
    scale = 1, rel_min_height = 0.01
  ) +
  # Add Mean OR Points (from summary)
  geom_point(
    data = m1_ranef_df, 
    aes(x = m1_re_or_mean, y = as.factor(cluster_rank)), 
    color = "black", size = 2
  ) +
  
  # Add 95% CI Error Bars (from summary)
  geom_errorbarh(
    data = m1_ranef_df, 
    aes(xmin = m1_re_or_lower, xmax = m1_re_or_upper, y = as.factor(cluster_rank)), 
    height = 0.2, color = "black"
  ) +
  geom_vline(
    xintercept = 1, 
    linetype = "dashed", 
    colour = "darkred"
  ) +
  scale_x_log10() +  # Log-scale for ORs
  labs(
    title = "Neighbourhood-level odds ratios for Mtb immunoreactivity",
    x = "Estimated odds ratio (log scale)",
    y = "Neighbourhood",
    caption = "Black points: mean OR; black bars: 95% CrI"
  ) +
  
  theme_bw() +
  coord_cartesian(xlim = c(0.1,10))
```


## Sensitivity analysis: adjusting for recruitment type

```{r, eval = FALSE}
m1_sens <- brm(
  qftp ~ age + sex + rec_location + (1|cluster),
  data = u5m,
  prior = priors,
  family = bernoulli(link = "logit"),
  chains = 4, iter = 4000, cores = 4, 
  seed = 12345
)
```

```{r}
m1_sens %>% summary()
```

### Model checking

```{r}
plot(m1_sens, ask = FALSE)
```

```{r}
pp_check(m1_sens)  # Posterior predictive check
```

```{r}
plot(conditional_effects(m1_sens), ask = FALSE)
```

```{r}
mcmc_plot(m1_sens, type = "dens_overlay", prob = 0.95)
```


```{r}
m1_sens_ranef <- ranef(m1_sens)$cluster


m1_sens_ranef_df <- data.frame(
  cluster = rownames(m1_sens_ranef),
  m1_sens_re_mean = m1_sens_ranef[, "Estimate", "Intercept"],   
  m1_sens_re_sd   = m1_sens_ranef[, "Est.Error", "Intercept"]     
) %>%
    mutate(
    m1_sens_re_or_mean  = exp(m1_sens_re_mean),  
    m1_sens_re_or_lower = exp(m1_sens_re_mean - 1.96 * m1_sens_re_sd),  # 95% CI lower bound
    m1_sens_re_or_upper = exp(m1_sens_re_mean + 1.96 * m1_sens_re_sd),   # 95% CI upper bound
    cluster_num = as.numeric(gsub("c", "", cluster)),
  cluster_rank = case_when(cluster_num >14 & cluster_num <48 ~ cluster_num - 12, 
                             cluster_num >= 48 ~ cluster_num - 16,
                             cluster_num <=14 ~ cluster_num)
    )


m1_sens_ranef_df %>% dplyr::select(cluster, cluster_rank, m1_sens_re_or_mean, m1_sens_re_or_lower, m1_sens_re_or_upper) %>% arrange(m1_sens_re_or_mean)

```


## Model 2 - adding area-level covariables

```{r, eval = FALSE}
m2_mod <- brm(
  qftp ~ age + sex + 
        poorest_third + hiv_prev_mean + dens_2020u_scaled +
        (1 | cluster),
  data = u5m,
  prior = priors,
  family = bernoulli(link = "logit"),
  seed = 12345,
  chains = 4, iter = 4000, cores = 4, 
  control=list(adapt_delta=0.9)
)

```

```{r}
plot(m2_mod, ask = FALSE)
```

```{r}
m2_mod %>% summary()
```

```{r}
pp_check(m2_mod)  # Posterior predictive check
```

```{r}
plot(conditional_effects(m2_mod), ask = FALSE)
```


## Comparing models

```{r}
m1_tidy <- 
  broom::tidy(m1, conf.int = TRUE, exponentiate = TRUE) %>%
  mutate(simple = paste0(
    round(estimate, 2), " (", round(conf.low, 2), "-", round(conf.high, 2), ")"
  )) %>%
  dplyr::select(term, simple)

m2_mod_tidy<- 
  broom::tidy(m2_mod, conf.int = TRUE, exponentiate = TRUE) %>%
  mutate(extended = paste0(
    round(estimate, 2), " (", round(conf.low, 2), "-", round(conf.high, 2), ")"
  )) %>%
  dplyr::select(term, extended)

tidy_models <- 
m1_tidy %>% right_join(m2_mod_tidy, by = c("term"))

tidy_models
```
### LOO and WAIC 

```{r}
# LOO
loo_m1 <- loo(m1)
loo_m2_mod <- loo(m2_mod)
loo_m1_sens <- loo(m1_sens)

loo_comparison <- loo_compare(
  loo_m1,
  loo_m2_mod,
  loo_m1_sens
)

# Alternatively, WAIC
waic_m1 <- waic(m1)
waic_m2_mod <- waic(m2_mod)
waic_m1_sens <- waic(m1_sens)

waic_comparison <- loo_compare(
  waic_m1,
  waic_m2_mod,
  waic_m1_sens
)

# Extract model fit statistics
model_fit <- tibble(
  term = c("LOOIC", "WAIC"),
  
  simple = c(
    round(loo_m1$estimates["looic", "Estimate"], 2),
    round(waic_m1$estimates["waic", "Estimate"], 2)
  ),
  
  extended = c(
    round(loo_m2_mod$estimates["looic", "Estimate"], 2),
    round(waic_m2_mod$estimates["waic", "Estimate"], 2)
  ),
  
  sensitivity = c(
    round(loo_m1_sens$estimates["looic", "Estimate"], 2),
    round(waic_m1_sens$estimates["waic", "Estimate"], 2)
  )
)

model_fit
```

```{r}
post_samples_m1 <- as_draws_df(m1)
# Standard deviation of the residual (fixed)
sd_residual <- sqrt(pi^2 / 3)

# Posterior samples of the random effect standard deviation (sd(Intercept))
sd_cluster_samples_m1 <- post_samples_m1$`sd_cluster__Intercept`

# Compute ICC for each posterior sample
icc_samples_m1 <- sd_cluster_samples_m1^2 / (sd_cluster_samples_m1^2 + sd_residual^2)

# Calculate 95% credible interval
icc_ci_m1 <- quantile(icc_samples_m1, probs = c(0.025, 0.5, 0.975))

#Now for M2
post_samples_m2 <- as_draws_df(m2_mod)
sd_cluster_samples_m2 <- post_samples_m2$`sd_cluster__Intercept`
icc_samples_m2 <- sd_cluster_samples_m2^2 / (sd_cluster_samples_m2^2 + sd_residual^2)

# Calculate 95% credible interval
icc_ci_m2 <- quantile(icc_samples_m2, probs = c(0.025, 0.5, 0.975))

icc_tib<- 
icc_ci_m1 %>% bind_rows(icc_ci_m2) %>% 
  mutate(model= c("simple", "extended"), 
         icc = paste0(
    round(`50%`, 3), " (", round(`2.5%`, 3), "-", round(`97.5%`, 3), ")"
         )) %>% 
  dplyr::select(model, icc) %>%
  pivot_wider(names_from = model, values_from = icc) %>% mutate(term = "ICC")
icc_tib
```

### Model comparison table

```{r}
# Merge with model results
models_table <- tidy_models %>%
  bind_rows(model_fit) %>%
  filter(term !="(Intercept") %>%
  bind_rows(icc_tib)

models_table
```

## Figure 2 maps

```{r}
m2_mod_ranef <- ranef(m2_mod)$cluster

m2_mod_ranef_df <- data.frame(
  cluster = rownames(m2_mod_ranef),
  m2_mod_re_mean = m2_mod_ranef[, "Estimate", "Intercept"],   
  m2_mod_re_sd   = m2_mod_ranef[, "Est.Error", "Intercept"]     
) %>% 
  mutate(m2_mod_re_or_mean = exp(m2_mod_re_mean)) # exponentiate for OR
cluster_sum_sf <- cluster_sum_sf%>% left_join(m2_mod_ranef_df, by = c("cluster"))
cluster_sum_sf %>% dplyr::select(cluster, contains("re_mean"))
```

### Draw maps with OSM backgrounds
Background

```{r}
timasamala_area<-st_union(cluster_sum_sf) 
timasamala_buffer <- timasamala_area %>%
  st_buffer(dist = 0.008) %>% st_bbox()

ti_osm_buffer<- readRDS("ti_osm_buffer.rds")
```

```{r}
clinics<-blantyre_clinics%>% st_set_crs(4326) #in mlwdata
timasamala_clinics <- blantyre_clinics %>%
  filter(clinic=="Bangwe Health Centre"|
           clinic=="Limbe Health Centre"|
           clinic=="Ndirande Health Centre") %>%
  mutate(clinic = str_remove(clinic, " Health Centre"))
qech <- blantyre_clinics %>%
  filter(clinic=="Queen Elizabeth hospital") %>%
  mutate(clinic="QECH")
```

```{r}
m1_osm <-tm_shape(ti_osm_buffer) +
      tm_rgb() +
      tm_shape(cluster_sum_sf) + 
      tm_polygons(col = "m1_re_or_mean", 
              palette = c("blue", "white", "red"),  # Manually setting colors
              title = "OR",  # Change legend label
              style = "cont",
              breaks = c(0.5, 1, 2),       # Matching ggplot2 breakpoints
              contrast = 1,                         # Ensures full color contrast
              auto.palette.mapping = FALSE,         # Ensures manual colors are used
              legend.is.portrait = TRUE,
              legend.reverse = TRUE,      
              alpha = 0.9) +
      tm_layout(legend.position = c("right","top"),
                legend.outside = FALSE,
                         legend.bg.color = "white", 
                title = "A",
                asp = 0) +
  tm_shape(timasamala_clinics) +
  tm_dots(col="black", size=0.5) +
  tm_text("clinic", col="black", bg.color = "white", bg.alpha = 0.75,
          just = "left", xmod=0.3, ymod=-0.3) + 
  tm_scale_bar(breaks = c(0, 1, 2, 3, 4)) +
  tm_add_legend('symbol', col="black", size=0.5,
                      labels="Clinic") 
  
m2_osm <-tm_shape(ti_osm_buffer) +
      tm_rgb() +
      tm_shape(cluster_sum_sf) + 
      tm_polygons(col = "m2_mod_re_or_mean", 
              palette = c("blue", "white", "red"),  # Manually setting colors
              title = "OR",  # Change legend label
              style = "cont",
              breaks = c(0.5, 1, 2),       # Matching ggplot2 breakpoints
              contrast = 1,                         # Ensures full color contrast
              auto.palette.mapping = FALSE,         # Ensures manual colors are used
              legend.is.portrait = TRUE,
              legend.reverse = TRUE,      
              alpha = 0.9) +
      tm_layout(
  legend.position = c("right", "top"),
  legend.outside = FALSE,
  legend.bg.color = "white",
  title = "B",  
  asp = 0) +
  tm_shape(timasamala_clinics) +
  tm_dots(col="black", size=0.5) +
  tm_text("clinic", col="black", bg.color = "white", bg.alpha = 0.75,
          just="left", xmod=0.3, ymod=-0.3) + 
  tm_scale_bar(breaks = c(0, 1, 2, 3, 4)) +
  tm_add_legend('symbol', col="black", size=0.5,
                      labels="Clinic") 


tmap <- tmap_arrange(m1_osm, m2_osm, ncol = 2, outer.margins = FALSE)
tmap
```

## Inspect for residual spatial autocorrelation using Moran's I

### Construct weights matrix

```{r}
nb <- poly2nb(cluster_sum_sf, queen = TRUE, snap = 0.001)   # Define neighbors

#Adjacency matrix 
adj_matrix <- nb2mat(nb, style = "B", zero.policy = TRUE) 
listw <- nb2listw(nb, style = "W", zero.policy = TRUE) 

```

### Add residuals from models to cluster_sum_sf

```{r}
ind_residuals_m1 <- data.frame(
  cluster = m1$data$cluster,  # Ensure cluster ID is available
  resid = residuals(m1)  # Extract individual-level residuals
)

cluster_residuals_m1 <- ind_residuals_m1 %>%
  group_by(cluster) %>%
  summarise(mean_resid_m1 = mean(resid.Estimate, na.rm = TRUE))  # Aggregate residuals
cluster_sum_sf <- cluster_sum_sf %>%
  left_join(cluster_residuals_m1, by = "cluster")

```

```{r}
moran_test_m1 <- moran.test(cluster_sum_sf$mean_resid_m1, listw)
print(moran_test_m1)
```

### And now for M2
Add residuals from models to cluster_sum_sf

```{r}
ind_residuals_m2 <- data.frame(
  cluster = m2_mod$data$cluster,  # Ensure cluster ID is available
  resid = residuals(m2_mod)  # Extract individual-level residuals
)

cluster_residuals_m2 <- ind_residuals_m2 %>%
  group_by(cluster) %>%
  summarise(mean_resid_m2 = mean(resid.Estimate, na.rm = TRUE))  # Aggregate residuals
cluster_sum_sf <- cluster_sum_sf %>%
  left_join(cluster_residuals_m2, by = "cluster")

moran_test_m2 <- moran.test(cluster_sum_sf$mean_resid_m2, listw)
print(moran_test_m2)
```

## Calculating ARTIs

-   With 50% male and female\
-   Fix age at 3\

```{r}
# Create new dataset with both Male & Female for each cluster
new_data <- expand.grid(
  cluster = unique(u5m$cluster),
  sex = c("Male", "Female"),
  age = 3  # Fix age at 3
)

# Extract posterior draws for both sexes
m1_post_draws <- posterior_linpred(m1, newdata = new_data, re_formula = ~(1|cluster))

# Convert posterior draws to a tibble (long format)
m1_post_df <- as_tibble(m1_post_draws) %>%
  mutate(draw = row_number()) %>%  # Add draw ID
  pivot_longer(-draw, names_to = "obs_id", values_to = "logit_qftp") %>%  # Long format
  mutate(
    cluster = new_data$cluster[as.numeric(gsub("V", "", obs_id))],  # Match cluster
    sex = new_data$sex[as.numeric(gsub("V", "", obs_id))]  # Match sex
  ) %>%
  dplyr::select(-obs_id)  

# Convert logit to probability scale
m1_post_df <- m1_post_df %>%
  mutate(prob_qftp = plogis(logit_qftp))

# Average Male & Female predictions within each draw & cluster
m1_post_avg <- m1_post_df %>%
  group_by(cluster) %>%
  summarise(mean_prob_qftp = mean(prob_qftp), 
            median_prob_qftp = median(prob_qftp),  
            qftp_ci_lo = quantile(prob_qftp, 0.025),  # 2.5% CI
            qftp_ci_hi = quantile(prob_qftp, 0.975),
            .groups = "drop") %>%
  mutate(mean_arti = 1-(1-mean_prob_qftp)^(1/3), 
         m1_arti_ci_lo = 1-(1-qftp_ci_lo)^(1/3),
         m1_arti_ci_hi = 1-(1-qftp_ci_hi)^(1/3)
         )

# View first few rows
m1_post_avg <- m1_post_avg %>%
  mutate(m1_qftp = mean_prob_qftp, 
         m1_arti = mean_arti, 
         cluster_num = as.numeric(gsub("c", "", cluster)),
    cluster_rank = rank(cluster_num)) %>%
  dplyr::select(cluster_rank, cluster, m1_qftp, m1_arti, m1_arti_ci_lo, m1_arti_ci_hi)

m1_post_avg %>% arrange(m1_arti) %>% dplyr::select(cluster_rank, starts_with("m1_arti"))

```

## Sensitivity analysis

```{r}
m1_sens_ranef_df <- data.frame(
  cluster = rownames(m1_sens_ranef),
  m1_sens_re_mean = m1_sens_ranef[, "Estimate", "Intercept"],
  m1_sens_re_sd   = m1_sens_ranef[, "Est.Error", "Intercept"]
) 

m1_sens_ranef_df %>%
  dplyr::select(
    cluster,
    m1_sens_re_or_mean,
    m1_sens_re_or_lower,
    m1_sens_re_or_upper
  ) %>%
  arrange(m1_sens_re_or_mean)
```

### Map of neighbourhoods using output of sensitivity analysis

```{r}
# Join to spatial data
cluster_sum_sf_sens <- cluster_sum_sf %>%
  left_join(
    m1_sens_ranef_df,
    by = c("cluster", "cluster_num")
  )


# Plot the sensitivity model
m1_sens_osm <- tm_shape(ti_osm_buffer) +
  tm_rgb() +
  tm_shape(cluster_sum_sf_sens) + 
  tm_polygons(
    col = "m1_sens_re_or_mean", 
    palette = c("blue", "white", "red"),
    title = "OR",
    style = "cont",
    breaks = c(0.5, 1, 2),
    contrast = 1,
    auto.palette.mapping = FALSE,
    legend.is.portrait = TRUE,
    legend.reverse = TRUE,
    alpha = 0.9
  ) +
  tm_layout(
    legend.position = c("right", "top"),
    legend.outside = FALSE,
    legend.bg.color = "white", 
    title = "A",
    asp = 0
  ) +
  tm_shape(timasamala_clinics) +
  tm_dots(col = "black", size = 0.5) +
  tm_text(
    "clinic",
    col = "black",
    bg.color = "white",
    bg.alpha = 0.75,
    just = "left",
    xmod = 0.3,
    ymod = -0.3
  ) + 
  tm_scale_bar(breaks = c(0, 1, 2, 3, 4)) +
  tm_add_legend(
    "symbol",
    col = "black",
    size = 0.5,
    labels = "Clinic"
  )

m1_sens_osm
```

### Ranking

```{r}
# Random effects from original model
m1_re <- tibble(
  cluster = rownames(m1_ranef),
  m1_ranef = m1_ranef[, "Estimate", "Intercept"]
)

# Random effects from sensitivity model
sens_re <- tibble(
  cluster = rownames(m1_sens_ranef),
  m1_sens_ranef = m1_sens_ranef[, "Estimate", "Intercept"]
)

# Compare
re_compare <- m1_re %>%
  left_join(sens_re, by = "cluster") %>%
  mutate(
    OR_m1 = exp(m1_ranef),
    OR_sens = exp(m1_sens_ranef),
    difference = m1_sens_ranef - m1_ranef,
    OR_ratio = exp(m1_sens_ranef - m1_ranef)
  ) %>%
  mutate(
    rank_m1 = rank(m1_ranef),
    rank_sens = rank(m1_sens_ranef)
  )

cor(
  re_compare$rank_m1,
  re_compare$rank_sens,
  method = "spearman"
)
```

### Compare sensitivity and base model

```{r}
rho <- cor(
  re_compare$m1_ranef,
  re_compare$m1_sens_ranef,
  method = "spearman",
  use = "complete.obs"
)

ggplot(re_compare, aes(x = m1_ranef, y = m1_sens_ranef)) +
  geom_point() +
  geom_abline(
    slope = 1,
    intercept = 0,
    linetype = "dashed"
  ) +
  annotate(
    "text",
    x = -Inf,
    y = Inf,
    label = paste0("Spearman \u03c1 = ", round(rho, 4)),
    hjust = -0.1,
    vjust = 1.5,
    size = 5
  ) +
  labs(
    x = "Cluster random effect: main model",
    y = "Cluster random effect: sensitivity model"
  ) +
  theme_minimal()
```

## Figure 2

### Figure 2A

```{r}
cluster_sum_sf <- cluster_sum_sf %>% left_join(m1_post_avg, by=c("cluster"))

#Add ranks
cluster_sum_sf <-cluster_sum_sf %>% 
  mutate(m1_arti_rank = rank(m1_arti), 
         cnr_rank = rank(cnr_2020u)) 

#Select highest ARTI areas
highlight_arti <- cluster_sum_sf %>%
  filter(m1_arti_rank >= 26)

#Draw map
cnr_osm <-tm_shape(ti_osm_buffer) +
      tm_rgb() +
      tm_shape(cluster_sum_sf) + 
      tm_polygons(col = "cnr_2020u", 
              palette = c("lemonchiffon", "orange", "red"),  # Manually setting colors
              title = "CNR",  # Change legend label
              style = "cont",
              contrast = 1,                         # Ensures full color contrast
              auto.palette.mapping = FALSE,         # Ensures manual colors are used
              legend.is.portrait = TRUE,
              legend.reverse = TRUE) +
  tm_shape(highlight_arti) + 
  tm_polygons(
    border.col = "blue",  # Set border color to blue
    alpha = 0.5,  # Adjust transparency if needed
    lwd = 2.5  # Line width of the borders
  ) +
  tm_add_legend(
    type = "fill",  # Add a fill legend
    col="white",
    labels = "Highest ARTI",  # Legend label
    size = 3,       # Adjust the size of the legend symbol
    border.col = "blue"  # Set the border color of the legend box to blue
  ) +
  tm_shape(timasamala_clinics) +
  tm_dots(col="black", size=0.5) +
  tm_text("clinic", col="black", bg.color = "white", bg.alpha = 0.75,
          just = "left", xmod=0.3, ymod=-0.3) + 
  tm_scale_bar(breaks = c(0, 1, 2, 3, 4)) +
  tm_add_legend('symbol', col="black", size=0.5,
                      labels="Clinic") +
  tm_layout(legend.position = c("right","top"),
                legend.outside = FALSE,
                         legend.bg.color = "white",
              title = "A"
) 

```

### Figure 2B

```{r}
# Top 8 by cnr_2020u (red box)
top_cnr <- cluster_sum_sf %>%
  top_n(8, cnr_2020u)

red_box <- data.frame(
  ymin = min(top_cnr$m1_arti - 0.0015),
  ymax = max(top_cnr$m1_arti + 0.0015),
  xmin = min(top_cnr$cnr_2020u - 5),
  xmax = max(top_cnr$cnr_2020u + 5),
  box_type = "Highest CNR neighbourhoods"  # Add a column to categorize the red box
)

# Top 8 by m1_arti (blue box)
top_arti <- cluster_sum_sf %>%
  top_n(8, m1_arti)

blue_box <- data.frame(
  ymin = min(top_arti$m1_arti - 0.0005),
  ymax = max(top_arti$m1_arti + 0.0005),
  xmin = min(top_arti$cnr_2020u - 15),
  xmax = max(top_arti$cnr_2020u + 15),
  box_type = "Highest ARTI neighbourhoods"  # Add a column to categorize the blue box
)

# Combine both red and blue box data
boxes <- rbind(red_box, blue_box)

# Add both rectangles to plot
g_cnr_arti2 <- cluster_sum_sf %>%
  ggplot(aes(
    x = cnr_2020u, 
    y = m1_arti, 
    label = cluster
  )) + 
  geom_point(alpha = 0.5, size = 3) +
  geom_rect(data = boxes, 
            aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, color = box_type),  # Map color to the box type
            fill = NA, linetype = "dashed", size = 1, inherit.aes = FALSE) +
  scale_color_manual(
    values = c("Highest CNR neighbourhoods" = "red", "Highest ARTI neighbourhoods" = "blue"),  # Map colors to box types
    labels = c("Highest ARTI neighbourhoods", "Highest CNR neighbourhoods")  # Labels for the legend
  ) +
  theme_bw() + 
  labs(
    x = "CNR, per 100,000 per year", 
    y = "ARTI", 
    title = "B"
  ) +
  scale_y_continuous(labels = scales::percent, limits = c(0, NA)) +  # Set lower limit of y-axis to 0
  scale_x_continuous(limits = c(0, NA)) +  # Set lower limit of x-axis to 0
  theme(
    legend.title = element_blank(),   # Remove legend title
    legend.position="bottom", 
    aspect.ratio = 1, 
    axis.text = element_text(size = 14),  # Increase axis text size
    axis.title = element_text(size = 16),  # Increase axis title size
    legend.text = element_text(size = 14),  # Increase legend text size
  ) 

```

### Combined figure

```{r}
cnr_osm_grob <- grid.grabExpr(print(cnr_osm))

# Combine the tmap image with the ggplot object

fig2 <- grid.arrange(
  cnr_osm_grob,  # tmap plot as an image
  g_cnr_arti2,  # CNR ggplot 
  ncol = 2,  
  widths = c(0.73, 1)  # Control the relative width of the plots 
)
plot(fig2)

```
