Reporte con gtsummary
¿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 negritaadd_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 = TRUEaplicaexp()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/LaTeXPara 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") # imagenEn 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 = TRUEen 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_summaryno 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.