1 Instrumentos de medida

As medidas de referência foram obtidas utilizando dispositivos eletrônicos comerciais destinados ao monitoramento domiciliar de parâmetros fisiológicos, metabólicos e antropométricos. A pressão arterial sistólica (PAS), pressão arterial diastólica (PAD) e frequência cardíaca (FC) foram medidas por meio de um monitor automático de braço Omron HEM-7346T (Omron Healthcare Co., Kyoto, Japão), equipado com tecnologia Bluetooth para armazenamento e transferência dos dados.

A massa corporal total (MCT) e a composição corporal foram obtidas utilizando uma balança de bioimpedância Omron HBF-222T (Omron Healthcare Co., Kyoto, Japão). A temperatura corporal foi medida com um termômetro digital G-Tech TH-186 (Accumed-Glicomed, Duque de Caxias, RJ, Brasil). A saturação periférica de oxigênio (SpO₂) e a frequência cardíaca também foram avaliadas por um oxímetro de pulso de dedo Multilaser Saúde HC261 (Multilaser Industrial S.A., Extrema, MG, Brasil).

A glicose intersticial foi monitorada continuamente por meio do sistema FreeStyle Libre (Abbott Diabetes Care Inc., Alameda, CA, EUA), um sensor de monitorização contínua da glicose (CGM) aplicado na região posterior do braço, capaz de registrar automaticamente as concentrações de glicose ao longo do dia sem necessidade de punções digitais rotineiras.

As medidas obtidas pelos dispositivos de referência foram comparadas às fornecidas por dispositivos vestíveis da Samsung Electronics Co. (Suwon, Coreia do Sul), incluindo smartwatch Galaxy Watch e pulseira Galaxy Fit. As variáveis avaliadas compreenderam frequência cardíaca, saturação periférica de oxigênio, massa corporal total, percentual de gordura corporal e demais parâmetros fisiológicos disponibilizados pelos dispositivos.

Todos os equipamentos foram utilizados de acordo com as instruções dos fabricantes. As medições foram realizadas em ambiente domiciliar durante o período de acompanhamento longitudinal do participante. Os dispositivos Omron, G-Tech, Multilaser e FreeStyle Libre foram considerados métodos de referência para fins de comparação e avaliação de equivalência metrológica dos dispositivos vestíveis.

A equivalência entre os métodos foi investigada por meio da abordagem estrutural implementada no pacote eirasAgree, baseada na avaliação sequencial de viés estrutural, precisão estrutural e concordância estrutural.

2 Monitorização Ambulatorial da Pressão Arterial (MAPA)

A Monitorização Ambulatorial da Pressão Arterial (MAPA) consiste no registro automático e repetido da pressão arterial ao longo de 24 horas durante as atividades habituais do indivíduo. Diferentemente da medida isolada obtida em consultório, a MAPA permite avaliar a variabilidade circadiana da pressão arterial, identificar padrões de vigília e sono e reduzir a influência do efeito do avental branco.

Em geral, o equipamento realiza medições em intervalos regulares, tipicamente a cada 15 ou 20 minutos durante o período diurno e a cada 20 a 30 minutos durante o período noturno. A partir dessas medidas podem ser calculadas estatísticas descritivas, médias diurnas e noturnas, cargas pressóricas e indicadores derivados da pressão arterial.

A pressão arterial média (PAM) é uma medida frequentemente utilizada para representar a perfusão média dos tecidos ao longo do ciclo cardíaco. Em condições fisiológicas, pode ser aproximada por

\[ PAM \approx PAD + \frac{PAS-PAD}{3} \]

em que:

Neste estudo foram comparadas três medidas relacionadas à pressão arterial média:

A simples correlação entre essas medidas não é suficiente para afirmar equivalência. Duas técnicas podem apresentar elevada correlação e, ainda assim, diferirem sistematicamente em viés, precisão ou concordância individual. Por esse motivo, a comparação entre PAM, PAM13 e MAPA foi realizada utilizando os testes estruturais de acurácia, precisão e concordância implementados no pacote eirasagree, conforme metodologia proposta por Silveira, Vieira e Siqueira (2024).

A análise de equivalência foi conduzida por meio de três comparações pareadas:

  1. PAM versus MAPA;
  2. PAM13 versus MAPA;
  3. PAM13 versus PAM.

A equivalência completa somente pode ser assumida quando não há evidências de diferenças de acurácia, precisão e concordância entre os métodos avaliados.

3 Objetivo

Analisar as variáveis digitais:

Temperatura, SpO2_WTC, SpO2_ML, PI_ML, FC_ML, FC_WTC, FC_Gfit, FR_Gfit, Glicemia, MCT_ML, GorduraCorp_WTC.

A análise usa apenas a janela temporal em que há pelo menos uma variável digital observada.

As comparações de equivalência de métodos são feitas diretamente com eirasagree::AllStructuralTests(), sem reimplementar o método:

As demais variáveis digitais são analisadas por descrição, gráficos temporais e associação com FC, MCT e gordura corporal da balança.

4 Funções

load_pkgs <- function(pkgs) {
  for (p in pkgs) {
    if (!requireNamespace(p, quietly = TRUE)) {
      stop("Pacote ausente: ", p, ". Instale com install.packages('", p, "') ou conforme documentação do pacote.")
    }
    suppressPackageStartupMessages(library(p, character.only = TRUE))
  }
  invisible(TRUE)
}

safe_locale_ptbr <- function() {
  suppressWarnings(try(Sys.setlocale("LC_ALL", "pt_BR.UTF-8"), silent = TRUE))
  invisible(TRUE)
}

as_num <- function(x) {
  suppressWarnings(as.numeric(x))
}

stop_if_missing <- function(df, vars) {
  miss <- setdiff(vars, names(df))
  if (length(miss) > 0) stop("Colunas ausentes: ", paste(miss, collapse = ", "))
  invisible(TRUE)
}

date_range_str <- function(d) {
  d <- as.Date(d)
  d <- d[is.finite(d)]
  if (length(d) == 0) return(NA_character_)
  paste0("Início: ", format(min(d), "%Y-%m-%d"), "  Fim: ", format(max(d), "%Y-%m-%d"))
}

axis_month_year <- function(dates, by = "1 month") {
  dates <- as.Date(dates)
  dates <- dates[is.finite(dates)]
  if (length(dates) == 0) return(invisible(NULL))
  at <- seq(from = as.Date(cut(min(dates), "month")),
            to   = as.Date(cut(max(dates), "month")),
            by   = by)
  axis(1, at = at, labels = format(at, "%m/%y"), las = 2, cex.axis = 0.8)
  invisible(at)
}

plot_calendar_var <- function(df, date_col, y_col, main = NULL, ylab = NULL,
                              by = "1 month", hlines = NULL, show_smooth = TRUE) {
  stop_if_missing(df, c(date_col, y_col))
  d <- as.Date(df[[date_col]])
  y <- as_num(df[[y_col]])
  ok <- is.finite(d) & is.finite(y)
  d <- d[ok]
  y <- y[ok]
  if (length(y) < 1) {
    plot.new()
    title(main = paste0(y_col, ": sem dados"))
    return(invisible(NULL))
  }
  o <- order(d)
  d <- d[o]
  y <- y[o]
  if (is.null(main)) main <- paste0("Paciente: JOS | ", y_col)
  if (is.null(ylab)) ylab <- y_col
  yl <- range(y, na.rm = TRUE)
  if (diff(yl) == 0) yl <- yl + c(-1, 1)
  plot(d, y, type = "p", cex = 0.55, xaxt = "n", xlim = range(d), ylim = yl,
       main = main, xlab = "Mês/Ano", ylab = ylab, col = "black")
  lines(d, y, col = "gray")
  axis_month_year(d, by = by)
  if (!is.null(hlines)) {
    for (h in hlines) abline(h = h, lty = 2)
  }
  if (show_smooth && length(y) >= 10) {
    x <- seq_along(y)
    bw <- suppressWarnings(KernSmooth::dpill(x, y, gridsize = length(y)))
    if (!is.finite(bw) || bw <= 0) bw <- max(1, length(y) / 20)
    lp <- KernSmooth::locpoly(x, y, bandwidth = bw, gridsize = length(y))
    sm <- stats::approx(lp$x, lp$y, xout = x, rule = 2)$y
    lines(d, sm, lwd = 1.5)
  }
  invisible(data.frame(Data = d, y = y))
}


round_numeric <- function(df, digits = 4) {
  out <- as.data.frame(df)
  num <- vapply(out, is.numeric, logical(1))
  out[num] <- lapply(out[num], round, digits = digits)
  out
}

summary_numeric <- function(df, vars) {
  out <- data.frame(
    variavel = character(0),
    n = integer(0),
    media = numeric(0),
    dp = numeric(0),
    mediana = numeric(0),
    q1 = numeric(0),
    q3 = numeric(0),
    minimo = numeric(0),
    maximo = numeric(0)
  )
  for (v in vars) {
    y <- as_num(df[[v]])
    y <- y[is.finite(y)]
    if (length(y) == 0) {
      linha <- data.frame(variavel = v, n = 0, media = NA, dp = NA, mediana = NA,
                          q1 = NA, q3 = NA, minimo = NA, maximo = NA)
    } else {
      linha <- data.frame(
        variavel = v,
        n = length(y),
        media = mean(y),
        dp = stats::sd(y),
        mediana = stats::median(y),
        q1 = as.numeric(stats::quantile(y, 0.25, na.rm = TRUE)),
        q3 = as.numeric(stats::quantile(y, 0.75, na.rm = TRUE)),
        minimo = min(y),
        maximo = max(y)
      )
    }
    out <- rbind(out, linha)
  }
  out
}

cor_pair_table <- function(df, y_vars, x_vars) {
  out <- data.frame(
    y = character(0),
    x = character(0),
    n = integer(0),
    pearson = numeric(0),
    spearman = numeric(0),
    p_pearson = numeric(0),
    p_spearman = numeric(0)
  )
  for (yv in y_vars) {
    for (xv in x_vars) {
      y <- as_num(df[[yv]])
      x <- as_num(df[[xv]])
      ok <- is.finite(x) & is.finite(y)
      n <- sum(ok)
      if (n >= 4) {
        ct1 <- suppressWarnings(stats::cor.test(x[ok], y[ok], method = "pearson"))
        ct2 <- suppressWarnings(stats::cor.test(x[ok], y[ok], method = "spearman", exact = FALSE))
        linha <- data.frame(y = yv, x = xv, n = n,
                            pearson = unname(ct1$estimate),
                            spearman = unname(ct2$estimate),
                            p_pearson = ct1$p.value,
                            p_spearman = ct2$p.value)
      } else {
        linha <- data.frame(y = yv, x = xv, n = n,
                            pearson = NA, spearman = NA,
                            p_pearson = NA, p_spearman = NA)
      }
      out <- rbind(out, linha)
    }
  }
  out
}

scatter_ref <- function(df, y_col, x_col, main = NULL) {
  y <- as_num(df[[y_col]])
  x <- as_num(df[[x_col]])
  ok <- is.finite(x) & is.finite(y)
  if (sum(ok) < 3) {
    plot.new()
    title(main = paste0(y_col, " vs ", x_col, ": dados insuficientes"))
    return(invisible(NULL))
  }
  if (is.null(main)) main <- paste0(y_col, " vs ", x_col)
  plot(x[ok], y[ok], pch = 1, cex = 0.65, col = "black",
       xlab = x_col, ylab = y_col, main = main)
  abline(stats::lm(y[ok] ~ x[ok]), lty = 1)
  invisible(TRUE)
}

make_eiras_data <- function(df, reference_col, newmethod_col) {
  stop_if_missing(df, c(reference_col, newmethod_col))
  x <- as_num(df[[reference_col]])
  y <- as_num(df[[newmethod_col]])
  ok <- is.finite(x) & is.finite(y)
  out <- data.frame(Referencia = x[ok], NovoMetodo = y[ok])
  ## Nomes simples evitam erro de arquivos gerados pelo eirasagree
  ## com caracteres especiais, como %, parênteses e acentos.
  out
}

safe_file_label <- function(x) {
  x <- iconv(x, from = "", to = "ASCII//TRANSLIT")
  x <- gsub("[^A-Za-z0-9]+", "_", x)
  x <- gsub("^_+|_+$", "", x)
  x
}

run_eirasagree_pair <- function(df, reference_col, newmethod_col,
                                alpha = 0.05, out.format = "html") {
  dat <- make_eiras_data(df, reference_col, newmethod_col)

  cat("\n\nComparação estrutural: ", newmethod_col, " vs ", reference_col, "  \n", sep = "")
  cat("n pares completos: ", nrow(dat), "  \n", sep = "")

  if (nrow(dat) < 5) {
    cat("Dados insuficientes para comparação estrutural.\n")
    return(invisible(NULL))
  }

  subdir <- paste0(safe_file_label(newmethod_col), "_vs_", safe_file_label(reference_col))
  outdir <- file.path("eirasagree_outputs", subdir)
  dir.create(outdir, recursive = TRUE, showWarnings = FALSE)

  oldwd <- getwd()
  setwd(outdir)

  zz <- file("console_eirasagree.txt", open = "wt")
  sink(zz)
  sink(zz, type = "message")

  out <- NULL
  erro <- NULL

  tryCatch(
    {
      out <- eirasagree::AllStructuralTests(
        dat,
        reference.cols = 1,
        newmethod.cols = 2,
        alpha = alpha,
        out.format = out.format
      )
    },
    error = function(e) {
      erro <<- e
    }
  )

  sink(type = "message")
  sink()
  close(zz)
  setwd(oldwd)

  if (!is.null(erro)) {
    stop("Erro em eirasagree::AllStructuralTests para ",
         newmethod_col, " vs ", reference_col, ": ", conditionMessage(erro))
  }

  html_file <- file.path(outdir, "Referencia_and_NovoMetodo.html")
  console_file <- file.path(outdir, "console_eirasagree.txt")

  if (file.exists(html_file)) {
    cat("Relatório HTML: [abrir resultado](", html_file, ")  \n", sep = "")
  } else {
    cat("Relatório HTML não localizado. Ver console: [console_eirasagree.txt](", console_file, ")  \n", sep = "")
  }

  invisible(out)
}

5 Leitura e janela temporal digital

pkgs <- c("readxl", "dplyr", "knitr", "KernSmooth", "GGally", "eirasagree")
load_pkgs(pkgs)
safe_locale_ptbr()

df0 <- readxl::read_excel(params$xlsx_path, sheet = params$sheet_pressoes)

vars_digitais <- c(
  "Temperatura",
  "SpO2_WTC",
  "SpO2_ML",
  "PI_ML",
  "FC_ML",
  "FC_WTC",
  "FC_Gfit",
  "FR_Gfit",
  "Glicemia",
  "MCT_ML",
  "GorduraCorp_WTC"
)

vars_referencia <- c(
  "Pulso(bpm)",
  "Peso(kg)",
  "Gordura corporal(%)"
)

stop_if_missing(df0, c("Data da medição", vars_digitais, vars_referencia))

df <- as.data.frame(df0)
df[["Data da medição"]] <- as.POSIXct(df[["Data da medição"]], tz = "America/Sao_Paulo")

for (v in c(vars_digitais, vars_referencia)) {
  df[[v]] <- as_num(df[[v]])
}

linhas_digitais <- rowSums(!is.na(df[, vars_digitais, drop = FALSE])) > 0
df_digital <- df[linhas_digitais, , drop = FALSE]
df_digital <- df_digital[order(df_digital[["Data da medição"]]), , drop = FALSE]

cat("Janela digital | ", date_range_str(df_digital[["Data da medição"]]), "\n")
## Janela digital |  Início: 2023-10-22  Fim: 2024-02-01
cat("n linhas na janela digital:", nrow(df_digital), "\n")
## n linhas na janela digital: 98

6 Resumo descritivo

resumo <- summary_numeric(df_digital, c(vars_digitais, vars_referencia))
knitr::kable(round_numeric(resumo, 4), caption = "Resumo descritivo na janela temporal digital")
Resumo descritivo na janela temporal digital
variavel n media dp mediana q1 q3 minimo maximo
Temperatura 98 35.6143 0.2577 35.60 35.400 35.800 34.9 36.2
SpO2_WTC 86 93.7674 2.5607 94.00 92.000 95.750 90.0 100.0
SpO2_ML 90 96.4778 1.2382 96.00 96.000 98.000 94.0 99.0
PI_ML 90 1009.1678 6690.4728 6.35 4.600 7.900 1.3 45142.0
FC_ML 90 78.3111 5.9806 78.00 74.000 82.750 66.0 94.0
FC_WTC 89 83.0449 6.8853 82.00 79.000 87.000 70.0 101.0
FC_Gfit 85 79.8118 6.8112 80.00 75.000 85.000 64.0 99.0
FR_Gfit 86 16.1977 1.6579 16.00 15.000 17.000 13.0 20.0
Glicemia 28 99.5000 5.7252 99.50 94.750 102.750 89.0 113.0
MCT_ML 81 85.7370 0.6972 85.70 85.300 86.200 83.6 87.3
GorduraCorp_WTC 79 29.0114 0.8419 29.10 28.700 29.600 26.1 30.5
Pulso(bpm) 98 80.5612 7.4930 81.00 74.000 85.000 68.0 105.0
Peso(kg) 98 85.2429 0.7355 85.20 84.800 85.675 83.3 87.1
Gordura corporal(%) 98 28.8194 0.3366 28.90 28.625 29.075 28.0 29.6

7 Gráficos temporais na janela efetiva de cada variável

Cada gráfico usa apenas as datas em que a própria variável tem valor observado. Isso evita eixo temporal artificialmente longo.

for (v in vars_digitais) {
  plot_calendar_var(
    df = df_digital,
    date_col = "Data da medição",
    y_col = v,
    main = paste0("Paciente: JOS | ", v),
    ylab = v,
    by = "1 month"
  )
}

8 Correlações com FC, MCT e gordura corporal da balança

cor_tab <- cor_pair_table(
  df = df_digital,
  y_vars = vars_digitais,
  x_vars = vars_referencia
)
knitr::kable(round_numeric(cor_tab, 4), caption = "Correlação das variáveis digitais com FC, MCT e gordura corporal da balança")
Correlação das variáveis digitais com FC, MCT e gordura corporal da balança
y x n pearson spearman p_pearson p_spearman
Temperatura Pulso(bpm) 98 0.3317 0.2898 0.0008 0.0038
Temperatura Peso(kg) 98 -0.2366 -0.2288 0.0190 0.0234
Temperatura Gordura corporal(%) 98 -0.2017 -0.2035 0.0464 0.0445
SpO2_WTC Pulso(bpm) 86 0.0192 0.0257 0.8609 0.8140
SpO2_WTC Peso(kg) 86 0.1835 0.2357 0.0908 0.0289
SpO2_WTC Gordura corporal(%) 86 0.0694 0.1532 0.5255 0.1591
SpO2_ML Pulso(bpm) 90 -0.3842 -0.3740 0.0002 0.0003
SpO2_ML Peso(kg) 90 0.2809 0.2762 0.0073 0.0084
SpO2_ML Gordura corporal(%) 90 0.2688 0.2774 0.0104 0.0081
PI_ML Pulso(bpm) 90 -0.0564 0.3502 0.5975 0.0007
PI_ML Peso(kg) 90 0.1827 -0.0084 0.0847 0.9377
PI_ML Gordura corporal(%) 90 0.1558 -0.0168 0.1424 0.8753
FC_ML Pulso(bpm) 90 0.7416 0.6960 0.0000 0.0000
FC_ML Peso(kg) 90 -0.2202 -0.1862 0.0371 0.0789
FC_ML Gordura corporal(%) 90 -0.2006 -0.1862 0.0580 0.0789
FC_WTC Pulso(bpm) 89 0.5301 0.5389 0.0000 0.0000
FC_WTC Peso(kg) 89 -0.0315 -0.0782 0.7696 0.4663
FC_WTC Gordura corporal(%) 89 -0.1016 -0.1226 0.3434 0.2525
FC_Gfit Pulso(bpm) 85 0.6742 0.6079 0.0000 0.0000
FC_Gfit Peso(kg) 85 -0.2624 -0.2525 0.0152 0.0198
FC_Gfit Gordura corporal(%) 85 -0.2621 -0.2581 0.0154 0.0171
FR_Gfit Pulso(bpm) 86 0.0891 0.0540 0.4144 0.6217
FR_Gfit Peso(kg) 86 -0.4480 -0.4280 0.0000 0.0000
FR_Gfit Gordura corporal(%) 86 -0.4230 -0.4448 0.0000 0.0000
Glicemia Pulso(bpm) 28 -0.0121 -0.0629 0.9514 0.7506
Glicemia Peso(kg) 28 0.1015 0.0967 0.6072 0.6245
Glicemia Gordura corporal(%) 28 0.1128 0.1147 0.5675 0.5610
MCT_ML Pulso(bpm) 81 -0.1836 -0.1485 0.1009 0.1858
MCT_ML Peso(kg) 81 0.8744 0.8827 0.0000 0.0000
MCT_ML Gordura corporal(%) 81 0.7882 0.7994 0.0000 0.0000
GorduraCorp_WTC Pulso(bpm) 79 -0.2953 -0.3261 0.0082 0.0034
GorduraCorp_WTC Peso(kg) 79 -0.0516 -0.0428 0.6514 0.7080
GorduraCorp_WTC Gordura corporal(%) 79 -0.0408 -0.0005 0.7214 0.9964

9 Matrizes de dispersão

vars_pair <- c(vars_digitais, vars_referencia)
vars_pair <- vars_pair[vars_pair %in% names(df_digital)]

ok_n <- sapply(vars_pair, function(v) sum(is.finite(as_num(df_digital[[v]]))))
vars_pair <- vars_pair[ok_n >= 3]

if (length(vars_pair) >= 2) {
  print(GGally::ggpairs(
    df_digital[, vars_pair, drop = FALSE],
    title = "Variáveis digitais e referências da balança",
    diag = list(continuous = "densityDiag")
  ))
}

10 Dispersões contra referências

for (v in vars_digitais) {
  scatter_ref(df_digital, y_col = v, x_col = "Pulso(bpm)")
  scatter_ref(df_digital, y_col = v, x_col = "Peso(kg)")
  scatter_ref(df_digital, y_col = v, x_col = "Gordura corporal(%)")
}

11 Comparação estrutural de métodos com eirasagree

Método aplicado diretamente por eirasagree::AllStructuralTests().

Apenas pares que medem o mesmo construto são submetidos à comparação estrutural:

11.1 FC_ML versus Pulso(bpm)

Comparação estrutural: FC_ML vs Pulso(bpm)
n pares completos: 90
Relatório HTML não localizado. Ver console: console_eirasagree.txt

11.2 FC_WTC versus Pulso(bpm)

Comparação estrutural: FC_WTC vs Pulso(bpm)
n pares completos: 89
Relatório HTML não localizado. Ver console: console_eirasagree.txt

11.3 FC_Gfit versus Pulso(bpm)

Comparação estrutural: FC_Gfit vs Pulso(bpm)
n pares completos: 85
Relatório HTML não localizado. Ver console: console_eirasagree.txt

11.4 SpO2_ML versus SpO2_WTC

Comparação estrutural: SpO2_ML vs SpO2_WTC
n pares completos: 86
Relatório HTML não localizado. Ver console: console_eirasagree.txt

11.5 MCT_ML versus Peso(kg)

Comparação estrutural: MCT_ML vs Peso(kg)
n pares completos: 81
Relatório HTML não localizado. Ver console: console_eirasagree.txt

11.6 GorduraCorp_WTC versus Gordura corporal(%)

Comparação estrutural: GorduraCorp_WTC vs Gordura corporal(%)
n pares completos: 79
Relatório HTML não localizado. Ver console: console_eirasagree.txt

12 MAPA: leitura, descrição e equivalência entre PAM, PAM13 e MAPA

Nesta seção, a análise fica restrita à janela temporal da própria planilha MAPA. A comparação estrutural é feita com eirasagree::AllStructuralTests(), sem uso de Bland-Altman clássico como critério decisório.

df_mapa0 <- readxl::read_excel(params$xlsx_path, sheet = params$sheet_mapa)
stop_if_missing(df_mapa0, c("HORA", "PAS", "PAD", "PAM", "MAPA", "PAM13", "FC"))

df_mapa <- as.data.frame(df_mapa0)
df_mapa[["HORA"]] <- as.POSIXct(df_mapa[["HORA"]], tz = "America/Sao_Paulo")

for (v in c("PAS", "PAD", "PAM", "MAPA", "PAM13", "FC")) {
  df_mapa[[v]] <- as_num(df_mapa[[v]])
}

df_mapa <- df_mapa[is.finite(df_mapa[["HORA"]]), , drop = FALSE]
df_mapa <- df_mapa[order(df_mapa[["HORA"]]), , drop = FALSE]

df_mapa <- df_mapa |>
  dplyr::group_by(HORA) |>
  dplyr::summarise(
    PAS = mean(PAS, na.rm = TRUE),
    PAD = mean(PAD, na.rm = TRUE),
    PAM = mean(PAM, na.rm = TRUE),
    MAPA = mean(MAPA, na.rm = TRUE),
    PAM13 = mean(PAM13, na.rm = TRUE),
    FC = mean(FC, na.rm = TRUE),
    .groups = "drop"
  )

cat("Janela MAPA | ", date_range_str(df_mapa[["HORA"]]), "\n")
## Janela MAPA |  Início: 2023-01-20  Fim: 2023-01-21
cat("n tempos observados:", nrow(df_mapa), "\n")
## n tempos observados: 77

12.1 Resumo descritivo: MAPA

resumo_mapa <- summary_numeric(df_mapa, c("PAS", "PAD", "PAM", "MAPA", "PAM13", "FC"))
knitr::kable(round_numeric(resumo_mapa, 4), caption = "Resumo descritivo da planilha MAPA")
Resumo descritivo da planilha MAPA
variavel n media dp mediana q1 q3 minimo maximo
PAS 77 131.3377 11.6930 130.0000 125.0000 140.0000 98.0000 166.0000
PAD 77 96.2468 10.3035 96.0000 89.0000 101.0000 75.0000 125.0000
PAM 77 109.4652 10.2288 109.3098 102.0984 115.3171 88.7916 137.9888
MAPA 77 112.3377 10.0218 112.0000 105.0000 118.0000 89.0000 141.0000
PAM13 77 107.9437 9.9504 107.6667 101.3333 113.3333 87.3333 135.3333
FC 77 81.7662 14.2172 80.0000 71.0000 89.0000 62.0000 133.0000

12.2 Séries temporais: PAM, MAPA e PAM13

plot_calendar_var(df_mapa, "HORA", "PAM", main = "MAPA | PAM", ylab = "PAM", by = "1 day", hlines = c(70, 90, 100))

plot_calendar_var(df_mapa, "HORA", "MAPA", main = "MAPA | MAPA", ylab = "MAPA", by = "1 day", hlines = c(70, 90, 100))

plot_calendar_var(df_mapa, "HORA", "PAM13", main = "MAPA | PAM13", ylab = "PAM13", by = "1 day", hlines = c(70, 90, 100))

12.3 Comparação gráfica simples: PAM, MAPA e PAM13

o <- order(df_mapa[["HORA"]])
x <- seq_along(o)

plot(x, df_mapa[["PAM"]][o], type = "p", cex = 0.55, pch = 1,
     main = "MAPA | Comparação PAM, MAPA e PAM13",
     xlab = "Tempo ordenado", ylab = "mmHg", col = "black")
lines(x, df_mapa[["PAM"]][o], col = "gray")

points(x, df_mapa[["MAPA"]][o], pch = 2, cex = 0.55, col = "black")
lines(x, df_mapa[["MAPA"]][o], col = "gray")

points(x, df_mapa[["PAM13"]][o], pch = 3, cex = 0.55, col = "black")
lines(x, df_mapa[["PAM13"]][o], col = "gray")

abline(h = c(70, 90, 100), lty = 2)
legend("topright", legend = c("PAM", "MAPA", "PAM13"), pch = c(1, 2, 3), bty = "n")

12.4 Correlações entre PAM, MAPA e PAM13

X_mapa <- df_mapa[, c("PAM", "MAPA", "PAM13"), drop = FALSE]
knitr::kable(round(stats::cor(X_mapa, use = "pairwise.complete.obs"), 4),
             caption = "Correlação entre PAM, MAPA e PAM13")
Correlação entre PAM, MAPA e PAM13
PAM MAPA PAM13
PAM 1.0000 0.9912 0.9942
MAPA 0.9912 1.0000 0.9931
PAM13 0.9942 0.9931 1.0000

12.5 Equivalência estrutural: PAM versus MAPA

Comparação estrutural: PAM vs MAPA
n pares completos: 77
Relatório HTML não localizado. Ver console: console_eirasagree.txt

12.6 Equivalência estrutural: PAM13 versus MAPA

Comparação estrutural: PAM13 vs MAPA
n pares completos: 77
Relatório HTML não localizado. Ver console: console_eirasagree.txt

12.7 Equivalência estrutural: PAM13 versus PAM

Comparação estrutural: PAM13 vs PAM
n pares completos: 77
Relatório HTML não localizado. Ver console: console_eirasagree.txt

13 Observações metodológicas

O procedimento clássico de Bland-Altman não foi usado como critério decisório. A equivalência entre métodos foi avaliada pelo procedimento estrutural do pacote eirasagree, com decomposição em acurácia, precisão e concordância com a bissetriz.

Para variáveis que não medem o mesmo construto da balança, como Temperatura, PI_ML, FR_Gfit e Glicemia, não há comparação de método contra FC, MCT ou gordura corporal. SpO2_WTC e SpO2_ML medem o mesmo construto e, por isso, foram comparadas por eirasagree::AllStructuralTests(). Na planilha MAPA, as três estimativas de pressão arterial média (PAM, PAM13 e MAPA) foram comparadas par a par: PAM versus MAPA, PAM13 versus MAPA e PAM13 versus PAM.

14 Resumo das análises de equivalência

A equivalência entre os dispositivos vestíveis e os respectivos métodos de referência foi avaliada utilizando a abordagem estrutural implementada no pacote eirasAgree. A análise foi conduzida em três níveis complementares: viés estrutural (acurácia), precisão estrutural e concordância estrutural.

A frequência cardíaca medida pelo Galaxy Fit apresentou ausência de viés estrutural, manutenção da precisão estrutural e concordância estrutural compatível com a reta identidade. Esse foi o único dispositivo que satisfez simultaneamente todos os critérios de equivalência.

A frequência cardíaca medida pelo oxímetro Multilaser apresentou viés estrutural negativo e perda de precisão estrutural em relação ao monitor Omron, embora a hipótese de concordância estrutural não tenha sido rejeitada.

A frequência cardíaca medida pelo Galaxy Watch apresentou viés estrutural positivo em relação ao monitor Omron. Apesar da manutenção da precisão estrutural e da concordância estrutural, a presença de viés impediu a demonstração de equivalência.

A estimativa de gordura corporal fornecida pelo Galaxy Watch apresentou viés estrutural positivo e perda de precisão estrutural em relação à balança Omron HBF-222T.

A massa corporal total medida pela balança Multilaser apresentou viés estrutural positivo em relação à balança Omron HBF-222T, embora a precisão estrutural tenha sido preservada.

A saturação periférica de oxigênio medida pelo Galaxy Watch apresentou viés estrutural positivo e perda de precisão estrutural em relação ao oxímetro Multilaser HC261.

15 Conclusões dos testes

Os resultados demonstraram que a frequência cardíaca obtida pelo Galaxy Fit foi a única variável que apresentou evidência simultânea de ausência de viés estrutural, manutenção da precisão estrutural e concordância estrutural com o método de referência.

Os demais dispositivos apresentaram evidência de viés estrutural, perda de precisão estrutural ou ambos. Embora a hipótese de concordância estrutural não tenha sido rejeitada em nenhuma das comparações, a presença de viés e/ou perda de precisão impede a caracterização de equivalência metrológica.

Esses resultados evidenciam que a concordância estrutural, isoladamente, não é suficiente para demonstrar equivalência entre métodos de medida. A equivalência requer simultaneamente ausência de viés estrutural, manutenção da precisão estrutural e compatibilidade com a reta identidade.

16 Tabela-resumo dos resultados

Variável Método novo Método de referência Diferença média (Novo − Referência) IC95% do viés Viés estrutural Precisão estrutural Concordância estrutural
Frequência cardíaca (bpm) Galaxy Fit Omron HEM-7346T -0,46 -1,77 a 0,77 Não rejeitado Não rejeitada Não rejeitada
Frequência cardíaca (bpm) Oxímetro Multilaser HC261 Omron HEM-7346T -2,00 -2,92 a -1,08 Rejeitado Rejeitada Não rejeitada
Frequência cardíaca (bpm) Galaxy Watch Omron HEM-7346T +2,75 1,36 a 4,11 Rejeitado Não rejeitada Não rejeitada
Gordura corporal (%) Galaxy Watch Omron HBF-222T +0,26 0,03 a 0,46 Rejeitado Rejeitada Não rejeitada
Massa corporal total (kg) Balança Multilaser Omron HBF-222T +0,60 0,52 a 0,68 Rejeitado Não rejeitada Não rejeitada
Saturação periférica de oxigênio (%) Galaxy Watch Oxímetro Multilaser HC261 +2,74 2,43 a 3,05 Rejeitado Rejeitada Não rejeitada

17 Síntese dos resultados

Dos seis testes realizados, apenas um apresentou ausência de viés estrutural. Três comparações apresentaram simultaneamente viés estrutural e perda de precisão estrutural. Em todas as comparações a hipótese de concordância estrutural foi mantida. Entretanto, somente a frequência cardíaca medida pelo Galaxy Fit apresentou evidência compatível com equivalência metrológica completa em relação ao método de referência.

18 Interpretação da análise de equivalência: PAM, PAM13 e MAPA

A análise foi conduzida utilizando os testes estruturais do pacote eirasagree, que avaliam três propriedades hierárquicas:

  1. Acurácia (ausência de viés médio);
  2. Precisão (ausência de erro proporcional ou heterocedasticidade);
  3. Concordância (equivalência global entre métodos).

Uma correlação elevada não implica equivalência. Dois métodos podem apresentar correlação próxima de 1 e ainda assim apresentar viés sistemático ou diferenças de precisão.

18.1 PAM versus MAPA

A correlação foi extremamente elevada (\(r=0,991\)), indicando forte associação linear entre as medidas. Entretanto, observou-se viés médio de aproximadamente −2,87 mmHg, com IC95% entre −3,22 e −2,55 mmHg. O teste de acurácia rejeitou a hipótese de ausência de viés.

O teste de precisão não rejeitou a hipótese de linha horizontal, indicando que a diferença entre os métodos permanece aproximadamente constante ao longo da faixa de pressão observada.

O teste de concordância pela regressão de Deming e a região elíptica não rejeitaram a hipótese de equivalência estrutural após a correção do viés.

Interpretação: PAM e MAPA diferem por um viés constante de aproximadamente −2,9 mmHg, mas apresentam boa precisão e concordância estrutural.

18.2 PAM13 versus MAPA

A correlação também foi extremamente elevada (\(r=0,993\)). Contudo, o viés médio foi maior, aproximadamente −4,39 mmHg, com IC95% entre −4,70 e −4,11 mmHg. O teste de acurácia rejeitou a hipótese de ausência de viés.

O teste de precisão não foi rejeitado, indicando ausência de erro proporcional relevante ao longo da faixa de medidas.

A regressão de Deming e a análise elíptica indicaram concordância estrutural após a correção do viés médio.

Interpretação: PAM13 produz valores sistematicamente inferiores aos do MAPA em aproximadamente 4,4 mmHg, porém mantém boa precisão e concordância estrutural.

18.3 PAM13 versus PAM

A correlação permaneceu extremamente elevada (\(r=0,994\)). O viés médio foi de aproximadamente −1,52 mmHg, com IC95% entre −1,80 e −1,26 mmHg. O teste de acurácia rejeitou a hipótese de ausência de viés.

Diferentemente das comparações anteriores, o teste de precisão foi rejeitado. Além disso, o teste de Shukla rejeitou \(\lambda=1\), indicando diferenças entre as variâncias dos erros dos métodos.

Apesar disso, a regressão de Deming e a região elíptica não rejeitaram a concordância estrutural.

Interpretação: PAM13 e PAM apresentam pequeno viés médio, mas também diferenças de precisão, sugerindo que não devem ser considerados intercambiáveis sem correção.

18.4 Tabela-resumo

Comparação Correlação Viés médio (mmHg) Acurácia Precisão Concordância
PAM × MAPA 0,991 −2,87 Reprovada Aprovada Aprovada
PAM13 × MAPA 0,993 −4,39 Reprovada Aprovada Aprovada
PAM13 × PAM 0,994 −1,52 Reprovada Reprovada Aprovada

18.5 Conclusão geral

Os três métodos apresentam correlações extremamente elevadas (\(r>0,99\)), porém todos exibem viés sistemático estatisticamente significativo. A menor discrepância média foi observada entre PAM13 e PAM (−1,52 mmHg), enquanto a maior ocorreu entre PAM13 e MAPA (−4,39 mmHg). PAM e MAPA apresentaram a melhor combinação de propriedades métricas, com viés moderado, precisão preservada e concordância estrutural. Já PAM13 e PAM mostraram diferenças tanto de acurácia quanto de precisão, indicando que não são estritamente equivalentes apesar da elevada correlação.