Generación con R de tablas y gráficos listos para publicar

VII Seminario Análisis de datos avanzados en Ciencias de la Salud

Autor/a

Emilio L. Cano (emilio.lopez@urjc.es)

Fecha de publicación

22 de octubre de 2024

Introducción

Este documento es el material de la sesión “Generación con R de tablas y gráficos listos para publicar” del VII Seminario Análisis de datos avanzados en Ciencias de la Salud celebrado en la Facultad de Ciencias de la Salud de la Universidad Rey Juan Carlos en el Campus de Alcorcón en octubre de 2024.

En los proyectos de Ciencia de Datos, está bastante aceptado el flujo de trabajo que se describe en (Wickham y Grolemund 2016) y que se muestra en la figura.

A menudo en las fases previas a “Comunicar” vamos haciendo nuestros análisis de datos y generando resultados intermedios en forma de tablas y gráficos que nos da igual estén bonitos o más feos. Pero a la hora de preparar los resultados finales para esa fase de comunicación (que puede ser un artículo científico, una presentación de un congreso, o simplemente un documento interno) sí hace falta presentar los resultados en tablas y gráficos que sean efectivos para comunicar la historia que nos están contando los datos.

Las salidas “estándar” de R a veces no son suficientes para esta preparación, y muchos analistas optan por preparar ese formato final con otros programas (Word, Excel, etc.) para tener el resultado deseado. Pero con algunos paquetes especializados podemos tener una presentación final impactante de nuestros análisis, y eso es lo que vamos a aprender en esta sesión del seminario.

Preparación del entorno

Debes tener R y RStudio instalado en tu ordenador, preferiblemente las últimas versiones. Estas son las que se han utilizado para generar este material:

R.Version()$version.string
[1] "R version 4.4.1 (2024-06-14)"
# rstudioapi::versionInfo()$version -> 2022.10.0.9

Alternativamente, puedes utilizar rstudio.cloud, creando una cuenta gratuita. Funciona en el navegador sin tener que preocuparse de dependencias del sistema operativo.

Además, vamos a utilizar los siguientes paquetes. Posiblemente algunos ya los tengas instalados. Puedes hacerlo al principio o instalar cada uno antes de usarlo la primera vez con la función install.packages().

palmerpenguins
summarytools
flextable
kableExtra
gt
gtsummary
ggstatsplot
quarto
knitr
markdown
dplyr
modelsummary
report
effectsize
reactable

El paquete dplyr lo usaremos para manipular los datos.

Nota

Para seguir el material, tienes varias opciones:

  1. Crear un proyecto vacío de RStudio. Crea un script y pega el código para ir probándolo, y un documento quarto como se explica más adelante para ver cómo queda en el formato de salida.

  2. Si utilizas git y GitHub, crea el proyecto a partir de este material 1. Abre el script index.R que ya contiene el código y ve ejecutándolo directamente.

  3. Descarga el material2 con esta expresión en la consola. El proyecto se abre automáticamente:

usethis::use_course("emilopezcano/seminario_fcs_urjc_2024")

Datos de ejemplo

En esta sesión utilizaremos para los ejemplos el conjunto de datos penguins, que se encuentra en el paquete {palmerpenguins} (Horst, Hill, y Gorman (2022)). Vamos a revisar la descripción de los datos en la propia web del paquete:

https://allisonhorst.github.io/palmerpenguins/

Practica

Explora la documentación. Intenta entender qué significan las variables y qué tipos de datos nos encontramos.

Ahora vamos a explorar los datos en el espacio de trabajo. Tenemos que cargar el paquete, y a partir de ahí lo podemos visualizar en la consola:

library(palmerpenguins)
penguins
# A tibble: 344 × 8
   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
 1 Adelie  Torgersen           39.1          18.7               181        3750
 2 Adelie  Torgersen           39.5          17.4               186        3800
 3 Adelie  Torgersen           40.3          18                 195        3250
 4 Adelie  Torgersen           NA            NA                  NA          NA
 5 Adelie  Torgersen           36.7          19.3               193        3450
 6 Adelie  Torgersen           39.3          20.6               190        3650
 7 Adelie  Torgersen           38.9          17.8               181        3625
 8 Adelie  Torgersen           39.2          19.6               195        4675
 9 Adelie  Torgersen           34.1          18.1               193        3475
10 Adelie  Torgersen           42            20.2               190        4250
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>

Para tenerlos disponibles en el espacio de trabajo, vamos a crear un objeto explícitamente:

pingu <- penguins
Practica

Abre la tabla de datos en el explorador de datos. Comprueba los tipos de datos y piensa qué tipo de tablas podrías hacer.

No obstante, como la sesión va a ser muy práctica, te recomiendo que intentes reproducir los ejemplos con datos de tu propia investigación o interés. Deben estar en formato “rectangular”, es decir, una columna para cada variable y una fila para cada observación, en un data.frame de R. A partir de ahí, solo tendrás que cambiar pingu por el nombre de tu data.frame y los nombres de las variables pingüineras por los tuyos.

Formateo de tablas listas para publicar

Cuando “mandamos” una tabla de datos a la consola, tenemos un formato de texto plano que generalmente no nos sirve para insertar en una publicación decente. Entonces tenemos que darle un formato adecuado y atractivo.

Pero antes de nada, vamos a ver qué tipos de tablas podemos querer generar.

  • Tablas de datos. Si tenemos pocos datos, es posible que queramos mostrarlos todos en una tabla.

  • Tablas resumen. Si tenemos muchos datos, podemos querer mostrar un resumen de los estadísticos más relevantes.

  • Tablas de frecuencias. Son tablas de recuentos o proporciones (o porcentajes si multiplicamos por 100), que pueden tener totales de filas o columnas. Generalmente para datos de tipo cualitativo, pero también son adecuadas en datos cuantitativos discretos si hay pocos posibles valores, y en datos continuos agrupando en intervalos.

  • Tablas de resultados de modelos. Aquí puede ser muy variado: coeficientes de un modelo de regresión, de un ANOVA, de los efectos, etc.

  • Tablas estandarizadas de estudios específicos, por ejemplo ensayos clínicos.

Piensa

¿Se te ocurren más? ¿Usas tú alguna tabla más específica?

Otra cuestión importante es en qué formato de salida queremos nuestra tabla. R va a generar las tablas en formato de texto plano con la estructura necesaria para que el formato de salida lo interprete. Hay muchos formatos, pero los más importantes son los siguientes:

  • \(\LaTeX\). Se genera código latex para usar en documentos de este tipo.

  • HTML. Se genera código para archivos HTML que se abren en el navegador.

  • Word. Estas tablas se generan directamente dentro de un documento Word.

  • markdown. Es un formato que interpretan los sistemas modernos como Rmarkdown y Quarto.

En RStudio se utiliza el programa pandoc para convertir entre formatos (no solo tablas, cualquier documento). Y el flujo de trabajo es crear el archivo (o tabla) en markdown, y de ahí convertir al formato final. Pero esto va a ser transparente para nosotros.

Paquetes disponibles

Hay varios paquetes que se pueden utilizar para dar formato adecuado a las tablas a partir de un data.frame y poder utilizarlo en las publicaciones. Entre ellos:

  • {xtable} (Dahl et al. (2019)): Es un clásico, y antes de aparecer los más modernos era el preferido par LaTeX y html (no lo usaremos aquí).

  • {knitr} (Xie (2024)): Incorpora la función kable() para hacer tablas sencillas.

  • {kableExtra} (Zhu (2024)): Paquete que amplía la funcionalidad de la función kable() para crear tablas bien complejas. Muy centrado en formato de salida HTML.

  • {flextable} (Gohel y Skintzos (2024)): Tiene su propia gramática y se pueden hacer auténticas virguerías. La principal ventaja es que las tablas generadas en Word quedan muy bien.

  • {gt} (Iannone et al. (2024)): Creado por el equipo de RStudio que sigue la filosofía tidy. Mucho potencial, gramática parecida a {flextable}, pero no se termina de integrar bien con Word. Para HTML estático, la mejor opción.

Hay algunos paquetes con los que se pueden crear tablas interactivas, que permiten ordenar, paginar, filtrar, etc. En este caso solo para formato de salida HTML. {reactable} es una de las opciones, veremos un ejemplo.

Por otra parte, hay paquetes que preparan los datos de la salida estándar en forma de tablas de datos, o directamente en tablas formateadas.

  • {gtsummary} (Sjoberg et al. (2024)): Amplía la funcionalidad de {gt} y produce tablas estandarizadas que se usan en muchos tipos de estudios. Por ejemplo, la habitual “Tabla 1” que describe los datos de un ensayo clínico.

  • {summarytools} (Comtois (2022)): Tiene funciones para obtener los resúmenes de datos más habituales.

  • {modelsummary} (Arel-Bundock (2024)): Produce tablas de resumen de datos, pero también de los resultados de modelos ajustados sobre los datos.

  • {report} (Makowski et al. (2024)): Este paquete produce textos completos (en inglés) interpretando los resultados de un modelo o análisis. Ideal para acompañar a una tabla.

  • {effectsize} (Ben-Shachar et al. (2024)): Produce tablas con tamaño de efectos y multitud de métricas en formato de tabla.

  • {broom} (Robinson, Hayes, y Couch (2024)): La función tidy() guarda en un data.frame los coeficientes de un modelo, y la función glance() las principales métricas.

Por último antes de empezar con los ejemplos, hay que destacar que las funciones utilizadas producirán o bien texto en la consola, o bien HTML en el panel Viewer. Si insertamos el código en informes reproducibles Rmarkdown o quarto, entonces tendremos la tabla integrada con el resto de narrativa y gráficos de la publicación.

Informes con quarto

Para tener la tabla formateada en un formato de salida, tienes que crear un documento R Markdown o quarto, y así irás viendo el resultado en un formato de salida a medida que avancemos. Es recomendable trabajar con el formato HTML, que es más rápido, y al final ver cómo queda también en Word o PDF.

Por supuesto también se pueden generar las tablas desde un script, y después copiar desde el visualizador para pegar en otro programa.

Practica
  1. Crear documento

  2. Insertar chunks (pegar el código de este documento)

  3. Insertar código

  4. Insertar referencias cruzadas

Uso de los paquetes y ejemplos

Tablas de datos con knitr::kable()

library(dplyr)
library(knitr)
pingu |> 
  slice_head(n = 10) |> 
  kable()
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
Adelie Torgersen 39.1 18.7 181 3750 male 2007
Adelie Torgersen 39.5 17.4 186 3800 female 2007
Adelie Torgersen 40.3 18.0 195 3250 female 2007
Adelie Torgersen NA NA NA NA NA 2007
Adelie Torgersen 36.7 19.3 193 3450 female 2007
Adelie Torgersen 39.3 20.6 190 3650 male 2007
Adelie Torgersen 38.9 17.8 181 3625 female 2007
Adelie Torgersen 39.2 19.6 195 4675 male 2007
Adelie Torgersen 34.1 18.1 193 3475 NA 2007
Adelie Torgersen 42.0 20.2 190 4250 NA 2007
Practica

Prueba a cambiar opciones con los argumentos de la función. Mira la ayuda de la función poniendo en la consola ?knitr.

Resúmenes de datos con {summarytools}

library(summarytools)
pingu |> 
  descr(bill_length_mm)

Descriptive Statistics
pingu$bill_length_mm
N: 344

bill_length_mm
Mean 43.92
Std.Dev 5.46
Min 32.10
Q1 39.20
Median 44.45
Q3 48.50
Max 59.60
MAD 7.04
IQR 9.27
CV 0.12
Skewness 0.05
SE.Skewness 0.13
Kurtosis -0.89
N.Valid 342.00
Pct.Valid 99.42
pingu |> 
  dfSummary()

Data Frame Summary
pingu
Dimensions: 344 x 8
Duplicates: 0

No Variable Stats / Values Freqs (% of Valid) Graph Valid Missing
1 species [factor] 1. Adelie 2. Chinstrap 3. Gentoo 152 (44.2%) 68 (19.8%) 124 (36.0%) IIIIIIII III IIIIIII 344 (100.0%) 0 (0.0%)
2 island [factor] 1. Biscoe 2. Dream 3. Torgersen 168 (48.8%) 124 (36.0%) 52 (15.1%) IIIIIIIII IIIIIII III 344 (100.0%) 0 (0.0%)
3 bill_length_mm [numeric] Mean (sd) : 43.9 (5.5) min < med < max: 32.1 < 44.5 < 59.6 IQR (CV) : 9.3 (0.1) 164 distinct values . . : . : : : : : : : : : : : : : : : : : . : : : : : : : : . 342 (99.4%) 2 (0.6%)
4 bill_depth_mm [numeric] Mean (sd) : 17.2 (2) min < med < max: 13.1 < 17.3 < 21.5 IQR (CV) : 3.1 (0.1) 80 distinct values : : : : . : : : . . : : : : : : : : : : : : : . . 342 (99.4%) 2 (0.6%)
5 flipper_length_mm [integer] Mean (sd) : 200.9 (14.1) min < med < max: 172 < 197 < 231 IQR (CV) : 23 (0.1) 55 distinct values : . : : : : . . . : : : : : : : : : : : : : : : 342 (99.4%) 2 (0.6%)
6 body_mass_g [integer] Mean (sd) : 4201.8 (802) min < med < max: 2700 < 4050 < 6300 IQR (CV) : 1200 (0.2) 94 distinct values : . : : : : : : : : : : . . : : : : : : 342 (99.4%) 2 (0.6%)
7 sex [factor] 1. female 2. male 165 (49.5%) 168 (50.5%) IIIIIIIII IIIIIIIIII 333 (96.8%) 11 (3.2%)
8 year [integer] Mean (sd) : 2008 (0.8) min < med < max: 2007 < 2008 < 2009 IQR (CV) : 2 (0) 2007 : 110 (32.0%) 2008 : 114 (33.1%) 2009 : 120 (34.9%) IIIIII IIIIII IIIIII 344 (100.0%) 0 (0.0%)

Tabla de frecuencias

Podemos crear todas las combinaciones de tablas con las funciones de R base, y formatearlas por ejemplo con {kableExtra}:

library(kableExtra)
pingu |> 
  select(species, sex) |> 
  table() |> 
  prop.table() |> 
  addmargins() |> 
  kable()
female male Sum
Adelie 0.2192192 0.2192192 0.4384384
Chinstrap 0.1021021 0.1021021 0.2042042
Gentoo 0.1741742 0.1831832 0.3573574
Sum 0.4954955 0.5045045 1.0000000

Al cargar {kableExtra} la función ´kable()´ se “sobreescribe”.

Pero el paquete {summarytools} tiene la función ctable() que quizás os guste más:

ctable(pingu$species, pingu$sex)

Cross-Tabulation, Row Proportions
species * sex
Data Frame: pingu

sex female male Total
species
Adelie 73 (48.0%) 73 (48.0%) 6 (3.9%) 152 (100.0%)
Chinstrap 34 (50.0%) 34 (50.0%) 0 (0.0%) 68 (100.0%)
Gentoo 58 (46.8%) 61 (49.2%) 5 (4.0%) 124 (100.0%)
Total 165 (48.0%) 168 (48.8%) 11 (3.2%) 344 (100.0%)
Practica

Explora las opciones de la función (por ejemplo, para calcular las frecuencias condicionadas por columna en vez de por fila).

Tabla 1 con {gtsummary}

library(gtsummary)
pingu |> 
  select(body_mass_g, species, bill_length_mm, sex) |> 
  tbl_summary(by = sex)
Characteristic female
N = 1651
male
N = 1681
body_mass_g 3,650 (3,350, 4,550) 4,300 (3,900, 5,325)
species

    Adelie 73 (44%) 73 (43%)
    Chinstrap 34 (21%) 34 (20%)
    Gentoo 58 (35%) 61 (36%)
bill_length_mm 42.8 (37.6, 46.2) 46.8 (41.0, 50.4)
1 Median (Q1, Q3); n (%)

Más ejemplos: https://www.danieldsjoberg.com/gtsummary/.

Resúmenes de modelos

Varios de los paquetes mencionados nos permiten extraer la información de los modelos. La siguiente expresión crea un modelo de regresión lineal:

m <- pingu |> 
  lm(body_mass_g ~bill_length_mm + bill_depth_mm + sex, data = _ )

Con estas expresiones primero guardamos los coeficientes en una tabla, y después formateamos la tabla con {flextable}.

library(flextable)
library(broom)
m |>
  tidy() |> 
  flextable()

term

estimate

std.error

statistic

p.value

(Intercept)

6,551.43868

394.407804

16.610824

0.000000000000000000000000000000000000000000002001677077

bill_length_mm

36.51909

5.299908

6.890513

0.000000000028399315285228353129431846507544539084944013

bill_depth_mm

-257.31457

14.892245

-17.278427

0.000000000000000000000000000000000000000000000004668175

sexmale

923.30524

60.723442

15.205087

0.000000000000000000000000000000000000000646111991760402

Practica

Mira la ayuda y formatea las columnas.

Con el paquete modelsummary sería así:

library(modelsummary)
modelsummary(m)
(1)
(Intercept) 6551.439
(394.408)
bill_length_mm 36.519
(5.300)
bill_depth_mm -257.315
(14.892)
sexmale 923.305
(60.723)
Num.Obs. 333
R2 0.687
R2 Adj. 0.684
AIC 5023.2
BIC 5042.3
Log.Lik. -2506.617
F 240.980
RMSE 449.64

Se pueden comparar varios modelos. Ver más ejemplos: https://vincentarelbundock.github.io/modelsummary/articles/modelsummary.html

También el paquete {gtsummary} produce tablas de modelos (puede requerir que instales un paquete adicional):

tbl_regression(m)
Characteristic Beta 95% CI1 p-value
bill_length_mm 37 26, 47 <0.001
bill_depth_mm -257 -287, -228 <0.001
sex


    female
    male 923 804, 1,043 <0.001
1 CI = Confidence Interval

Informes e interpretación

Hay un conjunto de paquetes llamado {easystats} que producen tanto tablas como resúmenes textuales con la interpretación de los análisis estadísticos. El paquete {effectsize} calcula y visualiza los efectos de un modelo, tanto en forma de tabla como gráfica.

library(effectsize)
cohens_d(body_mass_g ~ sex, data = pingu) 
Cohen's d |         95% CI
--------------------------
-0.94     | [-1.16, -0.71]

- Estimated using pooled SD.
interpret_cohens_d(-0.94)
[1] "large"
(Rules: cohen1988)
maov <- aov(body_mass_g ~ species, data = pingu)
eta_squared(maov)
# Effect Size for ANOVA

Parameter | Eta2 |       95% CI
-------------------------------
species   | 0.67 | [0.63, 1.00]

- One-sided CIs: upper bound fixed at [1.00].
interpret_eta_squared(0.67)
[1] "large"
(Rules: field2013)

El paquete {report} devuelve párrafos completos, combinando toda la información

library(report)
report(maov)
The ANOVA (formula: body_mass_g ~ species) suggests that:

  - The main effect of species is statistically significant and large (F(2, 339)
= 343.63, p < .001; Eta2 = 0.67, 95% CI [0.63, 1.00])

Effect sizes were labelled following Field's (2013) recommendations.

Para que salga el texto y las tablas anteriores formateados en el formato de salida, hay que añadir la opción results: 'asis' en el encabezado del chunk. A continuación un ejemplo completo con el modelo, el efecto y la interpretación.

library(gt)
maov |> tidy() |> 
  gt() |> 
  sub_missing(missing_text = "")
term df sumsq meansq statistic p.value
species 2 146864214 73432107.1 343.6263 2.892368e-82
Residuals 339 72443483 213697.6

tbl_regression(lm(maov))
Characteristic Beta 95% CI1 p-value
species


    Adelie
    Chinstrap 32 -100, 165 0.6
    Gentoo 1,375 1,265, 1,486 <0.001
1 CI = Confidence Interval
print_md(eta_squared(maov))
Effect Size for ANOVA
Parameter Eta2 95% CI
species 0.67 [0.63, 1.00]

One-sided CIs: upper bound fixed at [1.00].

report(maov)

The ANOVA (formula: body_mass_g ~ species) suggests that:

  • The main effect of species is statistically significant and large (F(2, 339) = 343.63, p < .001; Eta2 = 0.67, 95% CI [0.63, 1.00])

Effect sizes were labelled following Field’s (2013) recommendations.

Tablas interactivas

Hay varios paquetes que producen tablas interactivas para publicar en HTML, ya sea compartiendo el archivo que se abre en el navegador, o para publicar en la web. {DT} es uno de los más populares, pero el paquete {reactable} está ganando terreno.

El siguiente código genera una tabla interactiva para explorar todo el dataset de los pingüinos.

library(reactable)
pingu |> 
  reactable(filterable = TRUE,
            striped = TRUE)

Gráficos listos para publicar

Podemos crear gráficos suficientemente bonitos e impactantes con {ggplot2}. Pero a veces requiere mucho “tuneo”.

El paquete {ggstatsplot} (@R-ggstatsplot) crea gráficos que incluyen detalles estadísticos que son muy convenientes para publicar.

library(ggstatsplot)
pingu |> ggbarstats(x = species, y = sex)

pingu |> ggpiestats(x = species, y = sex)

pingu |> ggbetweenstats(species, body_mass_g, bf.message = FALSE)

Aquí puede que os pida instalare algunos paquetes adicionales

m |> ggcoefstats()

Practica

Explora el resto de funciones del paquete.

Paquetes más especializados aún

Referencias

Arel-Bundock, Vincent. 2024. modelsummary: Summary Tables and Plots for Statistical Models and Data: Beautiful, Customizable, and Publication-Ready. https://modelsummary.com.
Ben-Shachar, Mattan S., Dominique Makowski, Daniel Lüdecke, Indrajeet Patil, Brenton M. Wiernik, Rémi Thériault, y Philip Waggoner. 2024. effectsize: Indices of Effect Size. https://easystats.github.io/effectsize/.
Comtois, Dominic. 2022. summarytools: Tools to Quickly and Neatly Summarize Data. https://github.com/dcomtois/summarytools.
Dahl, David B., David Scott, Charles Roosen, Arni Magnusson, y Jonathan Swinton. 2019. xtable: Export Tables to LaTeX or HTML. http://xtable.r-forge.r-project.org/.
Gohel, David, y Panagiotis Skintzos. 2024. flextable: Functions for Tabular Reporting. https://ardata-fr.github.io/flextable-book/.
Horst, Allison, Alison Hill, y Kristen Gorman. 2022. palmerpenguins: Palmer Archipelago (Antarctica) Penguin Data. https://allisonhorst.github.io/palmerpenguins/.
Iannone, Richard, Joe Cheng, Barret Schloerke, Ellis Hughes, Alexandra Lauer, y JooYoung Seo. 2024. gt: Easily Create Presentation-Ready Display Tables. https://gt.rstudio.com.
Makowski, Dominique, Daniel Lüdecke, Indrajeet Patil, Rémi Thériault, Mattan S. Ben-Shachar, y Brenton M. Wiernik. 2024. report: Automated Reporting of Results and Statistical Models. https://easystats.github.io/report/.
Robinson, David, Alex Hayes, y Simon Couch. 2024. broom: Convert Statistical Objects into Tidy Tibbles. https://broom.tidymodels.org/.
Sjoberg, Daniel D., Joseph Larmarange, Michael Curry, Jessica Lavery, Karissa Whiting, y Emily C. Zabor. 2024. gtsummary: Presentation-Ready Data Summary and Analytic Result Tables. https://github.com/ddsjoberg/gtsummary.
Wickham, Hadley, y Garrett Grolemund. 2016. R for data science: import, tidy, transform, visualize, and model data. " O’Reilly Media, Inc.". https://r4ds.had.co.nz.
Xie, Yihui. 2024. knitr: A General-Purpose Package for Dynamic Report Generation in R. https://yihui.org/knitr/.
Zhu, Hao. 2024. kableExtra: Construct Complex Table with kable and Pipe Syntax. http://haozhu233.github.io/kableExtra/.

Notas

  1. Para ello debes pegar este enlace en el cuadro de diálogo de crear proyecto desde control de versiones: git@github.com:emilopezcano/seminario_fcs_urjc_2024.git↩︎

  2. Por defecto lo hace en el escritorio↩︎