2 Trabajando con observaciones

En el flujo de trabajo de cualquier científico de datos, o persona que quiera analizar datos, se encuentra la tarea de limpiar8 y manipular las bases de datos. Como vimos en el Capítulo 1 el paquete dplyr, y los otros que hacen parte del paquete tidyverse facilitan ese flujo de trabajo. También presentamos el operador pipe (%>%) del paquete dplyr que permite encadenar diferentes comandos. En este capítulo nos concentraremos en operaciones relacionadas a las filas de un objeto con datos que se encuentre en formato data.frame o tibble (Ver sección 1.2.2 para una breve introducción a esta clase de objetos).

Antes de entrar en detalle, debemos aclarar dos términos comunes en la comunidad de usuarios de R y dplyr: los casos y los verbos. El primer ser refiere a las filas de los datos. Ya sabes que las filas de un objeto de clase data.frame o tibble corresponden a las observaciones de la base y las columnas a las variables. Otro nombre común para las observaciones es casos (cases en inglés).

El segundo término común cuando trabajamos en dplyr es “verbos”. Los verbos son las funciones que se emplean para realizar operaciones sobre los objetos que contienen datos. Por ejemplo, emplearemos el verbo filter() (filtrar en español) para filtrar los datos.

Ahora sí, entremos en detalle de los tres verbos que emplearemos para operar sobre los casos de un objeto con datos.

2.1 Filtrar

En este capítulo nos concentraremos en el cómo ejecutar con dplyr tres tareas que tienen que ver con las observaciones. La primera operación es escoger observaciones (filas) que cumplan una condición deseada de un objeto de clase data.frame o tibble. Es decir, extraeremos un subconjunto de observaciones (casos) que cumplan una condición deseada. A esta acción de escoger observaciones que cumplen un criterio se le conoce en este contexto como filtrar. En la Figura 2.1 puedes observar una representación de esta tarea. En este caso el resultado será un un objeto de clase tibble con las mismas variables de la base original, pero con menos observaciones (solo aquellos casos que cumplen la condición deseada). Y siguiendo la lógica de los verbos de este paquete, para hacer esta operación emplearemos la función filter()9.



Figura 2.1: Representación del proceso de filtrar

Representación del proceso de filtrar



Para emplear esta función se requieren dos argumentos. El primero es el objeto con los datos, que típicamente se pasa empleando el operador %>%. Y el segundo argumento es la condición que deben cumplir las observaciones que pasarán el filtro.

Veamos un ejemplo utilizando los datos del objeto gpaminder del paquete gapminder (Bryan, 2017). Este objeto tiene datos para la mayoría de países del mundo. Veamos rápidamente las características de este objeto.

# cargar paquete
# install.packages("gapminder")
library(gapminder)
# cargar datos
data("gapminder")
# clase 
class(gapminder)
## [1] "tbl_df"     "tbl"        "data.frame"
# primeras observaciones
gapminder
## # A tibble: 1,704 × 6
##    country     continent  year lifeExp      pop gdpPercap
##    <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
##  1 Afghanistan Asia       1952    28.8  8425333      779.
##  2 Afghanistan Asia       1957    30.3  9240934      821.
##  3 Afghanistan Asia       1962    32.0 10267083      853.
##  4 Afghanistan Asia       1967    34.0 11537966      836.
##  5 Afghanistan Asia       1972    36.1 13079460      740.
##  6 Afghanistan Asia       1977    38.4 14880372      786.
##  7 Afghanistan Asia       1982    39.9 12881816      978.
##  8 Afghanistan Asia       1987    40.8 13867957      852.
##  9 Afghanistan Asia       1992    41.7 16317921      649.
## 10 Afghanistan Asia       1997    41.8 22227415      635.
## # … with 1,694 more rows

El objeto gapminder contiene datos del país (country), continente (continent), año (year), esperanza de vida (lifeExp), población (pop) y PIB per cápita (gdpPercap). Supongamos que solo queremos trabajar con las observaciones que están en el continente americano y solo nos interesa el año 2007. Para obtener la nueva base deseada (la llamaremos americas_2007), debemos aplicar dos filtros:

  • Filtro 1: el continente que es de clase factor debe ser igual a “Americas”.
  • Filtro 2: el año (year) que es de clase integer debe ser igual a 2007.

Esto lo podemos traducir en el siguiente código:

# cargando paquete
library(dplyr)
# filtrando los datos
americas_2007 <- gapminder %>% 
  # Filtro 1
  filter(continent == "Americas") %>% 
  # Filtro 2
  filter(year == 2007)

americas_2007
## # A tibble: 25 × 6
##    country            continent  year lifeExp     pop gdpPercap
##    <fct>              <fct>     <int>   <dbl>   <int>     <dbl>
##  1 Argentina          Americas   2007    75.3  4.03e7    12779.
##  2 Bolivia            Americas   2007    65.6  9.12e6     3822.
##  3 Brazil             Americas   2007    72.4  1.90e8     9066.
##  4 Canada             Americas   2007    80.7  3.34e7    36319.
##  5 Chile              Americas   2007    78.6  1.63e7    13172.
##  6 Colombia           Americas   2007    72.9  4.42e7     7007.
##  7 Costa Rica         Americas   2007    78.8  4.13e6     9645.
##  8 Cuba               Americas   2007    78.3  1.14e7     8948.
##  9 Dominican Republic Americas   2007    72.2  9.32e6     6025.
## 10 Ecuador            Americas   2007    75.0  1.38e7     6873.
## # … with 15 more rows

Es decir, tenemos ahora un conjunto de datos para 25 países que pertenecen al continente americano y con datos solo para el año 2007. Nota que el mismo resultado lo podemos obtener empleando el verbo filter() una sola vez. Esto se puede lograr empleando el operador lógico “y” (&) para definir la condición en una sola aplicación del verbo. Es decir, la condición sería: continent == “Americas” & year == 2007. Así, siguiendo la segunda Recomendación de Estilo de la sección 1.2.110, la siguiente línea de código genera exactamente el mismo resultado.

americas_2007 <- filter(gapminder, 
                      continent == "Americas" &
                         year == 2007 ) 

americas_2007
## # A tibble: 25 × 6
##    country            continent  year lifeExp     pop gdpPercap
##    <fct>              <fct>     <int>   <dbl>   <int>     <dbl>
##  1 Argentina          Americas   2007    75.3  4.03e7    12779.
##  2 Bolivia            Americas   2007    65.6  9.12e6     3822.
##  3 Brazil             Americas   2007    72.4  1.90e8     9066.
##  4 Canada             Americas   2007    80.7  3.34e7    36319.
##  5 Chile              Americas   2007    78.6  1.63e7    13172.
##  6 Colombia           Americas   2007    72.9  4.42e7     7007.
##  7 Costa Rica         Americas   2007    78.8  4.13e6     9645.
##  8 Cuba               Americas   2007    78.3  1.14e7     8948.
##  9 Dominican Republic Americas   2007    72.2  9.32e6     6025.
## 10 Ecuador            Americas   2007    75.0  1.38e7     6873.
## # … with 15 more rows

Ahora, supongamos que queremos extraer no solamente los datos del año 2007 para los países del continente americano, sino también los de Europa. En este caso podemos emplear el operador %in% y la lista de continentes. Es decir:

Euro_ame_2007 <- gapminder %>% 
  filter(continent %in% c("Americas", "Europe")) %>% 
  filter(year == 2007)

Euro_ame_2007 
## # A tibble: 55 × 6
##    country             continent  year lifeExp    pop gdpPercap
##    <fct>               <fct>     <int>   <dbl>  <int>     <dbl>
##  1 Albania             Europe     2007    76.4 3.60e6     5937.
##  2 Argentina           Americas   2007    75.3 4.03e7    12779.
##  3 Austria             Europe     2007    79.8 8.20e6    36126.
##  4 Belgium             Europe     2007    79.4 1.04e7    33693.
##  5 Bolivia             Americas   2007    65.6 9.12e6     3822.
##  6 Bosnia and Herzego… Europe     2007    74.9 4.55e6     7446.
##  7 Brazil              Americas   2007    72.4 1.90e8     9066.
##  8 Bulgaria            Europe     2007    73.0 7.32e6    10681.
##  9 Canada              Americas   2007    80.7 3.34e7    36319.
## 10 Chile               Americas   2007    78.6 1.63e7    13172.
## # … with 45 more rows

En el Cuadro 2.1 se presentan algunas de las funciones y operadores lógicos y de relación que se pueden emplear con la función filter(). Utilizando dichos operadores y el verbo filter() tenemos una poderosa herramienta para manipular cualquier base de datos sin importar su tamaño.



Cuadro 2.1: Principales funciones y operadores lógicos y de relación que se pueden emplear con el verbo filter()
Operador Descripción Operador Descripción
< Menor que <= Menor o igual que
> Mayor que >= Mayor o igual que
== Igual a != No es igual a
! NO lógico & Y lógico
| ó lógico %in% pertenece al vector
is.na() es un valor perdido between(a, b) está entre a y b



2.2 Ordenar observaciones

La segunda tarea que estudiaremos es ordenar las observaciones. En algunas ocasiones es deseable ordenar los datos de manera ascendente11 o descendente con respecto a una o varias variables. Para realizar esto podemos emplear el verbo arrange() (organizar en español). Similar al verbo filter(), el primer argumento de arrange() es el objeto con los datos (clase data.frame o tibble) que puede pasarse por medio del operador %>%. Y el segundo elemento es la variable que se empleará para organizar de manera ascendente los datos. Si se desea un ordenamiento descendente de los datos, entonces se emplea el verbo desc() aplicado a la variable de interés. En la Figura 2.2 se presenta de manera esquemática el procedimiento efectuado cuando empleamos el verbo arrange con un objeto con datos.

Figura 2.2: Representación del proceso de ordenar.

Representación del proceso de ordenar.



Veamos un ejemplo. Supongamos que deseamos extraer del objeto gapminder un nuevo conjunto de datos que solamente tenga los países de Oceanía y datos para los años disponibles de este siglo. Además queremos tener los datos organizados en orden alfabético por país.

Esto lo podemos hacer con el siguiente código:

oceania_s21 <- gapminder %>% 
  filter(continent == "Oceania") %>% 
  filter(year > 2000 ) %>% 
  arrange(country)

oceania_s21 
## # A tibble: 4 × 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Australia   Oceania    2002    80.4 19546792    30688.
## 2 Australia   Oceania    2007    81.2 20434176    34435.
## 3 New Zealand Oceania    2002    79.1  3908037    23190.
## 4 New Zealand Oceania    2007    80.2  4115771    25185.

Ahora supongamos que queremos organizar el año de manera descendente, de tal manera que el último año disponible (para cada país) se presente al principio. En este caso el código sería:

oceania_s21 <- gapminder %>% 
  filter(continent == "Oceania") %>% 
  filter(year > 2000 ) %>% 
  arrange(desc(year))

oceania_s21 
## # A tibble: 4 × 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Australia   Oceania    2007    81.2 20434176    34435.
## 2 New Zealand Oceania    2007    80.2  4115771    25185.
## 3 Australia   Oceania    2002    80.4 19546792    30688.
## 4 New Zealand Oceania    2002    79.1  3908037    23190.

2.3 Resumir casos

La tercera tarea tiene que ver con resumir las observaciones con una característica común al interior de los datos. Por ejemplo, supongamos que queremos conocer el promedio de la esperanza de vida para un año determinado por continente. Es decir, promediar todas las esperanzas de vida de los países de un mismo continente. O que queremos conocer la población total por continente. En estos dos casos estaríamos creando un nuevo conjunto de datos cuyas filas serían los continentes. En el primer caso como variable tendríamos el promedio calculado por continente, y en el segundo caso una suma calculada por continente.

Para realizar este tipo de operaciones, tenemos que usar dos verbos. Primero agrupamos por (group_by() ) un criterio (en este caso el continente) y después de tener los grupos resumimos (summarise() ), ya sea empleando una suma (sum() ) o un promedio12 (mean() ). En la Figura 2.3 puedes observar una representación de este proceso.



Figura 2.3: Representación del proceso de agrupar por y resumir

Representación del proceso de agrupar por y resumir



Ya debe ser clara la gramática que emplean los verbos del paquete dplyr. El primer argumento de ambos verbos es el objeto con los datos al que se le quiere realizar la operación, este se puede pasar por medio del operador %>%. Para el verbo group_by() el segundo argumento es la variable que se desea emplear para hacer la agrupación. En el caso del verbo summarise()13, el segundo argumento corresponderá a la operación que queremos realizar por grupo.

Por ejemplo, encontremos el promedio de la esperanza de vida al nacer por continente para el año 2007. Esto implicará que primero tenemos que filtrar (filter()) los datos para solo tener los datos de 2007, después debemos agruparlos (group_by()) por continente y finalmente, resumir summarise() los datos con la media mean() del grupo. Esto en términos de código, se puede traducir de la siguiente manera:

EV_continetes <- gapminder %>% 
  filter(year ==2007) %>% 
  group_by(continent) %>% 
  summarise(Exp_Promedio = mean(lifeExp))

EV_continetes 
## # A tibble: 5 × 2
##   continent Exp_Promedio
##   <fct>            <dbl>
## 1 Africa            54.8
## 2 Americas          73.6
## 3 Asia              70.7
## 4 Europe            77.6
## 5 Oceania           80.7

Nota que el nuevo objeto EV_continetes tiene 5 observaciones (una por cada grupo armado) y una variable, la que decidimos crear. Así mismo, en el verbo summarise() hemos especificado el nombre de la nueva variable en la que guardará el resultado de nuestro cálculo.

Naturalmente, podemos emplear otros verbos para organizar los resultados. Por ejemplo, podríamos organizar de manera descendente los resultados:

EV_continetes <- gapminder %>% 
  filter(year ==2007) %>% 
  group_by(continent) %>% 
  summarise(Exp_Promedio = mean(lifeExp)) %>% 
  arrange(desc(Exp_Promedio))

EV_continetes 
## # A tibble: 5 × 2
##   continent Exp_Promedio
##   <fct>            <dbl>
## 1 Oceania           80.7
## 2 Europe            77.6
## 3 Americas          73.6
## 4 Asia              70.7
## 5 Africa            54.8

También podemos crear varias variables en el resumen. Por ejemplo, supongamos que queremos incluir en nuestro nuevo conjunto de datos la población total del continente y el número de países en cada continente (el verbo n() cuenta cuántos casos hay por grupo14). Esto lo podemos hacer separando con comas las nuevas variables que deseamos crear con el verbo summarise().

BD_continetes <- gapminder %>% 
  filter(year ==2007) %>% 
  group_by(continent) %>% 
  summarise(Exp_Promedio = mean(lifeExp), 
            Pob_total = sum(pop), 
            Paises = n()) %>% 
  arrange(desc(Exp_Promedio))

BD_continetes 
## # A tibble: 5 × 4
##   continent Exp_Promedio  Pob_total Paises
##   <fct>            <dbl>      <dbl>  <int>
## 1 Oceania           80.7   24549947      2
## 2 Europe            77.6  586098529     30
## 3 Americas          73.6  898871184     25
## 4 Asia              70.7 3811953827     33
## 5 Africa            54.8  929539692     52



Recomendación de Estilo

Cuando los argumentos de una función no quepan todos en una línea, coloca cada argumento en una linea y usa la sangría de dos espacios.

# Buena práctica
BD_continetes <- gapminder %>% 
  filter(year ==2007) %>% 
  group_by(continent) %>% 
  summarise(Exp_Promedio = mean(lifeExp), 
            P_tot = sum(pop), 
            Paises = n()) %>% 

# Mala práctica
BD_continetes <- gapminder %>% 
  filter(year ==2007) %>% 
  group_by(continent) %>% 
  summarise(Exp_Promedio = mean(lifeExp), P_tot = sum(pop), 
Paises = n()) %>% 



En el Cuadro 2.2 se presentan algunas de las funciones que se pueden emplear con la función summarise() para resumir una variable. Empleando estas funciones tenemos una poderosa herramienta para resumir cualquier base de datos sin importar su tamaño.

Cuadro 2.2: Principales funciones que se pueden emplear para hacer operaciones sobre una variable con el verbo summarise()
Función Descripción Función Descripción
min() valor mínimo max() valor máximo
quantile() el cuantil deseado IQR() rango intercuartílico
last() último valor first() mayor o igual que
mean() media median() mediana
var() varianza sd() desviación estándar
n() número de casos n_distinct número de caso que no se repiten



En algunas ocasiones no queremos agregar los datos de un grupo con una suma o un promedio, pero queremos encontrar el caso con el valor más grande de una variable o las primeras 10 observaciones con los valores mas altos (top 10) en su respectivo grupo. En este caso empleamos el verbo top_n(). El primer argumento son los datos, que se pasan con el operador %>%, el segundo es n, el número de valores top que queremos obtener; el último es la variable para la cual vamos a buscar los n casos.

Veamos un ejemplo. Supongamos que queremos encontrar los países que en cada continente tienen la esperanza de vida al nacer más grande en 2007. Esto lo podemos hacer de la siguiente manera:

gapminder %>% 
  filter(year ==2007) %>% 
  group_by(continent) %>% 
  top_n(1, lifeExp)
## # A tibble: 5 × 6
## # Groups:   continent [5]
##   country   continent  year lifeExp       pop gdpPercap
##   <fct>     <fct>     <int>   <dbl>     <int>     <dbl>
## 1 Australia Oceania    2007    81.2  20434176    34435.
## 2 Canada    Americas   2007    80.7  33390141    36319.
## 3 Iceland   Europe     2007    81.8    301931    36181.
## 4 Japan     Asia       2007    82.6 127467972    31656.
## 5 Reunion   Africa     2007    76.4    798094     7670.

Noten que el verbo top_n() extrae la o las observaciones con el mayor valor para la variable deseada (en este caso lifeExp), pero el resultado trae todas las variables correspondientes a ese caso (no solo la que se empleó para hacer el filtrado de los datos).

Veamos otro ejemplo. Ahora, busquemos los 5 países con el PIB per cápita mas grande en su respectivo continente en 2007. Y ordenemos el resultado en orden alfabético por continente y al interior del continente por el orden decente del PIB per cápita.

gapminder %>% 
  filter(year ==2007) %>% 
  group_by(continent) %>% 
  top_n(5, gdpPercap) %>% 
  arrange(continent, desc(gdpPercap))
## # A tibble: 22 × 6
## # Groups:   continent [5]
##    country             continent  year lifeExp    pop gdpPercap
##    <fct>               <fct>     <int>   <dbl>  <int>     <dbl>
##  1 Gabon               Africa     2007    56.7 1.45e6    13206.
##  2 Botswana            Africa     2007    50.7 1.64e6    12570.
##  3 Equatorial Guinea   Africa     2007    51.6 5.51e5    12154.
##  4 Libya               Africa     2007    74.0 6.04e6    12057.
##  5 Mauritius           Africa     2007    72.8 1.25e6    10957.
##  6 United States       Americas   2007    78.2 3.01e8    42952.
##  7 Canada              Americas   2007    80.7 3.34e7    36319.
##  8 Puerto Rico         Americas   2007    78.7 3.94e6    19329.
##  9 Trinidad and Tobago Americas   2007    69.8 1.06e6    18009.
## 10 Chile               Americas   2007    78.6 1.63e7    13172.
## # … with 12 more rows

Observa que para Oceanía solo se presentan dos países, pues son los dos únicos disponibles en los datos.

Finalmente, también está disponible el verbo ungroup() (desagrupar) que permite quitar las agrupaciones realizadas y retornar en el flujo de trabajo a la base original. Esta función no requiere de argumentos diferentes al objeto con los datos.

2.4 Comentarios finales

En este capítulo estudiamos diferentes verbos del paquete dplyr que nos permiten extraer diferentes observaciones, ordenarlas y agruparlas para calcular algunas cantidades que resuman los grupos. Estas nuevas herramientas son muy útiles para hacerle preguntas a los datos. En el Capítulo 3 veremos verbos que nos permitirán manipular variables.

Antes de terminar con la discusión de los verbos asociados a los casos, es importante mencionar que existen muchos más verbos que serán útiles en diferentes ocasiones. Puedes encontrar ayuda rápidamente en la comunidad por medio de los diferentes buscadores. A medida que empieces a usar este paquete y seas más fluido en sus funciones, probablemente te encontrarás en la necesidad de encontrar más verbos para expresarte en tus análisis.

Referencias

Bryan, J. (2017). Gapminder: Data from gapminder. https://CRAN.R-project.org/package=gapminder

  1. Si quieres ver una breve introducción a la actividad de limpieza de datos puedes ver el siguiente video: https://youtu.be/V2uXaKTIWv4 .↩︎

  2. Hay que tener mucho cuidado con esta función, pues tiene el mismo nombre de otra función del paquete base de R. Si en alguna ocasión te das cuenta que no está corriendo la función del paquete dplyr, puedes especificar en el código que deseas emplear la función del paquete dplyr colocando la siguiente expresión dplyr::filter(). Los :: le informan a R que se empleará la función que está a la derecha de dicho símbolo y el paquete a la izquierda de este. En general, este truco se puede usar para solucionar problemas de compatibilidad de dplyr con otros paquetes o la base de R. Esto era más común en las primeras versiones del paquete. Esos problemas se han solucionado cada vez más; por lo tanto es poco probable que surjan inconvenientes.↩︎

  3. Según esa recomendación no tiene sentido hacer tuberías de un solo paso. Por eso no se incluye el operador %>% y se especifica el argumento de los datos directamente en la función.↩︎

  4. Esto implica ordenar de menor a mayor si se trata de una variable de clase numeric o integer. Si se trata de una variable de clase character el orden ascendente implica seguir el orden alfabético (de la A a la Z). Lo mismo ocurre para variables de clase factor a las que no se les ha establecido un orden con anterioridad. Si a la variable de clase factor se le ha establecido un orden, entonces se empleará dicho orden para organizarla.↩︎

  5. La media que es un sinónimo de promedio en inglés es mean.↩︎

  6. Un verbo sinónimo (hace lo mismo) de summarise() es summarize(). ) ↩︎

  7. Otra forma de contar cuántos casos existen para un posible valor de una variable, y no necesariamente para generar una agrupación, es el verbo count() . El primer argumento es, como ya lo intuiste, el objeto con los datos. El segundo argumento es la variable o variables que quieres contar. Adicionalmente, este verbo tiene el argumento lógico sort que permite organizar los resultados. Intenta contar los países por continente pasando el objeto gapminder, filtrando para un año (filter(year ==2007)) y pasando el resultado al verbo contar (count(continent, sort = TRUE)). ↩︎