Capítulo 4 Tidyverse

La mayoría del contenido visto en Introducción a R se ha enfocado al uso del lenguaje R y al uso de ciertas funciones sin considerar su rendimiento. Como bien se menciono en su momento, R no esta diseñado para ser rápido; lo que a través de los años ha sido un tema de interés a medida que la información aumenta y las necesidades por analizar y ejecutar procesos que traten con grandes cantidades de datos. Por tales razones, el uso de paquetes que estén diseñados para trabajar de manera eficiente ha sido fundamental en la mayoría de lenguajes de programación; tal es el caso del Tidyverse.

Como bien se especifica en su página oficial, “The tidyverse is an opinionated collection of R packages designed for data science. All packages share an underlying design philosophy, grammar, and data structures”. Estos paquetes están enfocados a tener un mejor flujo de escritura, a tener un mejor entendimiento de la estructura de los procesos, dar funciones que solucionen problemas comunes y, como ya se menciono, mejorar el rendimiento de las funciones.

Dentro de este conjunto de paquetes se encuentran funciones para leer distintos tipos de archivos como aquellos con extensión .csv y .xls, además aquellas para reconocer archivos dados por SPSS, Stata y SAS, manipular archivos JSON, XML, dar una interfaz para trabajar con APIs, hacer web scraping y tener comunicación con diferentes administradores de bases de datos como SQL, MariaDB, etc.

Se tienen paquetes especializados en la manipulación y limpieza de datos, también para crear modelos con estos, dar características especiales como catalogar y tratar a ciertas variables como fechas y factores, aplicar técnicas de expresiones regulares en dichos datos, aplicar funciones optimizadas que pueden remplazar a las de la familia apply y crear gráficas profesionales con una mayor fluidez.

Aquí se verán solo algunos de los paquetes que componen todo este “universo limpio” y se comenzará con un operador fundamental proporcionado por el paquete magritt: %>%, el cual se podrá utilizar casi siempre en R.

Dicho operador tiene por nombre pipe, el cual tiene un uso similar al dado en otros lenguajes de programación como Python: . y bash: |. Este tiene como objetivo encadenar procesos de tal forma que el resultado dado en la cadena sirve como input del siguiente eslabón en dicha cadena, lo cual ayuda a evitar el anidamiento de funciones, minimizar la cantidad de objetos locales y facilitar la lectura e implementación de una secuencia de operaciones.

Supongase que se desea resolver la tarea sencilla de aplicar una cantidad definida de operaciones sobre un número; por ejemplo, obtener la raíz cuadrada del logaritmo natural de un número multiplicado por el cuadrado de otro, a dicho valor sumarlo con los primeros 10 naturales y, finalmente, obtener la raíz cuadrada de dicho resultado. Esto se puede resolver de la siguiente manera; supongamos que los números son 10 y 20.

sqrt(sum(sqrt(log(prod(10, 20^2))), 1:10))
[1] 7.607887

En tal caso se tuvo que anidar las funciones para aplicar estas cada resultado obtenido. Con %>% la solución se ve de esta forma

library(magrittr)
10 %>% # 10
  prod(20^2) %>% #se multiplica por 20^2
  log() %>% #se obtiene el logaritmo de dicho número
  sqrt() %>% #Se obtiene la raíz cuadrada
  sum(1:10) %>% #A eso se le suman los primeros 10 naturales
  sqrt() #Se obtiene la raíz cuadrada.
[1] 7.607887

La anterior solución tiene una estructura más fiel a como se fue resolviendo el problema poco a poco sin tiene que escribir hacia la izquierda para aplicar resultados de funciones anidadas. Más adelante se verá que el uso de dicho operador resulta fundamental para ahorra tiempo al momento de escribir código.

Otro ejemplo:

matrix(1:100, ncol = 5, byrow = 20) %>% #Creación de una matriz con los primeros 100 naturales.
  rowSums(10) %>% #Se suma por renglón añadiendo 10 unidades a dicho resultado.
  as.matrix() %>% #Se convierte dicho resultado a matriz.
  scale() %>% #Se normalizan los datos.
  sum() #Se comprueba que estos sumen media cero.
[1] 0

Como la mayoría de la información que se utilizará se tendrá que cargar con alguna librería, aquí se dejan algunos ejemplos de algunas funciones útiles en la lectura de información.

La función read_csv() es una de las más comunes para leer archivos separados por comas, en la cual se puede dar el path del archivo o la dirección URL de dichos datos. En este caso se esta utilizando unos datos donde se relaciona la información sobre exceso de velocidad de ciertos automóviles y señales de advertencia. Para más información consúltese el siguiente enlace.

library(readr)
library(tibble)
(amis <- readr::read_csv("Data/amis.csv") %>% head(10))
# A tibble: 10 x 5
      X1 speed period warning  pair
   <dbl> <dbl>  <dbl>   <dbl> <dbl>
 1     1    26      1       1     1
 2     2    26      1       1     1
 3     3    26      1       1     1
 4     4    26      1       1     1
 5     5    27      1       1     1
 6     6    28      1       1     1
 7     7    28      1       1     1
 8     8    28      1       1     1
 9     9    28      1       1     1
10    10    29      1       1     1
readr::read_csv("https://vincentarelbundock.github.io/Rdatasets/csv/boot/amis.csv") %>% head(10)
# A tibble: 10 x 5
      X1 speed period warning  pair
   <dbl> <dbl>  <dbl>   <dbl> <dbl>
 1     1    26      1       1     1
 2     2    26      1       1     1
 3     3    26      1       1     1
 4     4    26      1       1     1
 5     5    27      1       1     1
 6     6    28      1       1     1
 7     7    28      1       1     1
 8     8    28      1       1     1
 9     9    28      1       1     1
10    10    29      1       1     1

En caso de que se desee leer un archivo por algún otro delimitador, se puede usar la función read_delim() y en el caso de tener archivos donde el delimitador sea “\t” usar la función read_tsv(). Los datos que se utilizan son proporcionados por el US Census Bureau, los cuales fueron indirectamente obtenidos del siguiente enlance.

library(readxl)
(US_Census_Bureau <- readr::read_delim("Data/US Census Bureau.txt", delim = "|"))
# A tibble: 54 x 11
   X1      `1900` `1901` `1902` `1903` `1904` `1905` `1906` `1907` `1908` `1909`
   <chr>    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl> <chr> 
 1 U.S.     76094  77585  79160  80632  82165  83820  85437  87000  88709 90,492
 2 Northe…  21059  21401  21815  22248  22716  23214  23769  24320  24879 25,440
 3 North_…  26359  26722  27126  27446  27830  28203  28524  28868  29187 29,530
 4 South    24565  25114  25599  26055  26492  27003  27475  27879  28406 28,963
 5 West      4112   4351   4620   4882   5127   5398   5671   5934   6234 6,557 
 6 AL        1830   1907   1935   1957   1978   2012   2045   2058   2070 2,108 
 7 AR        1314   1341   1360   1384   1419   1447   1465   1484   1513 1,545 
 8 AZ         124    131    138    144    151    158    167    176    186 196   
 9 CA        1490   1550   1623   1702   1792   1893   1976   2054   2161 2,282 
10 CO         543    581    621    652    659    680    707    733    757 775   
# … with 44 more rows
read_tsv("Data/US Census Bureautsv.txt")
# A tibble: 54 x 11
   X1      `1900` `1901` `1902` `1903` `1904` `1905` `1906` `1907` `1908` `1909`
   <chr>    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl> <chr> 
 1 U.S.     76094  77585  79160  80632  82165  83820  85437  87000  88709 90,492
 2 Northe…  21059  21401  21815  22248  22716  23214  23769  24320  24879 25,440
 3 North_…  26359  26722  27126  27446  27830  28203  28524  28868  29187 29,530
 4 South    24565  25114  25599  26055  26492  27003  27475  27879  28406 28,963
 5 West      4112   4351   4620   4882   5127   5398   5671   5934   6234 6,557 
 6 AL        1830   1907   1935   1957   1978   2012   2045   2058   2070 2,108 
 7 AR        1314   1341   1360   1384   1419   1447   1465   1484   1513 1,545 
 8 AZ         124    131    138    144    151    158    167    176    186 196   
 9 CA        1490   1550   1623   1702   1792   1893   1976   2054   2161 2,282 
10 CO         543    581    621    652    659    680    707    733    757 775   
# … with 44 more rows

El paquete readxl esta diseño para leer archivos excel. La siguiente información proporciona la población por sexo y entidad federativa según grupos de edad quinquenales de acuerdo a los tabulados básicos de la CONAPO.

(Population_FEntity <- readxl::read_xls("Data/Population_Sex_FEntity.xls", range = "B5:Q38"))
# A tibble: 33 x 16
   ...1    ...2 `0 a 4` `5 a 9` `10 a 14` `15 a 19` `20 a 24` `25 a 29`
   <chr>  <dbl>   <dbl>   <dbl>     <dbl>     <dbl>     <dbl>     <dbl>
 1 Repú… 1.03e8    9.31   10.4      11.2      10.1       8.33      7.33
 2 Agua… 1.07e6   10.3    11.0      11.1      10.9       8.27      7.34
 3 Baja… 2.86e6    9.41   10.3      11.1      10.7       8.43      7.82
 4 Baja… 5.12e5    9.00    8.44     10.1      10.8       8.75      7.37
 5 Camp… 7.56e5    8.34   10.4      12.7       9.52      9.23      7.74
 6 Coah… 2.50e6    9.78   10.1      10.6       9.96      8.31      7.11
 7 Coli… 5.69e5    8.09    8.43     10.0      11.0       9.29      7.11
 8 Chia… 4.29e6   11.4    11.8      13.2      11.0       9.16      7.15
 9 Chih… 3.24e6    7.83   10.2      11.0       9.49      7.71      6.03
10 Dist… 8.74e6    7.82    8.82      8.09      8.19      7.86      7.89
# … with 23 more rows, and 8 more variables: `30 a 34` <dbl>, `35 a 39` <dbl>,
#   `40 a 44` <dbl>, `45 a 49` <dbl>, `50 a 54` <dbl>, `55 a 59` <dbl>, `60 y
#   más` <dbl>, NE <dbl>

Algo que hay que tener en cuenta al trabajar con los paquetes del tidyverse es el uso de tibbles en lugar de data frames.

class(amis)
[1] "tbl_df"     "tbl"        "data.frame"

Un tibble es una versión moderna de un data frame que trabaja de manera perezosa (es decir, que realiza menos operaciones) evitando problemas comunes y supuestos que un data frame puede llegar a asumir. Por ejemplo, los tibbles no coercionan automáticamente los caracteres a factores, no crean nombres para las observaciones y no cambia los nombres de columnas que sean nombres no sintácticos, por ejemplo

tibble(":)" = "feliz", ":("="triste")
# A tibble: 1 x 2
  `:)`  `:(`  
  <chr> <chr> 
1 feliz triste
data.frame(":)" = "feliz", ":("="triste")
    X..  X...1
1 feliz triste

Se pueden utilizar variables desde la construcción del tibble

(t <- tibble(x = 1:20, y = x-1))
# A tibble: 20 x 2
       x     y
   <int> <dbl>
 1     1     0
 2     2     1
 3     3     2
 4     4     3
 5     5     4
 6     6     5
 7     7     6
 8     8     7
 9     9     8
10    10     9
11    11    10
12    12    11
13    13    12
14    14    13
15    15    14
16    16    15
17    17    16
18    18    17
19    19    18
20    20    19
  • ¿Qué sucede al ejecutar data.frame(x = 1:20, y = x-1)?

Además se tienen diferencias respecto a la impresión entre un tibble y un data frame, un tibble permite agregar listas directamente sin tener que usar la función I() y los tibbles nunca hacen emparejamiento parcial.

t2 <- tibble(xy=1:20, z = 1:20)
d2 <- data.frame(xy = 1:20, z = 1:20)
str(t2$x)
 NULL
str(d2$x)
 int [1:20] 1 2 3 4 5 6 7 8 9 10 ...

Finalmente, los tibbles permiten hacer substracciones con el operador pipe.

#t$x
t %>% .$x
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
#t[["x"]]
t %>% .[["x"]]
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
  • ¿Qué hace la función tribble()?