Reporte con gtsummary

r
estadistica
El estándar moderno para tablas estadísticas publicables. tbl_summary() para descriptivas por grupo, tbl_regression() para modelos, personalización de labels y estadísticos, y export a Word/PDF/HTML.

¿Por qué gtsummary?

Hacer tablas publicables a mano (Excel, copiar-pegar de summary(), formatear en Word) es donde más tiempo se pierde en un análisis. Sin reproducibilidad, sin consistencia, propenso a errores de transcripción.

gtsummary resuelve esto: una línea de código produce una tabla con formato editorial, descriptivas por grupo, regresiones, comparaciones, directamente publicable en revistas científicas.

Es el sucesor moderno de tableone y se ha convertido en el estándar de facto en epidemiología y ensayos clínicos.

install.packages("gtsummary")
library(gtsummary)
library(dplyr)

tbl_summary(): tabla descriptiva por grupo

El caso de uso más frecuente: una tabla “Tabla 1” comparando características entre grupos.

library(survival)   # para el dataset lung (cáncer de pulmón)

lung |>
  select(age, sex, ph.karno, wt.loss, status) |>
  mutate(
    sex = factor(sex, labels = c("Hombre", "Mujer")),
    status = factor(status, labels = c("Censurado", "Fallecido"))
  ) |>
  tbl_summary(by = status)

Genera una tabla con:

  • Una fila por variable del dataset.
  • Dos columnas (una por nivel de status).
  • Estadísticos automáticos según tipo:
    • Continuas: mediana (Q1, Q3).
    • Categóricas: n (%).

Casi todo lo que necesitas para un Material y Métodos en cinco líneas de código.

Personalizar estadísticas y labels

Por defecto tbl_summary da mediana (Q1, Q3) para continuas. Para cambiar a media (SD):

lung |>
  tbl_summary(
    by = status,
    statistic = list(
      all_continuous()  ~ "{mean} ({sd})",
      all_categorical() ~ "{n} ({p}%)"
    )
  )

La sintaxis {stat} es una mini-plantilla, admite {mean}, {sd}, {median}, {p25}, {p75}, {min}, {max}, {n}, {p}, {N} y combinaciones.

Para labels legibles:

lung |>
  tbl_summary(
    by = status,
    label = list(
      age      ~ "Edad (años)",
      ph.karno ~ "Karnofsky físico (0-100)",
      wt.loss  ~ "Pérdida de peso (kg)",
      sex      ~ "Sexo"
    )
  )

label acepta una lista mapping variable ~ "Label legible". Las variables no mencionadas mantienen su nombre original.

Añadir p-values y N total

Dos extensiones casi siempre necesarias:

lung |>
  tbl_summary(by = status) |>
  add_p() |>                            # añade p-value por fila (test automático)
  add_overall() |>                      # añade columna con el total
  add_n() |>                            # añade columna con n por variable
  bold_labels()                         # nombres de variables en negrita

add_p() elige el test apropiado automáticamente: chi-cuadrado o Fisher para categóricas, Wilcoxon para continuas (no asume normalidad). Si quieres forzar t-test:

add_p(test = list(all_continuous() ~ "t.test"))

tbl_regression(): tabla de modelo

Para reportar un modelo de regresión:

modelo <- coxph(Surv(time, status) ~ age + sex + ph.karno, data = lung)

modelo |>
  tbl_regression(exponentiate = TRUE)

Salida:

  • Una fila por coeficiente.
  • Columnas: HR (o OR, o coef bruto), IC 95 %, p-value.
  • exponentiate = TRUE aplica exp() automáticamente, necesario para logística (OR) y supervivencia (HR).

Para regresión lineal:

lm(mpg ~ wt + hp + cyl, data = mtcars) |>
  tbl_regression()

Por defecto da el coeficiente sin exponenciar (porque en lm el coeficiente es el efecto directo).

Combinar varios modelos: tbl_merge()

Cuando quieres mostrar un modelo univariado y uno multivariado lado a lado (patrón clásico en epidemiología):

# Univariado
uni <- tbl_uvregression(
  lung |> select(time, status, age, sex, ph.karno),
  method = coxph,
  y = Surv(time, status),
  exponentiate = TRUE
)

# Multivariado
multi <- coxph(Surv(time, status) ~ age + sex + ph.karno, data = lung) |>
  tbl_regression(exponentiate = TRUE)

# Combinar
tbl_merge(
  list(uni, multi),
  tab_spanner = c("**Univariado**", "**Multivariado**")
)

Una tabla con dos paneles. Es el formato estándar para introducir un análisis multivariado en un paper.

Exportar: Word, PDF, HTML

gtsummary se renderiza nativamente en Quarto/RMarkdown:

tabla |> as_gt()           # objeto gt (default en HTML)
tabla |> as_flex_table()   # para Word
tabla |> as_kable_extra()  # para PDF/LaTeX

Para guardar a archivo:

library(gt)

tabla |> as_gt() |> gtsave("tabla1.html")
tabla |> as_gt() |> gtsave("tabla1.docx")   # requiere flextable
tabla |> as_gt() |> gtsave("tabla1.png")    # imagen

En un documento Quarto, simplemente print(tabla) (o que sea la última línea del chunk) la renderiza correctamente según el formato de salida.

Patrón completo: descriptiva + univariado + multivariado

El patrón que cubre el 90 % de los análisis clínicos:

library(gtsummary)
library(survival)
library(dplyr)

datos <- lung |>
  mutate(
    sex    = factor(sex, labels = c("Hombre", "Mujer")),
    status = factor(status, labels = c("Censurado", "Fallecido"))
  )

# 1. Descriptiva por grupo
tabla_desc <- datos |>
  select(age, sex, ph.karno, wt.loss, status) |>
  tbl_summary(
    by = status,
    label = list(
      age      ~ "Edad (años)",
      ph.karno ~ "Karnofsky",
      wt.loss  ~ "Pérdida peso (kg)",
      sex      ~ "Sexo"
    )
  ) |>
  add_p() |>
  add_overall() |>
  bold_labels()

# 2. Regresión multivariada
modelo <- coxph(Surv(time, status == "Fallecido") ~ age + sex + ph.karno,
                data = datos)

tabla_modelo <- modelo |>
  tbl_regression(
    exponentiate = TRUE,
    label = list(
      age      ~ "Edad (años)",
      sex      ~ "Sexo",
      ph.karno ~ "Karnofsky"
    )
  )

Las dos tablas están listas para inclusión en un manuscrito.

Trampas habituales

  • Olvidar exponentiate = TRUE en logística o supervivencia. El coeficiente bruto es log-odds o log-HR, ininterpretable directamente. Casi siempre quieres exponenciar.
  • Confiar en el test automático de add_p() sin pensar. Por defecto usa Wilcoxon (no t-test) para continuas. Si reportas resultados como “comparación de medias” y la tabla muestra Wilcoxon, hay contradicción. Verifica.
  • Tablas con variables que no entendiste. tbl_summary no juzga: te muestra lo que le pasaste. Si una variable es categórica codificada como entero, te dará estadísticos numéricos en lugar de conteos. Convierte a factor antes.
  • Sobrecargar la tabla. Con 15 variables y 3 grupos, la Tabla 1 se vuelve ilegible. Selecciona las variables relevantes. Mete el resto en material suplementario.

En la siguiente entrega

Has cubierto todas las piezas: descriptiva, distribuciones, tests, regresión, diagnóstico, tamaños de efecto, reporte. El último tutorial las junta en un caso completo: un análisis clínico end-to-end con un dataset real, desde la pregunta inicial hasta la tabla final publicable. Es el cierre.