3.3 Transformando los datos

Habiendo revisado el contenido de un dataframe (y agregado alguna variable si hiciera falta), comenzamos a hacernos idea de los ajustes que necesita para que los datos tomen el formato que necesitamos. Estos ajustes pueden ser correcciones (por ejemplo, de errores de tipeo cuando se cargaron los datos), la creación de nuevas variables derivadas de las existentes, o un reordenamiento de los datos para simplificar nuestro trabajo.

Para hacer todo esto, y mucho más, vamos a aprender funciones que representan cinco verbos básicos para la transformación de datos:

  • select(): seleccionar -elegir- columnas por su nombre
  • filter(): filtrar, es decir quedarse sólo con las filas que cumplan cierta condición
  • arrange(): ordenar las filas de acuerdo a su contenido o algún otro índice
  • mutate(): mutar -cambiar- un dataframe, modificando el contenido de sus columnas o creando columnas (es decir, variables) nuevas
  • summarise(): producir sumarios -un valor extraído de muchos, por ejemplo el promedio- con el contenido de las columnas

Estas funciones tienen una sintaxis, una forma de escribirse, uniforme. El primer argumento que toman siempre es un dataframe; los siguientes indican qué hacer con los datos. El resultado siempre es un nuevo dataframe.

Las funciones son parte de dplyr, uno de los componentes de la familia de paquetes Tidyverse. Ya tenemos disponible todo lo necesario, activado cuando invocamos library(tidyverse) al comienzo.

Manos a la obra.

3.3.1 Seleccionar columnas con select()

Muchas veces tendremos que lidiar con datasets con decenas de variables. Alguna que otra vez, con centenas. En esos casos el primer problema es librarnos de semejante cantidad de columnas, reteniendo sólo aquellas en las que estamos interesados. Para un dataset como el de reclamos de los ciudadanos, que tiene pocas columnas, select() no es tan importante. Aún así, podemos usar select() con fines demostrativos.

Sabemos que el dataset tiene 5 columnas:

names(atencion_ciudadano)
## [1] "PERIODO"         "RUBRO"           "TIPO_PRESTACION" "BARRIO"          "total"           "COMUNA"

Si quisiéramos sólo las que contienen el período y el total, las seleccionamos por nombre, a continuación del nombre del dataframe:

seleccion <- select(atencion_ciudadano, PERIODO, total)

head(seleccion)
##   PERIODO total
## 1  201301     6
## 2  201301   172
## 3  201301    92
## 4  201301    45
## 5  201301    79
## 6  201301    10

También podemos seleccionar por contigüidad, por ejemplo “todas las columnas que van de RUBRO a BARRIO”:

seleccion <- select(atencion_ciudadano, RUBRO:BARRIO)

head(seleccion)
##    RUBRO TIPO_PRESTACION    BARRIO
## 1 ACERAS         RECLAMO AGRONOMIA
## 2 ACERAS         RECLAMO   ALMAGRO
## 3 ACERAS         RECLAMO BALVANERA
## 4 ACERAS         RECLAMO  BARRACAS
## 5 ACERAS         RECLAMO  BELGRANO
## 6 ACERAS         RECLAMO      BOCA

Y podemos seleccionar por omisión. Si nos interesara todo el contenido del dataset menos la variable RUBRO, usaríamos

seleccion <- select(atencion_ciudadano, -RUBRO)

head(seleccion)
##   PERIODO TIPO_PRESTACION    BARRIO total COMUNA
## 1  201301         RECLAMO AGRONOMIA     6     15
## 2  201301         RECLAMO   ALMAGRO   172      5
## 3  201301         RECLAMO BALVANERA    92      3
## 4  201301         RECLAMO  BARRACAS    45      4
## 5  201301         RECLAMO  BELGRANO    79     13
## 6  201301         RECLAMO      BOCA    10      4

Al igual que con las selección por inclusión, podemos seleccionar por omisión de un rango de columnas contiguas (escritas entre paréntesis), o de varias columnas nombradas:

seleccion <- select(atencion_ciudadano, -(TIPO_PRESTACION:total))

head(seleccion)
##   PERIODO  RUBRO COMUNA
## 1  201301 ACERAS     15
## 2  201301 ACERAS      5
## 3  201301 ACERAS      3
## 4  201301 ACERAS      4
## 5  201301 ACERAS     13
## 6  201301 ACERAS      4
seleccion <- select(atencion_ciudadano, -RUBRO, -BARRIO)

head(seleccion)
##   PERIODO TIPO_PRESTACION total COMUNA
## 1  201301         RECLAMO     6     15
## 2  201301         RECLAMO   172      5
## 3  201301         RECLAMO    92      3
## 4  201301         RECLAMO    45      4
## 5  201301         RECLAMO    79     13
## 6  201301         RECLAMO    10      4

3.3.2 Filtrar filas con filter()

Una de las tareas más frecuentes en el análisis de datos es la de identificar observaciones que cumplen con determinada condición. filter() permite extraer subconjuntos del total en base a sus variables.

Por ejemplo, para seleccionar registros que correspondan a Retiro, ocurridos en el primer mes de 2014 (período 201401):

seleccion <- filter(atencion_ciudadano, BARRIO == "RETIRO", PERIODO == 201401)
head(seleccion)
##   PERIODO               RUBRO TIPO_PRESTACION BARRIO total COMUNA
## 1  201401              ACERAS         RECLAMO RETIRO    10      1
## 2  201401           ALUMBRADO         RECLAMO RETIRO    34      1
## 3  201401           ALUMBRADO       SOLICITUD RETIRO     2      1
## 4  201401            ARBOLADO         RECLAMO RETIRO    10      1
## 5  201401            ARBOLADO       SOLICITUD RETIRO     3      1
## 6  201401 ATENCION AL PUBLICO           QUEJA RETIRO     3      1

3.3.2.1 Comparaciones

Aquí hemos usado un recurso nuevo, la comparación. R provee una serie de símbolos que permite comparar valores entre sí:

* ==   igual a 
* !=   no igual a 
* >    mayor a 
* >=   mayor o igual a 
* <    menor a 
* <=   menor o igual a 

Atención especial merece el símbolo que compara igualdad, ==. Un error muy común es escribir BARRIO = "RETIRO", (un sólo símbolo =) que le indica a R que guarde el valor “RETIRO” dentro de la variable BARRIO, en lugar de verificar si son iguales. Para ésto último, lo correcto es BARRIO == "RETIRO", tal como lo usamos en el ejemplo de filter().

También hay que tener en cuenta el uso de comillas. Para que R no se confunda, cuando queramos usar valores de texto (de tipo character) los rodeamos con comillas para que quede claro que no nos referimos a una variable con ese nombre, si la hubiera, sino en forma literal a esa palabra o secuencia de texto. En el caso de los números, no hace falta el uso de comillas, ya que en R ningún nombre de variable puede comenzar con o estar compuesta sólo por números.

Filtrando los registros de períodos para los cuales se registran más de 100 incidentes:

seleccion <- filter(atencion_ciudadano, total > 100)
head(seleccion)
##   PERIODO     RUBRO TIPO_PRESTACION    BARRIO total COMUNA
## 1  201301    ACERAS         RECLAMO   ALMAGRO   172      5
## 2  201301    ACERAS         RECLAMO CABALLITO   109      6
## 3  201301    ACERAS         RECLAMO    FLORES   111      7
## 4  201301    ACERAS         RECLAMO   PALERMO   113     14
## 5  201301 ALUMBRADO         RECLAMO   ALMAGRO   130      5
## 6  201301 ALUMBRADO         RECLAMO  BARRACAS   118      4

3.3.2.2 Operadores lógicos

Cuando le pasamos múltiples condiciones a filter(), la función devuelve las filas que cumplen con todas.

Por ejemplo, con

seleccion <- filter(atencion_ciudadano, PERIODO == 201508,  RUBRO == "SALUD")

head(seleccion)
##   PERIODO RUBRO TIPO_PRESTACION    BARRIO total COMUNA
## 1  201508 SALUD           QUEJA  BARRACAS     1      4
## 2  201508 SALUD           QUEJA CABALLITO     1      6
## 3  201508 SALUD           QUEJA   COGHLAN     1     12
## 4  201508 SALUD           QUEJA  RECOLETA     1      2

obtenemos todos los registros cuyo rubro es “SALUD”, y cuyo período es 20108, agosto de 2015.

Siguiendo el mismo formato, si intentamos

seleccion <- filter(atencion_ciudadano, BARRIO == "RETIRO", BARRIO == "PALERMO")

head(seleccion)
## [1] PERIODO         RUBRO           TIPO_PRESTACION BARRIO          total           COMUNA         
## <0 rows> (or 0-length row.names)

obtenemos un conjunto vacío. ¿Por qué? Es debido a que ninguna observación cumple con todas las condiciones; el ningún registro el barrio es Retiro y es Palermo. ¡Suena razonable!. Para obtener registros ocurrido en Retiro ó en Palermo, usamos el operador lógico | que significa… “ó”.

seleccion <- filter(atencion_ciudadano, BARRIO == "RETIRO" | BARRIO == "PALERMO")

head(seleccion)
##   PERIODO               RUBRO TIPO_PRESTACION  BARRIO total COMUNA
## 1  201301              ACERAS         RECLAMO PALERMO   113     14
## 2  201301              ACERAS         RECLAMO  RETIRO    15      1
## 3  201301              ACERAS       SOLICITUD PALERMO     2     14
## 4  201301 ACTOS DE CORRUPCION        DENUNCIA PALERMO     4     14
## 5  201301           ALUMBRADO         RECLAMO PALERMO    74     14
## 6  201301           ALUMBRADO         RECLAMO  RETIRO    15      1

Se trata de la lógica de conjuntos, o lógica booleana, que con un poco de suerte recordamos de nuestra época de escolares. Los símbolos importantes son &, |, y !: “y”, “ó”, y la negación que invierte preposiciones:

* a & b     a y b
* a | b     a ó b
* a & !b    a, y no b
* !a & b    no a, y b
* !(a & b)  no (a y b) 

Hemos visto ejemplos de a & b (PERIODO == 201508, RUBRO == "SALUD", que filter toma como un &) y de a | b (BARRIO == "RETIRO" | BARRIO == "PALERMO")

Un ejemplo de a & !b, filas en las que el tipo de prestación sea “TRAMITE”, y en las que el rubro no sea “REGISTRO CIVIL”:

filter(atencion_ciudadano, TIPO_PRESTACION == "TRAMITE" & !(RUBRO == "REGISTRO CIVIL"))

Y como ejemplo de !(a & b), todas las filas excepto las de tipo “DENUNCIA”, y rubro “SEGURIDAD E HIGIENE”:

seleccion <- filter(atencion_ciudadano, !(TIPO_PRESTACION == "DENUNCIA" & RUBRO == "SEGURIDAD E HIGIENE"))

head(seleccion)
##   PERIODO  RUBRO TIPO_PRESTACION    BARRIO total COMUNA
## 1  201301 ACERAS         RECLAMO AGRONOMIA     6     15
## 2  201301 ACERAS         RECLAMO   ALMAGRO   172      5
## 3  201301 ACERAS         RECLAMO BALVANERA    92      3
## 4  201301 ACERAS         RECLAMO  BARRACAS    45      4
## 5  201301 ACERAS         RECLAMO  BELGRANO    79     13
## 6  201301 ACERAS         RECLAMO      BOCA    10      4

3.3.3 Ordenar filas con arrange()

La función arrange() cambia el orden en el que aparecen las filas de un dataframe. Como primer parámetro toma un dataframe, al igual que el resto de los verbos de transformación que estamos aprendiendo. A continuación, espera un set de columnas para definir el orden.

Por ejemplo, para ordenar por total de registros:

ordenado <- arrange(atencion_ciudadano, total)

head(ordenado)
##   PERIODO  RUBRO TIPO_PRESTACION        BARRIO total COMUNA
## 1  201301 ACERAS         RECLAMO PUERTO MADERO     1      1
## 2  201301 ACERAS       SOLICITUD      BARRACAS     1      4
## 3  201301 ACERAS       SOLICITUD          BOCA     1      4
## 4  201301 ACERAS       SOLICITUD         BOEDO     1      5
## 5  201301 ACERAS       SOLICITUD       COGHLAN     1     12
## 6  201301 ACERAS       SOLICITUD  CONSTITUCION     1      1

Si agregamos más columnas, se usan en orden para “desempatar”. Por ejemplo, si queremos que las filas con el mismo valor en total aparezcan en el orden alfabético del barrio que les corresponde, sólo necesitamos agregar esa columna:

ordenado <- arrange(atencion_ciudadano, total, BARRIO)

head(ordenado)
##   PERIODO           RUBRO TIPO_PRESTACION    BARRIO total COMUNA
## 1  201301       ALUMBRADO       SOLICITUD AGRONOMIA     1     15
## 2  201301 ATENCION SOCIAL         RECLAMO AGRONOMIA     1     15
## 3  201301 ESPACIO PUBLICO         RECLAMO AGRONOMIA     1     15
## 4  201301           QUEJA           QUEJA AGRONOMIA     1     15
## 5  201301   RECUPERADORES         RECLAMO AGRONOMIA     1     15
## 6  201301       SEGURIDAD         RECLAMO AGRONOMIA     1     15

Si no se aclara lo contrario, el orden siempre es ascendente (de menor a mayor). Si quisiéramos orden de mayor a menor, usamos desc():

ordenado <- arrange(atencion_ciudadano, desc(total))

head(ordenado)
##   PERIODO          RUBRO TIPO_PRESTACION      BARRIO total COMUNA
## 1  201502 REGISTRO CIVIL         TRAMITE   MONSERRAT 19221      1
## 2  201403 REGISTRO CIVIL         TRAMITE SAN NICOLAS 19209      1
## 3  201402 REGISTRO CIVIL         TRAMITE SAN NICOLAS 17032      1
## 4  201504 REGISTRO CIVIL         TRAMITE   MONSERRAT 16746      1
## 5  201503 REGISTRO CIVIL         TRAMITE   MONSERRAT 16730      1
## 6  201506 REGISTRO CIVIL         TRAMITE   MONSERRAT 14674      1

3.3.3.1 Valores faltantes

En el último ejemplo, aparecen varias filas cuyo valor para la columna BARRIO es NA. R representa los valores ausentes, desconocidos, con NA (“no disponible”, del inglés Not Available). Hay que tener cuidado con los valores NA, porque la mayoría de las comparaciones y operaciones lógicas que los involucran resultan indefinidas. En la práctica:

¿Es 10 mayor a un valor desconocido?

10 > NA
## [1] NA

R no sabe. (Nadie lo sabe, para ser justos)

¿A cuanto asciende la suma de 10 más un valor desconocido?

NA + 10
## [1] NA

Y en particular… ¿es un valor desconocido igual a otro valor desconocido?

NA == NA
## [1] NA

Por supuesto, la respuesta es desconocida también. La insistencia de R en no definir operaciones que involucran NA’s podría parecer irritante a primera vista, pero en realidad nos hace un favor. Al evitar extraer conclusiones cuando trata con datos faltantes, nos evita caer en errores garrafales en los casos en que analizamos y comparamos datos incompletos. Además, podemos preguntar a R si un valor es desconocido, y allí si contesta con seguridad. La función requerida es is.na().

desconocido <- NA

is.na(desconocido)
## [1] TRUE

Algo más a tener en cuenta con los valores desconocidos es cómo son interpretados cuando usamos funciones de transformación de datos. Por ejemplo, filter() ignora las filas que contienen NA’s en la variable que usa para filtrar. arrange() muestra las filas con NA’s en el campo por el que ordena, pero todas al final.

3.3.4 Agregar nuevas variables con mutate()

Recurrimos a la función mutate() cuando queremos agregarle columnas adicionales a nuestro dataframe, en general en base a los valores de las columnas ya existentes. Vamos a ilustrarlo con un ejemplo sencillo. Imaginemos que tenemos el siguiente dataset:

circulos <- data.frame(nombre = c("Círculo 1", "Círculo 2", "Círculo 3"),
                       tamaño = c("Pequeño", "Mediano", "Grande"),
                       radio  = c(1, 3, 5))

circulos
##      nombre  tamaño radio
## 1 Círculo 1 Pequeño     1
## 2 Círculo 2 Mediano     3
## 3 Círculo 3  Grande     5

Podemos agregar una columna con el área de cada círculo con mutate():

mutate(circulos, area = 3.1416 * radio^2)
##      nombre  tamaño radio    area
## 1 Círculo 1 Pequeño     1  3.1416
## 2 Círculo 2 Mediano     3 28.2744
## 3 Círculo 3  Grande     5 78.5400

Usando mutate(), definimos la columna “area”, indicando que su contenido será el valor de la columna “radio” en cada registro puesto en la fórmula del área de un círculo. Los operadores aritméticos (+, -, *, /, ^) son con frecuencia útiles para usar en conjunto con mutate().

Volvamos ahora a nuestro dataframe con datos de reclamos. Supongamos que nos interesa agregar columnas con el mes y el año de cada registro. La columna período, con valores del tipo “201301”, contiene la información necesaria para derivar estas dos nuevas variables. Para separar la parte del año de la parte del mes, la función substr(), que extrae porciones de una variable de texto, nos va a dar una mano. La usamos así: el primer parámetro es una secuencia de caracteres, y los dos siguientes indican donde queremos que empiece y termine la porción a extraer.

atencion_ciudadano <- mutate(atencion_ciudadano,
                             AÑO = substr(PERIODO, 1, 4),
                             MES = substr(PERIODO, 5, 6))
                                
head(atencion_ciudadano) 
##   PERIODO  RUBRO TIPO_PRESTACION    BARRIO total COMUNA  AÑO MES
## 1  201301 ACERAS         RECLAMO AGRONOMIA     6     15 2013  01
## 2  201301 ACERAS         RECLAMO   ALMAGRO   172      5 2013  01
## 3  201301 ACERAS         RECLAMO BALVANERA    92      3 2013  01
## 4  201301 ACERAS         RECLAMO  BARRACAS    45      4 2013  01
## 5  201301 ACERAS         RECLAMO  BELGRANO    79     13 2013  01
## 6  201301 ACERAS         RECLAMO      BOCA    10      4 2013  01

3.3.5 Extraer sumarios con summarise()

Llegamos al último de los verbos fundamentales para transformar datos. summarise() (por “resumir” en inglés) toma un dataframe completo y lo resume un una sola fila, de acuerdo a la operación que indiquemos. Por ejemplo, el promedio de la columna “total”:

summarise(atencion_ciudadano, promedio = mean(total))
##   promedio
## 1  34.8478

Por si sola, summarise() no es de mucha ayuda. La gracia está en combinarla con group_by(), que cambia la unidad de análisis del dataframe completo a grupos individuales. Usar summarise() sobre un dataframe al que antes agrupamos con group_by resulta en resúmenes “por grupo”.

agrupado <- group_by(atencion_ciudadano, AÑO)

summarise(agrupado, promedio_totales = mean(total))
## # A tibble: 3 × 2
##   AÑO   promedio_totales
##   <chr>            <dbl>
## 1 2013              29.5
## 2 2014              30.2
## 3 2015              45.4

Podemos agrupar por múltiples columnas, generando más subgrupos; por ejemplo, promedios por por año y mes…

agrupado <- group_by(atencion_ciudadano, AÑO, MES)

sumario <- summarise(agrupado, promedio = mean(total))
## `summarise()` has grouped output by 'AÑO'. You can override using the `.groups` argument.
head(sumario)
## # A tibble: 6 × 3
## # Groups:   AÑO [1]
##   AÑO   MES   promedio
##   <chr> <chr>    <dbl>
## 1 2013  01        25.1
## 2 2013  02        26.1
## 3 2013  03        26.9
## 4 2013  04        29.5
## 5 2013  05        28.0
## 6 2013  06        28.9

… o por año, mes y barrio:

agrupado <- group_by(atencion_ciudadano, AÑO, MES, BARRIO)

sumario <- summarise(agrupado, promedio = mean(total))
## `summarise()` has grouped output by 'AÑO', 'MES'. You can override using the `.groups` argument.
head(sumario)
## # A tibble: 6 × 4
## # Groups:   AÑO, MES [1]
##   AÑO   MES   BARRIO    promedio
##   <chr> <chr> <chr>        <dbl>
## 1 2013  01    AGRONOMIA    14.6 
## 2 2013  01    ALMAGRO      29.5 
## 3 2013  01    BALVANERA    23.6 
## 4 2013  01    BARRACAS     19.4 
## 5 2013  01    BELGRANO     24.4 
## 6 2013  01    BOCA          9.97

Con summarise() podemos usar cualquier función que tome una lista de valores y devuelva un sólo restado. Para empezar, algunas de las que más podrían ayudarnos son:

* `mean()`: Obtiene el promedio de los valores
* `sum()`: Obtiene la suma
* `min()`: Obtiene el valor más bajo
* `max()`: Obtiene el valor más alto

3.3.6 ¡BONUS! El operador “pipe”: %>%

Antes de terminar, vamos a presentar una herramienta más: el operador pipe (pronúnciese “paip”, es el término en inglés que significa “tubo”).

El pipe es un operador: un símbolo que relaciona dos entidades. Dicho en forma más simple, el pipe de R, cuyo símbolo es %>% está en familia con otros operadores más convencionales, como +, - o /. Y al igual que los otros operadores, entrega un resultado en base a los operandos que recibe. Ahora bien… ¿Para qué sirve? En resumidas cuentas, hace que el código necesario para realizar una serie de operaciones de transformación de datos sea mucho más simple de escribir y de interpretar.

Por ejemplo, si quisiéramos obtener el top 5 de los barrios que más reclamos y denuncias de los ciudadanos han registrado durante 2015, la forma de lograrlo en base a lo que ya sabemos sería así:

1. Filtramos los datos para aislar los registros del 2014;
2. agrupamos por Barrio;
3. hacemos un sumario, creando una variable resumen que contiene la suma de los registros para cada barrio;
4. los ordenamos en forma descendiente,
5. mostramos sólo los primeros 5 (esto se puede hacer con la función `head()`, aclarando cuantas filas queremos ver)

En código:

solo2014 <- filter(atencion_ciudadano, AÑO == 2014)

solo2014_agrupado_barrio <- group_by(solo2014, BARRIO)

total_por_barrio_2014 <- summarise(solo2014_agrupado_barrio, total = sum(total))

total_por_barrio_2014_ordenado <- arrange(total_por_barrio_2014, desc(total))

head(total_por_barrio_2014_ordenado, 5)
## # A tibble: 5 × 2
##   BARRIO        total
##   <chr>         <int>
## 1 SAN NICOLAS  180956
## 2 PALERMO       22569
## 3 CABALLITO     19706
## 4 FLORES        15919
## 5 VILLA DEVOTO  15720

¡Funciona! Pero… el problema es que hemos generado un puñado de variables (“solo2014”, “solo2014_agrupado_barrio”, etc) que, es probable, no volveremos a usar. Además de ser inútiles una vez obtenido el resultado buscado, estas variables intermedias requieren que las nombremos. Decidir el nombre de estas variables que no nos importan toma tiempo (sobre todo cuando producimos muchas), y nos distrae de lo importante, que es el análisis.

El pipe, %>%, permite encadenar operaciones, conectando el resultado de una como el dato de entrada de la siguiente. La misma secuencia que realizamos antes puede resolverse con pipes, quedando así:

atencion_ciudadano %>% 
    filter(AÑO == 2014) %>% 
    group_by(BARRIO) %>% 
    summarise(total = sum(total)) %>% 
    arrange(desc(total)) %>% 
    head(5)
## # A tibble: 5 × 2
##   BARRIO        total
##   <chr>         <int>
## 1 SAN NICOLAS  180956
## 2 PALERMO       22569
## 3 CABALLITO     19706
## 4 FLORES        15919
## 5 VILLA DEVOTO  15720

Una manera de pronunciar %>% cuando leemos código es “y luego…”. Algo así como “tomamos el dataframe”atencion_ciudadano" y luego filtramos los registros del año 2014, y luego agrupamos por barrio, y luego calculamos el total de registros para cada grupo, y luego los ordenamos en forma descendente por total, y luego vemos los cinco primeros".

El uso de pipes permite concentrarse en las operaciones de transformación, y no en lo que está siendo transformado en cada paso. Esto hace al código mucho más sencillo de leer e interpretar. En el ejemplo con pipe, sólo tuvimos que nombrar un dataframe con el cual trabajar un única vez, al principio.

Detrás de escena, x %>% f(y) se transforma en f(x, y). Por eso,

filter(atencion_ciudadano, AÑO == 2014)

es equivalente a

atencion_ciudadano %>% filter(AÑO == 2014)

Trabajar con pipes es una de las ventajas que hacen de R un lenguaje muy expresivo y cómodo para manipular datos, y a partir de aquí lo usaremos de forma habitual.

Con esto cerramos la sección de transformación de datos. Las técnicas para examinar un dataframe, como sumamry() nos permiten entender de forma rápida con que clase de variables vamos a trabajar. Los cinco verbos de manipulación que aprendimos, usados en conjunto, brindan una enorme capacidad para adaptar el formato de los datos a nuestras necesidades. Y el operador pipe nos ayuda a escribir nuestro código de forma sucinta y fácil de interpretar.

A medida que vayamos progresando en nuestra familiaridad con las funciones -y agregando técnicas nuevas- vamos a ser capaces de procesar grandes cantidades de datos con soltura. Y obtener en pocos minutos lo que de otra forma, sin herramientas computacionales, tardaría días o sería inviable por lo tedioso.