Hacer cuadros/tablas para mostrar resultados es una parte importante
(y time consuming) de cualquier informe o investigación. En este
tutorial explicaré como presentar nuestros resultados en tablas en
documentos (ya sean estos .html, .pdf etc..) creados a partir de
archivos .Rmd
.
Veremos que presentar los resultados (previamente almacenados en un
df) como un cuadro en el documento final es muy sencillo, sólo tenemos
que llamar a la función kable()
, pero si queremos tener más
flexibilidad y opciones para mejorar nuestras tablas tendremos que usar
otros paquetes como kableextra
, formatable
,
flextable
, DT
o más recientemente
gt
y reactable
En este hilo de Twitter se habló sobre cuales eran los mejores paquetes R para hacer tablas. En este post, David Keyes hace un muy buen tutorial sobre los paquetes disponibles para hacer tablas en R.
Al igual que con los gráficos, para hacer buenas tablas 1 hace falta
tener en cuenta algunos aspectos. Este post
proporciona 10 reglas o consejos para generar tablas efectivas. En este
otro post,
Thomas Mock implementa esas 10 reglas con el paquete
gt
.
En el siguiente apartado utilizaremos unos datos de ejemplo para
obtener unos resultados que queremos mostrar como tablas en nuestro
informe. Esto nos servirá para practicar un poco más con
dplyr
y aprender a crear tablas desde documentos .Rmd, ya
que una vez tengamos los resultados que queremos mostrar, presentaremos
distintas posibilidades para mostrarlos en tablas/cuadros
good-looking.
En esta sección utilizaremos lo que hemos aprendido de
dplyr para obtener los resultados que queremos mostrar
en los cuadros. Con dplyr calcularemos los estadísticos que queremos
mostrar almacenándolos en un dataframe/tibble. Una vez tengamos los
resultados en un dataframe nos quedará la tarea de presentarlos como
cuadros en el documento final. En un primer momento (esta sección)
utilizaremos la función knitr::kable()
para hacer tablas y
en los siguientes apartados presentaremos paquetes especialmente
diseñados para hacer tablas.
Ilustraremos la creación de tablas con datos de la segunda ronda de PIACC (Programa para la Evaluación Internacional de las Competencias de los adultos de la OCDE. Información detallada sobre el programa PIAAC puede encontrarse en su pagina web http://www.oecd.org/skills/piaac/. Los datos se obtuvieron a partir del paquete RPIAAC creado Por Jose C. Pernias, para finalmente seleccionar 10 variables de 4 países: ESP, FRA, GBR, ITA. No recuerdo el año de los datos!!
Cargamos los datos que vamos a utilizar:
my_url <- "https://raw.githubusercontent.com/perezp44/iris_data/master/data/PIAAC_data_small.csv"
df <- read_csv(my_url)
Miramos un poco los datos:
#- remotes::install_github("perezp44/pjpv2020.01")
df_aa <- pjpv2020.01::pjp_f_estadisticos_basicos(df) #- estadísticos básicos del df
df_bb <- pjpv2020.01::pjp_f_unique_values(df) #- valores únicos de cada variable de df
Para iniciar nuestra andadura con las tablas, intentaremos mostrar en tablas algunos resultados. Por ejemplo:
df_tt_1 <- df %>%
count(Country)
df_tt_1 <- df %>%
group_by(Country) %>%
summarise(NN = n())
knitr::kable(df_tt_1)
Country | NN |
---|---|
ESP | 1991 |
FRA | 3346 |
GBR | 1075 |
ITA | 1610 |
df_tt_2 <- df %>%
group_by(Country) %>%
summarise(NN = n(), percent = n()/nrow(.) )
df_tt_2 <- df %>%
group_by(Country) %>%
summarise (NN = n()) %>%
mutate(percent = NN / sum(NN))
knitr::kable(df_tt_2)
Country | NN | percent |
---|---|---|
ESP | 1991 | 0.2481925 |
FRA | 3346 | 0.4171030 |
GBR | 1075 | 0.1340065 |
ITA | 1610 | 0.2006981 |
df_tt_3 <- df %>%
count(Country, Gender) %>%
group_by(Country) %>%
mutate(percent = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = Gender,
values_from = percent) %>%
ungroup()
knitr::kable(df_tt_3)
Country | Female | Male |
---|---|---|
ESP | 0.4841788 | 0.5158212 |
FRA | 0.5065750 | 0.4934250 |
GBR | 0.6893023 | 0.3106977 |
ITA | 0.4850932 | 0.5149068 |
df_tt_4 <- df %>%
count(Education, Gender) %>%
group_by(Education) %>%
mutate(percent = n / sum(n)) %>%
select(-n) %>%
pivot_wider(names_from = Education,
values_from = percent) %>%
ungroup()
knitr::kable(df_tt_4)
Gender | Primary | Secondary | Tertiary | Upper_second | NA |
---|---|---|---|---|---|
Female | 0.4377224 | 0.4804831 | 0.6159875 | 0.579646 | 0.8 |
Male | 0.5622776 | 0.5195169 | 0.3840125 | 0.420354 | 0.2 |
df_tt_5 <- df %>%
group_by(Country) %>%
summarise(W_hora_medio = mean(Wage_hour , na.rm = TRUE),
W_mes_medio = mean(Wage_month, na.rm = TRUE)) %>% ungroup()
knitr::kable(df_tt_5)
Country | W_hora_medio | W_mes_medio |
---|---|---|
ESP | 9.386217 | 1437.340 |
FRA | 12.850094 | 1992.606 |
GBR | 10.624071 | 1507.472 |
ITA | 11.746581 | 1808.500 |
df_tt_6 <- df %>%
group_by(Country) %>%
summarise(W_medio = mean(Wage_month, na.rm = TRUE) ,
W_minimo = min(Wage_month, na.rm = TRUE) ,
W_maximo = max(Wage_month, na.rm = TRUE) ,
W_sd = sd(Wage_month, na.rm = TRUE) ) %>%
ungroup()
knitr::kable(df_tt_6)
Country | W_medio | W_minimo | W_maximo | W_sd |
---|---|---|---|---|
ESP | 1437.340 | 110 | 3800 | 702.6642 |
FRA | 1992.606 | 115 | 5850 | 906.5507 |
GBR | 1507.472 | 116 | 10000 | 1003.7897 |
ITA | 1808.500 | 150 | 5000 | 819.6863 |
df_tt_7 <- df %>%
filter(Country == "ESP") %>%
group_by(Gender) %>%
summarise(W_hora_medio = mean(Wage_hour, na.rm = TRUE),
W_mes_medio = mean(Wage_month, na.rm = TRUE) ) %>%
ungroup()
knitr::kable(df_tt_7)
Gender | W_hora_medio | W_mes_medio |
---|---|---|
Female | 8.789178 | 1245.076 |
Male | 9.946525 | 1617.810 |
df_tt_8 <- df %>%
group_by(Country, Gender) %>%
summarise(W_hora_medio = mean(Wage_hour, na.rm = TRUE),
W_mes_medio = mean(Wage_month, na.rm = TRUE) ) %>%
ungroup()
knitr::kable(df_tt_8)
Country | Gender | W_hora_medio | W_mes_medio |
---|---|---|---|
ESP | Female | 8.789178 | 1245.076 |
ESP | Male | 9.946525 | 1617.810 |
FRA | Female | 12.443723 | 1802.254 |
FRA | Male | 13.266840 | 2188.032 |
GBR | Female | 10.609256 | 1377.953 |
GBR | Male | 10.656991 | 1794.817 |
ITA | Female | 11.584393 | 1629.828 |
ITA | Male | 11.898988 | 1976.826 |
df_tt_9 <- df %>%
group_by(Country, Gender) %>%
summarise(W_mes_medio = mean(Wage_month, na.rm = TRUE)) %>%
ungroup() %>%
pivot_wider(names_from = Gender, values_from = W_mes_medio) %>%
mutate(dif_W = Male-Female, dif_percent_W = dif_W/Female)
knitr::kable(df_tt_9)
Country | Female | Male | dif_W | dif_percent_W |
---|---|---|---|---|
ESP | 1245.076 | 1617.810 | 372.7344 | 0.2993669 |
FRA | 1802.254 | 2188.032 | 385.7784 | 0.2140533 |
GBR | 1377.953 | 1794.817 | 416.8640 | 0.3025240 |
ITA | 1629.828 | 1976.826 | 346.9983 | 0.2129048 |
df_tt_10 <- df %>%
group_by(Country, Education) %>%
summarise(Numeracy_media = mean(Numeracy_score, na.rm = TRUE)) %>%
ungroup()
knitr::kable(df_tt_10)
Country | Education | Numeracy_media |
---|---|---|
ESP | Primary | 213.0135 |
ESP | Secondary | 245.1109 |
ESP | Tertiary | 282.1070 |
ESP | Upper_second | 265.7699 |
FRA | Primary | 184.9306 |
FRA | Secondary | 246.6609 |
FRA | Tertiary | 304.0075 |
FRA | Upper_second | 289.0088 |
FRA | NA | 197.6502 |
GBR | Primary | 207.3143 |
GBR | Secondary | 252.9237 |
GBR | Tertiary | 286.1454 |
GBR | Upper_second | 263.2052 |
GBR | NA | 213.1912 |
ITA | Primary | 200.7297 |
ITA | Secondary | 257.5339 |
ITA | Tertiary | 279.7166 |
ITA | Upper_second | 271.6400 |
df_tt_11 <- df_tt_10 %>%
pivot_wider(names_from = Education,
values_from = Numeracy_media)
knitr::kable(df_tt_11)
Country | Primary | Secondary | Tertiary | Upper_second | NA |
---|---|---|---|---|---|
ESP | 213.0135 | 245.1109 | 282.1070 | 265.7699 | NA |
FRA | 184.9306 | 246.6609 | 304.0075 | 289.0088 | 197.6502 |
GBR | 207.3143 | 252.9237 | 286.1454 | 263.2052 | 213.1912 |
ITA | 200.7297 | 257.5339 | 279.7166 | 271.6400 | NA |
Bien, no está mal, pero quiero ordenar la tabla de menor a mayor nivelo educativo
Resulta que en el cuadro, las categorías de niveles de estudio se han ordenado alfabéticamente pero quiero ordenarla de menor a mayor nivel de estudios. Se puede hacer de otras maneras, pero lo haremos usando factores.
La variable Education tiene 4 categorías: Primary, Secondary,
Upper-second y Tertiary; ADEMÁS estas categorías
están/son fijas, no cambian. En estos casos es mejor tratar esas
variables como factores; PERO si miramos la clase de la
variable “Education” con class(df$Education)
veremos que es
character, así que vamos a convertir la variable “Education” a
factor.
Podríamos usar la función as.factor()
, pero una vez más
usaremos pkgs del tidyverse; en este caso el pkg
“forcats” y la función as_factor()
df <- df %>%
mutate(Education = forcats::as_factor(Education))
levels(df$Education)
#> [1] "Primary" "Tertiary" "Secondary" "Upper_second"
Como vemos, la variable Education
, que ahora es un
factor, tiene cuatro categorías o “levels”
Resulta que el nombre de las 4 categorías está en inglés y lo
queremos en castellano. Lo haremos con
forcats::fct_recode()
. Para saber un poco más sobre como
manipular factores debes ir aquí
df <- df %>%
mutate(Education = forcats::fct_recode(Education,
"Primaria" = "Primary",
"Secundaria" = "Secondary",
"Secundaria_post" = "Upper_second",
"Terciaria" = "Tertiary" ))
df %>% count(Education)
Education | n |
---|---|
Primaria | 562 |
Terciaria | 1914 |
Secundaria | 4637 |
Secundaria_post | 904 |
NA | 5 |
Como volvemos a ver, la variable “Education”, que ahora es un factor
tiene 4 “levels” pero están ordenados de forma que no nos conviene,
queremos cambiar el orden de los factores con
forcast::fct_relevel()
para que se muestren los cuatro
grupos educativos de menor a mayor nivel.
df <- df %>%
mutate(Education = forcats::fct_relevel(Education,
"Primaria", "Secundaria", "Secundaria_post"))
df %>% count(Education)
Education | n |
---|---|
Primaria | 562 |
Secundaria | 4637 |
Secundaria_post | 904 |
Terciaria | 1914 |
NA | 5 |
Como se puede ver en el cuadro de arriba, ya hemos cambiado el orden de los “levels” para que se muestren de menor a mayor nivel educativo, así que volvamos a rehacer el cuadro con el Numeracy Score por países:
df_tt_12 <- df %>%
group_by(Country, Education) %>%
summarise(Numeracy_media = mean(Numeracy_score, na.rm = TRUE)) %>%
ungroup() %>%
pivot_wider(names_from = Education,
values_from = Numeracy_media)
knitr::kable(df_tt_12)
Country | Primaria | Secundaria | Secundaria_post | Terciaria | NA |
---|---|---|---|---|---|
ESP | 213.0135 | 245.1109 | 265.7699 | 282.1070 | NA |
FRA | 184.9306 | 246.6609 | 289.0088 | 304.0075 | 197.6502 |
GBR | 207.3143 | 252.9237 | 263.2052 | 286.1454 | 213.1912 |
ITA | 200.7297 | 257.5339 | 271.6400 | 279.7166 | NA |
Si los resultados que queremos mostrar son cosas básicas como
porcentajes, frecuencias etc… obviamente podemos calcularlos nosotros
con dplyr
, pero para estos casos el paquete
janitor
, concretamente la función
janitor::tabyl()
nos facilita mucho la tarea2. Para ver todas las
posibilidades que tiene el paquete janitor
puedes ir a su
página web aquí.
1 y 2. Cuadro con el número de observaciones de cada país
df_tt_1_j <- df %>% janitor::tabyl(Country)
knitr::kable(df_tt_1_j)
Country | n | percent |
---|---|---|
ESP | 1991 | 0.2481925 |
FRA | 3346 | 0.4171030 |
GBR | 1075 | 0.1340065 |
ITA | 1610 | 0.2006981 |
df_tt_3_j <- df %>% janitor::tabyl(Country, Gender)
df_tt_3_j <- df %>% janitor::tabyl(Country, Gender) %>%
janitor::adorn_percentages(denominator = "row")
df_tt_3_j
Country | Female | Male |
---|---|---|
ESP | 0.4841788 | 0.5158212 |
FRA | 0.5065750 | 0.4934250 |
GBR | 0.6893023 | 0.3106977 |
ITA | 0.4850932 | 0.5149068 |
df_tt_4_j <- df %>% janitor::tabyl(Gender, Education) %>%
janitor:: adorn_percentages(denominator = "col")
df_tt_4_j
Gender | Primaria | Secundaria | Secundaria_post | Terciaria | NA_ |
---|---|---|---|---|---|
Female | 0.4377224 | 0.4804831 | 0.579646 | 0.6159875 | 0.8 |
Male | 0.5622776 | 0.5195169 | 0.420354 | 0.3840125 | 0.2 |
Para ver todas las posibilidades que tiene el paquete
janitor
puedes ir a su página web aquí
Hay otros paquetes con funcionalidades parecidas a
janitor
. Por ejemplo, el paquete freqtables
o el paquete sjmisc
.
knitr::kable()
Bueno, ya sabemos, aunque en realidad lo vimos al presentar
dplyr
y tidyr
, como ir generando un dataframe
con los estadísticos descriptivos que nos interesa mostrar. En realidad
esos df ya son tablas de datos que puedo exportar a Excel para
trabajarlas a nuestro gusto; pero en el curso hacemos todo desde R.
Ya habéis visto que con la función knitr::kable()
puedo
mostrarlas en mi documento final sólo tengo que ejecutar lo siguiente:
knitr::kable(my_df)
.
En los próximos apartados mostraremos algunos paquetes específicos para formatear los cuadros y hacerlos BONITOS, pero también conviene saber que kable() también tiene opciones para personalizar un poco nuestras tablas. Por ejemplo:
knitr::kable(df_tt_12,
align = "c",
caption = "Numeracy Score por país",
digits = 2,
format.args = list(decimal.mark = ",", big.mark = "."))
Country | Primaria | Secundaria | Secundaria_post | Terciaria | NA |
---|---|---|---|---|---|
ESP | 213,01 | 245,11 | 265,77 | 282,11 | NA |
FRA | 184,93 | 246,66 | 289,01 | 304,01 | 197,65 |
GBR | 207,31 | 252,92 | 263,21 | 286,15 | 213,19 |
ITA | 200,73 | 257,53 | 271,64 | 279,72 | NA |
kableExtra
pkgkableExtra
es un package de que permite mejorar las tablas creadas con
knitr::kable()
.
La descripción de kableExtra
en CRAN es:
Build complex HTML or ‘LaTeX’ tables using ‘kable()’ from ‘knitr’ and the piping syntax from ‘magrittr’. Function ‘kable()’ is a light weight table generator coming from ‘knitr’. This package simplifies the way to manipulate the HTML or ‘LaTeX’ codes generated by ‘kable()’ and allows users to construct complex tables and customize styles using a readable syntax.
Aquí tienes la vignette para elaborar tablas para documentos html.
Para mejorar las tablas usaremos la función
kableExtra::kable_styling()
:
knitr::kable(df_tt_12) %>%
kableExtra::kable_styling(bootstrap_options = c("striped", "hover"))
Country | Primaria | Secundaria | Secundaria_post | Terciaria | NA |
---|---|---|---|---|---|
ESP | 213.0135 | 245.1109 | 265.7699 | 282.1070 | NA |
FRA | 184.9306 | 246.6609 | 289.0088 | 304.0075 | 197.6502 |
GBR | 207.3143 | 252.9237 | 263.2052 | 286.1454 | 213.1912 |
ITA | 200.7297 | 257.5339 | 271.6400 | 279.7166 | NA |
Se le puede poner un scroll:
df_tt_12 %>%
knitr::kable() %>%
kableExtra::kable_styling(font_size = 11) %>%
kableExtra::scroll_box(width = "50%", height = "60%")
Country | Primaria | Secundaria | Secundaria_post | Terciaria | NA |
---|---|---|---|---|---|
ESP | 213.0135 | 245.1109 | 265.7699 | 282.1070 | NA |
FRA | 184.9306 | 246.6609 | 289.0088 | 304.0075 | 197.6502 |
GBR | 207.3143 | 252.9237 | 263.2052 | 286.1454 | 213.1912 |
ITA | 200.7297 | 257.5339 | 271.6400 | 279.7166 | NA |
Se puede incluso poner las tablas junto con el texto.
knitr::kable(df_tt_12) %>%
kableExtra::kable_styling(bootstrap_options = "striped",
full_width = F,
position = "float_right")
Country | Primaria | Secundaria | Secundaria_post | Terciaria | NA |
---|---|---|---|---|---|
ESP | 213.0135 | 245.1109 | 265.7699 | 282.1070 | NA |
FRA | 184.9306 | 246.6609 | 289.0088 | 304.0075 | 197.6502 |
GBR | 207.3143 | 252.9237 | 263.2052 | 286.1454 | 213.1912 |
ITA | 200.7297 | 257.5339 | 271.6400 | 279.7166 | NA |
Para ello tienes que usar la opción
position = "float_right
dentro de
kableExtra::kable_styling()
.
Si tenemos una tabla muy larga, podemos fijar el encabezamiento de la
tabla con fixed_thead
knitr::kable(df_tt_12) %>%
kableExtra::kable_styling(fixed_thead = list(enabled = T,
background = "lightblue"))
Country | Primaria | Secundaria | Secundaria_post | Terciaria | NA |
---|---|---|---|---|---|
ESP | 213.0135 | 245.1109 | 265.7699 | 282.1070 | NA |
FRA | 184.9306 | 246.6609 | 289.0088 | 304.0075 | 197.6502 |
GBR | 207.3143 | 252.9237 | 263.2052 | 286.1454 | 213.1912 |
ITA | 200.7297 | 257.5339 | 271.6400 | 279.7166 | NA |
Se pueden formatear columnas por separado con
kableExtra::column_spec()
, o las filas con
kableExtra::row_spec()
. La tabla no nos saldrá muy chula
pero nos sirve para ver las posibilidades de
kable_styling()
.
library(kableExtra)
knitr::kable(df_tt_12) %>%
kableExtra::kable_styling(full_width = F) %>%
column_spec(1, bold = T, border_right = T) %>%
column_spec(3, width = "20em", background = "yellow") %>%
row_spec(3:4, bold = T, color = "white", background = "#D7261E") %>%
row_spec(0, angle = 10)
Country | Primaria | Secundaria | Secundaria_post | Terciaria | NA |
---|---|---|---|---|---|
ESP | 213.0135 | 245.1109 | 265.7699 | 282.1070 | NA |
FRA | 184.9306 | 246.6609 | 289.0088 | 304.0075 | 197.6502 |
GBR | 207.3143 | 252.9237 | 263.2052 | 286.1454 | 213.1912 |
ITA | 200.7297 | 257.5339 | 271.6400 | 279.7166 | NA |
Puedes ver las posibilidades de este pkg en su vignette. Fijate que tabla más chula hay allí:
mtcars[1:8, 1:8] %>%
kbl() %>%
kable_paper(full_width = F) %>%
column_spec(2, color = spec_color(mtcars$mpg[1:8]),
link = "https://haozhu233.github.io/kableExtra/") %>%
column_spec(6, color = "white",
background = spec_color(mtcars$drat[1:8], end = 0.7),
popover = paste("am:", mtcars$am[1:8]))
mpg | cyl | disp | hp | drat | wt | qsec | vs | |
---|---|---|---|---|---|---|---|---|
Mazda RX4 | 21.0 | 6 | 160.0 | 110 | 3.90 | 2.620 | 16.46 | 0 |
Mazda RX4 Wag | 21.0 | 6 | 160.0 | 110 | 3.90 | 2.875 | 17.02 | 0 |
Datsun 710 | 22.8 | 4 | 108.0 | 93 | 3.85 | 2.320 | 18.61 | 1 |
Hornet 4 Drive | 21.4 | 6 | 258.0 | 110 | 3.08 | 3.215 | 19.44 | 1 |
Hornet Sportabout | 18.7 | 8 | 360.0 | 175 | 3.15 | 3.440 | 17.02 | 0 |
Valiant | 18.1 | 6 | 225.0 | 105 | 2.76 | 3.460 | 20.22 | 1 |
Duster 360 | 14.3 | 8 | 360.0 | 245 | 3.21 | 3.570 | 15.84 | 0 |
Merc 240D | 24.4 | 4 | 146.7 | 62 | 3.69 | 3.190 | 20.00 | 1 |
Uno de los paquetes que se pueden usar para generar tablas es pander
, pero
no lo incluyo en el tutorial porque en realidad, su objetivo es más
general, su objetivo es facilitar la exportación de objetos R a
pandoc
. De hecho, pander
puede ser una
alternativa a knitr
; de hecho, el título de su pagina web
es: pander: An R Pandoc Writer
.
Si presento, de forma sencilla los paquetes formatable
y
dos paquetes muy prometedores como flextable
y
gt
, pero existen más posibilidades, más paquetes para hacer
tablas en R.
formatable
pkgEl paquete formattable
is designed for applying formatting on vectors and data frames to make data presentation easier, richer, more flexible and hopefully convey more information.
En estos dos post: aquí
y aquí
explican con detalle las posibilidades del paquete
formattable.
Para ilustrar las posibilidades de formattable
utilizo
uno de los ejemplos de su página web:
library(formattable)
df %>% formattable(list(
age = color_tile("white", "orange"),
grade = formatter("span", style = x ~ ifelse(x == "A",
style(color = "green", font.weight = "bold"), NA)),
area(col = c(test1_score, test2_score)) ~ normalize_bar("pink", 0.2),
final_score = formatter("span",
style = x ~ style(color = ifelse(rank(-x) <= 3, "green", "gray")),
x ~ sprintf("%.2f (rank: %02d)", x, rank(-x))),
registered = formatter("span",
style = x ~ style(color = ifelse(x, "green", "red")),
x ~ icontext(ifelse(x, "ok", "remove"), ifelse(x, "Yes", "No")))
))
id | name | age | grade | test1_score | test2_score | final_score | registered |
---|---|---|---|---|---|---|---|
1 | Bob | 28 | C | 8.9 | 9.1 | 9.00 (rank: 06) | Yes |
2 | Ashley | 27 | A | 9.5 | 9.1 | 9.30 (rank: 03) | No |
3 | James | 30 | A | 9.6 | 9.2 | 9.40 (rank: 02) | Yes |
4 | David | 28 | C | 8.9 | 9.1 | 9.00 (rank: 06) | No |
5 | Jenny | 29 | B | 9.1 | 8.9 | 9.00 (rank: 06) | Yes |
6 | Hans | 29 | B | 9.3 | 8.5 | 8.90 (rank: 08) | Yes |
7 | Leo | 27 | B | 9.3 | 9.2 | 9.25 (rank: 04) | Yes |
8 | John | 27 | A | 9.9 | 9.3 | 9.60 (rank: 01) | No |
9 | Emily | 31 | C | 8.5 | 9.1 | 8.80 (rank: 09) | No |
10 | Lee | 30 | C | 8.6 | 8.8 | 8.70 (rank: 10) | No |
flextable
pkgEl paquete flextable
es relativamente nuevo y tiene la ventaja de que provee un entorno para
crear de forma relativamente sencilla tablas estadísticas para informes.
Una de sus ventajas es que puede generar tablas para documentos html,
Word, Powerpoint y pdf. Aquí tienes un
bookdown sobre flextable.
Las tablas hechas con flextable
pueden exportarse a
:
officer
pagedown
Si quieres ver las posibilidades de este paquete tendrás que ir a su página web. Como ejemplo:
library(flextable)
ft <- flextable::flextable(head(iris), col_keys = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width", "Species" ))
ft %>% autofit() %>% align(align = "center", part = "all")
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species |
5.1 | 3.5 | 1.4 | 0.2 | setosa |
4.9 | 3.0 | 1.4 | 0.2 | setosa |
4.7 | 3.2 | 1.3 | 0.2 | setosa |
4.6 | 3.1 | 1.5 | 0.2 | setosa |
5.0 | 3.6 | 1.4 | 0.2 | setosa |
5.4 | 3.9 | 1.7 | 0.4 | setosa |
gt
packageEl paquete gt
también es relativamente nuevo pero creo
que se está convirtiendo en el paquete de referencia para hacer tablas
en R. Su página web está aquí. Hay
que entender un poco su terminología, pero una vez lo haces, hacer
tablas con gt
es muy sencillo.
Podemos empezar con una web con algunos
ejemplos de tablas hechas con gt
o ir a su página web o a su vignette
introductoria, o a este
post donde se anuncia la aparición de gt(v0.2)
.
Por su parte, Thomas Mock ha
realizado una serie de fantásticos post y presentaciones acerca del
paquete gt
; por ejemplo: Beautiful
tables in R, 10+
Guidelines for Better Tables in R, Functions
and Themes for gt tables, Embedding
custom HTML in gt tables y gt
- a (G)rammar of (T)ables.
Si quieres ver el material de un tutorial de gt
diseñado
por su desarrollador, Richard
Iannone, puedes encontrarlo en este
repo.
Hacer una tabla básica con gt
es tan fácil como hacer
gt(my_df)
. Veámoslo:
library(gt) #-remotes::install_github("rstudio/gt")
df_tt_4 %>% gt()
Gender | Primary | Secondary | Tertiary | Upper_second | NA |
---|---|---|---|---|---|
Female | 0.4377224 | 0.4804831 | 0.6159875 | 0.579646 | 0.8 |
Male | 0.5622776 | 0.5195169 | 0.3840125 | 0.420354 | 0.2 |
Nos queda arreglar/tunear/añadir elementos a nuestra tabla. Para
ello, lo primero es conocer como se llaman los distintos elementos de
una tabla hecha con gt
:
La tabla que hemos hecho antes con gt
, es una tabla muy
básica, sólo tiene dos elementos: the “Column Labels” y the “Table Body”
(que es donde están los valores). Vamos a añadir más elementos a nuestra
tabla:
tab_header()
. Con ella podemos añadir un título y subtitulo
a la tabla. Además podemos dar formato al título, usando
markdown!!, con la función md()
gt_tbl <- df_tt_4 %>% gt()
gt_tbl <- gt_tbl %>% tab_header(title = md("**Genero y nivel educativo**"),
subtitle = md("Porcentaje de *H y M* en cada nivel educativo"))
gt_tbl
Genero y nivel educativo | |||||
---|---|---|---|---|---|
Porcentaje de H y M en cada nivel educativo | |||||
Gender | Primary | Secondary | Tertiary | Upper_second | NA |
Female | 0.4377224 | 0.4804831 | 0.6159875 | 0.579646 | 0.8 |
Male | 0.5622776 | 0.5195169 | 0.3840125 | 0.420354 | 0.2 |
tab_source_note()
. Se pueden poner varias notas al
pie.gt_tbl <- gt_tbl %>%
tab_source_note(md("Fuente: datos de [PIAAC](http://www.oecd.org/skills/piaac/)")) %>%
tab_source_note(md("Obtenidos a partir del paquete RPIAAC"))
gt_tbl
Genero y nivel educativo | |||||
---|---|---|---|---|---|
Porcentaje de H y M en cada nivel educativo | |||||
Gender | Primary | Secondary | Tertiary | Upper_second | NA |
Female | 0.4377224 | 0.4804831 | 0.6159875 | 0.579646 | 0.8 |
Male | 0.5622776 | 0.5195169 | 0.3840125 | 0.420354 | 0.2 |
Fuente: datos de PIAAC | |||||
Obtenidos a partir del paquete RPIAAC |
tab_footnote()
. La función auxiliar
cells_body()
se usa para especificar que celdas de datos
tendrán la marca de la llamada al pie de página. Se puede incluso usar
condiciones para seleccionar que celdas llevarán la footnote.# Añadimos la misma nota al pie a dos celdas de la tabla
# Para seleccionar las celdas que llevarán la footnote se usa la f. `cell_body()`
gt_tbl <- gt_tbl %>% tab_footnote("Por encima del 60%", cells_body(c(Tertiary, `NA`), rows = 1) )
# Show the gt Table
gt_tbl
Genero y nivel educativo | |||||
---|---|---|---|---|---|
Porcentaje de H y M en cada nivel educativo | |||||
Gender | Primary | Secondary | Tertiary | Upper_second | NA |
Female | 0.4377224 | 0.4804831 | 1 0.6159875 | 0.579646 | 1 0.8 |
Male | 0.5622776 | 0.5195169 | 0.3840125 | 0.420354 | 0.2 |
Fuente: datos de PIAAC | |||||
Obtenidos a partir del paquete RPIAAC | |||||
1 Por encima del 60% |
gt(rowname_col = "nombre")
An easy way to generate a Stub part is by specifying a stub column in the gt() function with the rowname_col argument. Alternatively, we can have an input dataset with a column named rowname—this magic column will signal to gt that that column should be used as the stub, making row labels. Let’s add a stub with our islands_tbl dataset by modifying the call to gt():
gt_tbl <- df_tt_4 %>%
gt(rowname_col = "Gender") %>%
tab_stubhead(label = md("**Género**"))
gt_tbl
Género | Primary | Secondary | Tertiary | Upper_second | NA |
---|---|---|---|---|---|
Female | 0.4377224 | 0.4804831 | 0.6159875 | 0.579646 | 0.8 |
Male | 0.5622776 | 0.5195169 | 0.3840125 | 0.420354 | 0.2 |
Con gt
se pueden hacer muchas más cosas, puedes verlas
en su página web
, o
en esta secuencia de posts de Thomas Mock: 10+
Guidelines for Better Tables in R, Functions
and Themes for gt tables, Embedding
custom HTML in gt tables y gt
- a (G)rammar of (T)ables.
Un ejemplo que me ha gustado es este de Sharla Gelfand en el que añade una columna de imágenes a su tabla. Voy a hacerlo yo con 3 imágenes de Wikimedia Commons y una tabla con datos del data.frame iris.
Para ello, primero, añado una columna con los url’s de las fotos al
data.frame iris
:
urls_lirios <- c("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Iris_latifolia-Lac_Aule-Laruns-2522~2013_07_29.JPG/1280px-Iris_latifolia-Lac_Aule-Laruns-2522~2013_07_29.JPG",
"https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Lilium_michiganense_2.jpg/310px-Lilium_michiganense_2.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Nacho_Vegas_Fib.jpg/800px-Nacho_Vegas_Fib.jpg")
iris_con_urls <- iris %>% group_by(Species) %>% slice_max(Sepal.Length, n = 1) %>% add_column(urls_lirios) %>% ungroup()
Para después transformar los urls a imágenes con:
iris_con_urls %>% gt() %>%
gt::text_transform(locations = cells_body(columns = c(urls_lirios)),
fn = function(x) {gt::web_image(x, height = 30)})
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | urls_lirios |
---|---|---|---|---|---|
5.8 | 4.0 | 1.2 | 0.2 | setosa | |
7.0 | 3.2 | 4.7 | 1.4 | versicolor | |
7.9 | 3.8 | 6.4 | 2.0 | virginica |
En este
post se muestra como incluir, no solo imágenes, sino también
gráficos ggplot en una tabla hecha con gt
. Para ello usa el
paquete gtExtras
.
Por ejemplo está maravilla de tabla:
tt_08_img_02_tabla-ganadores-tour.png
Aquí
tienes un tutorial exclusivamente dedicado a tablas interactivas. Aquí
sólo presentaré algún ejemplo de tabla interactiva con los paquetes
DT
, reactable
y rpivottable
.
DT
packageEl paquete DT es “a wrapper of the JavaScript library DataTables”. La mejor forma de aprender a hacer tablas con DT es su página web.
El autor de DT
es Yihui
Xie el desarrollador de knitr
. El principal atractivo
del paquete es que permite hacer tablas interactivas con funcionalidades
como filtrar, paginar, ordenar, etc… los valores de las tablas.
Hacer tablas básicas con DT es muy sencillo. Por ejemplo:
DT::datatable(iris)
Las tablas hechas con DT tienen muchas más posibilidades. Como ejemplo la siguiente tabla.La mejor forma de aprender a hacer tablas con DT es su página web
DT::datatable(iris, filter = 'top',
options = list(pageLength = 5, autoWidth = TRUE ))
Una posibilidad de las tablas hechas con DT
es que se
pueden descargar. Lo explican en detalle aquí:
iris %>% DT::datatable(extensions = 'Buttons',
options = list(dom = 'Blfrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
pageLength = 5, autoWidth = TRUE ))
reactable
packageEl paquete reactable
también permite hacer tablas interactivas, esta vez se basa en la
librería de JS React.
El uso básico es tan sencillo como:
reactable::reactable(iris)
Pero se pueden hacer tablas tan fabulosas como esta o esta o esta.
rpivotTable
packageThe rpivotTable package is an R htmlwidget built around the pivottable JS library.
El paquete rpivotTable
genera tablas que permiten al usuario generar sus propios
resultados.
Como las tablas que genera el paquete rpivotTable
pueden, según las opciones que utilice el usuario de la tabla, cambiar
mucho de tamaño, voy a poner un ejemplo de esta tabla pero al final de
este documento, después de la bibliografía.
Crear una tabla con rpivotTable
es muy sencillo. Podéis
verla tabla al final del documento.
library(rpivotTable) #- remotes::install_github(c("ramnathv/htmlwidgets", "smartinsightsfromdata/rpivotTable"))
rpivotTable(df, rows = "Gender", cols = c("Country"), width = "100%", height = "400px")
R es un lenguaje/entorno para hacer análisis estadísticos, así que
muchas veces se han de presentar los resultados de estimación de algún
modelo estadístico, PERO, los resultados de estimación de estos modelos,
en general, no se almacenan en data.frames, sino en listas. ¿Es esto
importante? Sí, si lo queremos es presentar los resultados de estimación
en una tabla. La razón consiste en que los paquetes para tablas que
hemos visto anteriormente (gt
, DT
, etc …) solo
generan tablas de resultados almacenados en data.frames, así que no los
podremos usar, al menos directamente, para hacer tablas de resultados
asociados a modelos estadísticos. Afortunadamente hay unos cuantos
paquetes en R cuyo propósito es precisamente ese, presentar los
resultados de estimación de algún modelo o técnica estadística como
tablas.
Por ejemplo, vamos a estimar un modelo de regresión lineal. Utilizaremos estos datos:
urla <- "https://raw.githubusercontent.com/perezp44/iris_data/master/data/PIAAC_data_small.csv"
df <- read_csv(urla)
Con ellos estimamos el siguiente modelo lineal:
my_model <- lm(Wage_hour ~ Numeracy_score + Gender , data = df)
my_model
#>
#> Call:
#> lm(formula = Wage_hour ~ Numeracy_score + Gender, data = df)
#>
#> Coefficients:
#> (Intercept) Numeracy_score GenderMale
#> 3.46022 0.02988 0.52874
Generalmente hay funciones específicas para mostrar los resultados de
estimación, por ejemplo la función summary()
:
summary(my_model)
#>
#> Call:
#> lm(formula = Wage_hour ~ Numeracy_score + Gender, data = df)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -10.903 -3.453 -1.059 2.097 104.179
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 3.460222 0.351822 9.835 < 0.0000000000000002 ***
#> Numeracy_score 0.029881 0.001325 22.554 < 0.0000000000000002 ***
#> GenderMale 0.528745 0.134064 3.944 0.0000808 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 5.973 on 7992 degrees of freedom
#> (27 observations deleted due to missingness)
#> Multiple R-squared: 0.06324, Adjusted R-squared: 0.063
#> F-statistic: 269.8 on 2 and 7992 DF, p-value: < 0.00000000000000022
Los resultados mostrados por summary()
permiten hacerse
una idea de los resultados de la estimación pero no suelen ser adecuados
para presentarlos en un informe o en un artículo científico.
Hay bastantes paquetes que permiten obtener tablas con los resultados
de la estimación de modelos estadísticos, por ejemplo, el paquete finalfit
y muchos otros, sin embargo, en este tutorial solo se mostrará algún
ejemplo para los paquetes stargazer
,
modelsummary
,
gtsummary
y sjPlot
stargazer
El paquete stargazer
permite fácilmente presentar tablas con resultados de regresión. Permite
crear tablas en latex, html o ascii. Aquí hay una
cheatsheet sobre stargazer
stargazer::stargazer(my_model, type = "html")
Dependent variable: | |
Wage_hour | |
Numeracy_score | 0.030*** |
(0.001) | |
GenderMale | 0.529*** |
(0.134) | |
Constant | 3.460*** |
(0.352) | |
Observations | 7,995 |
R2 | 0.063 |
Adjusted R2 | 0.063 |
Residual Std. Error | 5.973 (df = 7992) |
F Statistic | 269.760*** (df = 2; 7992) |
Note: | p<0.1; p<0.05; p<0.01 |
Se pueden presentar los resultados de varios modelos en una tabla. Veámoslo. Para ello vamos a estimar tres modelos y los almacenaremos en una lista:
df <- df %>% mutate(Numeracy_score_b = ifelse(Numeracy_score > mean(Numeracy_score, na.rm = TRUE), 1, 0)) #- binary variable for logit model
my_models <- list()
my_models[['W: OLS 1']] <- lm( Wage_hour ~ Numeracy_score + Gender , df)
my_models[['Nu: OLS 2']] <- lm( Numeracy_score ~ Education + Gender , df)
my_models[['Nu: Logit 1']] <- glm( Numeracy_score_b ~ Education + Gender , df, family = binomial())
Una vez hemos estimado los 3 modelos y almacenado sus resultados de
estimación en la lista my_models
stargazer::stargazer(my_models, type = "html", title = "Results", align = TRUE)
Dependent variable: | |||
Wage_hour | Numeracy_score | Numeracy_score_b | |
OLS | OLS | logistic | |
(1) | (2) | (3) | |
Numeracy_score | 0.030*** | ||
(0.001) | |||
EducationSecondary | 46.752*** | 1.788*** | |
(1.983) | (0.134) | ||
EducationTertiary | 89.531*** | 3.424*** | |
(2.137) | (0.143) | ||
EducationUpper_second | 75.903*** | 2.857*** | |
(2.388) | (0.150) | ||
GenderMale | 0.529*** | 12.947*** | 0.448*** |
(0.134) | (1.000) | (0.049) | |
Constant | 3.460*** | 196.447*** | -2.270*** |
(0.352) | (1.955) | (0.134) | |
Observations | 7,995 | 8,014 | 8,014 |
R2 | 0.063 | 0.229 | |
Adjusted R2 | 0.063 | 0.229 | |
Log Likelihood | -4,894.853 | ||
Akaike Inf. Crit. | 9,799.706 | ||
Residual Std. Error | 5.973 (df = 7992) | 44.382 (df = 8009) | |
F Statistic | 269.760*** (df = 2; 7992) | 596.336*** (df = 4; 8009) | |
Note: | p<0.1; p<0.05; p<0.01 |
modelsummary
El paquete modelsummary
es más moderno, y también permite hacer tablas resumen de estimaciones
de modelos. Por ejemplo:
library(modelsummary) #- remotes::install_github('vincentarelbundock/modelsummary')
mm <- modelsummary::msummary(my_models, title = "Resultados de estimación")
mm
W: OLS 1 | Nu: OLS 2 | Nu: Logit 1 | |
---|---|---|---|
(Intercept) | 3.460 | 196.447 | −2.270 |
(0.352) | (1.955) | (0.134) | |
Numeracy_score | 0.030 | ||
(0.001) | |||
GenderMale | 0.529 | 12.947 | 0.448 |
(0.134) | (1.000) | (0.049) | |
EducationSecondary | 46.752 | 1.788 | |
(1.983) | (0.134) | ||
EducationTertiary | 89.531 | 3.424 | |
(2.137) | (0.143) | ||
EducationUpper_second | 75.903 | 2.857 | |
(2.388) | (0.150) | ||
Num.Obs. | 7995 | 8014 | 8014 |
R2 | 0.063 | 0.229 | |
R2 Adj. | 0.063 | 0.229 | |
AIC | 51272.9 | 83541.3 | 9799.7 |
BIC | 51300.8 | 83583.2 | 9834.7 |
Log.Lik. | −25632.432 | −41764.631 | −4894.853 |
RMSE | 5.97 | 44.37 | 0.46 |
Tiene muchas más posibilidades.
gtsummary
El paquete gtsummary
permite, además de realizar tablas resumen de data.frames, confeccionar
tablas de modelos de regresión de forma sencilla y muy
configurables.
Como he dicho gtsummary
tiene muchas posibilidades,
algunas de ellas puedes verlas en este video, en estas transparencias,
o en este post.
Como ejemplos de su uso, una tabla resumen de un data.frame:
iris %>% gtsummary::tbl_summary()
Characteristic | N = 1501 |
---|---|
Sepal.Length | 5.80 (5.10, 6.40) |
Sepal.Width | 3.00 (2.80, 3.30) |
Petal.Length | 4.35 (1.60, 5.10) |
Petal.Width | 1.30 (0.30, 1.80) |
Species | |
setosa | 50 (33%) |
versicolor | 50 (33%) |
virginica | 50 (33%) |
1 Median (IQR); n (%) |
Ahora una tabla de un modelo de regresión:
gtsummary::tbl_regression(my_model)
Characteristic | Beta | 95% CI1 | p-value |
---|---|---|---|
Numeracy_score | 0.03 | 0.03, 0.03 | <0.001 |
Gender | |||
Female | — | — | |
Male | 0.53 | 0.27, 0.79 | <0.001 |
1 CI = Confidence Interval |
sjPlot
El paquete sjPlot
tiene muchas posibilidades, pero en concreto puede hacer tablas de
modelos:
sjPlot::tab_model(my_model)
Wage hour | |||
---|---|---|---|
Predictors | Estimates | CI | p |
(Intercept) | 3.46 | 2.77 – 4.15 | <0.001 |
Numeracy score | 0.03 | 0.03 – 0.03 | <0.001 |
Gender [Male] | 0.53 | 0.27 – 0.79 | <0.001 |
Observations | 7995 | ||
R2 / R2 adjusted | 0.063 / 0.063 |
Tiene otras utilidades, por ejemplo:
sjPlot::plot_model(my_model)
Hay muchos más paquetes especializados en hacer tablas con resultados
de la estimación de modelos. Aquí solo mostraré algo de 3 de ellos, los
paquetes report
y apaTables
, pero existen
otros paquetes como:
flipRegression
.
En este
post explican como usar sus funciones.
papaja
,
es un paquete para producir documentos y tablas conformes a la American
Psychological Association (APA) manuscript guidelines (6th
Edition).
academicWriteR
,
está relacionado con papaja
. Es un paquete con algunas
funciones útiles para escribir informes estadísticos. Por ejemplo la
función count_word()
.
report
Según pone en su página web:
It automatically produces reports of models and dataframes according to best practice guidelines (e.g., APA’s style guide), ensuring standardization and quality in results reporting.
library(report) #- devtools::install_github("neuropsychology/report")
rr <- report(my_model)
as.report_table(rr)
También permite obtener una descripción verbal de los resultados en texto:
as.report_text(rr)
apaTables
Según su página web:
This package creates Word files (.doc files) containing APA style tables for several types of analyses. Using this package minimizes transcription errors and reduces the number commands needed by the user.
library(apaTables) #- install.packages("apaTables",dep = TRUE)
apa.reg.table(my_model)
En este hilo de Twitter se habla sobre los mejores paquetes R para hacer tablas.
David Keyes señala aquí, que quieres hacer tablas para Word, posiblemente la mejor opción sea el paquete flextable
Si lo que necesitas es hacer tablas para documentos pdf, puedes empezar por aquí. Al final remite a este otro documento. También puedes usar este generador de tablas.
Si quieres tablas de porcentajes etc… aquí plantean varias posibilidades para hacerlas.
Si quieres hacer tablas parecidas a las que se hacen con PROC
FREQ de SAS o CROSSTABS en SPSS puedes usar la función
CrossTable()
del paquete gmodels
. En este
post muestran otras alternativas.
Si necesitas hacer 3-way tables, puedes hacerlas
con stats::xtabs()
. Para después imprimirla con
ftable(my_table)
. Lo explicanaquí
y aquí.
Por ejemplo:
my_table <- stats::xtabs(~ Education + Country + Gender, data = df)
stats::ftable(my_table)
Otra alternativa para tablas de frecuencias de 3 variables es usar
janitor::tabyl(X1, X2, X3)
. Lo explican aquí
df %>% janitor::tabyl(Education, Country, Gender)
Los paquetes rhandsontable
y excelR
permiten crear tablas editables por el usuario.
El paquete expss
hace tablas similares a las de SPSS.
En este post hacen un repaso de paquetes que permiten crear tablas con características especiales como que sus valores sean editables por el usuario, que tengan formato condicionado a los valores de las tablas, que se puedan ordenar y filtrar sus valores etc …
Un post con las tablas ganadoras del “2020 RStudio Table Contest”.
rpivotTable
El paquete rpivotTable
:
The rpivotTable package is an R htmlwidget built around the pivottable library.
rpivotTable::rpivotTable(df, rows = "Gender", cols = c("Country"), width = "100%", height = "400px")