Generación con R de tablas y gráficos listos para publicar
VII Seminario Análisis de datos avanzados en Ciencias de la Salud
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.
Para seguir el material, tienes varias opciones:
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.
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.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/
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:
<- penguins pingu
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.
¿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ónglance()
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.
Crear documento
Insertar chunks (pegar el código de este documento)
Insertar código
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 |
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%) |
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:
<- pingu |>
m 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 |
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)
<- aov(body_mass_g ~ species, data = pingu)
maov 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)
|> tidy() |>
maov 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))
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)
|> ggbarstats(x = species, y = sex) pingu
|> ggpiestats(x = species, y = sex) pingu
|> ggbetweenstats(species, body_mass_g, bf.message = FALSE) pingu
Aquí puede que os pida instalare algunos paquetes adicionales
|> ggcoefstats() m
Practica
Explora el resto de funciones del paquete.