Capítulo 3 Funcionamiento avanzado de R

3.1 Introducción

En el capitulo anterior se mostraron de manera muy rápida las funciones básicas de R, esto porque de ahora en adelante se va a enfocar en el uso de funciones del tidyverse (Wickham et al., 2019). Este meta-paquete es uno que engloba a un montón de paquetes que se rigen bajo el paradigma de datos ordenados (tidy data). Datos ordenados quiere decir una observación por fila y cada variable en su columna. Lo que se cubre en este capitulo y más se puede encontrar en “R for Data Science” (Grolemund & Wickham, 2016).

En este capitulo se van a utilizar los siguientes paquetes:

library(babynames)
library(nycflights13)
library(gapminder)
library(DT)
library(rio)
library(tidyverse)

Los tres primeros corresponden con conjuntos de datos. Así mismo se vuelven a importar los datos con que se venia trabajando:

data("airquality")
dat1 <- import("data/LungCapData2.csv", setclass = 'tibble')
titanic <- import("data/titanic.csv", setclass = 'tibble')

Los paquetes más usados e importantes del tidyverse son:

  • dplyr: Manipulación de datos mediante selección, filtrado, creación, agrupamiento, arreglo y resumen de tablas
  • tidyr: Convierte datos a ordenados y viceversa
  • ggplot2: Paquete para crear gráficos de alta calidad y personalizables
  • purrr: Brinda funciones para la racionero sobre vectores, listas y tablas
  • forcats: Manipulación de datos categóricos (factores)
  • stringr: Manipulación de datos de texto
  • lubridate: Manipulación de fechas

Un punto importante a destacar, es que en todas (sino la mayoría) de las funciones del tidyverse la tabla de datos es el primer argumento.

3.2 Operadores lógicos

Operadores lógicos permiten hacer comparaciones o pruebas, donde usualmente el resultado es TRUE o FALSE. En términos numéricos TRUE equivale a 1 y FALSE a 0. Los operadores lógicos más usados son:

  • <, menor que
  • <=, menor o igual que
  • ==, igual que
  • !=, no igual que
  • >, mayor que
  • >=, mayor o igual que
  • %in%, pertenencia

Aquí se asigna 4 a x y se aplican varios de los operadores lógicos para cuestionar el contenido de el objeto x.

x <- 4
x == 4
## [1] TRUE
x != 4
## [1] FALSE
x < 4
## [1] FALSE
x <= 5
## [1] TRUE

Estos operadores se pueden usar igualmente en vectores. Recordando que el resultado de una operación lógica es TRUE o FALSE, el vector resultante es del tipo lógico. Si se desea acceder a los elementos que cumplen la condición hay que aplicar el vector lógico sobre el vector, donde va a extraer los elementos que coinciden con TRUE.

Aquí se crea un vector numérico, y se tratan de extraer los valores menores a 70. Se muestra la forma básica de R (vector[condicion]) y una forma más directa e intuitiva que ofrece purrr.

y <- c(95, 90, 58, 87, 62, 75)
y < 70
## [1] FALSE FALSE  TRUE FALSE  TRUE FALSE
y[y < 70]
## [1] 58 62
keep(y, ~.x < 70) # purrr
## [1] 58 62

3.3 Operador de secuencia (Pipe operator)

Uno de los operadores básicos en el tidyverse es el pipe operator (%>%). Este permite que el resultado antes del operador sea la (primer) entrada de lo que se va a hacer después del operador (x %>% f(y) es lo mismo que f(x,y)).

El shortcut para escribirlo es:

  • Mac: Cmd + Shift + M
  • Windows: Ctrl + Shift + M

La ventaja es que permite encadenar operaciones sin necesidad de salvar objetos intermedios y es más fácil de leer que encerrar operaciones una dentro de la otra. Se ejemplifica con un caso sencillo, donde se tiene un vector de errores y se quiere calcular el error cuadrático medio (RMSE por sus siglas en ingles).

set.seed(26)
e = runif(50,-10,10)
round(sqrt(mean(e^2)),3) # forma clasica
## [1] 5.595
e %>% .^2 %>% mean() %>% sqrt() %>% round(3) # usando el operador
## [1] 5.595

Lo anterior se lee: agarre el vector e, eleve sus valores al cuadrado, después calcule la media, después sáquele la raíz y por ultimo redondeélo a 3 cifras.

3.4 Resumen de variables

Para resumir datos la función principal es summarise, que colapsa una o varias columnas a un dato resumen. Muchas veces se tiene una variable agrupadora (factor) en los datos y se requiere calcular estadísticas por grupo, para esto se usa group_by junto con summarise. En group_by se pueden incluir más de una variable agrupadora.

Funciones que ayudan a resumir datos son:

  • first(n), el primer elemento del vector x
  • last(x), el ultimo elemento del vector x
  • nth(x, n), el elemento n del vector x
  • n(), el numero de filas en una tabla u observaciones en un grupo
  • n_distinct(x), el numero de valores únicos en el vector x
dat1 %>% 
  group_by(Gender) %>% 
  summarise(mean(Age))
## # A tibble: 2 x 2
##   Gender `mean(Age)`
##   <chr>        <dbl>
## 1 female        9.84
## 2 male         10.0
dat1 %>% 
  group_by(Gender,Smoke) %>% 
  summarise(mean(Age))
## # A tibble: 4 x 3
##   Gender Smoke `mean(Age)`
##   <chr>  <chr>       <dbl>
## 1 female no           9.37
## 2 female yes         13.3 
## 3 male   no           9.69
## 4 male   yes         13.9
dat1 %>% 
  group_by(Gender) %>% 
  summarise(N=n(),
            mean(Age),
            mean(Height),
            mean(LungCap))
## # A tibble: 2 x 5
##   Gender     N `mean(Age)` `mean(Height)` `mean(LungCap)`
##   <chr>  <int>       <dbl>          <dbl>           <dbl>
## 1 female   318        9.84           60.2            5.35
## 2 male     336       10.0            62.0            6.44

3.5 Selección y renombre de variables

Para seleccionar columnas la función es select, la cual puede usar números o nombres y los nombres no tienen que llevar comillas. Esto también permite reordenar las columnas de una tabla. Para el caso de obtener una columna como vector se usa pull con el numero o nombre de la columna a jalar. Durante la selección se puede cambiar el nombre de la variable, o usando rename.

Funciones que ayudan a seleccionar variables son:

  • starts_with('X'), todas las columnas que empiezan con ‘X’,
  • ends_with('X'), todas las columnas que terminan con ‘X’,
  • contains('X'), todas las columnas que contienen ‘X’,
  • matches('X'), todas las columnas que coinciden con ‘X’

Aquí se mezcla funciones de resumen y selección para crear resumen de las variables seleccionadas.

dat1 %>% 
  group_by(Gender) %>% 
  select(Age,Height) %>% 
  summarise_all(mean)
## # A tibble: 2 x 3
##   Gender   Age Height
##   <chr>  <dbl>  <dbl>
## 1 female  9.84   60.2
## 2 male   10.0    62.0
dat1 %>% 
  group_by(Gender) %>% 
  select(Age,Height) %>% 
  summarise_all(.funs = list(~mean(.),
                             ~sd(.)))
## # A tibble: 2 x 5
##   Gender Age_mean Height_mean Age_sd Height_sd
##   <chr>     <dbl>       <dbl>  <dbl>     <dbl>
## 1 female     9.84        60.2   2.93      4.79
## 2 male      10.0         62.0   2.98      6.33

Aquí se muestra la selección de variables, que resulta en un reordenamiento de las mismas. Así mismo se puede deseleccionar lo que no se quiere, usando - para indicar las columnas que no se quieren.

airquality %>% 
  select(Temp,Wind,Ozone)
## # A tibble: 153 x 3
##     Temp  Wind Ozone
##    <int> <dbl> <int>
##  1    67   7.4    41
##  2    72   8      36
##  3    74  12.6    12
##  4    62  11.5    18
##  5    56  14.3    NA
##  6    66  14.9    28
##  7    65   8.6    23
##  8    59  13.8    19
##  9    61  20.1     8
## 10    69   8.6    NA
## # … with 143 more rows
dat1 %>% 
  select(-Smoke)
## # A tibble: 654 x 4
##      Age LungCap Height Gender
##    <int>   <dbl>  <dbl> <chr> 
##  1     9    3.12   57   female
##  2     8    3.17   67.5 female
##  3     7    3.16   54.5 female
##  4     9    2.67   53   male  
##  5     9    3.68   57   male  
##  6     8    5.01   61   female
##  7     6    3.76   58   female
##  8     6    2.24   56   female
##  9     8    3.96   58.5 female
## 10     9    3.83   60   female
## # … with 644 more rows
dat1 %>% 
  pull(Age)
##   [1]  9  8  7  9  9  8  6  6  8  9  6  8  8  8  8  7  5  6  9  9  5  5  4  7  9
##  [26]  3  9  5  8  9  5  9  8  7  5  8  9  8  8  8  9  8  5  8  5  9  7  8  6  8
##  [51]  5  9  9  8  6  9  9  7  4  8  8  8  6  4  8  6  9  7  5  9  8  8  9  9  9
##  [76]  7  5  5  9  6  7  6  8  8  7  8  7  9  5  9  9  9  7  8  8  9  9  9  7  8
## [101]  8  7  9  4  9  6  8  6  7  7  8  7  7  7  7  8  7  5  8  7  9  7  7  6  8
## [126]  8  8  9  7  8  9  8  8  9  8  6  6  8  9  5  7  9  6  9  9  9  6  8  9  8
## [151]  8  9  9  9  7  8  6  9  9  9  7  8  5  8  9  6  9  6  8  5  7  7  4  9  8
## [176]  9  9  9  5  9  7  6  9  9  9  7  5  8  9  7  9  8  9  6  6  8  9  5  6  6
## [201]  9  7  9  8  5  7  6  9  7  9  9  8  9  7  9  4  9  5  8  9  8  3  9  8  6
## [226]  9  8  8  7  6  8  9  4  7  8  8  9  6  8  6  8  9  8  7  9  8  7  9  8  9
## [251]  6  8  9  8  9  9  8  7  5  7  8  9  9  6  8  7  9  7  7  5  9  9  8  8  9
## [276]  6  7  5  9  5  7  6  8  7  8  4  8  5  8  7  7  9  9  8  9  6  8  9  4  6
## [301]  7  9  8  6  8  7  5  8  7 11 10 14 11 11 12 10 11 10 14 13 14 12 12 10 13
## [326] 10 11 10 11 10 13 14 11 10 11 13 10 10 12 10 10 10 11 11 11 10 11 11 13 13
## [351] 11 11 14 11 10 10 10 14 13 10 14 10 11 13 12 13 10 13 11 14 11 13 11 11 10
## [376] 11 11 10 11 13 12 10 10 14 11 10 11 10 11 13 13 10 11 11 12 10 10 11 10 11
## [401] 14 13 12 11 11 11 14 12 10 12 11 10 11 13 10 10 11 13 10 11 10 13 11 10 11
## [426] 11 14 11 13 11 11 10 13 10 13 10 12 10 14 12 10 11 14 12 10 10 10 10 12 13
## [451] 11 12 11 12 11 11 12 12 13 11 12 10 12 13 10 12 10 12 10 11 10 12 14 10 10
## [476] 12 10 10 13 12 12 11 13 12 10 11 11 13 12 13 13 10 12 12 14 11 10 13 11 11
## [501] 13 12 10 10 12 13 11 10 11 11 11 11 11 14 12 13 13 10 12 10 10 12 11 12 11
## [526] 11 12 12 14 11 10 11 12 13 12 11 11 11 14 11 13 12 10 12 13 10 10 10 10 14
## [551] 12 11 11 12 14 14 10 11 11 10 10 12 12 11 12 10 12 13 10 12 10 13 12 10 12
## [576] 10 11 12 11 12 10 13 12 11 11 11 11 12 14 11 11 12 14 11 13 11 10 13 12 11
## [601] 13 14 10 11 11 15 15 18 19 19 16 17 15 15 15 15 15 19 18 16 17 16 15 15 15
## [626] 18 17 15 17 17 16 17 15 15 16 16 15 18 15 16 17 16 16 15 18 15 16 17 16 16
## [651] 15 18 16 15

3.5.1 select helpers

Se muestran diferentes usos y resultados de usar select helpers para facilidad de selección de columnas que cumplan con ciertos criterios. A su vez, se ejemplifica el renombrar las columnas durante la selección o usando rename (nuevo_nombre = nombre_actual). Un operador especial es everything() que selecciona todo; esto es útil cuando se quiere reordenar y poner una o varias columnas de primero y después el resto sin tener que escribir todos los nombres.

select(storms, name:pressure) # columnas desde name hasta pressure
## # A tibble: 10,010 x 11
##    name   year month   day  hour   lat  long status      category  wind pressure
##    <chr> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <chr>       <ord>    <int>    <int>
##  1 Amy    1975     6    27     0  27.5 -79   tropical d… -1          25     1013
##  2 Amy    1975     6    27     6  28.5 -79   tropical d… -1          25     1013
##  3 Amy    1975     6    27    12  29.5 -79   tropical d… -1          25     1013
##  4 Amy    1975     6    27    18  30.5 -79   tropical d… -1          25     1013
##  5 Amy    1975     6    28     0  31.5 -78.8 tropical d… -1          25     1012
##  6 Amy    1975     6    28     6  32.4 -78.7 tropical d… -1          25     1012
##  7 Amy    1975     6    28    12  33.3 -78   tropical d… -1          25     1011
##  8 Amy    1975     6    28    18  34   -77   tropical d… -1          30     1006
##  9 Amy    1975     6    29     0  34.4 -75.8 tropical s… 0           35     1004
## 10 Amy    1975     6    29     6  34   -74.8 tropical s… 0           40     1002
## # … with 10,000 more rows
storms %>% 
  select(-c(name, pressure)) # columnas menos name y pressure
## # A tibble: 10,010 x 11
##     year month   day  hour   lat  long status category  wind ts_diameter
##    <dbl> <dbl> <int> <dbl> <dbl> <dbl> <chr>  <ord>    <int>       <dbl>
##  1  1975     6    27     0  27.5 -79   tropi… -1          25          NA
##  2  1975     6    27     6  28.5 -79   tropi… -1          25          NA
##  3  1975     6    27    12  29.5 -79   tropi… -1          25          NA
##  4  1975     6    27    18  30.5 -79   tropi… -1          25          NA
##  5  1975     6    28     0  31.5 -78.8 tropi… -1          25          NA
##  6  1975     6    28     6  32.4 -78.7 tropi… -1          25          NA
##  7  1975     6    28    12  33.3 -78   tropi… -1          25          NA
##  8  1975     6    28    18  34   -77   tropi… -1          30          NA
##  9  1975     6    29     0  34.4 -75.8 tropi… 0           35          NA
## 10  1975     6    29     6  34   -74.8 tropi… 0           40          NA
## # … with 10,000 more rows, and 1 more variable: hu_diameter <dbl>
iris %>% 
  select(starts_with("Sepal")) # columnas que empiezan con 'Sepal'
## # A tibble: 150 x 2
##    Sepal.Length Sepal.Width
##           <dbl>       <dbl>
##  1          5.1         3.5
##  2          4.9         3  
##  3          4.7         3.2
##  4          4.6         3.1
##  5          5           3.6
##  6          5.4         3.9
##  7          4.6         3.4
##  8          5           3.4
##  9          4.4         2.9
## 10          4.9         3.1
## # … with 140 more rows
iris %>% 
  select(ends_with("Width")) # columnas que terminan con 'Width'
## # A tibble: 150 x 2
##    Sepal.Width Petal.Width
##          <dbl>       <dbl>
##  1         3.5         0.2
##  2         3           0.2
##  3         3.2         0.2
##  4         3.1         0.2
##  5         3.6         0.2
##  6         3.9         0.4
##  7         3.4         0.3
##  8         3.4         0.2
##  9         2.9         0.2
## 10         3.1         0.1
## # … with 140 more rows
storms %>% 
  select(contains("d")) # columnas que contienen 'd'
## # A tibble: 10,010 x 4
##      day  wind ts_diameter hu_diameter
##    <int> <int>       <dbl>       <dbl>
##  1    27    25          NA          NA
##  2    27    25          NA          NA
##  3    27    25          NA          NA
##  4    27    25          NA          NA
##  5    28    25          NA          NA
##  6    28    25          NA          NA
##  7    28    25          NA          NA
##  8    28    30          NA          NA
##  9    29    35          NA          NA
## 10    29    40          NA          NA
## # … with 10,000 more rows
iris %>% 
  select(Especie = Species, everything()) # renombrar seleccion y seleccionar el resto
## # A tibble: 150 x 5
##    Especie Sepal.Length Sepal.Width Petal.Length Petal.Width
##    <fct>          <dbl>       <dbl>        <dbl>       <dbl>
##  1 setosa           5.1         3.5          1.4         0.2
##  2 setosa           4.9         3            1.4         0.2
##  3 setosa           4.7         3.2          1.3         0.2
##  4 setosa           4.6         3.1          1.5         0.2
##  5 setosa           5           3.6          1.4         0.2
##  6 setosa           5.4         3.9          1.7         0.4
##  7 setosa           4.6         3.4          1.4         0.3
##  8 setosa           5           3.4          1.5         0.2
##  9 setosa           4.4         2.9          1.4         0.2
## 10 setosa           4.9         3.1          1.5         0.1
## # … with 140 more rows
iris %>% 
  rename(Especie = Species) # renombrar columna
## # A tibble: 150 x 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Especie
##           <dbl>       <dbl>        <dbl>       <dbl> <fct>  
##  1          5.1         3.5          1.4         0.2 setosa 
##  2          4.9         3            1.4         0.2 setosa 
##  3          4.7         3.2          1.3         0.2 setosa 
##  4          4.6         3.1          1.5         0.2 setosa 
##  5          5           3.6          1.4         0.2 setosa 
##  6          5.4         3.9          1.7         0.4 setosa 
##  7          4.6         3.4          1.4         0.3 setosa 
##  8          5           3.4          1.5         0.2 setosa 
##  9          4.4         2.9          1.4         0.2 setosa 
## 10          4.9         3.1          1.5         0.1 setosa 
## # … with 140 more rows

3.6 Filtrado de observaciones

Para filtrar observaciones de acuerdo a uno a varios criterios se usa la función filter, así como operadores lógicos y funciones auxiliares.

Funciones que ayudan a filtrar observaciones son las mismas de los Operadores lógicos.

Dos de las funciones auxiliares más útiles son:

  • between(x,left,right) que filtra observaciones para la variable x que se encuentren entre left (limite inferior) y right (limite superior); esta es más útil para variables numéricas,
  • x %in% c(a,b,c) que filtra observaciones para la variable x que se encuentren en el vector c(a,b,c); esta es más útil para variables de texto o factor

Se muestran diferentes ejemplos de como filtrar observaciones. Cuando se requiere que una observación cumpla varios criterios, estas condiciones se pueden separar por medio de comas (,), que es lo mismo que usar el operador lógico &. Si se requiere una u otra condición se puede usar el operador lógico |, pero en ese caso y dependiendo de lo deseado es mejor usar between() o %in%.

filter(airquality,Temp > 85)
## # A tibble: 34 x 6
##    Ozone Solar.R  Wind  Temp Month   Day
##    <int>   <int> <dbl> <int> <int> <int>
##  1    NA     273   6.9    87     6     8
##  2    71     291  13.8    90     6     9
##  3    39     323  11.5    87     6    10
##  4    NA     259  10.9    93     6    11
##  5    NA     250   9.2    92     6    12
##  6    77     276   5.1    88     7     7
##  7    97     267   6.3    92     7     8
##  8    97     272   5.7    92     7     9
##  9    85     175   7.4    89     7    10
## 10    NA     291  14.9    91     7    14
## # … with 24 more rows
airquality %>% 
  filter(Temp > 85)
## # A tibble: 34 x 6
##    Ozone Solar.R  Wind  Temp Month   Day
##    <int>   <int> <dbl> <int> <int> <int>
##  1    NA     273   6.9    87     6     8
##  2    71     291  13.8    90     6     9
##  3    39     323  11.5    87     6    10
##  4    NA     259  10.9    93     6    11
##  5    NA     250   9.2    92     6    12
##  6    77     276   5.1    88     7     7
##  7    97     267   6.3    92     7     8
##  8    97     272   5.7    92     7     9
##  9    85     175   7.4    89     7    10
## 10    NA     291  14.9    91     7    14
## # … with 24 more rows
airquality %>% 
  filter(Temp > 75, Wind > 10)
## # A tibble: 38 x 6
##    Ozone Solar.R  Wind  Temp Month   Day
##    <int>   <int> <dbl> <int> <int> <int>
##  1    45     252  14.9    81     5    29
##  2    NA     264  14.3    79     6     6
##  3    71     291  13.8    90     6     9
##  4    39     323  11.5    87     6    10
##  5    NA     259  10.9    93     6    11
##  6    NA     332  13.8    80     6    14
##  7    NA     322  11.5    79     6    15
##  8    21     191  14.9    77     6    16
##  9    13     137  10.3    76     6    20
## 10    NA      98  11.5    80     6    28
## # … with 28 more rows
airquality %>% 
  filter(between(Temp, 70, 80))
## # A tibble: 53 x 6
##    Ozone Solar.R  Wind  Temp Month   Day
##    <int>   <int> <dbl> <int> <int> <int>
##  1    36     118   8      72     5     2
##  2    12     149  12.6    74     5     3
##  3     7      NA   6.9    74     5    11
##  4    11     320  16.6    73     5    22
##  5   115     223   5.7    79     5    30
##  6    37     279   7.4    76     5    31
##  7    NA     286   8.6    78     6     1
##  8    NA     287   9.7    74     6     2
##  9    NA     264  14.3    79     6     6
## 10    NA     332  13.8    80     6    14
## # … with 43 more rows
airquality %>% 
  filter(Temp > 75, Wind > 10) %>% 
  select(Ozone,Solar.R)
## # A tibble: 38 x 2
##    Ozone Solar.R
##    <int>   <int>
##  1    45     252
##  2    NA     264
##  3    71     291
##  4    39     323
##  5    NA     259
##  6    NA     332
##  7    NA     322
##  8    21     191
##  9    13     137
## 10    NA      98
## # … with 28 more rows
babynames %>% 
  filter(name %in% c("Acura", "Lexus", "Yugo"))
## # A tibble: 57 x 5
##     year sex   name      n       prop
##    <dbl> <chr> <chr> <int>      <dbl>
##  1  1990 F     Lexus    36 0.0000175 
##  2  1990 M     Lexus    12 0.00000558
##  3  1991 F     Lexus   102 0.0000502 
##  4  1991 M     Lexus    16 0.00000755
##  5  1992 F     Lexus   193 0.0000963 
##  6  1992 M     Lexus    25 0.0000119 
##  7  1993 F     Lexus   285 0.000145  
##  8  1993 M     Lexus    30 0.0000145 
##  9  1994 F     Lexus   381 0.000195  
## 10  1994 F     Acura     6 0.00000308
## # … with 47 more rows

3.7 Orden de acuerdo a variables

arrange se usa para ordenar los datos de acuerdo a una o más variables, donde por defecto lo hace de manera ascendente, para ordenarlos de manera descendente se encierra la variable dentro de desc(var). Si se ordena por una variable numérica se hará de menor a mayor o viceversa, si se ordena por una variable factor se hará de acuerdo al orden de los niveles del factor, y si se ordena por una variable de texto se hará por orden alfabético.

airquality %>% 
  arrange(Temp)
## # A tibble: 153 x 6
##    Ozone Solar.R  Wind  Temp Month   Day
##    <int>   <int> <dbl> <int> <int> <int>
##  1    NA      NA  14.3    56     5     5
##  2     6      78  18.4    57     5    18
##  3    NA      66  16.6    57     5    25
##  4    NA      NA   8      57     5    27
##  5    18      65  13.2    58     5    15
##  6    NA     266  14.9    58     5    26
##  7    19      99  13.8    59     5     8
##  8     1       8   9.7    59     5    21
##  9     8      19  20.1    61     5     9
## 10     4      25   9.7    61     5    23
## # … with 143 more rows
airquality %>% 
  arrange(desc(Temp))
## # A tibble: 153 x 6
##    Ozone Solar.R  Wind  Temp Month   Day
##    <int>   <int> <dbl> <int> <int> <int>
##  1    76     203   9.7    97     8    28
##  2    84     237   6.3    96     8    30
##  3   118     225   2.3    94     8    29
##  4    85     188   6.3    94     8    31
##  5    NA     259  10.9    93     6    11
##  6    73     183   2.8    93     9     3
##  7    91     189   4.6    93     9     4
##  8    NA     250   9.2    92     6    12
##  9    97     267   6.3    92     7     8
## 10    97     272   5.7    92     7     9
## # … with 143 more rows
gss_cat %>% 
  arrange(marital)
## # A tibble: 21,483 x 9
##     year marital    age race  rincome     partyid      relig   denom     tvhours
##    <int> <fct>    <int> <fct> <fct>       <fct>        <fct>   <fct>       <int>
##  1  2000 No answ…    28 Other $10000 - 1… Ind,near dem Buddhi… Not appl…       2
##  2  2006 No answ…    NA White Not applic… Strong repu… Protes… Other          NA
##  3  2006 No answ…    NA White No answer   Strong repu… None    Not appl…       2
##  4  2006 No answ…    63 White No answer   Strong demo… None    Not appl…      NA
##  5  2006 No answ…    40 Other $20000 - 2… Not str dem… Protes… No denom…      NA
##  6  2006 No answ…    45 White No answer   No answer    No ans… No answer      NA
##  7  2006 No answ…    NA White No answer   No answer    No ans… No answer      NA
##  8  2008 No answ…    62 White Not applic… Strong demo… Protes… Episcopal      NA
##  9  2008 No answ…    43 White No answer   Independent  Christ… No denom…       1
## 10  2008 No answ…    50 White No answer   Ind,near dem Protes… Other           4
## # … with 21,473 more rows

3.8 Creación de variables

Para crear o modificar variables se usa mutate. Algunas veces se requiere o desea categorizar una variable continua de acuerdo a ciertos criterios o puntos de quiebre; lo anterior puede realizarse por medio de lo que se conoce como if statements, donde una función que realiza la misma tarea pero de forma más eficiente es case_when.

En el primer ejemplo se trabaja con la tabla del titanic, donde se tienen varias variables como texto (‘Pclass’, ‘Survived’, ‘Sex’) y se quieren convertir a factor, por lo que simplemente se re-definen estas variables. Este cambio se puede ver con glimpse para el antes y después, donde el tipo de variable cambia.

glimpse(titanic)
## Rows: 891
## Columns: 12
## $ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17…
## $ Survived    <int> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, …
## $ Pclass      <int> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, …
## $ Name        <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (F…
## $ Sex         <chr> "male", "female", "female", "female", "male", "male", "ma…
## $ Age         <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, 39, 14,…
## $ SibSp       <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, …
## $ Parch       <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, …
## $ Ticket      <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "3…
## $ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625…
## $ Cabin       <chr> "", "C85", "", "C123", "", "", "E46", "", "", "", "G6", "…
## $ Embarked    <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S…
titanic = titanic %>% 
  mutate(Pclass = as_factor(Pclass),
         Survived = as_factor(Survived),
         Sex = as_factor(Sex))
glimpse(titanic)
## Rows: 891
## Columns: 12
## $ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17…
## $ Survived    <fct> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, …
## $ Pclass      <fct> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, …
## $ Name        <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (F…
## $ Sex         <fct> male, female, female, female, male, male, male, male, fem…
## $ Age         <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, 39, 14,…
## $ SibSp       <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, …
## $ Parch       <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, …
## $ Ticket      <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "3…
## $ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625…
## $ Cabin       <chr> "", "C85", "", "C123", "", "", "E46", "", "", "", "G6", "…
## $ Embarked    <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S…

Se pueden crear variables nuevas que dependen de otra en la tabla. En el ejemplo se calcula la altura en centímetros a partir de la altura en pulgadas (1 pulgada = 2.54 cm)

dat1 %>% 
  mutate(Altura = Height*2.54)
## # A tibble: 654 x 6
##      Age LungCap Height Gender Smoke Altura
##    <int>   <dbl>  <dbl> <chr>  <chr>  <dbl>
##  1     9    3.12   57   female no      145.
##  2     8    3.17   67.5 female no      171.
##  3     7    3.16   54.5 female no      138.
##  4     9    2.67   53   male   no      135.
##  5     9    3.68   57   male   no      145.
##  6     8    5.01   61   female no      155.
##  7     6    3.76   58   female no      147.
##  8     6    2.24   56   female no      142.
##  9     8    3.96   58.5 female no      149.
## 10     9    3.83   60   female no      152.
## # … with 644 more rows

En el tercer ejemplo se re define la variable ‘Month’ pasándola a factor donde se le cambian las etiquetas a algo más explicito. A su vez, se define una nueva variable condicionada en los valores de otra (sensación dependiendo del valor de la temperatura). Aquí se ejemplifica case_when, donde la estructura es:

case_when(condicion1 ~ resultado1,
          condicion2 ~ resultado2,
          T ~ resultado3)
airq = airquality %>% 
  mutate(Month = factor(Month,
                        levels = 5:9,
                        labels = c("Mayo", "Junio", "Julio", 
                                   "Agosto", "Setiembre")),
         Sensation = case_when(Temp < 60 ~ 'Cold',
                               Temp < 70 ~ 'Cool',
                               Temp < 85 ~ 'Warm',
                               T ~ 'Hot') %>% 
           as.factor())
airq
## # A tibble: 153 x 7
##    Ozone Solar.R  Wind  Temp Month   Day Sensation
##    <int>   <int> <dbl> <int> <fct> <int> <fct>    
##  1    41     190   7.4    67 Mayo      1 Cool     
##  2    36     118   8      72 Mayo      2 Warm     
##  3    12     149  12.6    74 Mayo      3 Warm     
##  4    18     313  11.5    62 Mayo      4 Cool     
##  5    NA      NA  14.3    56 Mayo      5 Cold     
##  6    28      NA  14.9    66 Mayo      6 Cool     
##  7    23     299   8.6    65 Mayo      7 Cool     
##  8    19      99  13.8    59 Mayo      8 Cold     
##  9     8      19  20.1    61 Mayo      9 Cool     
## 10    NA     194   8.6    69 Mayo     10 Cool     
## # … with 143 more rows
airquality %>% 
  as_tibble()
## # A tibble: 153 x 6
##    Ozone Solar.R  Wind  Temp Month   Day
##    <int>   <int> <dbl> <int> <int> <int>
##  1    41     190   7.4    67     5     1
##  2    36     118   8      72     5     2
##  3    12     149  12.6    74     5     3
##  4    18     313  11.5    62     5     4
##  5    NA      NA  14.3    56     5     5
##  6    28      NA  14.9    66     5     6
##  7    23     299   8.6    65     5     7
##  8    19      99  13.8    59     5     8
##  9     8      19  20.1    61     5     9
## 10    NA     194   8.6    69     5    10
## # … with 143 more rows

3.9 Conteo de variables cualitativas

Para contar casos de variables discretas de una manera más expedita se puede usar count. Esta función realiza un agrupamiento (group_by) y resumen (summarise) a la vez.

mpg %>% 
  count(manufacturer, year)
## # A tibble: 30 x 3
##    manufacturer  year     n
##    <chr>        <int> <int>
##  1 audi          1999     9
##  2 audi          2008     9
##  3 chevrolet     1999     7
##  4 chevrolet     2008    12
##  5 dodge         1999    16
##  6 dodge         2008    21
##  7 ford          1999    15
##  8 ford          2008    10
##  9 honda         1999     5
## 10 honda         2008     4
## # … with 20 more rows

3.10 Tabla interactiva

Este es un ejemplo de como convertir una tabla estática a interactiva. Se usa el paquete DT (Xie et al., 2019) y la función datatable, donde se pueden definir otra serie de argumentos. Tiene la ventaja de que para columnas numéricas puedo filtrar por medio de sliders, y para columnas de facto puedo seleccionar los niveles.

airq %>% 
  DT::datatable(filter = 'top', options = list(dom = 't'))

3.11 Datos relacionales

En caso de tener datos de observaciones en diferentes tablas, estas se pueden unir para juntar los datos en una única tabla (uniones de transformación), o relacionar para filtrar los datos de una tabla con respecto a otra (uniones de filtro).

De manera general las uniones se van a realizar de acuerdo a las columnas que tengan el mismo nombre en ambas tablas. Si se desea especificar una columna en especifico se usa el argumento by = 'col'. Si el nombre difiere entre las tablas se define la unión de acuerdo a by = c('a' = 'b'), donde 'a' corresponde con el nombre de la columna en la primer tabla, y 'b' corresponde con el nombre de la columna en la segunda tabla. Esto aplica para todas las funciones de unión (*_join).

3.11.1 Uniones de transformación

Estas uniones agregan columnas de una tabla a otra.

Un tipo de unión es left_join(x, y), donde se unen los datos de la tabla de la derecha (y) a la de la izquierda (x) de acuerdo a una columna en común, y manteniendo todas las observaciones de x.

flights %>% 
  left_join(airlines)
## # A tibble: 336,776 x 20
##     year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
##  1  2013     1     1      517            515         2      830            819
##  2  2013     1     1      533            529         4      850            830
##  3  2013     1     1      542            540         2      923            850
##  4  2013     1     1      544            545        -1     1004           1022
##  5  2013     1     1      554            600        -6      812            837
##  6  2013     1     1      554            558        -4      740            728
##  7  2013     1     1      555            600        -5      913            854
##  8  2013     1     1      557            600        -3      709            723
##  9  2013     1     1      557            600        -3      838            846
## 10  2013     1     1      558            600        -2      753            745
## # … with 336,766 more rows, and 12 more variables: arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>,
## #   name <chr>
flights %>% 
  left_join(airports, c("dest" = "faa"))
## # A tibble: 336,776 x 26
##     year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
##  1  2013     1     1      517            515         2      830            819
##  2  2013     1     1      533            529         4      850            830
##  3  2013     1     1      542            540         2      923            850
##  4  2013     1     1      544            545        -1     1004           1022
##  5  2013     1     1      554            600        -6      812            837
##  6  2013     1     1      554            558        -4      740            728
##  7  2013     1     1      555            600        -5      913            854
##  8  2013     1     1      557            600        -3      709            723
##  9  2013     1     1      557            600        -3      838            846
## 10  2013     1     1      558            600        -2      753            745
## # … with 336,766 more rows, and 18 more variables: arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>, time_hour <dttm>,
## #   name <chr>, lat <dbl>, lon <dbl>, alt <int>, tz <dbl>, dst <chr>,
## #   tzone <chr>

Otro tipo de unión es inner_join(x, y), donde se mantienen observaciones que se encuentran en ambas tablas.

df1 <- tibble(x = c(1, 2), y = 2:1)
df2 <- tibble(x = c(1, 3), a = 10, b = "a")

df1 %>% 
  inner_join(df2)
## # A tibble: 1 x 4
##       x     y     a b    
##   <dbl> <int> <dbl> <chr>
## 1     1     2    10 a

Otro tipo de unión es full_join(x, y), donde se mantienen todas las observaciones de ambas tablas.

df1 %>% 
  full_join(df2)
## # A tibble: 3 x 4
##       x     y     a b    
##   <dbl> <int> <dbl> <chr>
## 1     1     2    10 a    
## 2     2     1    NA <NA> 
## 3     3    NA    10 a

3.11.2 Uniones de filtro

Se filtran las observaciones de una tabla de acuerdo a si coinciden o no con las de otra tabla.

Un tipo es semi_join(x, y), donde se mantienen todas las observaciones de x que coinciden con observaciones en y, pero sin agregar columnas de y. El opuesto seria anti_join(x, y), donde se eliminan todas las observaciones de x que coinciden con observaciones en y, pero sin agregar columnas de y.

df1 <- tibble(x = c(1, 1, 3, 4), y = 1:4)
df2 <- tibble(x = c(1, 1, 2), z = c("a", "b", "a"))

df1 %>% 
  semi_join(df2)
## # A tibble: 2 x 2
##       x     y
##   <dbl> <int>
## 1     1     1
## 2     1     2
df1 %>% 
  anti_join(df2)
## # A tibble: 2 x 2
##       x     y
##   <dbl> <int>
## 1     3     3
## 2     4     4

3.12 Datos ordenados (Tidy data)

3.12.1 Formatos largo y ancho

Los datos ordenados corresponden con cada variable en su columna, cada fila corresponde con una observación, y en las celdas van los valores correspondientes. Esto corresponde con un formato largo (Figura 3.1).

Estructura e ideología de datos ordenados (Grolemund & Wickham, 2016).

Figura 3.1: Estructura e ideología de datos ordenados (Grolemund & Wickham, 2016).

El ejemplo que se muestra a continuación no esta ordenado. La tabla tiene 3 variables pero no definidas correctamente. Una variable seria el país, otra seria el año (las columnas), y la tercera seria el numero de casos (las celdas). Esto se conoce como datos en formato ancho (En algunos casos puede ser necesario este formato, pero en la mayoría de ocasiones se prefiere el formato largo).

casos <- tribble(
  ~pais, ~"2011", ~"2012", ~"2013",
   "FR",    7000,    6900,    7000,
   "DE",    5800,    6000,    6200,
   "US",   15000,   14000,   13000
)

Para pasar de un formato ancho a largo, se usa la función pivot_longer(cols, names_to, values_to), donde cols son las columnas a agrupar en una sola, names_to es el nombre que se le va a dar a la columna que va a contener las columnas a agrupar, y values_to es el nombre que se le va a dar a la columna que va a contener los valores de las celdas y que corresponden con una variable.

En este caso se van a agrupar todas las columnas menos el país, se le va a llamar ‘anho’ y lo que estaba en las celdas pasa a ser la columna ‘casos’.

casos_tidy = casos %>% 
  pivot_longer(cols = -pais, names_to = 'anho', values_to = 'casos')
casos_tidy
## # A tibble: 9 x 3
##   pais  anho  casos
##   <chr> <chr> <dbl>
## 1 FR    2011   7000
## 2 FR    2012   6900
## 3 FR    2013   7000
## 4 DE    2011   5800
## 5 DE    2012   6000
## 6 DE    2013   6200
## 7 US    2011  15000
## 8 US    2012  14000
## 9 US    2013  13000

De igual manera se puede volver al formato ancho con pivot_wider(id_cols, names_from, values_from), donde id_cols es una columna que identifica a cada observación, names_from es la columna a usar para nuevas columnas, y values_from es la columna donde están los valores a poner en las celdas.

casos_tidy %>% 
  pivot_wider(id_cols = pais, names_from = anho, values_from = casos)
## # A tibble: 3 x 4
##   pais  `2011` `2012` `2013`
##   <chr>  <dbl>  <dbl>  <dbl>
## 1 FR      7000   6900   7000
## 2 DE      5800   6000   6200
## 3 US     15000  14000  13000

3.12.2 Separar y unir

Otro caso de datos no ordenados es cuando una columna contiene 2 o más datos, por lo que es necesario separar cada dato en un su propia columna.

En el ejemplo la columna ‘tasa’ corresponde con ‘casos’ y ‘poblacion’, por lo que hay que separarla. La función separate tiene el argumento into que corresponde con un vector de texto donde se deben definir los nombres de las columnas resultantes.

casos2 <- tribble(
          ~pais, ~anho,               ~tasa,
  "Afghanistan",  2001,      '745/19987071',
       "Brasil",  2001,   '37737/172006362',
        "China",  2001, '212258/1272915272'
)
casos2 %>% 
  separate(tasa, into = c('casos', 'poblacion'))
## # A tibble: 3 x 4
##   pais         anho casos  poblacion 
##   <chr>       <dbl> <chr>  <chr>     
## 1 Afghanistan  2001 745    19987071  
## 2 Brasil       2001 37737  172006362 
## 3 China        2001 212258 1272915272

Por defecto separate va a separar la columna en cualquier carácter especial que encuentre. Si se quiere especificar se puede usar el argumento sep.

casos2 %>% 
  separate(tasa, into = c('casos', 'poblacion'), sep = '/')
## # A tibble: 3 x 4
##   pais         anho casos  poblacion 
##   <chr>       <dbl> <chr>  <chr>     
## 1 Afghanistan  2001 745    19987071  
## 2 Brasil       2001 37737  172006362 
## 3 China        2001 212258 1272915272

El tipo de columna resultante de separate es de texto, pero en algunos casos ese no es el tipo deseado, por lo que se le puede pedir a la función que trate de adivinar y convertir las columnas al tipo correcto por medio del argumento convert = TRUE.

casos2_sep = casos2 %>% 
  separate(tasa, into = c('casos', 'poblacion'), convert = T)
casos2_sep
## # A tibble: 3 x 4
##   pais         anho  casos  poblacion
##   <chr>       <dbl>  <int>      <int>
## 1 Afghanistan  2001    745   19987071
## 2 Brasil       2001  37737  172006362
## 3 China        2001 212258 1272915272

El unir columnas se hace por medio de unite, donde se le pasan, primero, el nombre de la nueva columna, y segundo los nombres de las columnas a unir, así como el carácter a usar para separar los datos.

casos2_sep %>% 
  unite(tasa, casos, poblacion, sep = '-')
## # A tibble: 3 x 3
##   pais         anho tasa             
##   <chr>       <dbl> <chr>            
## 1 Afghanistan  2001 745-19987071     
## 2 Brasil       2001 37737-172006362  
## 3 China        2001 212258-1272915272

3.13 Datos anidados (Nesting)

Esta es una de las ventajas de los tibbles, donde una columna puede ser una lista, y como una lista puede contener lo que sea, esto permite flexibilidad en el análisis y manipulación de datos, como se va a ver en el próximo capitulo.

Esto es muy usado junto con group_by, donde primero se agrupa la tabla y luego se crea una columna donde para cada grupo se va a tener su tabla única (las observaciones que corresponden con ese grupo) y diferente al resto.

iris %>% 
  group_by(Species) %>% 
  nest()
## # A tibble: 3 x 2
##   Species    data             
##   <fct>      <list>           
## 1 setosa     <tibble [50 × 4]>
## 2 versicolor <tibble [50 × 4]>
## 3 virginica  <tibble [50 × 4]>
airq %>% 
  group_by(Month) %>% 
  nest()
## # A tibble: 5 x 2
##   Month     data             
##   <fct>     <list>           
## 1 Mayo      <tibble [31 × 6]>
## 2 Junio     <tibble [30 × 6]>
## 3 Julio     <tibble [31 × 6]>
## 4 Agosto    <tibble [31 × 6]>
## 5 Setiembre <tibble [30 × 6]>

3.14 Recursos

Se presentan recursos a consultar para ahondar más en los temas presentados.

tidyverse

DT

ModernDive Libro que cubre diversos temas desde una perspectiva moderna.

Modern R with the tidyverse

Strings in R Para manipular caracteres.

Referencias

Grolemund, G., & Wickham, H. (2016). R for Data Science. O’Reilly. https://bookdown.org/roy_schumacher/r4ds/

Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L. D., François, R., Grolemund, G., Hayes, A., Henry, L., Hester, J., Kuhn, M., Pedersen, T. L., Miller, E., Bache, S. M., Müller, K., Ooms, J., Robinson, D., Seidel, D. P., Spinu, V., … Yutani, H. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686

Xie, Y., Cheng, J., & Tan, X. (2019). DT: A Wrapper of the JavaScript Library ’DataTables’. https://CRAN.R-project.org/package=DT