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.
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
<- gapminder %>%
americas_2007 # 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.
<- filter(gapminder,
americas_2007 == "Americas" &
continent == 2007 )
year
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:
<- gapminder %>%
Euro_ame_2007 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.
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.
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:
<- gapminder %>%
oceania_s21 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:
<- gapminder %>%
oceania_s21 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.
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:
<- gapminder %>%
EV_continetes 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:
<- gapminder %>%
EV_continetes 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().
<- gapminder %>%
BD_continetes 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
<- gapminder %>%
BD_continetes filter(year ==2007) %>%
group_by(continent) %>%
summarise(Exp_Promedio = mean(lifeExp),
P_tot = sum(pop),
Paises = n()) %>%
# Mala práctica
<- gapminder %>%
BD_continetes 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.
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
Si quieres ver una breve introducción a la actividad de limpieza de datos puedes ver el siguiente video: https://youtu.be/V2uXaKTIWv4 .↩︎
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.↩︎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.↩︎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.↩︎
La media que es un sinónimo de promedio en inglés es mean.↩︎
Un verbo sinónimo (hace lo mismo) de summarise() es summarize(). ) ↩︎
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)
). ↩︎