Bloqueio da bainha dos músculos retos abdominais guiado por ultrassom em hernioplastias umbilicais ambulatoriais: uma análise comparativa do uso de clonidina como adjuvante — estudo clínico prospectivo randomizado

1 INTRODUÇÃO

As hérnias umbilicais constituem uma das indicações cirúrgicas mais prevalentes, sendo frequentemente tratadas em regime ambulatorial. O bloqueio da bainha dos músculos retos abdominais (BMRA) guiado por ultrassom tem se destacado como técnica anestésica regional promissora para analgesia perioperatória nesses procedimentos. A clonidina, um agonista α2-adrenérgico, é investigada como adjuvante aos anestésicos locais por potencializar o efeito analgésico e reduzir o consumo de opioides, porém sua eficácia e segurança em BMRA para hernioplastia ambulatorial permanecem pouco estabelecidas.

2 Diagrama CONSORT 2025

O fluxograma apresenta todas as etapas do ensaio clínico: número de indivíduos avaliados para elegibilidade, razões de exclusão, número de participantes randomizados, distribuição entre os grupos de intervenção e controle, perdas e exclusões durante o seguimento e quantidade de participantes incluídos na análise estatística final, em conformidade com as recomendações do CONSORT 2025.

3 OBJETIVOS

Objetivo Geral: Comparar a eficácia da adição de clonidina à solução de anestésico local versus anestésico local isolado no bloqueio da bainha dos músculos retos abdominais guiado por ultrassom, em pacientes submetidos a hernioplastia umbilical ambulatorial, quanto à redução do consumo de opioides intra e pós-operatórios (até 48 horas), bem como à diminuição dos escores da escala visual numérica da dor (EVN) em repouso e em movimento, avaliados na alta da sala de recuperação pós-anestésica (SRPA), 24h e 48h após a cirurgia.

Objetivos Específicos:

  1. Avaliar a qualidade da recuperação dos pacientes utilizando o questionário de Qualidade de Recuperação (Quality of Recovery, QoR-15) nas avaliações de 24 e 48h após a cirurgia;
  2. Avaliar as incidências de complicações, incluindo bradicardia, náuseas e vômitos pós-operatórios, conversão para anestesia geral, sedação residual excessiva (avaliada pela Escala de Agitação e sedação de Richmond - RASS), necessidade de internação hospitalar, hematoma de parede abdominal e infecção.

4 Descrição do conjunto de dados

O conjunto RSB_clonidine_39 corresponde ao banco do estudo comparando bloqueio da bainha dos retos (BMRA) e BMRA-CLO (clonidina). As variáveis contemplam dados demográficos e clínicos relevantes (idade, sexo, peso, altura, IMC, ASA e comorbidades), com alocação em randomization_group.

Estratificação: randomization_groupBMRA = 0; BMRA-CLO = 1.

# Procura o CSV em locais comuns; edite a lista se necessário
candidatos <- c(
  "RSB_clonidine_39.csv",
  "C:/RSB_clonidine_39.csv",
  "C:/Users/T-GAMER/Downloads/RSB_clonidine_39.csv",
  file.path(Sys.getenv("USERPROFILE"), "Downloads", "RSB_clonidine_39.csv")
)
data_path <- candidatos[file.exists(candidatos)][1]
if (is.na(data_path))
  stop("CSV não encontrado. Locais procurados:\n", paste(" -", candidatos, collapse = "\n"))
message("Lendo: ", normalizePath(data_path))
dados <- readr::read_csv(data_path, show_col_types = FALSE)
tem_col <- function(nm) nm %in% names(dados)

# Casa nomes de colunas ignorando acentos, caixa e pontuação
norm_nm <- function(s) gsub("[^a-z]", "", tolower(iconv(s, to = "ASCII//TRANSLIT")))
get_bin_factor <- function(cands) {
  hit <- names(dados)[norm_nm(names(dados)) %in% norm_nm(cands)]
  if (length(hit)) factor(dados[[hit[1]]], levels = c(0,1), labels = c("Nao","Sim"))
  else factor(rep(NA_integer_, nrow(dados)), levels = c(0,1), labels = c("Nao","Sim"))
}

# QoR-15 recalculado a partir dos 15 itens (escore canônico). Itens sem sufixo = SRPA.
soma_qor <- function(suf = "") {
  it <- c(paste0("qor_a", 1:10, suf), paste0("qor_b", 1:5, suf))
  it <- it[it %in% names(dados)]
  if (length(it) != 15) return(rep(NA_real_, nrow(dados)))
  rowSums(dados[, it])
}

dados <- dados %>%
  mutate(
    id    = factor(study_id),
    group = factor(randomization_group, levels = c(0,1), labels = c("BMRA","BMRA-CLO")),
    sexo  = factor(sex, levels = c(1,0), labels = c("M","F")),
    idade = age, peso = weight, altura = height,
    imc   = if (tem_col("bmi")) bmi else weight/(height^2),
    asa   = factor(asa_ps, levels = c(1,2), labels = c("I","II")),

    HAS              = get_bin_factor("HAS"),
    DM               = get_bin_factor(c("DM","Diabetes")),
    Asma             = get_bin_factor("Asma"),
    Dislipidemia     = get_bin_factor(c("Dislipidemia","DLP")),
    Hipotireoidismo  = get_bin_factor("Hipotireoidismo"),
    Hipertireoidismo = get_bin_factor("Hipertireoidismo"),
    IRC              = get_bin_factor(c("Insuficiencia Renal Cronica","Insuficiência Renal","Insuficiencia Renal")),
    Epilepsia        = get_bin_factor("Epilepsia"),
    Ansiedade        = get_bin_factor("Ansiedade"),
    Depressao        = get_bin_factor(c("Depressão","Depressao")),
    Anemia           = get_bin_factor("Anemia"),
    ArtriteReumatoide = get_bin_factor(c("Artrite Reumatóide","Artrite Reumatoide")),
    Obesidade        = get_bin_factor("Obesidade"),

    fentanyl_per_kg = fentanyl / weight,
    qor_srpa = soma_qor(""),
    qor_24h  = soma_qor("_24h"),
    qor_48h  = soma_qor("_48h")
  )

Reconciliação dos totais de QoR-15: linhas em que o total armazenado no banco diverge da soma dos 15 itens (referência adotada na análise).

recon <- tibble::tibble(
  study_id        = dados$study_id,
  SRPA_itens      = dados$qor_srpa,
  SRPA_armazenado = if (tem_col("qor15_24h_total")) dados$qor15_24h_total else NA_real_,
  h24_itens       = dados$qor_24h,
  h24_armazenado  = if (tem_col("QoR-15 24h")) dados[["QoR-15 24h"]] else NA_real_,
  h48_itens       = dados$qor_48h,
  h48_armazenado  = if (tem_col("QoR-48h")) dados[["QoR-48h"]] else NA_real_
) %>%
  filter(SRPA_itens != SRPA_armazenado |
         h24_itens  != h24_armazenado  |
         h48_itens  != h48_armazenado)

if (nrow(recon))
  knitr::kable(recon, caption = "Divergências entre total armazenado e soma dos 15 itens")
Table 4.1: Divergências entre total armazenado e soma dos 15 itens
study_id SRPA_itens SRPA_armazenado h24_itens h24_armazenado h48_itens h48_armazenado
29 144 144 148 150 150 150
30 140 139 148 148 149 149
36 125 115 133 123 145 145

Estatística descritiva básica:

dados %>%
  dplyr::select(any_of(c(
    "group","sexo","idade","peso","altura","imc","asa",
    "HAS","DM","Asma","Dislipidemia","Hipotireoidismo","Hipertireoidismo",
    "IRC","Epilepsia","Ansiedade","Depressao","Anemia","ArtriteReumatoide","Obesidade",
    "qor_srpa","qor_24h","qor_48h"
  ))) %>%
  summary()
##       group    sexo       idade            peso            altura     
##  BMRA    :19   M:20   Min.   :20.00   Min.   : 47.50   Min.   :1.470  
##  BMRA-CLO:20   F:19   1st Qu.:36.00   1st Qu.: 68.30   1st Qu.:1.610  
##                       Median :44.00   Median : 74.60   Median :1.660  
##                       Mean   :44.23   Mean   : 75.42   Mean   :1.667  
##                       3rd Qu.:51.00   3rd Qu.: 81.20   3rd Qu.:1.730  
##                       Max.   :64.00   Max.   :114.00   Max.   :1.830  
##       imc        asa      HAS       DM      Asma    Dislipidemia
##  Min.   :18.60   I :10   Nao:25   Nao:36   Nao:38   Nao:38      
##  1st Qu.:25.45   II:29   Sim:14   Sim: 3   Sim: 1   Sim: 1      
##  Median :26.90                                                  
##  Mean   :27.02                                                  
##  3rd Qu.:29.35                                                  
##  Max.   :35.60                                                  
##  Hipotireoidismo Hipertireoidismo  IRC     Epilepsia Ansiedade Depressao
##  Nao:39          Nao:38           Nao:39   Nao:39    Nao:37    Nao:37   
##  Sim: 0          Sim: 1           Sim: 0   Sim: 0    Sim: 2    Sim: 2   
##                                                                         
##                                                                         
##                                                                         
##                                                                         
##  Anemia   ArtriteReumatoide Obesidade    qor_srpa        qor_24h     
##  Nao:39   Nao:39            Nao:37    Min.   :100.0   Min.   :124.0  
##  Sim: 0   Sim: 0            Sim: 2    1st Qu.:128.0   1st Qu.:141.0  
##                                       Median :135.0   Median :145.0  
##                                       Mean   :133.6   Mean   :143.7  
##                                       3rd Qu.:140.5   3rd Qu.:148.5  
##                                       Max.   :150.0   Max.   :150.0  
##     qor_48h     
##  Min.   :129.0  
##  1st Qu.:146.0  
##  Median :149.0  
##  Mean   :147.3  
##  3rd Qu.:150.0  
##  Max.   :150.0

5 Tabela 1

Características basais, comorbidades, valor-p e teste aplicado:

N    <- table(dados$group)
col1 <- sprintf("BMRA (n=%d)", N["BMRA"])
col2 <- sprintf("BMRA-CLO (n=%d)", N["BMRA-CLO"])
addrow <- function(rot, v1, v2, p)
  tibble(Variável = rot, !!col1 := v1, !!col2 := v2, `Valor-p` = p)

linhas <- list()

cont_map <- c(idade="Idade (anos)", peso="Peso (kg)", altura="Altura (m)", imc="IMC (kg/m²)")
for (v in names(cont_map)) {
  tt <- get_test_cont(dados[[v]], dados$group)
  linhas[[v]] <- addrow(cont_map[[v]],
    fmt_mean_sd(dados[[v]][dados$group=="BMRA"]),
    fmt_mean_sd(dados[[v]][dados$group=="BMRA-CLO"]),
    fmt_p_sym(tt$p, tt$sym))
}

tt <- get_test_cat(dados$sexo, dados$group)
for (lv in levels(dados$sexo)) {
  n1 <- sum(dados$sexo==lv & dados$group=="BMRA", na.rm=TRUE)
  n2 <- sum(dados$sexo==lv & dados$group=="BMRA-CLO", na.rm=TRUE)
  linhas[[paste0("sexo_",lv)]] <- addrow(paste0("Sexo — ", lv),
    fmt_n_pct(n1, N["BMRA"]), fmt_n_pct(n2, N["BMRA-CLO"]),
    if (lv==levels(dados$sexo)[1]) fmt_p_sym(tt$p, tt$sym) else "")
}

tt <- get_test_cat(dados$asa, dados$group)
for (lv in levels(dados$asa)) {
  n1 <- sum(dados$asa==lv & dados$group=="BMRA", na.rm=TRUE)
  n2 <- sum(dados$asa==lv & dados$group=="BMRA-CLO", na.rm=TRUE)
  linhas[[paste0("asa_",lv)]] <- addrow(paste0("ASA — ", lv),
    fmt_n_pct(n1, N["BMRA"]), fmt_n_pct(n2, N["BMRA-CLO"]),
    if (lv==levels(dados$asa)[1]) fmt_p_sym(tt$p, tt$sym) else "")
}

comorb_map <- c(HAS="Hipertensão arterial", DM="Diabetes mellitus", Asma="Asma",
                Dislipidemia="Dislipidemia", Hipotireoidismo="Hipotireoidismo",
                Hipertireoidismo="Hipertireoidismo", IRC="Insuficiência renal crônica",
                Epilepsia="Epilepsia", Ansiedade="Ansiedade", Depressao="Depressão",
                Anemia="Anemia", ArtriteReumatoide="Artrite reumatoide", Obesidade="Obesidade")
for (v in names(comorb_map)) {
  if (sum(dados[[v]]=="Sim", na.rm=TRUE) == 0) next   # pula comorbidade ausente
  tt <- get_test_cat(dados[[v]], dados$group)
  n1 <- sum(dados[[v]]=="Sim" & dados$group=="BMRA", na.rm=TRUE)
  n2 <- sum(dados[[v]]=="Sim" & dados$group=="BMRA-CLO", na.rm=TRUE)
  linhas[[v]] <- addrow(comorb_map[[v]],
    fmt_n_pct(n1, N["BMRA"]), fmt_n_pct(n2, N["BMRA-CLO"]), fmt_p_sym(tt$p, tt$sym))
}

Tabela1 <- bind_rows(linhas)

Tabela1 %>%
  kbl(align = c("l","c","c","c"), escape = FALSE,
      caption = "<b style='color:#2C3E50'>Tabela 1 — Características basais e comorbidades</b>") %>%
  kable_styling(bootstrap_options = c("hover","condensed","striped"),
                full_width = FALSE, font_size = 14, position = "center") %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>%
  column_spec(1, bold = TRUE, color = "#2C3E50", width = "20em") %>%
  footnote(general = paste0(
      "Contínuas: média ± DP; categóricas: n (%).<br>",
      "<sup>*</sup> Qui-quadrado; <sup>&sect;</sup> Exato de Fisher; ",
      "<sup>&dagger;</sup> t de Welch; <sup>&Dagger;</sup> Mann–Whitney.<br>",
      "<b>Negrito</b> indica significância estatística (p &lt; 0,05)."),
    general_title = "Legenda: ", footnote_as_chunk = TRUE, escape = FALSE)
Table 5.1: Tabela 1 — Características basais e comorbidades
Variável BMRA (n=19) BMRA-CLO (n=20) Valor-p
Idade (anos) 44,95 ± 10,46 43,55 ± 12,24 0,703
Peso (kg) 77,47 ± 15,02 73,48 ± 12,21 0,371
Altura (m) 1,67 ± 0,09 1,66 ± 0,08 0,830
IMC (kg/m²) 27,61 ± 3,78 26,45 ± 3,47 0,329
Sexo — M 9 (47,4%) 11 (55,0%) 0,634*
Sexo — F 10 (52,6%) 9 (45,0%)
ASA — I 4 (21,1%) 6 (30,0%) 0,716§
ASA — II 15 (78,9%) 14 (70,0%)
Hipertensão arterial 9 (47,4%) 5 (25,0%) 0,146*
Diabetes mellitus 1 (5,3%) 2 (10,0%) 1,000§
Asma 1 (5,3%) 0 (0,0%) 0,487§
Dislipidemia 1 (5,3%) 0 (0,0%) 0,487§
Hipertireoidismo 0 (0,0%) 1 (5,0%) 1,000§
Ansiedade 0 (0,0%) 2 (10,0%) 0,487§
Depressão 0 (0,0%) 2 (10,0%) 0,487§
Obesidade 2 (10,5%) 0 (0,0%) 0,231§
Legenda: Contínuas: média ± DP; categóricas: n (%).
* Qui-quadrado; § Exato de Fisher; t de Welch; Mann–Whitney.
Negrito indica significância estatística (p < 0,05).

6 Visualizações

6.1 Perfil etário da população estudada

media_idade   <- mean(dados$idade,   na.rm = TRUE)
mediana_idade <- median(dados$idade, na.rm = TRUE)
dp_idade      <- sd(dados$idade,     na.rm = TRUE)
bw_idade      <- diff(range(dados$idade, na.rm = TRUE)) / 20

ggplot(dados, aes(x = idade)) +
  geom_histogram(aes(fill = after_stat(count)),
                 binwidth = bw_idade, boundary = 0, closed = "left", alpha = 0.95) +
  # densidade reescalada para contagem (× binwidth) para casar com as barras
  stat_density(aes(y = after_stat(count) * bw_idade),
               geom = "line", linewidth = 1.1, na.rm = TRUE) +
  geom_vline(xintercept = media_idade,   linewidth = 0.8) +
  geom_vline(xintercept = mediana_idade, linetype = "dashed", linewidth = 0.8) +
  geom_rug(sides = "b", alpha = 0.25, linewidth = 0.3) +
  scale_fill_gradient(low = "#E9EDF5", high = "#4FA2DA") +
  labs(title = "Distribuição da idade",
       subtitle = sprintf("Média = %.1f ± %.1f | Mediana = %.1f",
                          media_idade, dp_idade, mediana_idade),
       x = "Idade (anos)", y = "Frequência") +
  guides(fill = "none")

A distribuição etária dos participantes demonstra uma população predominantemente de meia-idade, típica para cirurgias de hérnia umbilical em regime ambulatorial. A sobreposição da curva de densidade indica uma distribuição relativamente simétrica, sugerindo que a amostra é representativa e não apresenta desvios extremos de idade que pudessem enviesar a farmacocinética dos anestésicos.

6.2 Evolução da dor pós-operatória (repouso vs. movimento)

colmap <- c("NRS RPA r"="SRPA_Repouso","NRS RPA m"="SRPA_Movimento",
            "NRS 24h r"="24h_Repouso", "NRS 24h m"="24h_Movimento",
            "NRS 48h r"="48h_Repouso", "NRS 48h m"="48h_Movimento")
present <- intersect(names(colmap), names(dados))

df_long <- dados %>%
  select(group, all_of(present)) %>%
  rename(!!!setNames(present, colmap[present])) %>%
  pivot_longer(-group, names_to = "key", values_to = "nrs") %>%
  separate(key, into = c("momento","cond"), sep = "_") %>%
  mutate(nrs = as.numeric(nrs),
         momento = factor(momento, levels = c("SRPA","24h","48h")),
         cond    = factor(cond,    levels = c("Repouso","Movimento"))) %>%
  filter(!is.na(nrs))

painel_test <- function(d) {
  ct <- table(d$group)
  if (length(ct) < 2 || any(ct < 2)) return(NA_real_)
  norm <- tapply(d$nrs, d$group, function(v) if (length(v) >= 3) shapiro.test(v)$p.value >= .05 else FALSE)
  if (all(unlist(norm))) t.test(nrs ~ group, d)$p.value
  else suppressWarnings(wilcox.test(nrs ~ group, d, exact = FALSE)$p.value)
}

tests <- df_long %>%
  group_by(momento, cond) %>%
  group_modify(~ tibble(p = painel_test(.x))) %>%
  ungroup() %>%
  mutate(p_label = ifelse(is.na(p), "",
                          ifelse(p < .001, "p < 0,001",
                                 paste0("p = ", nv(p, 3)))))
y_pos  <- df_long %>% group_by(momento, cond) %>%
  summarise(y_p = min(10, max(nrs) + 0.6), .groups = "drop")
tests  <- left_join(tests, y_pos, by = c("momento","cond"))

pd <- position_dodge(width = 0.8)
ggplot(df_long, aes(momento, nrs, fill = group)) +
  geom_boxplot(width = 0.65, position = pd, outlier.shape = 8, outlier.size = 2, alpha = 0.92) +
  stat_summary(aes(group = group), fun = mean, geom = "point",
               shape = 18, size = 3, color = "black", position = pd) +
  stat_summary(aes(label = sprintf("%.2f", after_stat(y)), group = group),
               fun = mean, geom = "text", fontface = "bold", vjust = -0.6, size = 3.2, position = pd) +
  geom_text(data = tests, aes(momento, y_p, label = p_label),
            fontface = "bold", size = 3.2, inherit.aes = FALSE) +
  facet_wrap(~ cond, nrow = 1) +
  scale_fill_manual(values = pal_grupo, name = "Grupo") +
  labs(title = "EVN de dor (0–10) em repouso e movimento — SRPA, 24h e 48h",
       x = "Tempo de avaliação", y = "EVN (0–10)") +
  coord_cartesian(ylim = c(0, 10)) +
  theme(legend.position = "right")

6.3 Trajetória da qualidade de recuperação (QoR-15)

dados_long <- dados %>%
  transmute(group, SRPA = qor_srpa, `24h` = qor_24h, `48h` = qor_48h) %>%
  pivot_longer(c("SRPA","24h","48h"), names_to = "tempo", values_to = "qor15") %>%
  filter(!is.na(qor15)) %>%
  mutate(tempo = factor(tempo, levels = c("SRPA","24h","48h")))

sw <- dados_long %>% group_by(tempo, group) %>% shapiro_test(qor15)

testes <- dados_long %>%
  group_split(tempo) %>%
  lapply(function(d) {
    t_atual <- as.character(unique(d$tempo))
    sw_t    <- sw %>% filter(tempo == t_atual)
    n_min   <- min(table(d$group))
    if (any(sw_t$p < .05) || n_min < 8) {
      tt <- wilcox_test(d, qor15 ~ group); tt$test_used <- "Wilcoxon"
    } else {
      tt <- t_test(d, qor15 ~ group, var.equal = FALSE); tt$test_used <- "Welch"
    }
    tt$tempo <- t_atual; tt
  }) %>% bind_rows()

tests_lab <- testes %>%
  mutate(p_label = ifelse(p < .001, "p < 0,001", paste0("p = ", nv(p, 3))))
y_pos <- dados_long %>% group_by(tempo) %>%
  summarise(y_p = max(qor15, na.rm = TRUE) + 4, .groups = "drop")
tests_lab <- left_join(tests_lab, y_pos, by = "tempo") %>%
  mutate(tempo = factor(tempo, levels = c("SRPA","24h","48h")))

means_df <- dados_long %>% group_by(tempo, group) %>%
  summarise(y = mean(qor15, na.rm = TRUE), .groups = "drop")

pd <- position_dodge(width = 0.8)
ggplot(dados_long, aes(tempo, qor15, fill = group)) +
  geom_boxplot(width = 0.65, position = pd, outlier.shape = 8, outlier.size = 2, alpha = 0.90) +
  geom_point(data = means_df, aes(tempo, y, group = group),
             position = pd, shape = 18, size = 3, color = "black") +
  shadowtext::geom_shadowtext(
    data = means_df, aes(tempo, y, label = sprintf("%.1f", y), group = group),
    position = pd, fontface = "bold", size = 3.4, vjust = -0.6,
    colour = "black", bg.colour = "white", bg.r = 0.12) +
  geom_text(data = tests_lab, aes(tempo, y_p, label = p_label),
            fontface = "bold", size = 3.6, inherit.aes = FALSE) +
  scale_fill_manual(values = pal_grupo, name = "Grupo") +
  labs(title = "Qualidade de recuperação (QoR-15)", x = "Tempo de avaliação", y = "QoR-15") +
  coord_cartesian(clip = "off") +
  theme(legend.position = "right", plot.margin = margin(10, 20, 10, 10))

6.4 Relevância clínica das diferenças no QoR-15

A área sombreada em cinza representa a Diferença Mínima Clinicamente Importante (MCID) de ±8 pontos. O gráfico mostra que, em todos os momentos (SRPA, 24h e 48h), a diferença entre os grupos permanece dentro dessa faixa e os intervalos de confiança de 95% incluem zero, indicando ausência de significância estatística e de relevância clínica.

dl <- dados %>%
  transmute(id, group, SRPA = qor_srpa, `24h` = qor_24h, `48h` = qor_48h) %>%
  pivot_longer(c("SRPA","24h","48h"), names_to = "tempo", values_to = "qor15") %>%
  filter(!is.na(qor15), !is.na(group)) %>%
  mutate(tempo = factor(tempo, levels = c("SRPA","24h","48h")))

fit    <- lmer(qor15 ~ group * tempo + (1 | id), data = dl)
emm    <- emmeans(fit, ~ group | tempo)
emm_df <- as.data.frame(emm)

# pairwise devolve (BMRA − BMRA-CLO); inverter para (CLO − BMRA)
contr <- as.data.frame(confint(contrast(emm, method = "pairwise")))
contr$estimate <- -contr$estimate
tmp <- contr$lower.CL; contr$lower.CL <- -contr$upper.CL; contr$upper.CL <- -tmp

nv1  <- function(x) formatC(x, digits = 1, format = "f", decimal.mark = ",")
sgn  <- function(x) ifelse(x >= 0, paste0("+", nv1(x)), paste0("−", nv1(abs(x))))
delta_labs <- contr %>%
  transmute(tempo, delta_lab = sprintf("Δ=%s [%s; %s]", sgn(estimate), nv1(lower.CL), nv1(upper.CL)))

mcid_w <- 0.46
base_mcid <- emm_df %>% filter(group == "BMRA") %>%
  mutate(xn = as.numeric(tempo), xmin = xn - mcid_w, xmax = xn + mcid_w,
         ymin = emmean - 8, ymax = emmean + 8)

y_top <- emm_df %>% group_by(tempo) %>%
  summarise(y = max(upper.CL, na.rm = TRUE) + 3, .groups = "drop") %>%
  left_join(delta_labs, by = "tempo")
y_lo <- max(0, floor(min(c(emm_df$lower.CL, base_mcid$ymin), na.rm = TRUE) - 3))
y_hi <- ceiling(max(c(emm_df$upper.CL, base_mcid$ymax, y_top$y), na.rm = TRUE) + 2)

ggplot(emm_df, aes(tempo, emmean, color = group, group = group)) +
  geom_rect(data = base_mcid, aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax),
            inherit.aes = FALSE, fill = "grey60", alpha = 0.14) +
  geom_line(linewidth = 1) + geom_point(size = 3) +
  geom_errorbar(aes(ymin = lower.CL, ymax = upper.CL), width = .08, linewidth = 0.8) +
  geom_text(data = y_top, aes(tempo, y, label = delta_lab),
            inherit.aes = FALSE, size = 4, fontface = "bold") +
  scale_color_manual(values = pal_grupo, name = "Grupo") +
  labs(title = "QoR-15 — médias marginais estimadas (EMM), IC95% e faixa de MCID (±8)",
       x = "Tempo de avaliação", y = "QoR-15 (EMM)") +
  scale_y_continuous(limits = c(y_lo, y_hi), expand = expansion(mult = c(.02, .08))) +
  theme(legend.position = "right")

7 Tabela 2 — Desfechos

n1 <- sum(dados$group == "BMRA"); n2 <- sum(dados$group == "BMRA-CLO")
cB <- sprintf("BMRA (n=%d)", n1); cC <- sprintf("BMRA-CLO (n=%d)", n2)

get_row <- function(rot, col, tipo = c("auto","np")) {
  tipo <- match.arg(tipo)
  if (!col %in% names(dados)) return(NULL)
  x <- dados[[col]]; g <- dados$group
  if (tipo == "np") {
    tt <- list(p = suppressWarnings(wilcox.test(x ~ g, exact = FALSE)$p.value), sym = "&Dagger;")
    v1 <- fmt_non_param(x[g == "BMRA"]); v2 <- fmt_non_param(x[g == "BMRA-CLO"])
  } else {
    tt <- get_test_cont(x, g)
    v1 <- fmt_mean_sd(x[g == "BMRA"]);   v2 <- fmt_mean_sd(x[g == "BMRA-CLO"])
  }
  tibble(Variável = rot, !!cB := v1, !!cC := v2, `Valor-p` = fmt_p_sym(tt$p, tt$sym))
}

intra <- list(
  get_row("Propofol (mg)",            "propofol",            "np"),
  get_row("Fentanil (µg)",            "fentanyl",            "auto"),
  get_row("Fentanil (µg/kg)",         "fentanyl_per_kg",     "auto"),
  get_row("Midazolam (mg)",           "midazolam",           "auto"),
  get_row("Tempo de anestesia (min)", "anesthesia_time_min", "auto"),
  get_row("Tempo de cirurgia (min)",  "surgery_time_min",    "auto"))
hemo <- list(
  get_row("PAS inicial (mmHg)", "sys_bp_start",  "auto"),
  get_row("PAD inicial (mmHg)", "dyas_bp_start", "auto"),
  get_row("PAM inicial (mmHg)", "mean_bp_start", "auto"),
  get_row("FC inicial (bpm)",   "hr_start",      "auto"),
  get_row("PAS final (mmHg)",   "sys_bp_end",    "auto"),
  get_row("PAD final (mmHg)",   "dyas_bp_end",   "auto"),
  get_row("PAM final (mmHg)",   "mean_bp_end",   "auto"),
  get_row("FC final (bpm)",     "hr_end",        "auto"))
pos <- list(
  get_row("RASS (SRPA)",              "rass_rpa", "np"),
  get_row("Tempo até 1ª analgesia (h)","Primeira", "auto"),
  get_row("QoR-15 (SRPA)",            "qor_srpa", "np"),
  get_row("QoR-15 (24h)",             "qor_24h",  "np"),
  get_row("QoR-15 (48h)",             "qor_48h",  "np"))
dor <- list(
  get_row("EVN repouso (SRPA)",   "NRS RPA r", "np"),
  get_row("EVN movimento (SRPA)", "NRS RPA m", "np"),
  get_row("EVN repouso (24h)",    "NRS 24h r", "np"),
  get_row("EVN movimento (24h)",  "NRS 24h m", "np"),
  get_row("EVN repouso (48h)",    "NRS 48h r", "np"),
  get_row("EVN movimento (48h)",  "NRS 48h m", "np"))

T2 <- bind_rows(c(intra, hemo, pos, dor))

T2 %>%
  kbl(align = c("l","c","c","c"), escape = FALSE,
      caption = "<b style='color:#2C3E50'>Tabela 2 — Desfechos do estudo</b>") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE, font_size = 13) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#2C3E50") %>%
  column_spec(1, bold = TRUE, color = "#2C3E50", width = "18em") %>%
  pack_rows("Dados intraoperatórios",            1,  6,  label_row_css = "background:#ecf0f1;color:#2C3E50;font-weight:bold;") %>%
  pack_rows("Dados hemodinâmicos",               7,  14, label_row_css = "background:#ecf0f1;color:#2C3E50;font-weight:bold;") %>%
  pack_rows("Pós-operatório e recuperação",      15, 19, label_row_css = "background:#ecf0f1;color:#2C3E50;font-weight:bold;") %>%
  pack_rows("Escala visual numérica de dor (0–10)", 20, 25, label_row_css = "background:#ecf0f1;color:#2C3E50;font-weight:bold;") %>%
  footnote(general = paste0(
      "Dados como <b>média ± DP</b> ou <b>mediana (P25–P75) [mín–máx]</b>.<br>",
      "<sup>&dagger;</sup> t de Welch; <sup>&Dagger;</sup> Mann–Whitney.<br>",
      "<b>Negrito</b> indica significância estatística (p &lt; 0,05)."),
    general_title = " ", footnote_as_chunk = TRUE, escape = FALSE)
Table 7.1: Tabela 2 — Desfechos do estudo
Variável BMRA (n=19) BMRA-CLO (n=20) Valor-p
Dados intraoperatórios
Propofol (mg) 0 (0–0) [0–60] 0 (0–0) [0–30] 0,523
Fentanil (µg) 109,74 ± 34,01 61,00 ± 20,49 <0,001
Fentanil (µg/kg) 1,44 ± 0,44 0,84 ± 0,26 <0,001
Midazolam (mg) 6,00 ± 2,24 5,85 ± 2,28 0,837
Tempo de anestesia (min) 72,53 ± 18,41 69,40 ± 21,99 0,448
Tempo de cirurgia (min) 50,47 ± 15,85 47,75 ± 20,05 0,391
Dados hemodinâmicos
PAS inicial (mmHg) 138,26 ± 18,93 136,85 ± 15,80 0,802
PAD inicial (mmHg) 87,63 ± 10,40 85,25 ± 15,53 0,910
PAM inicial (mmHg) 104,51 ± 12,67 102,45 ± 14,37 0,637
FC inicial (bpm) 72,42 ± 12,79 79,05 ± 19,91 0,391
PAS final (mmHg) 115,00 ± 19,40 109,30 ± 15,19 0,316
PAD final (mmHg) 77,47 ± 14,83 70,70 ± 12,15 0,129
PAM final (mmHg) 89,98 ± 15,94 83,57 ± 12,91 0,177
FC final (bpm) 68,53 ± 14,22 67,50 ± 13,05 0,833
Pós-operatório e recuperação
RASS (SRPA) -1 (-1–-1) [-2–0] -1 (-1–0) [-3–0] 0,276
Tempo até 1ª analgesia (h) 8,47 ± 1,77 9,39 ± 1,80 0,118
QoR-15 (SRPA) 132 (128–142) [100–150] 137 (131–139) [106–150] 0,415
QoR-15 (24h) 144 (138–147) [124–150] 146 (143–149) [135–150] 0,185
QoR-15 (48h) 146 (144–150) [129–150] 149 (148–150) [146–150] 0,008
Escala visual numérica de dor (0–10)
EVN repouso (SRPA) 0 (0–0) [0–2] 0 (0–0) [0–1] 0,936
EVN movimento (SRPA) 0 (0–2) [0–5] 0 (0–1) [0–4] 0,675
EVN repouso (24h) 0 (0–3) [0–5] 1 (0–2) [0–4] 0,756
EVN movimento (24h) 3 (3–4) [0–7] 3 (3–4) [0–6] 0,896
EVN repouso (48h) 0 (0–2) [0–3] 0 (0–0) [0–2] 0,023
EVN movimento (48h) 2 (1–3) [0–4] 1 (1–2) [0–4] 0,231
Dados como média ± DP ou mediana (P25–P75) [mín–máx].
t de Welch; Mann–Whitney.
Negrito indica significância estatística (p < 0,05).

8 ANOVA de medidas repetidas

# QoR-15 — ANOVA mista 2×3 (tipo III, η² parcial, correção GG quando indicada)
afex::afex_options(type = 3)

dq <- dados %>%
  transmute(id, group, SRPA = qor_srpa, `24h` = qor_24h, `48h` = qor_48h) %>%
  pivot_longer(c("SRPA","24h","48h"), names_to = "tempo", values_to = "qor15") %>%
  filter(!is.na(qor15)) %>%
  mutate(tempo = factor(tempo, levels = c("SRPA","24h","48h")))

fit_qor <- afex::aov_car(qor15 ~ group * tempo + Error(id/tempo),
                         data = dq, anova_table = list(es = "pes"))
fit_qor
## Anova Table (Type 3 tests)
## 
## Response: qor15
##        Effect          df    MSE         F  pes p.value
## 1       group       1, 37 107.27      2.71 .068    .108
## 2       tempo 1.16, 42.76  54.14 63.24 *** .631   <.001
## 3 group:tempo 1.16, 42.76  54.14      0.05 .001    .855
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
## 
## Sphericity correction method: GG
emmeans(fit_qor, ~ group | tempo)
## tempo = SRPA:
##  group    emmean    SE df lower.CL upper.CL
##  BMRA        132 2.520 37      127      137
##  BMRA-CLO    135 2.460 37      130      140
## 
## tempo = X24h:
##  group    emmean    SE df lower.CL upper.CL
##  BMRA        142 1.380 37      139      145
##  BMRA-CLO    145 1.350 37      143      148
## 
## tempo = X48h:
##  group    emmean    SE df lower.CL upper.CL
##  BMRA        146 0.815 37      144      147
##  BMRA-CLO    149 0.794 37      147      150
## 
## Confidence level used: 0.95
contrast(emmeans(fit_qor, ~ group | tempo), method = "pairwise", adjust = "holm")
## tempo = SRPA:
##  contrast          estimate   SE df t.ratio p.value
##  BMRA - (BMRA-CLO)    -2.74 3.52 37  -0.778  0.4414
## 
## tempo = X24h:
##  contrast          estimate   SE df t.ratio p.value
##  BMRA - (BMRA-CLO)    -3.56 1.93 37  -1.844  0.0733
## 
## tempo = X48h:
##  contrast          estimate   SE df t.ratio p.value
##  BMRA - (BMRA-CLO)    -3.17 1.14 37  -2.783  0.0084
# EVN — ANOVA mista 2×3×2 (group × tempo × cond)
de <- dados %>%
  select(id, group, dplyr::starts_with("NRS ")) %>%
  pivot_longer(-c(id, group), names_to = "key", values_to = "nrs") %>%
  tidyr::extract(key, into = c("tempo","cond"), regex = "^NRS (RPA|24h|48h) (r|m)$") %>%
  mutate(tempo = factor(ifelse(tempo == "RPA","SRPA",tempo), levels = c("SRPA","24h","48h")),
         cond  = factor(ifelse(cond == "r","Repouso","Movimento"), levels = c("Repouso","Movimento"))) %>%
  filter(!is.na(nrs))

fit_evn <- afex::aov_car(nrs ~ group * tempo * cond + Error(id/(tempo*cond)),
                         data = de, anova_table = list(es = "pes"))
fit_evn
## Anova Table (Type 3 tests)
## 
## Response: nrs
##             Effect          df  MSE          F  pes p.value
## 1            group       1, 37 3.67       0.96 .025    .333
## 2            tempo 1.59, 58.86 1.96  48.85 *** .569   <.001
## 3      group:tempo 1.59, 58.86 1.96       1.15 .030    .313
## 4             cond       1, 37 0.82 116.39 *** .759   <.001
## 5       group:cond       1, 37 0.82       0.12 .003    .729
## 6       tempo:cond 1.81, 66.85 0.59  16.60 *** .310   <.001
## 7 group:tempo:cond 1.81, 66.85 0.59       0.04 .001    .948
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1
## 
## Sphericity correction method: GG

9 Modelos de regressão linear

Três modelos complementam as comparações simples: (1) ANCOVA para o desfecho primário (fentanil µg/kg), (2) regressão linear múltipla para o QoR-15 em 48h ajustado pelo basal, e (3) modelo linear misto para a trajetória do EVN em movimento. Em n=39, limitamos a no máximo 5 covariáveis por modelo (≈ 10 observações por preditor).

9.1 ANCOVA — desfecho primário (fentanil µg/kg)

fentanyl_per_kg ~ group + idade + imc + sexo + asa — fornece a diferença média ajustada entre grupos (CLO − BMRA) com IC 95% e p-valor, controlando desbalanços basais.

fit_ancova <- lm(fentanyl_per_kg ~ group + idade + imc + sexo + asa, data = dados)

# Tabela de coeficientes
coef_tab <- parameters::model_parameters(fit_ancova, ci = 0.95) %>%
  as.data.frame() %>%
  transmute(
    Termo       = Parameter,
    Coeficiente = nv(Coefficient, 3),
    `IC 95%`    = sprintf("[%s; %s]", nv(CI_low, 3), nv(CI_high, 3)),
    `Valor-p`   = ifelse(p < 0.001, "&lt;0,001", nv(p, 3))
  )
knitr::kable(coef_tab, align = c("l","c","c","c"),
             caption = "ANCOVA — coeficientes do modelo (fentanil µg/kg)")
(#tab:ancova_fent)ANCOVA — coeficientes do modelo (fentanil µg/kg)
Termo Coeficiente IC 95% Valor-p
(Intercept) 1,267 [0,070; 2,464] 0,039
groupBMRA-CLO -0,589 [-0,829; -0,349] <0,001
idade 0,001 [-0,010; 0,012] 0,865
imc 0,004 [-0,032; 0,039] 0,837
sexoF 0,198 [-0,065; 0,460] 0,135
asaII -0,094 [-0,379; 0,191] 0,507
# Diferença média ajustada (CLO − BMRA)
emm_anc       <- emmeans(fit_ancova, ~ group)
contr_anc_obj <- contrast(emm_anc, method = "pairwise")
diff_anc      <- as.data.frame(summary(contr_anc_obj, infer = c(TRUE, TRUE)))
diff_anc$estimate <- -diff_anc$estimate
tmp <- diff_anc$lower.CL; diff_anc$lower.CL <- -diff_anc$upper.CL; diff_anc$upper.CL <- -tmp

ajust <- tibble::tibble(
  Contraste                         = "BMRA-CLO − BMRA",
  `Diferença ajustada (µg/kg)`      = nv(diff_anc$estimate, 3),
  `IC 95%`                          = sprintf("[%s; %s]", nv(diff_anc$lower.CL, 3), nv(diff_anc$upper.CL, 3)),
  `Valor-p`                         = ifelse(diff_anc$p.value < 0.001, "&lt;0,001", nv(diff_anc$p.value, 3))
)
knitr::kable(ajust, align = c("l","c","c","c"),
             caption = "Diferença média ajustada por ANCOVA (com IC 95% e p)")
(#tab:ancova_fent)Diferença média ajustada por ANCOVA (com IC 95% e p)
Contraste Diferença ajustada (µg/kg) IC 95% Valor-p
BMRA-CLO − BMRA -0,589 [-0,829; -0,349] <0,001
# η² parcial (tamanho de efeito)
effectsize::eta_squared(fit_ancova, partial = TRUE)

10 Effect Size for ANOVA (Type I)

10.1 Parameter | Eta2 (partial) | 95% CI

group | 0.46 | [0.24, 1.00] idade | 8.59e-03 | [0.00, 1.00] imc | 5.04e-03 | [0.00, 1.00] sexo | 0.08 | [0.00, 1.00] asa | 0.01 | [0.00, 1.00]

  • One-sided CIs: upper bound fixed at [1.00].
# Pressupostos
shapiro.test(residuals(fit_ancova))   # normalidade dos resíduos
Shapiro-Wilk normality test

data: residuals(fit_ancova) W = 0.96615, p-value = 0.2841

car::ncvTest(fit_ancova)              # homocedasticidade (Breusch–Pagan)

Non-constant Variance Score Test Variance formula: ~ fitted.values Chisquare = 1.573386, Df = 1, p = 0.20972

# Forest plot dos coeficientes
fp <- parameters::model_parameters(fit_ancova, ci = 0.95) %>%
  as.data.frame() %>%
  filter(Parameter != "(Intercept)") %>%
  mutate(Parameter = factor(Parameter, levels = rev(Parameter)))

ggplot(fp, aes(Coefficient, Parameter)) +
  geom_vline(xintercept = 0, linetype = "dashed", color = "#7f8c8d") +
  geom_pointrange(aes(xmin = CI_low, xmax = CI_high), size = 0.6, color = cor_controle) +
  labs(title = "ANCOVA — coeficientes (IC 95%) para fentanil (µg/kg)",
       x = "Coeficiente", y = NULL)

10.2 Regressão linear múltipla — QoR-15 em 48h ajustado pelo basal

qor_48h ~ group + qor_srpa + idade + sexo + imc — o ajuste por qor_srpa (basal) aumenta a precisão da estimativa do efeito do grupo no momento clinicamente mais relevante.

fit_qor48 <- lm(qor_48h ~ group + qor_srpa + idade + sexo + imc, data = dados)

coef_tab2 <- parameters::model_parameters(fit_qor48, ci = 0.95) %>%
  as.data.frame() %>%
  transmute(
    Termo       = Parameter,
    Coeficiente = nv(Coefficient, 2),
    `IC 95%`    = sprintf("[%s; %s]", nv(CI_low, 2), nv(CI_high, 2)),
    `Valor-p`   = ifelse(p < 0.001, "&lt;0,001", nv(p, 3))
  )
knitr::kable(coef_tab2, align = c("l","c","c","c"),
             caption = "Regressão linear múltipla — QoR-15 em 48h")
(#tab:rlm_qor48)Regressão linear múltipla — QoR-15 em 48h
Termo Coeficiente IC 95% Valor-p
(Intercept) 129,16 [109,97; 148,35] <0,001
groupBMRA-CLO 2,79 [0,47; 5,12] 0,020
qor_srpa 0,13 [0,02; 0,25] 0,022
idade -0,06 [-0,17; 0,05] 0,309
sexoF 0,37 [-2,24; 2,98] 0,774
imc 0,04 [-0,29; 0,37] 0,806
# R² ajustado e diagnósticos
cat(sprintf("R² ajustado: %s\n", nv(summary(fit_qor48)$adj.r.squared, 3)))

R² ajustado: 0,198

shapiro.test(residuals(fit_qor48))
Shapiro-Wilk normality test

data: residuals(fit_qor48) W = 0.69833, p-value = 1.188e-07

car::ncvTest(fit_qor48)

Non-constant Variance Score Test Variance formula: ~ fitted.values Chisquare = 11.54788, Df = 1, p = 0.00067826

10.3 Modelo linear misto — trajetória do EVN em movimento

lmer(nrs ~ group * tempo + (1|id)) — random intercept por paciente. A ANOVA tipo III com graus de liberdade de Satterthwaite (via lmerTest) testa os efeitos principais e a interação grupo × tempo. As EMMs em cada momento permitem comparar grupos tempo a tempo.

de_mov <- dados %>%
  select(id, group, `NRS RPA m`, `NRS 24h m`, `NRS 48h m`) %>%
  pivot_longer(-c(id, group), names_to = "tempo", values_to = "nrs") %>%
  mutate(tempo = factor(dplyr::recode(tempo,
                                      "NRS RPA m" = "SRPA",
                                      "NRS 24h m" = "24h",
                                      "NRS 48h m" = "48h"),
                        levels = c("SRPA","24h","48h"))) %>%
  filter(!is.na(nrs))

fit_lmm_evn <- lmer(nrs ~ group * tempo + (1 | id), data = de_mov)

# Tabela ANOVA tipo III com Satterthwaite
anova_tab <- as.data.frame(anova(fit_lmm_evn)) %>%
  tibble::rownames_to_column("Termo") %>%
  transmute(
    Termo,
    `F`       = nv(`F value`, 2),
    `GL num`  = nv(NumDF, 1),
    `GL den`  = nv(DenDF, 1),
    `Valor-p` = ifelse(`Pr(>F)` < 0.001, "&lt;0,001", nv(`Pr(>F)`, 3))
  )
knitr::kable(anova_tab, align = c("l","c","c","c","c"),
             caption = "LMM — ANOVA tipo III (Satterthwaite) para EVN em movimento")
(#tab:lmm_evn_mov)LMM — ANOVA tipo III (Satterthwaite) para EVN em movimento
Termo F GL num GL den Valor-p
group 0,82 1,0 37,0 0,370
tempo 48,12 2,0 74,0 <0,001
group:tempo 0,51 2,0 74,0 0,601
# EMMs por tempo e contraste BMRA-CLO − BMRA em cada momento
emm_evn       <- emmeans(fit_lmm_evn, ~ group | tempo)
contr_evn_obj <- contrast(emm_evn, method = "pairwise")
contr_ev      <- as.data.frame(summary(contr_evn_obj, infer = c(TRUE, TRUE)))
contr_ev$estimate <- -contr_ev$estimate
tmp <- contr_ev$lower.CL; contr_ev$lower.CL <- -contr_ev$upper.CL; contr_ev$upper.CL <- -tmp

contr_tab <- contr_ev %>%
  transmute(
    Tempo                    = tempo,
    `Δ ajustada (CLO − BMRA)` = nv(estimate, 2),
    `IC 95%`                 = sprintf("[%s; %s]", nv(lower.CL, 2), nv(upper.CL, 2)),
    `Valor-p`                = ifelse(p.value < 0.001, "&lt;0,001", nv(p.value, 3))
  )
knitr::kable(contr_tab, align = c("l","c","c","c"),
             caption = "LMM — diferenças entre grupos em cada tempo (EVN movimento)")
(#tab:lmm_evn_mov)LMM — diferenças entre grupos em cada tempo (EVN movimento)
Tempo Δ ajustada (CLO − BMRA) IC 95% Valor-p
SRPA -0,19 [-1,08; 0,69] 0,662
24h -0,07 [-0,95; 0,81] 0,873
48h -0,59 [-1,48; 0,29] 0,183

11 Análises de Sensibilidade

O objetivo desta seção é verificar a robustez dos resultados sobre o consumo de fentanil através de diferentes métodos (Winsorização, teste de Yuen, permutação e bootstrap), garantindo que os achados não sejam fruto de outliers.

11.1 Fentanil (µg/kg)

df_fkg <- dados %>%
  filter(!is.na(group), is.finite(fentanyl_per_kg)) %>%
  select(group, fentanyl_val = fentanyl, fentanyl_per_kg)

wins_by_group <- function(x, g, trim = 0.05) {
  for (lv in levels(g)) {
    idx <- which(g == lv & is.finite(x))
    q   <- quantile(x[idx], c(trim, 1 - trim), na.rm = TRUE)
    x[idx] <- pmin(pmax(x[idx], q[1]), q[2])
  }
  x
}

# 1) t de Welch winsorizado (µg/kg)
x_wins <- wins_by_group(df_fkg$fentanyl_per_kg, df_fkg$group, trim = 0.05)
t.test(x_wins ~ df_fkg$group, var.equal = FALSE)
## 
##  Welch Two Sample t-test
## 
## data:  x_wins by df_fkg$group
## t = 5.6705, df = 26.455, p-value = 5.439e-06
## alternative hypothesis: true difference in means between group BMRA and group BMRA-CLO is not equal to 0
## 95 percent confidence interval:
##  0.3916251 0.8364061
## sample estimates:
##     mean in group BMRA mean in group BMRA-CLO 
##              1.4406338              0.8266182
# 2) Teste de Yuen (médias aparadas a 20%)
try(WRS2::yuen(fentanyl_per_kg ~ group, data = df_fkg, tr = 0.2), silent = TRUE)
## Call:
## WRS2::yuen(formula = fentanyl_per_kg ~ group, data = df_fkg, 
##     tr = 0.2)
## 
## Test statistic: 4.6112 (df = 17.4), p-value = 0.00024
## 
## Trimmed mean difference:  0.65968 
## 95 percent confidence interval:
## 0.3584     0.961 
## 
## Explanatory measure of effect size: 0.9
# 3) Teste de permutação (independência)
set.seed(123)
coin::independence_test(fentanyl_per_kg ~ group, data = df_fkg,
                        distribution = coin::approximate(B = 5000))
## 
##  Approximative General Independence Test
## 
## data:  fentanyl_per_kg by group (BMRA, BMRA-CLO)
## Z = 4.04, p-value < 2e-04
## alternative hypothesis: two.sided
# 4) Dose total — Mann–Whitney
wilcox.test(fentanyl_val ~ group, data = df_fkg, exact = FALSE, conf.int = TRUE)
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  fentanyl_val by group
## W = 333, p-value = 1.589e-05
## alternative hypothesis: true location shift is not equal to 0
## 95 percent confidence interval:
##  49.99994 50.00001
## sample estimates:
## difference in location 
##               49.99992
# 5) Bootstrap BCa da diferença de médias (CLO − BMRA), estratificado por grupo
boot_diff <- function(d, i) {
  d <- d[i, ]
  mean(d$fentanyl_val[d$group == "BMRA-CLO"]) - mean(d$fentanyl_val[d$group == "BMRA"])
}
set.seed(123)
br <- boot::boot(df_fkg, boot_diff, R = 2000, strata = df_fkg$group)
boot::boot.ci(br, type = "bca")
## BOOTSTRAP CONFIDENCE INTERVAL CALCULATIONS
## Based on 2000 bootstrap replicates
## 
## CALL : 
## boot::boot.ci(boot.out = br, type = "bca")
## 
## Intervals : 
## Level       BCa          
## 95%   (-66.79, -31.74 )  
## Calculations and Intervals on Original Scale
# 6) Casos influentes — distância de Cook (leave-one-out)
fit_lm <- lm(fentanyl_per_kg ~ group, data = df_fkg)
inf_df <- df_fkg %>%
  mutate(id = row_number(), cooks = cooks.distance(fit_lm), influente = cooks > 4/n())

ggplot(inf_df, aes(id, cooks, color = influente)) +
  geom_segment(aes(xend = id, y = 0, yend = cooks), color = "gray") +
  geom_point(size = 3) +
  geom_hline(yintercept = 4/nrow(inf_df), linetype = "dashed", color = "red") +
  scale_color_manual(values = c("TRUE" = "#E74C3C", "FALSE" = "#2C3E50")) +
  labs(title = "Distância de Cook (leave-one-out)",
       subtitle = "Pontos acima da linha vermelha distorcem isoladamente a média do grupo",
       x = "Paciente (índice)", y = "Distância de Cook") +
  theme(legend.position = "none")

12 Análises robustas adicionais para fortalecer a inferência

Esta seção acrescenta análises de robustez ao final do relatório original. O objetivo não é substituir as análises principais, mas verificar se as conclusões centrais permanecem estáveis quando são utilizados métodos menos dependentes de normalidade, homocedasticidade e ausência de pontos influentes.

As análises abaixo foram organizadas para responder a cinco perguntas práticas:

  1. O efeito da clonidina sobre o fentanil permanece quando usamos erros-padrão robustos, bootstrap e permutação?
  2. O resultado depende de um único paciente influente?
  3. A dor pode ser resumida por carga total de dor em 48h, em vez de múltiplas comparações tempo a tempo?
  4. O QoR-15, sujeito a efeito teto, mantém interpretação semelhante em análises robustas e categóricas?
  5. Os eventos adversos são melhor descritos por estimativas de risco e intervalos de confiança, e não apenas por p-valores?

12.1 Preparação dos pacotes e funções robustas

# -------------------------------------------------------------------------
# Pacotes adicionais para análises robustas
# -------------------------------------------------------------------------
# sandwich/lmtest: erros-padrão robustos HC3 para modelos lineares.
# broom: organização de saídas de modelos.
# purrr: programação funcional para bootstrap/AUC.
# ordinal: modelo ordinal misto para EVN, usado como análise de sensibilidade.
# logistf: regressão logística penalizada de Firth; opcional, útil quando há poucos eventos
#          ou separação completa/quase completa. Se não instalar, o script continua.
# -------------------------------------------------------------------------

pkgs_robustos <- c("sandwich", "lmtest", "broom", "purrr", "ordinal")
for (p in pkgs_robustos) {
  if (!requireNamespace(p, quietly = TRUE)) {
    install.packages(p, repos = "https://cloud.r-project.org")
  }
}

# Firth é opcional. Em alguns computadores pode demorar mais para instalar.
# O código tentará usar se estiver disponível; caso contrário, usa GLM comum com cautela.
if (!requireNamespace("logistf", quietly = TRUE)) {
  message("Pacote 'logistf' não instalado. Regressões de Firth serão omitidas, mas as análises principais continuarão.")
}

# -------------------------------------------------------------------------
# Função: tabela de coeficientes com erro-padrão robusto HC3
# -------------------------------------------------------------------------
# HC3 é recomendado em amostras pequenas por ser mais conservador que HC0/HC1
# diante de heterocedasticidade e observações de alta alavancagem.
# -------------------------------------------------------------------------

tabela_hc3 <- function(modelo, titulo = NULL) {
  vc <- sandwich::vcovHC(modelo, type = "HC3")
  ct <- lmtest::coeftest(modelo, vcov. = vc, df = df.residual(modelo))
  out <- as.data.frame(unclass(ct)) %>%
    tibble::rownames_to_column("Termo") %>%
    dplyr::rename(
      Estimativa = Estimate,
      EP_robusto = `Std. Error`,
      Estatistica = `t value`,
      p = `Pr(>|t|)`
    ) %>%
    dplyr::mutate(
      IC_low  = Estimativa - qt(0.975, df.residual(modelo)) * EP_robusto,
      IC_high = Estimativa + qt(0.975, df.residual(modelo)) * EP_robusto,
      `Estimativa` = nv(Estimativa, 3),
      `EP robusto HC3` = nv(EP_robusto, 3),
      `IC 95% robusto` = sprintf("[%s; %s]", nv(IC_low, 3), nv(IC_high, 3)),
      `Valor-p robusto` = ifelse(p < 0.001, "<0,001", nv(p, 3))
    ) %>%
    dplyr::select(Termo, Estimativa, `EP robusto HC3`, `IC 95% robusto`, `Valor-p robusto`)

  knitr::kable(out, align = c("l", "c", "c", "c", "c"), caption = titulo)
}

# -------------------------------------------------------------------------
# Função: extrair coeficiente do grupo BMRA-CLO em modelos lineares
# -------------------------------------------------------------------------
# Por convenção, groupBMRA-CLO representa a diferença BMRA-CLO − BMRA.
# Valor negativo para fentanil indica menor consumo no grupo clonidina.
# -------------------------------------------------------------------------

extrair_beta_grupo <- function(modelo, termo = "groupBMRA-CLO") {
  cf <- coef(modelo)
  if (!termo %in% names(cf)) return(NA_real_)
  unname(cf[termo])
}

# -------------------------------------------------------------------------
# Bootstrap estratificado por grupo para coeficiente do grupo em regressão linear
# -------------------------------------------------------------------------
# Reamostra pacientes dentro de cada grupo, preservando o tamanho de cada braço.
# Isso é mais adequado para ECR porque mantém a estrutura de alocação.
# -------------------------------------------------------------------------

bootstrap_beta_lm <- function(data, formula, termo = "groupBMRA-CLO", R = 5000,
                              strata = "group", seed = 123) {
  set.seed(seed)
  data <- data %>% dplyr::filter(stats::complete.cases(model.frame(formula, data = .)))
  data[[strata]] <- factor(data[[strata]], levels = c("BMRA", "BMRA-CLO"))

  beta_obs <- extrair_beta_grupo(lm(formula, data = data), termo = termo)
  grupos <- split(data, data[[strata]])

  betas <- replicate(R, {
    amostra <- dplyr::bind_rows(lapply(grupos, function(g) g[sample(seq_len(nrow(g)), replace = TRUE), , drop = FALSE]))
    amostra[[strata]] <- factor(amostra[[strata]], levels = c("BMRA", "BMRA-CLO"))
    tryCatch(extrair_beta_grupo(lm(formula, data = amostra), termo = termo), error = function(e) NA_real_)
  })

  betas <- betas[is.finite(betas)]
  tibble::tibble(
    Estimativa = beta_obs,
    IC_low = as.numeric(quantile(betas, 0.025, na.rm = TRUE)),
    IC_high = as.numeric(quantile(betas, 0.975, na.rm = TRUE)),
    R_validos = length(betas)
  )
}

# -------------------------------------------------------------------------
# Teste de permutação para coeficiente do grupo em regressão linear
# -------------------------------------------------------------------------
# Em um ensaio randomizado, permutar os rótulos dos grupos simula a hipótese nula
# compatível com a própria lógica da randomização.
# -------------------------------------------------------------------------

permutacao_beta_lm <- function(data, formula, termo = "groupBMRA-CLO", B = 10000,
                               seed = 123) {
  set.seed(seed)
  data <- data %>% dplyr::filter(stats::complete.cases(model.frame(formula, data = .)))
  data$group <- factor(data$group, levels = c("BMRA", "BMRA-CLO"))

  beta_obs <- extrair_beta_grupo(lm(formula, data = data), termo = termo)

  betas_perm <- replicate(B, {
    dp <- data
    dp$group <- factor(sample(dp$group), levels = c("BMRA", "BMRA-CLO"))
    tryCatch(extrair_beta_grupo(lm(formula, data = dp), termo = termo), error = function(e) NA_real_)
  })

  betas_perm <- betas_perm[is.finite(betas_perm)]
  p_perm <- mean(abs(betas_perm) >= abs(beta_obs))

  tibble::tibble(
    Estimativa_observada = beta_obs,
    `p por permutação` = p_perm,
    B_validas = length(betas_perm)
  )
}

# -------------------------------------------------------------------------
# Função geral para apresentar resultados bootstrap/permutação
# -------------------------------------------------------------------------

fmt_boot <- function(x, unidade = "") {
  x %>%
    dplyr::mutate(
      Estimativa = paste0(nv(Estimativa, 3), unidade),
      `IC 95% bootstrap` = sprintf("[%s; %s]%s", nv(IC_low, 3), nv(IC_high, 3), unidade)
    ) %>%
    dplyr::select(Estimativa, `IC 95% bootstrap`, R_validos)
}

12.2 Desfecho primário: fentanil µg/kg com modelos robustos

# -------------------------------------------------------------------------
# Três níveis de ajuste para o desfecho primário
# -------------------------------------------------------------------------
# 1) Modelo bruto: estima o efeito simples da randomização.
# 2) Modelo mínimo: adiciona IMC, por ser clinicamente relevante para dose/volume corporal.
# 3) Modelo ajustado principal: idade, IMC, sexo e ASA, conforme análise principal.
# -------------------------------------------------------------------------

df_fent_rob <- dados %>%
  dplyr::filter(!is.na(group), is.finite(fentanyl_per_kg), !is.na(idade), !is.na(imc), !is.na(sexo), !is.na(asa)) %>%
  dplyr::mutate(group = factor(group, levels = c("BMRA", "BMRA-CLO")))

fit_fent_bruto   <- lm(fentanyl_per_kg ~ group, data = df_fent_rob)
fit_fent_minimo  <- lm(fentanyl_per_kg ~ group + imc, data = df_fent_rob)
fit_fent_ajustado <- lm(fentanyl_per_kg ~ group + idade + imc + sexo + asa, data = df_fent_rob)

# Tabela comparativa dos modelos com estimativa clássica do coeficiente de grupo
modelos_fent <- list(
  "Bruto" = fit_fent_bruto,
  "Mínimo: grupo + IMC" = fit_fent_minimo,
  "Ajustado: grupo + idade + IMC + sexo + ASA" = fit_fent_ajustado
)

res_modelos_fent <- purrr::imap_dfr(modelos_fent, function(m, nm) {
  ci <- confint(m)
  beta <- extrair_beta_grupo(m)
  tibble::tibble(
    Modelo = nm,
    `β grupo BMRA-CLO − BMRA` = beta,
    `IC 95% low` = ci["groupBMRA-CLO", 1],
    `IC 95% high` = ci["groupBMRA-CLO", 2],
    `p clássico` = summary(m)$coefficients["groupBMRA-CLO", "Pr(>|t|)"],
    `R² ajustado` = summary(m)$adj.r.squared
  )
}) %>%
  dplyr::mutate(
    `β grupo BMRA-CLO − BMRA` = nv(`β grupo BMRA-CLO − BMRA`, 3),
    `IC 95%` = sprintf("[%s; %s]", nv(`IC 95% low`, 3), nv(`IC 95% high`, 3)),
    `p clássico` = ifelse(`p clássico` < 0.001, "<0,001", nv(`p clássico`, 3)),
    `R² ajustado` = nv(`R² ajustado`, 3)
  ) %>%
  dplyr::select(Modelo, `β grupo BMRA-CLO − BMRA`, `IC 95%`, `p clássico`, `R² ajustado`)

knitr::kable(res_modelos_fent, align = c("l", "c", "c", "c", "c"),
             caption = "Fentanil µg/kg — estabilidade do efeito do grupo em modelos com diferentes níveis de ajuste")
(#tab:robust_fentanil_modelos)Fentanil µg/kg — estabilidade do efeito do grupo em modelos com diferentes níveis de ajuste
Modelo β grupo BMRA-CLO − BMRA IC 95% p clássico R² ajustado
Bruto -0,601 [-0,832; -0,370] <0,001 0,414
Mínimo: grupo + IMC -0,609 [-0,845; -0,372] <0,001 0,400
Ajustado: grupo + idade + IMC + sexo + ASA -0,589 [-0,829; -0,349] <0,001 0,409
# Tabela robusta HC3 do modelo principal
cat("\n\n**Modelo ajustado principal com erro-padrão robusto HC3**\n\n")

Modelo ajustado principal com erro-padrão robusto HC3

tabela_hc3(fit_fent_ajustado, titulo = "ANCOVA robusta HC3 — fentanil µg/kg")
(#tab:robust_fentanil_modelos)ANCOVA robusta HC3 — fentanil µg/kg
Termo Estimativa EP robusto HC3 IC 95% robusto Valor-p robusto
(Intercept) 1,267 0,671 [-0,097; 2,632] 0,068
groupBMRA-CLO -0,589 0,117 [-0,827; -0,351] <0,001
idade 0,001 0,006 [-0,011; 0,013] 0,870
imc 0,004 0,021 [-0,038; 0,045] 0,863
sexoF 0,198 0,149 [-0,105; 0,500] 0,192
asaII -0,094 0,133 [-0,364; 0,176] 0,484
# -------------------------------------------------------------------------
# Bootstrap e permutação do coeficiente de grupo no modelo ajustado
# -------------------------------------------------------------------------

boot_fent <- bootstrap_beta_lm(
  data = df_fent_rob,
  formula = fentanyl_per_kg ~ group + idade + imc + sexo + asa,
  R = 5000,
  seed = 2026
)

perm_fent <- permutacao_beta_lm(
  data = df_fent_rob,
  formula = fentanyl_per_kg ~ group + idade + imc + sexo + asa,
  B = 10000,
  seed = 2026
)

knitr::kable(fmt_boot(boot_fent, unidade = " µg/kg"), align = c("c", "c", "c"),
             caption = "Bootstrap estratificado — efeito ajustado do grupo sobre fentanil µg/kg")
(#tab:robust_fentanil_boot_perm)Bootstrap estratificado — efeito ajustado do grupo sobre fentanil µg/kg
Estimativa IC 95% bootstrap R_validos
-0,589 µg/kg [-0,791; -0,368] µg/kg 5000
perm_tab <- perm_fent %>%
  dplyr::mutate(
    Estimativa_observada = paste0(nv(Estimativa_observada, 3), " µg/kg"),
    `p por permutação` = ifelse(`p por permutação` < 0.001, "<0,001", nv(`p por permutação`, 3))
  )
knitr::kable(perm_tab, align = c("c", "c", "c"),
             caption = "Teste de permutação — efeito ajustado do grupo sobre fentanil µg/kg")
(#tab:robust_fentanil_boot_perm)Teste de permutação — efeito ajustado do grupo sobre fentanil µg/kg
Estimativa_observada p por permutação B_validas
-0,589 µg/kg <0,001 10000

12.3 Influência individual: análise leave-one-out

# -------------------------------------------------------------------------
# Análise leave-one-out
# -------------------------------------------------------------------------
# Retira um paciente por vez e recalcula o coeficiente do grupo.
# Se o efeito permanece negativo e de magnitude semelhante, a conclusão não
# depende de um único paciente extremo.
# -------------------------------------------------------------------------

loo_fent <- purrr::map_dfr(seq_len(nrow(df_fent_rob)), function(i) {
  dloo <- df_fent_rob[-i, , drop = FALSE]
  fit <- lm(fentanyl_per_kg ~ group + idade + imc + sexo + asa, data = dloo)
  tibble::tibble(
    paciente_retirado = as.character(df_fent_rob$id[i]),
    beta_grupo = extrair_beta_grupo(fit),
    cooks_no_modelo_original = cooks.distance(fit_fent_ajustado)[i]
  )
})

res_loo <- tibble::tibble(
  `β original` = extrair_beta_grupo(fit_fent_ajustado),
  `Menor β leave-one-out` = min(loo_fent$beta_grupo, na.rm = TRUE),
  `Maior β leave-one-out` = max(loo_fent$beta_grupo, na.rm = TRUE),
  `Maior Cook no modelo original` = max(loo_fent$cooks_no_modelo_original, na.rm = TRUE),
  `Limite Cook 4/n` = 4 / nrow(df_fent_rob)
) %>%
  dplyr::mutate(dplyr::across(dplyr::everything(), ~ nv(.x, 3)))

knitr::kable(res_loo, align = c("c", "c", "c", "c", "c"),
             caption = "Leave-one-out e distância de Cook — fentanil µg/kg")
(#tab:robust_leave_one_out)Leave-one-out e distância de Cook — fentanil µg/kg
β original Menor β leave-one-out Maior β leave-one-out Maior Cook no modelo original Limite Cook 4/n
-0,589 -0,635 -0,560 0,130 0,103
ggplot(loo_fent, aes(x = seq_along(beta_grupo), y = beta_grupo)) +
  geom_hline(yintercept = extrair_beta_grupo(fit_fent_ajustado), linetype = "dashed") +
  geom_hline(yintercept = 0, linetype = "dotted") +
  geom_point(size = 2.5) +
  labs(
    title = "Leave-one-out — estabilidade do efeito da clonidina sobre fentanil µg/kg",
    subtitle = "Cada ponto representa o coeficiente do grupo após retirar um paciente",
    x = "Paciente retirado, em ordem do banco",
    y = "β grupo BMRA-CLO − BMRA"
  )

12.4 Dor pós-operatória: carga total de dor por AUC em 0–48h

# -------------------------------------------------------------------------
# Área sob a curva da EVN: carga total de dor em 48 horas
# -------------------------------------------------------------------------
# AUC pelo método trapezoidal usando tempos: SRPA = 0h, 24h e 48h.
# A AUC tem unidade EVN*h. Uma AUC menor indica menor carga total de dor.
# Também calculamos AUC média = AUC/48, interpretável como EVN média ponderada
# pelo tempo ao longo das primeiras 48 horas.
# -------------------------------------------------------------------------

calc_auc <- function(y, tempo = c(0, 24, 48)) {
  ok <- is.finite(y) & is.finite(tempo)
  y <- y[ok]
  tempo <- tempo[ok]
  if (length(y) < 2) return(NA_real_)
  ord <- order(tempo)
  y <- y[ord]
  tempo <- tempo[ord]
  sum(diff(tempo) * (head(y, -1) + tail(y, -1)) / 2)
}

dor_auc <- dados %>%
  dplyr::mutate(group = factor(group, levels = c("BMRA", "BMRA-CLO"))) %>%
  dplyr::transmute(
    id, group, idade, imc, sexo, asa,
    auc_evn_repouso = purrr::pmap_dbl(
      list(`NRS RPA r`, `NRS 24h r`, `NRS 48h r`),
      function(a, b, c) calc_auc(c(a, b, c))
    ),
    auc_evn_movimento = purrr::pmap_dbl(
      list(`NRS RPA m`, `NRS 24h m`, `NRS 48h m`),
      function(a, b, c) calc_auc(c(a, b, c))
    )
  ) %>%
  dplyr::mutate(
    evn_media_48h_repouso = auc_evn_repouso / 48,
    evn_media_48h_movimento = auc_evn_movimento / 48
  )

comparar_auc <- function(var_auc, label) {
  form <- as.formula(paste0(var_auc, " ~ group + idade + imc + sexo + asa"))
  d <- dor_auc %>% dplyr::filter(stats::complete.cases(model.frame(form, data = .)))
  fit <- lm(form, data = d)
  vc <- sandwich::vcovHC(fit, type = "HC3")
  ct <- lmtest::coeftest(fit, vcov. = vc, df = df.residual(fit))
  beta <- ct["groupBMRA-CLO", "Estimate"]
  se <- ct["groupBMRA-CLO", "Std. Error"]
  p <- ct["groupBMRA-CLO", "Pr(>|t|)"]
  ic_low <- beta - qt(0.975, df.residual(fit)) * se
  ic_high <- beta + qt(0.975, df.residual(fit)) * se

  boot <- bootstrap_beta_lm(d, form, R = 3000, seed = 2026)
  perm <- permutacao_beta_lm(d, form, B = 5000, seed = 2026)

  tibble::tibble(
    Desfecho = label,
    `β ajustado HC3` = beta,
    `IC 95% HC3` = sprintf("[%s; %s]", nv(ic_low, 2), nv(ic_high, 2)),
    `p HC3` = p,
    `IC 95% bootstrap` = sprintf("[%s; %s]", nv(boot$IC_low, 2), nv(boot$IC_high, 2)),
    `p permutação` = perm$`p por permutação`
  )
}

auc_tab <- dplyr::bind_rows(
  comparar_auc("auc_evn_repouso", "AUC EVN repouso 0–48h"),
  comparar_auc("auc_evn_movimento", "AUC EVN movimento 0–48h"),
  comparar_auc("evn_media_48h_repouso", "EVN média ponderada repouso"),
  comparar_auc("evn_media_48h_movimento", "EVN média ponderada movimento")
) %>%
  dplyr::mutate(
    `β ajustado HC3` = nv(`β ajustado HC3`, 2),
    `p HC3` = ifelse(`p HC3` < 0.001, "<0,001", nv(`p HC3`, 3)),
    `p permutação` = ifelse(`p permutação` < 0.001, "<0,001", nv(`p permutação`, 3))
  )

knitr::kable(auc_tab, align = c("l", "c", "c", "c", "c", "c"),
             caption = "Dor pós-operatória — AUC e EVN média ponderada em 0–48h")
(#tab:robust_auc_dor)Dor pós-operatória — AUC e EVN média ponderada em 0–48h
Desfecho β ajustado HC3 IC 95% HC3 p HC3 IC 95% bootstrap p permutação
AUC EVN repouso 0–48h -9,92 [-41,51; 21,68] 0,528 [-36,86; 19,22] 0,503
AUC EVN movimento 0–48h -17,72 [-52,05; 16,62] 0,301 [-47,86; 13,74] 0,318
EVN média ponderada repouso -0,21 [-0,86; 0,45] 0,528 [-0,77; 0,40] 0,503
EVN média ponderada movimento -0,37 [-1,08; 0,35] 0,301 [-1,00; 0,29] 0,318
# Gráfico exploratório da carga total de dor em movimento

ggplot(dor_auc, aes(x = group, y = auc_evn_movimento, fill = group)) +
  geom_boxplot(width = 0.55, alpha = 0.75, outlier.shape = NA) +
  geom_jitter(width = 0.08, alpha = 0.75, size = 2) +
  scale_fill_manual(values = pal_grupo) +
  labs(
    title = "Carga total de dor em movimento — AUC EVN 0–48h",
    subtitle = "Menor AUC indica menor carga acumulada de dor no período pós-operatório",
    x = NULL,
    y = "AUC EVN movimento (EVN*h)"
  ) +
  theme(legend.position = "none")

12.5 EVN em movimento: modelo ordinal misto como sensibilidade

# -------------------------------------------------------------------------
# Modelo ordinal misto para EVN em movimento
# -------------------------------------------------------------------------
# A EVN é uma escala discreta de 0 a 10. O modelo linear misto é útil e
# interpretável, mas o modelo ordinal respeita melhor a natureza ordinal do escore.
# Esta análise deve ser interpretada como sensibilidade, não como substituta do LMM.
# -------------------------------------------------------------------------

de_mov_ord <- de_mov %>%
  dplyr::mutate(
    nrs_round = round(nrs),
    nrs_round = pmax(0, pmin(10, nrs_round)),
    nrs_ord = ordered(nrs_round, levels = 0:10),
    group = factor(group, levels = c("BMRA", "BMRA-CLO")),
    tempo = factor(tempo, levels = c("SRPA", "24h", "48h"))
  ) %>%
  dplyr::filter(!is.na(nrs_ord), !is.na(group), !is.na(tempo), !is.na(id))

fit_ord <- tryCatch(
  ordinal::clmm(nrs_ord ~ group * tempo + (1 | id), data = de_mov_ord, Hess = TRUE),
  error = function(e) e
)

if (inherits(fit_ord, "error")) {
  cat("O modelo ordinal misto não convergiu nesta amostra. Mensagem do R:\n")
  cat(fit_ord$message)
} else {
  ord_coef <- coef(summary(fit_ord)) %>%
    as.data.frame() %>%
    tibble::rownames_to_column("Termo") %>%
    dplyr::filter(grepl("group|tempo", Termo)) %>%
    dplyr::mutate(
      `Odds ratio` = exp(Estimate),
      `IC 95% low` = exp(Estimate - 1.96 * `Std. Error`),
      `IC 95% high` = exp(Estimate + 1.96 * `Std. Error`),
      `Valor-p` = 2 * pnorm(abs(`z value`), lower.tail = FALSE)
    ) %>%
    dplyr::transmute(
      Termo,
      `OR acumulativo` = nv(`Odds ratio`, 2),
      `IC 95%` = sprintf("[%s; %s]", nv(`IC 95% low`, 2), nv(`IC 95% high`, 2)),
      `Valor-p` = ifelse(`Valor-p` < 0.001, "<0,001", nv(`Valor-p`, 3))
    )

  knitr::kable(ord_coef, align = c("l", "c", "c", "c"),
               caption = "Modelo ordinal misto — EVN em movimento")
}
(#tab:robust_ordinal_evn)Modelo ordinal misto — EVN em movimento
Termo OR acumulativo IC 95% Valor-p
groupBMRA-CLO 0,72 [0,15; 3,44] 0,679
tempo24h 58,58 [12,46; 275,37] <0,001
tempo48h 7,21 [1,85; 28,18] 0,004
groupBMRA-CLO:tempo24h 1,28 [0,21; 7,94] 0,794
groupBMRA-CLO:tempo48h 0,67 [0,11; 4,15] 0,670

12.6 QoR-15: análise robusta e efeito teto

# -------------------------------------------------------------------------
# QoR-15 em 48h: ANCOVA robusta, bootstrap e permutação
# -------------------------------------------------------------------------
# Como o QoR-15 pode apresentar efeito teto, a análise linear é complementada
# por bootstrap/permutação e por análise categórica de recuperação excelente.
# -------------------------------------------------------------------------

df_qor_rob <- dados %>%
  dplyr::filter(!is.na(group), is.finite(qor_48h), is.finite(qor_srpa), !is.na(idade), !is.na(imc), !is.na(sexo)) %>%
  dplyr::mutate(group = factor(group, levels = c("BMRA", "BMRA-CLO")))

fit_qor48_rob <- lm(qor_48h ~ group + qor_srpa + idade + sexo + imc, data = df_qor_rob)

cat("\n\n**QoR-15 em 48h — modelo ajustado com erro-padrão robusto HC3**\n\n")

QoR-15 em 48h — modelo ajustado com erro-padrão robusto HC3

tabela_hc3(fit_qor48_rob, titulo = "QoR-15 48h — regressão robusta HC3")
(#tab:robust_qor_hc3_boot)QoR-15 48h — regressão robusta HC3
Termo Estimativa EP robusto HC3 IC 95% robusto Valor-p robusto
(Intercept) 129,158 10,298 [108,208; 150,109] <0,001
groupBMRA-CLO 2,794 1,201 [0,351; 5,237] 0,026
qor_srpa 0,134 0,048 [0,037; 0,232] 0,008
idade -0,056 0,033 [-0,123; 0,011] 0,099
sexoF 0,371 1,544 [-2,770; 3,513] 0,812
imc 0,040 0,158 [-0,281; 0,362] 0,800
boot_qor <- bootstrap_beta_lm(
  data = df_qor_rob,
  formula = qor_48h ~ group + qor_srpa + idade + sexo + imc,
  R = 5000,
  seed = 2026
)

perm_qor <- permutacao_beta_lm(
  data = df_qor_rob,
  formula = qor_48h ~ group + qor_srpa + idade + sexo + imc,
  B = 10000,
  seed = 2026
)

knitr::kable(fmt_boot(boot_qor, unidade = " pontos"), align = c("c", "c", "c"),
             caption = "Bootstrap estratificado — efeito ajustado do grupo sobre QoR-15 48h")
(#tab:robust_qor_hc3_boot)Bootstrap estratificado — efeito ajustado do grupo sobre QoR-15 48h
Estimativa IC 95% bootstrap R_validos
2,794 pontos [0,876; 5,516] pontos 5000
perm_qor_tab <- perm_qor %>%
  dplyr::mutate(
    Estimativa_observada = paste0(nv(Estimativa_observada, 2), " pontos"),
    `p por permutação` = ifelse(`p por permutação` < 0.001, "<0,001", nv(`p por permutação`, 3))
  )
knitr::kable(perm_qor_tab, align = c("c", "c", "c"),
             caption = "Teste de permutação — efeito ajustado do grupo sobre QoR-15 48h")
(#tab:robust_qor_hc3_boot)Teste de permutação — efeito ajustado do grupo sobre QoR-15 48h
Estimativa_observada p por permutação B_validas
2,79 pontos 0,007 10000
# -------------------------------------------------------------------------
# Recuperação excelente: análise categórica do QoR-15
# -------------------------------------------------------------------------
# Dois limiares são apresentados:
# - QoR-15 >= 145: recuperação muito próxima do teto.
# - QoR-15 >= 135: pelo menos 90% do escore máximo de 150.
# -------------------------------------------------------------------------

df_qor_bin <- df_qor_rob %>%
  dplyr::mutate(
    qor48_excelente_145 = as.integer(qor_48h >= 145),
    qor48_excelente_135 = as.integer(qor_48h >= 135),
    melhora_qor_8pts = as.integer((qor_48h - qor_srpa) >= 8)
  )

calcular_risco_binario <- function(data, evento, label) {
  d <- data %>% dplyr::filter(!is.na(.data[[evento]]), !is.na(group))
  g1 <- d %>% dplyr::filter(group == "BMRA-CLO")
  g0 <- d %>% dplyr::filter(group == "BMRA")

  a <- sum(g1[[evento]] == 1, na.rm = TRUE)
  n1 <- nrow(g1)
  c <- sum(g0[[evento]] == 1, na.rm = TRUE)
  n0 <- nrow(g0)

  p1 <- a / n1
  p0 <- c / n0
  rd <- p1 - p0
  se_rd <- sqrt(p1 * (1 - p1) / n1 + p0 * (1 - p0) / n0)
  rd_low <- rd - 1.96 * se_rd
  rd_high <- rd + 1.96 * se_rd

  # Correção de Haldane-Anscombe para RR quando houver célula zero
  aa <- a; bb <- n1 - a; cc <- c; dd <- n0 - c
  if (any(c(aa, bb, cc, dd) == 0)) {
    aa <- aa + 0.5; bb <- bb + 0.5; cc <- cc + 0.5; dd <- dd + 0.5
  }
  rr <- (aa / (aa + bb)) / (cc / (cc + dd))
  se_log_rr <- sqrt(1 / aa - 1 / (aa + bb) + 1 / cc - 1 / (cc + dd))
  rr_low <- exp(log(rr) - 1.96 * se_log_rr)
  rr_high <- exp(log(rr) + 1.96 * se_log_rr)

  fisher_p <- fisher.test(matrix(c(a, n1 - a, c, n0 - c), nrow = 2, byrow = TRUE))$p.value

  tibble::tibble(
    Desfecho = label,
    `BMRA-CLO` = fmt_n_pct(a, n1),
    BMRA = fmt_n_pct(c, n0),
    `Diferença absoluta de risco` = rd,
    `IC 95% RD` = sprintf("[%s; %s]", nv(rd_low, 2), nv(rd_high, 2)),
    `Risco relativo` = rr,
    `IC 95% RR` = sprintf("[%s; %s]", nv(rr_low, 2), nv(rr_high, 2)),
    `p Fisher` = fisher_p
  )
}

qor_bin_tab <- dplyr::bind_rows(
  calcular_risco_binario(df_qor_bin, "qor48_excelente_145", "QoR-15 48h ≥ 145"),
  calcular_risco_binario(df_qor_bin, "qor48_excelente_135", "QoR-15 48h ≥ 135"),
  calcular_risco_binario(df_qor_bin, "melhora_qor_8pts", "Aumento QoR-15 ≥ 8 pontos desde SRPA")
) %>%
  dplyr::mutate(
    `Diferença absoluta de risco` = nv(`Diferença absoluta de risco`, 2),
    `Risco relativo` = nv(`Risco relativo`, 2),
    `p Fisher` = ifelse(`p Fisher` < 0.001, "<0,001", nv(`p Fisher`, 3))
  )

knitr::kable(qor_bin_tab, align = c("l", "c", "c", "c", "c", "c", "c", "c"),
             caption = "QoR-15 48h — recuperação excelente e melhora clinicamente relevante")
(#tab:robust_qor_binario)QoR-15 48h — recuperação excelente e melhora clinicamente relevante
Desfecho BMRA-CLO BMRA Diferença absoluta de risco IC 95% RD Risco relativo IC 95% RR p Fisher
QoR-15 48h ≥ 145 20 (100,0%) 14 (73,7%) 0,26 [0,07; 0,46] 1,35 [1,02; 1,78] 0,020
QoR-15 48h ≥ 135 20 (100,0%) 18 (94,7%) 0,05 [-0,05; 0,15] 1,06 [0,92; 1,22] 0,487
Aumento QoR-15 ≥ 8 pontos desde SRPA 16 (80,0%) 14 (73,7%) 0,06 [-0,20; 0,33] 1,09 [0,77; 1,54] 0,716
# Regressão logística penalizada de Firth, se o pacote estiver disponível.
# Útil quando há poucos eventos ou separação completa/quase completa.
if (requireNamespace("logistf", quietly = TRUE)) {
  firth_145 <- tryCatch(
    logistf::logistf(qor48_excelente_145 ~ group + qor_srpa + idade + sexo + imc, data = df_qor_bin),
    error = function(e) e
  )
  if (!inherits(firth_145, "error")) {
    cat("\n\n**Regressão logística de Firth para QoR-15 48h ≥ 145**\n\n")
    print(summary(firth_145))
  }
} else {
  cat("\n\nRegressão de Firth omitida porque o pacote 'logistf' não está instalado.\n")
}

Regressão logística de Firth para QoR-15 48h ≥ 145

logistf::logistf(formula = qor48_excelente_145 ~ group + qor_srpa + idade + sexo + imc, data = df_qor_bin)

Model fitted by Penalized ML Coefficients: coef se(coef) lower 0.95 upper 0.95 Chisq (Intercept) -0.459887503 6.28182842 -15.48639565 12.58491874 0.004851831 groupBMRA-CLO 2.267222191 1.21131442 0.07883478 7.09953707 4.176222508 qor_srpa 0.024326650 0.03906014 -0.05825120 0.12264863 0.335685735 idade -0.007001762 0.04616730 -0.14252755 0.09146808 0.017469084 sexoF -0.206546805 0.98360383 -2.46611053 2.02252856 0.037460936 imc -0.053853362 0.11699214 -0.32204219 0.21367942 0.179530661 p method (Intercept) 0.94446819 2 groupBMRA-CLO 0.04099498 2 qor_srpa 0.56233015 2 idade 0.89484931 2 sexoF 0.84652960 2 imc 0.67177689 2

Method: 1-Wald, 2-Profile penalized log-likelihood, 3-None

Likelihood ratio test=5.879402 on 5 df, p=0.3181311, n=39 Wald test = 11.75105 on 5 df, p = 0.03836283logistf::logistf(formula = qor48_excelente_145 ~ group + qor_srpa + idade + sexo + imc, data = df_qor_bin) Model fitted by Penalized ML Confidence intervals and p-values by Profile Likelihood

Coefficients: (Intercept) groupBMRA-CLO qor_srpa idade sexoF -0.459887503 2.267222191 0.024326650 -0.007001762 -0.206546805 imc -0.053853362

Likelihood ratio test=5.879402 on 5 df, p=0.3181311, n=39

12.7 Eventos adversos: diferença absoluta de risco, risco relativo e IC 95%

# -------------------------------------------------------------------------
# Eventos adversos e segurança
# -------------------------------------------------------------------------
# Em amostras pequenas, p-valores de Fisher raramente demonstram diferença.
# Por isso, o mais informativo é reportar a diferença absoluta de risco, o risco
# relativo e os respectivos intervalos de confiança, reconhecendo a incerteza.
# -------------------------------------------------------------------------

# Lista de eventos de segurança disponíveis no banco.
# Ajuste/adicione eventos caso novos campos sejam incluídos no REDCap.
eventos_seg <- c(
  bradicardya_rpa = "Bradicardia na SRPA",
  hypotension_rpa = "Hipotensão na SRPA",
  ponv_rpa = "NVPO na SRPA",
  general_anest_convertion = "Conversão para anestesia geral",
  hospital_internation_rpa = "Internação hospitalar",
  abdwall_hematoma_rpa = "Hematoma de parede abdominal"
)

eventos_seg <- eventos_seg[names(eventos_seg) %in% names(dados)]

seg_tab <- purrr::imap_dfr(eventos_seg, function(label, var) {
  calcular_risco_binario(dados %>% dplyr::mutate(group = factor(group, levels = c("BMRA", "BMRA-CLO"))), var, label)
}) %>%
  dplyr::mutate(
    `Diferença absoluta de risco` = nv(`Diferença absoluta de risco`, 2),
    `Risco relativo` = nv(`Risco relativo`, 2),
    `p Fisher` = ifelse(`p Fisher` < 0.001, "<0,001", nv(`p Fisher`, 3))
  )

knitr::kable(seg_tab, align = c("l", "c", "c", "c", "c", "c", "c", "c"),
             caption = "Eventos adversos — risco absoluto, risco relativo e teste exato de Fisher")
(#tab:robust_eventos_adversos)Eventos adversos — risco absoluto, risco relativo e teste exato de Fisher
Desfecho BMRA-CLO BMRA Diferença absoluta de risco IC 95% RD Risco relativo IC 95% RR p Fisher
Bradicardia na SRPA 1 (5,0%) 2 (10,5%) -0,06 [-0,22; 0,11] 0,48 [0,05; 4,82] 0,605
Hipotensão na SRPA 1 (5,0%) 2 (10,5%) -0,06 [-0,22; 0,11] 0,48 [0,05; 4,82] 0,605
NVPO na SRPA 0 (0,0%) 1 (5,3%) -0,05 [-0,15; 0,05] 0,32 [0,01; 7,35] 0,487
Conversão para anestesia geral 0 (0,0%) 0 (0,0%) 0,00 [0,00; 0,00] 0,95 [0,02; 45,74] 1,000
Internação hospitalar 0 (0,0%) 0 (0,0%) 0,00 [0,00; 0,00] 0,95 [0,02; 45,74] 1,000
Hematoma de parede abdominal 0 (0,0%) 0 (0,0%) 0,00 [0,00; 0,00] 0,95 [0,02; 45,74] 1,000

12.8 Multiplicidade: FDR para desfechos secundários exploratórios

# -------------------------------------------------------------------------
# Controle exploratório da multiplicidade
# -------------------------------------------------------------------------
# O desfecho primário deve ser interpretado hierarquicamente e não precisa ser
# penalizado da mesma forma que os secundários. Esta tabela aplica FDR de
# Benjamini-Hochberg a um conjunto de desfechos secundários/exploratórios.
# -------------------------------------------------------------------------

p_secundarios <- tibble::tibble(
  Desfecho = c(
    "QoR-15 48h ajustado",
    "AUC EVN repouso 0–48h",
    "AUC EVN movimento 0–48h",
    "EVN média ponderada repouso",
    "EVN média ponderada movimento"
  ),
  p = c(
    as.numeric(summary(fit_qor48_rob)$coefficients["groupBMRA-CLO", "Pr(>|t|)"]),
    as.numeric(gsub(",", ".", auc_tab$`p HC3`[auc_tab$Desfecho == "AUC EVN repouso 0–48h"])),
    as.numeric(gsub(",", ".", auc_tab$`p HC3`[auc_tab$Desfecho == "AUC EVN movimento 0–48h"])),
    as.numeric(gsub(",", ".", auc_tab$`p HC3`[auc_tab$Desfecho == "EVN média ponderada repouso"])),
    as.numeric(gsub(",", ".", auc_tab$`p HC3`[auc_tab$Desfecho == "EVN média ponderada movimento"]))
  )
) %>%
  dplyr::mutate(
    `p FDR` = p.adjust(p, method = "BH"),
    p = ifelse(p < 0.001, "<0,001", nv(p, 3)),
    `p FDR` = ifelse(`p FDR` < 0.001, "<0,001", nv(`p FDR`, 3))
  )

knitr::kable(p_secundarios, align = c("l", "c", "c"),
             caption = "Desfechos secundários/exploratórios — ajuste FDR de Benjamini-Hochberg")
(#tab:robust_fdr)Desfechos secundários/exploratórios — ajuste FDR de Benjamini-Hochberg
Desfecho p p FDR
QoR-15 48h ajustado 0,020 0,100
AUC EVN repouso 0–48h 0,528 0,528
AUC EVN movimento 0–48h 0,301 0,502
EVN média ponderada repouso 0,528 0,528
EVN média ponderada movimento 0,301 0,502

12.9 Síntese automática das análises robustas

# -------------------------------------------------------------------------
# Síntese final gerada a partir dos objetos das análises robustas
# -------------------------------------------------------------------------

beta_fent <- extrair_beta_grupo(fit_fent_ajustado)
p_fent_perm <- perm_fent$`p por permutação`

beta_qor <- extrair_beta_grupo(fit_qor48_rob)
p_qor_perm <- perm_qor$`p por permutação`

cat("### Interpretação operacional\n\n")

12.9.1 Interpretação operacional

cat(sprintf(
  "- **Fentanil µg/kg:** o efeito ajustado do grupo foi de **%s µg/kg** para BMRA-CLO − BMRA. Valor negativo indica menor consumo no grupo clonidina. O IC 95%% bootstrap foi **[%s; %s] µg/kg** e o p por permutação foi **%s**.\n",
  nv(beta_fent, 3), nv(boot_fent$IC_low, 3), nv(boot_fent$IC_high, 3),
  ifelse(p_fent_perm < 0.001, "<0,001", nv(p_fent_perm, 3))
))
  • Fentanil µg/kg: o efeito ajustado do grupo foi de -0,589 µg/kg para BMRA-CLO − BMRA. Valor negativo indica menor consumo no grupo clonidina. O IC 95% bootstrap foi [-0,791; -0,368] µg/kg e o p por permutação foi <0,001.
cat(sprintf(
  "- **QoR-15 em 48h:** o efeito ajustado do grupo foi de **%s pontos** para BMRA-CLO − BMRA. O IC 95%% bootstrap foi **[%s; %s] pontos** e o p por permutação foi **%s**.\n",
  nv(beta_qor, 2), nv(boot_qor$IC_low, 2), nv(boot_qor$IC_high, 2),
  ifelse(p_qor_perm < 0.001, "<0,001", nv(p_qor_perm, 3))
))
  • QoR-15 em 48h: o efeito ajustado do grupo foi de 2,79 pontos para BMRA-CLO − BMRA. O IC 95% bootstrap foi [0,88; 5,52] pontos e o p por permutação foi 0,007.
cat("- **Dor:** a AUC da EVN resume a carga total de dor em 48h e reduz a dependência de comparações isoladas em SRPA, 24h e 48h.\n")
  • Dor: a AUC da EVN resume a carga total de dor em 48h e reduz a dependência de comparações isoladas em SRPA, 24h e 48h.
cat("- **Segurança:** eventos adversos devem ser reportados com risco absoluto, risco relativo e IC 95%; ausência de significância estatística não prova equivalência de segurança em amostra pequena.\n")
  • Segurança: eventos adversos devem ser reportados com risco absoluto, risco relativo e IC 95%; ausência de significância estatística não prova equivalência de segurança em amostra pequena.
cat("- **Conclusão estatística:** se os resultados de HC3, bootstrap, permutação e leave-one-out apontarem na mesma direção, a inferência sobre redução de opioide fica substancialmente mais robusta.\n")
  • Conclusão estatística: se os resultados de HC3, bootstrap, permutação e leave-one-out apontarem na mesma direção, a inferência sobre redução de opioide fica substancialmente mais robusta.