Esta sección se la dedicamos a una de las características del tidyverse implementada a través del paquete específico magrittr
. Se trata de una manera diferente de crear objetos y utilizarlos a continuación en la siguiente operación que queremos realizar.
Hemos querido destacarlo por dos razones:
Porque las funcionalidades que tiene son realmente útiles si se conocen.
En segundo lugar, porque cada vez está más generalizado y encontraréis su uso en cada vez más ejemplos, tutoriales, foros de ayuda etc. Por lo cual es necesario entender los operadores que habilita para poder seguir el código.
En esencia, el paquete magrittr
permite el uso de varios operadores en el código que van a funcionar con la lógica opuesta a la manera tradicional de escribir en R.
Dicho operador se llama el pipeline, la tubería, por su manera de funcionar, y se escribe: %>%
. No es el único pero sí el más importante. Su función es coger un objeto determinado existente o el resultado de una operación y meterlo como primer argumento de la siguiente función.
Se puede cargar únicamente le paquete magrittr
que viene instalado con tidyverse, o directamente el tidyverse entero si necesitamos otras funcionalidades:
library(magrittr)
library(tidyverse)
La lógica es la siguiente: en vez de introducir el objeto o valor como argumento de la función con la que queremos trabajar, escribimos primero el objeto, luego el operador y a continuación la función, con el resto de argumentos que necesitemos. De esta manera, se cogerá como primer argumento el objeto que hemos introducido:
miojbetofinal<-miobjetoinicial %>% funcion(...)
Es equivalente a:
miojbetofinal<- funcion(miobjetoinicial)
La verdadera utilidad viene porque nos permite encadenar el resultado de la primera función con la siguiente. Lo que obtengamos no saldrá por pantalla, ni se almacenará en el objeto que queramos crear, sino que se pasará como argumento a la siguiente función. Así hasta que hayamos acabado.
miobjetoinicial %>% funcion1(...)%>% funcion2(...)%>%funcion3(...)
#o bien:
miresultadodeseado<-miobjetoinicial %>% funcion1(...)%>% funcion2(...)%>%funcion3(...)
El primer código producirá el resultado por pantalla, el segundo lo guardará en el objeto miresultadodeseado. Pero el funcionamiento es el mismo: el objeto inicial se mete con %>%
en la primera función como primer argumento (podrá haber otros), el resultado de eso se mete en la siguiente sin mostrarse en la pantalla, hasta la última función. Por eso se conocen como pipelines o tuberías, dado que el objeto inicial entra en una especie de tubería de funciones que se van encadenando hasta presentar un resultado al final. Con ejemplos esperamos verlo más claro.
miojbetofinal<-miobjetoinicial %>% funcion(...) %>% funcion2(...)
Vamos a verlo con un ejemplo práctico de datos de esperanza de vida desde 1800, por países.
setwd("C:/Users/Alejandro/Documents/ISM/Datasets")
life<-read_csv("life_expectancy_years.csv")
## Parsed with column specification:
## cols(
## .default = col_double(),
## country = col_character()
## )
## See spec(...) for full column specifications.
Pero, ¿qué utilidades tienen respecto a la manera tradicional de usar el código? La primera, según nuestra experiencia, es producir un código más claro y más limpio cuando uno tiene que realizar varias operaciones intermedias, que pueden inducir a cometer error. Especialmente cuando tenemos operaciones dentro de operaciones. Vamos a aproximarnos al mismo objetivo mediante tres códigos similares.
El objetivo es obtener a partir de nuestro objeto previo life, un segundo objeto life2
en el cual:
filter()
,select()
,na.omit()
,rowMeans()
.La primera manera de realizarlo es generando un objeto life2 a partir de la primera función y sobrescribiéndolo sucesivamente con cada función utilizando el objeto life2 previo como argumento. Tendremos cuatro líneas de código:
life2<-filter(life,,country=="Spain") #filtramos y nos quedamos con una sola variedad.
life2<-select(life2,starts_with("19")) #Nos quedamos solo con las filas que queremos
life2<-na.omit(life2) #eliminamos los datos NA
life2<-rowMeans(life2) #calculamos la media de cada fila.
En el segundo ejemplo solo creamos el objeto life2 una vez, porque el resultado de una función va encerrado como argumento de la siguiente función, así hasta 4 veces… Tenemos una sola línea y el objeto creado una sola vez, pero teniendo en cuenta que hay parámetros de la función que van entre paréntesis, nos encontramos con una línea de código algo compleja, que no se entiende bien dónde empieza y dónde acaba cada función, con 4 paréntesis encadenados al final. Un error de código aquí es muy difícil de encontrar, si nos falta un paréntesis, una coma etc.
life2<-rowMeans(na.omit(select(filter(life,country=="Spain"),starts_with=("19"))))
Una manera de simplificarlo porque el código queda más limpio es dejar cada función en una línea diferente, porque además Rstudio nos indentará el código hacia dentro haciendo más simple la lectura (esto es más claro verlo en vídeo). Aun así, sigue habiendo algo de problema con la presencia de los paréntesis.
life2<-rowMeans(
na.omit(
select(
filter(life,country=="Spain"),starts_with("19")
)
)
)
El uso del operador %>%
nos va permitir meter el objeto life y a continuación encadenar las funciones. Se considera por defecto como el primer argumento de la función, por lo que lo omitimos de los argumentos necesarios, y escribimos solo el resto de los argumentos. Así, lo que vemos en el siguiente código es lo siguiente:
Vamos a pedir que el resultado de lo que hagamos se almacene en el objeto life3
El objeto life
se mete a la función filter()
el resultado de filter()
, se mete a select()
esa selección se mete a na.omit()
el resultado ya sin datos NA se le mete a rowMeans.
Además para hacer el código más legible lo podemos dividir también en líneas, de manera que una línea acabe con el operador >%>
para que R sepa que tiene que seguir ejecutando. Así, si desde nuestro teclado con alt+enter ejecutamos la primera línea, se va a ejecutar toda la cadena.
life3<-life %>% filter(,country=="Spain")%>%select(starts_with("19"))%>%
na.omit()%>%
rowMeans()
Otros ejemplos pueden ser los siguientes: dado un objeto x que tiene un solo valor de 9, le vamos a pasar una serie de operaciones: sacar su raíz cuadrada con sqrt()
, luego su coseno, y luego su función exponencial (e^x).
x<-9
exp(cos(sqrt(x)))
## [1] 0.3715795
En modo pipeline, podremos expresarlo como:
x%>%
sqrt()%>%
cos()%>%
exp()
## [1] 0.3715795
o bien:
x%>% sqrt()%>% cos()%>% exp()
Por último, surge la pregunta de cómo utilizar el pipeline si lo que queremos es que la siguiente función no tome el resultado previo como el primer argumento, sino en otra posición. Simplemente necesitamos especificarlo con un punto en el argumento que queramos que coja el valor previo.
Vamos a ver el ejemplo con la función rnorm()
que genera datos aleatorios con distribución normal, como vamos a ver varias veces en el curso. Toma tres argumentos: el número de datos que se quiere generar, la media, y la desviación típica, por este orden. Si de lo que disponemos es de los datos de desviación típica y son los que queremos pasar con el operador %>%
, lo especificaremos al final, de esta manera:
desviacion<-4
desviacion%>%rnorm(100,10,.)
## [1] 13.4748676 6.9374774 8.6506157 4.2873842 14.6064869 8.8157508
## [7] 16.5832704 7.3126888 9.7451245 9.4422068 12.2385284 10.5635781
## [13] 13.8395619 13.7636189 13.1320577 7.3056177 10.3671213 5.3247273
## [19] 6.8598842 11.4624909 13.5410097 5.0265514 11.1576973 4.6899450
## [25] 6.9964632 12.4036065 4.9537469 5.8156565 12.3048234 13.0756209
## [31] 10.1576756 13.5096902 7.9621890 4.8817546 8.4542558 12.3766860
## [37] 7.2014263 19.5434209 11.3514805 12.9023307 5.6180200 10.4964306
## [43] 10.3442974 14.8635297 -0.1145625 9.6854151 14.4622773 9.5222661
## [49] 10.1207724 15.3241985 16.7357858 6.9106403 17.7344186 7.3132659
## [55] 2.3688075 7.3036947 13.7739761 12.6774828 8.5363650 15.7406511
## [61] 7.0187194 6.5846085 12.4214558 12.4973593 3.5885653 11.4096533
## [67] 1.3366127 7.8067421 5.6440526 6.5876442 16.2005882 12.7397620
## [73] 1.4654961 8.1983080 9.3839582 15.6186288 12.0941212 16.6812523
## [79] 8.5800502 6.4386545 11.3774443 15.2161357 10.4423307 12.6268185
## [85] 17.6705552 12.7787986 8.6010564 12.1424317 4.0597233 15.4206678
## [91] 15.8119300 11.0391403 5.5658999 7.1178041 8.1451230 14.2870021
## [97] 8.2284815 11.0847988 11.0525250 4.3271134
Así, metemos el objeto desviación, que vale 4, en el pipeline, y mediante el punto le especificamos que vaya en el tercer lugar. El número de datos es 100, la media es 10, y la desviación típica, el objeto desviación.