En esta sesión revisamos los paquetes flextable y kableExtra para dar formato a tablas para presentación final en informes o artículos.


Librerías

library(tidyverse)
library(flextable)    # para diseñar tablas
library(knitr)        # para comando kable
library(kableExtra)   # complementos para kable
library(officer)      # complementos para flextable
library(magick)       # para trabajar con imagenes


También se requiere el paquete webshot para salvar como imagen las tablas. Si es necesario, instale este paquete, y además, ejecute la siguiente línea de código:

# install.packages("webshot")   # para instalar el paquete (solo si se requiere)
webshot::install_phantomjs()    # para instalar el software phantomjs


Finalmente active el paquete:

library(webshot)                # para salvar las tablas como imagen


Manuales de los paquetes

  • Consulte el manual/libro del paquete flextable aquí.

  • Consulte el manual del paquete kableExtra aquí.

  • Taller interactivo para prácticar kableExtra.


Datos

Intentaremos dar formato para presentación a la tabla publicada por Oliva-Hernández, López-Herrera, & Castillo-Linares (2021) la cual se muestra en la siguiente Figura:



Enseguida escribimos código R para entrar esta tabla en un tibble:

tab <- tibble(
  Variable  = c("Prod. de follaje (g/árbol)", "Base húmeda",  "Base seca", 
                "Prod. de proteína bruta (g/árbol)", "Relación follaje/rama"),
  sequia_m = c(NA,794, 257, 37, 2.6),
  sequia_ee = c(NA,68,19,3,0.21),
  lluvia_m = c(NA,1049,271,37,1.8),
  lluvia_ee   = c(NA,68,19,3,0.21)
)
tab  # se imprime
# A tibble: 5 x 5
  Variable                          sequia_m sequia_ee lluvia_m lluvia_ee
  <chr>                                <dbl>     <dbl>    <dbl>     <dbl>
1 Prod. de follaje (g/árbol)            NA       NA        NA       NA   
2 Base húmeda                          794       68      1049       68   
3 Base seca                            257       19       271       19   
4 Prod. de proteína bruta (g/árbol)     37        3        37        3   
5 Relación follaje/rama                  2.6      0.21      1.8      0.21


Paquete flextable

Tabla por defecto

flextable(tab)


Modificando la tabla

autofit

flextable(tab) %>%
  autofit()


Agrupando filas

Generamos una sangría para las filas 2 y 3 de tal manera que se genere una apariencia de subordinación, jerarquía o agrupamiento a la variable de la fila 1:

flextable(tab) %>%
  padding(i = 2:3, j = 1, padding.left = 20) %>%
  autofit()


El aspecto de agrupamiento de filas se puede incorporar de forma explicita usando el comando as_grouped_data el cual crea un nuevo data.frame con la disposición correcta de columnas y NA’s para que flextable muestre filas con agrupamiento. Revisar un poco más de esta opción en esta pregunta en stackoverflow.


Juntando columnas con dplyr

Usamos mutate (dplyr) para concatenar las columnas de media y error estándar para cada época:

# Creamos tabla con columnas concatenadas:
tab1 <- tab %>%
  mutate(
    `Sequia (N = 30)` = paste0(sequia_m, " (", sequia_ee, ")" ),
    `Lluvia (N = 30)` = paste0(lluvia_m, " (", lluvia_ee, ")" )
  ) %>% select(-(2:5))
tab1
# A tibble: 5 x 3
  Variable                          `Sequia (N = 30)` `Lluvia (N = 30)`
  <chr>                             <chr>             <chr>            
1 Prod. de follaje (g/árbol)        NA (NA)           NA (NA)          
2 Base húmeda                       794 (68)          1049 (68)        
3 Base seca                         257 (19)          271 (19)         
4 Prod. de proteína bruta (g/árbol) 37 (3)            37 (3)           
5 Relación follaje/rama             2.6 (0.21)        1.8 (0.21)       
# Se actualiza la fila 1 con NA
tab1[1,2:3] <- NA
tab1
# A tibble: 5 x 3
  Variable                          `Sequia (N = 30)` `Lluvia (N = 30)`
  <chr>                             <chr>             <chr>            
1 Prod. de follaje (g/árbol)        <NA>              <NA>             
2 Base húmeda                       794 (68)          1049 (68)        
3 Base seca                         257 (19)          271 (19)         
4 Prod. de proteína bruta (g/árbol) 37 (3)            37 (3)           
5 Relación follaje/rama             2.6 (0.21)        1.8 (0.21)       


Ahora aplicamos flextable sobre la nueva tabla:

flextable(tab1) %>%
  padding(i = 2:3, j = 1, padding.left = 20) %>%
  align( j = 2:3, align = "center", part = "all" ) %>%
  autofit()


Agrupando columnas

El comando add_header_rows agrega filas a la parte del encabezado de la tabla con la opción de poner un texto que permita agrupar columnas de la tabla bajo la misma etiqueta o título. El siguiente código agrupa las columnas 2 y 3 bajo un mismo título (Época):

flextable(tab1) %>%
  padding(i = 2:3, j = 1, padding.left = 20) %>%
  add_header_row(values = c(" ", "Época"), colwidths = c(1,2)) %>%
  align( j = 2:3, align = "center", part = "all" ) %>%
  bold(i = 1, part = "header") %>%
  autofit()


Personalizando bordes

flextable(tab1) %>%
  padding(i = 2:3, j = 1, padding.left = 20) %>%
  add_header_row(values = c(" ", "Época"), colwidths = c(1,2)) %>%
  align( j = 2:3, align = "center", part = "all" ) %>%
  bold(part = "header") %>%
  autofit() %>%
  border_remove() %>%
  border(part = "header", i = 2, 
         border.bottom = fp_border(width = 0.5, color = "grey40")) %>%
  border(part = "header", i = 2, j = 2:3, 
         border.top = fp_border(width = 0.5, color = "grey40")) %>%
  border(part = "header", i = 1, border.top = fp_border(color = "orange")) %>%
  border(part = "body", i = 5, border.bottom = fp_border(color = "orange"))


Pies de tabla

ft <- flextable(tab1) %>%
  padding(i = 2:3, j = 1, padding.left = 20) %>%
  add_header_row(values = c(" ", "Época"), colwidths = c(1,2)) %>%
  align( j = 2:3, align = "center", part = "all" ) %>%
  bold(part = "header") %>%
  autofit() %>%
  border_remove() %>%
  border(part = "header", i = 2, 
         border.bottom = fp_border(width = 0.5, color = "grey40")) %>%
  border(part = "header", i = 2, j = 2:3, 
         border.top = fp_border(width = 0.5, color = "grey40")) %>%
  border(part = "header", i = 1, border.top = fp_border(color = "orange")) %>%
  border(part = "body", i = 5, border.bottom = fp_border(color = "orange")) %>%
  flextable::footnote(
    i = 1, j = 2, value = as_paragraph("Media (error estándar)"),
    part = "header")

ft   # se imprime


Exportando una flextable

La tabla creada por flextable se puede exportar como una imagen usando el comando save_as_image. El uso de este comando depende del paquete webshot. El siguiente código muestra la manera de utilizar este comando:

save_as_image(ft, path = "mitabla.png")

Descargue el png producido aquí.

La imagen queda en el directorio de trabajo actual. La tabla también se puede guardar como una imagen desde el botón de “guardar” en el Viewer de Rstudio.

También, las tablas creadas por flextable dentro de chunks de código R en documentos rmarkdown permiten imprimirse a html, word o latex/pdf.


Paquete kableExtra

El comando kable del paquete knitr en conjunto con el paquete kableExtra constituyen otra marco excelente para diseñar y formatear tablas para informes o artículos.


Tabla por defecto

kable(tab)
Variable sequia_m sequia_ee lluvia_m lluvia_ee
Prod. de follaje (g/árbol) NA NA NA NA
Base húmeda 794.0 68.00 1049.0 68.00
Base seca 257.0 19.00 271.0 19.00
Prod. de proteína bruta (g/árbol) 37.0 3.00 37.0 3.00
Relación follaje/rama 2.6 0.21 1.8 0.21


Para esconder los NA’s utilice el siguiente código:

options(knitr.kable.NA = '')
kable(tab)
Variable sequia_m sequia_ee lluvia_m lluvia_ee
Prod. de follaje (g/árbol)
Base húmeda 794.0 68.00 1049.0 68.00
Base seca 257.0 19.00 271.0 19.00
Prod. de proteína bruta (g/árbol) 37.0 3.00 37.0 3.00
Relación follaje/rama 2.6 0.21 1.8 0.21


Modificando la tabla

Ancho total

kable(tab) %>%
  kable_styling(full_width = F)
Variable sequia_m sequia_ee lluvia_m lluvia_ee
Prod. de follaje (g/árbol)
Base húmeda 794.0 68.00 1049.0 68.00
Base seca 257.0 19.00 271.0 19.00
Prod. de proteína bruta (g/árbol) 37.0 3.00 37.0 3.00
Relación follaje/rama 2.6 0.21 1.8 0.21


Agrupando filas

kable(tab[-1, ]) %>%
  kable_styling(full_width = F) %>%
  pack_rows(group_label = "Prod. de follaje (g/árbol)", 
            start_row = 1, end_row = 2)
Variable sequia_m sequia_ee lluvia_m lluvia_ee
Prod. de follaje (g/árbol)
Base húmeda 794.0 68.00 1049.0 68.00
Base seca 257.0 19.00 271.0 19.00
Prod. de proteína bruta (g/árbol) 37.0 3.00 37.0 3.00
Relación follaje/rama 2.6 0.21 1.8 0.21


Un efecto de sangría (indentación) también crea subordinación y la apariencia de agrupación de algunas filas:

kable(tab) %>%
  kable_styling(full_width = F) %>%
  add_indent(c(2,3))
Variable sequia_m sequia_ee lluvia_m lluvia_ee
Prod. de follaje (g/árbol)
Base húmeda 794.0 68.00 1049.0 68.00
Base seca 257.0 19.00 271.0 19.00
Prod. de proteína bruta (g/árbol) 37.0 3.00 37.0 3.00
Relación follaje/rama 2.6 0.21 1.8 0.21


Agrupando columnas

El comando add_header_above agrega una fila de etiquetas para agrupar columnas. Su argumento principal, header, es un vector nombrado de números enteros. La suma de los números debe ser igual a la cantidad de columnas de la tabla. En el siguiente código usamos la tabla tab1 creada arriba:

kable(tab1, align = c("l", "c", "c")) %>%
  kable_styling(full_width = F) %>%
  add_header_above(header = c(" " = 1, Época = 2)) %>%
  add_indent(c(2,3))
Época
Variable Sequia (N = 30) Lluvia (N = 30)
Prod. de follaje (g/árbol)
Base húmeda 794 (68) 1049 (68)
Base seca 257 (19) 271 (19)
Prod. de proteína bruta (g/árbol) 37 (3) 37 (3)
Relación follaje/rama 2.6 (0.21) 1.8 (0.21)


Temas

kable(tab1, align = c("l", "c", "c")) %>%
  kable_classic(full_width = F) %>%
  add_header_above(header = c(" " = 1, Época = 2)) %>%
  add_indent(c(2,3))
Época
Variable Sequia (N = 30) Lluvia (N = 30)
Prod. de follaje (g/árbol)
Base húmeda 794 (68) 1049 (68)
Base seca 257 (19) 271 (19)
Prod. de proteína bruta (g/árbol) 37 (3) 37 (3)
Relación follaje/rama 2.6 (0.21) 1.8 (0.21)
kable(tab1, align = c("l", "c", "c")) %>%
  kable_classic_2(full_width = F) %>%
  add_header_above(header = c(" " = 1, Época = 2)) %>%
  add_indent(c(2,3))
Época
Variable Sequia (N = 30) Lluvia (N = 30)
Prod. de follaje (g/árbol)
Base húmeda 794 (68) 1049 (68)
Base seca 257 (19) 271 (19)
Prod. de proteína bruta (g/árbol) 37 (3) 37 (3)
Relación follaje/rama 2.6 (0.21) 1.8 (0.21)
kable(tab1, align = c("l", "c", "c")) %>%
  kable_paper(full_width = F) %>%
  add_header_above(header = c(" " = 1, Época = 2)) %>%
  add_indent(c(2,3))
Época
Variable Sequia (N = 30) Lluvia (N = 30)
Prod. de follaje (g/árbol)
Base húmeda 794 (68) 1049 (68)
Base seca 257 (19) 271 (19)
Prod. de proteína bruta (g/árbol) 37 (3) 37 (3)
Relación follaje/rama 2.6 (0.21) 1.8 (0.21)


Pies de tabla

Los comandos add_footnote y footnote (más nuevo) del paquete kableExtra adicionan pies de tabla. La dificultad es que el comando no pone el símbolo en el interior de la tabla, sólo lo pone en el pie de tabla. El usuario debe encargarse de modificar la tabla para que el símbolo aparezca en el lugar correcto. Dado que usualmente estos símbolos se ponen como superíndices, o se utilizan símbolos no convencionales como *; †; ‡; etc; se debe tener en cuenta el tipo salida de la tabla (html o latex/pdf) y la forma diferencial de escribir estos símbolos para html o latex.

El siguiente código adiciona un pie de tabla asociado a la época con un superíndice de 1 para compilar a html:

kable(tab1, align = c("l", "c", "c")) %>%
  kable_styling(full_width = F) %>%
  add_header_above(header = c(" " = 1, "Época<sup>1</sup>" = 2), escape = F) %>%
  add_indent(c(2,3)) %>%
  kableExtra::footnote(number = "Media (error estándar)")

Época1

Variable Sequia (N = 30) Lluvia (N = 30)
Prod. de follaje (g/árbol)
Base húmeda 794 (68) 1049 (68)
Base seca 257 (19) 271 (19)
Prod. de proteína bruta (g/árbol) 37 (3) 37 (3)
Relación follaje/rama 2.6 (0.21) 1.8 (0.21)
1 Media (error estándar)

Los comandos footnote_maker_symbol, footnote_maker_alphabet y footnote_maker_number producen los símbolos correspondientes (y pueden ser usados en conjunto con el comando paste0 para agregar los símbolos a la tabla). Por ejemplo:

footnote_marker_symbol(1)
[1] "<sup>*</sup>"
footnote_marker_symbol(2)
[1] "<sup>&dagger;</sup>"
footnote_marker_alphabet(1)
[1] "<sup>a</sup>"
footnote_marker_alphabet(2)
[1] "<sup>b</sup>"


Exportando la tabla

El comando save_kable guarda la tabla como una imagen o como pdf. Se recomienda guardarla como pdf para mejorar la resolución.

Se guarda desde un html y con un estilo classic:

kable(tab1, align = c("l", "c", "c"), format = "html") %>%
  kable_classic(full_width = F) %>%
  add_header_above(header = c(" " = 1, "Época<sup>1</sup>" = 2), escape = F) %>%
  add_indent(c(2,3)) %>%
  kableExtra::footnote(number = "Media (error estándar)") %>%
  save_kable(file = "mitabla_classic.pdf")

Descargue el pdf producido aquí.


Se guarda desde un html y con un estilo classic:

kable(tab1, align = c("l", "c", "c"), format = "html") %>%
  kable_classic_2(full_width = F) %>%
  add_header_above(header = c(" " = 1, "Época<sup>1</sup>" = 2), escape = F) %>%
  add_indent(c(2,3)) %>%
  kableExtra::footnote(number = "Media (error estándar)") %>%
  save_kable(file = "mitabla_classic2.pdf")

Descargue el pdf producido aquí.


Se guarda desde latex y con la opción booktabs = T:

kable(tab1, align = c("l", "c", "c"), format = "latex", booktabs = T) %>%
  kable_styling(full_width = F) %>%
  add_header_above(header = c(" " = 1, "Época$^1$" = 2), escape = F) %>%
  add_indent(c(2,3)) %>%
  kableExtra::footnote(number = "Media (error estándar)") %>%
  save_kable(file = "mitabla_latex.pdf")

Descargue el pdf producido aquí


Referencias

Oliva-Hernández, J., López-Herrera, M.-A., & Castillo-Linares, E.-B. (2021). Composición química y producción de follaje de Erythrina americana (fabaceae) en cercos vivos durante dos épocas climáticas. Revista de Biología Tropical, 69(1), 90–101. Retrieved from https://revistas.ucr.ac.cr/index.php/rbt/article/view/41822/44787