Dataframes con librerías

Aunque ya conocemos diferentes maneras de trabajar con nuestros dataframes con las funciones básicas de R (también conocido como base), actualmente existen librerías que nos permiten realizar estas transformaciones de manera maá sencilla. En esta lección descubriremos la librería dplyr.

La libreria dplyr

La libreria dplyr proporciona una gramática para la manipulación y la realización de operaciones con dataframes. Esto es muy útil, ya que proporciona una abstracción que anteriormente no existía. Por último, cabe destacar que las funciones del paquete dplyr son muy rápidas, puesto que están implementadas con el lenguaje C++.

dplyr no proporciona ninguna nueva funcionalidad a R per se, en el sentido que todo aquello que podemos hacer con ella lo podríamos hacer con la sintaxis básica de R. Forma parte de tidyverse (enlace), una colección de paquetes de R diseñados para tareas de ciencia de datos. Todos los paquetes comparten una filosofía de diseño, gramática y estructuras de datos subyacentes.

La idea básica con la que trabaja tidyverse es la de proponer una forma consistente de organizar los datos en R, una organización llamada datos ordenados. Obtener datos en este formato requiere un trabajo inicial, pero ese trabajo vale la pena a largo plazo. Hay tres reglas interrelacionadas que hacen que un conjunto de datos esté ordenado:

  • Cada variable debe tener su propia columna.

  • Cada observación debe tener su propia fila.

  • Cada valor debe tener su propia celda.

El artículo llamado Tidy Data presenta una descripción de la teoría que sustenta esta organización de los datos.

Veamos los siguientes ejemplos:

  country      year  cases population
  <chr>       <int>  <int>      <int>
1 Afghanistan  1999    745   19987071
2 Afghanistan  2000   2666   20595360
3 Brazil       1999  37737  172006362
4 Brazil       2000  80488  174504898
5 China        1999 212258 1272915272
6 China        2000 213766 1280428583
  country      year type           count
  <chr>       <int> <chr>          <int>
1 Afghanistan  1999 cases            745
2 Afghanistan  1999 population  19987071
3 Afghanistan  2000 cases           2666
4 Afghanistan  2000 population  20595360
5 Brazil       1999 cases          37737
6 Brazil       1999 population 172006362
  country      year rate
  <chr>       <int> <chr>
1 Afghanistan  1999 745/19987071
2 Afghanistan  2000 2666/20595360
3 Brazil       1999 37737/172006362
4 Brazil       2000 80488/174504898
5 China        1999 212258/1272915272
6 China        2000 213766/1280428583

En este ejemplo, solo la primera está ordenada. Es la única representación donde cada columna es una variable.

Instalación

Podemos instalar el paquete desde CRAN y luego cargarlo para su uso:

> install.packages("dplyr")
> library(dplyr)

Posiblemente, cuando carguemos el paquete nos aparezcan una serie de alertas, esto es debido a que esta librería tiene funciones con el mismo nombre que otras librerías de R. Por el momento haremos caso omiso a estos avisos.

Selección y transformación de datos con dplyr

Como ya hemos comentado en la introducción de este capítulo, la librería dplyr no proporciona funcionalidades adicionales, sino que facilitan el trabajo proporcionando una serie de funciones y un operador que permiten realizar las mismas tareas que hemos aprendido a realizar con las funciones propias del núcleo de R.

Las cinco funciones clave que nos permiten resolver la gran mayoría de los desafíos de manipulación de datos son las siguientes:

  • filter: Seleccionar observaciones según sus valores.

  • arrange: Reordenar las filas.

  • select: Elegir las variables por sus nombres.

  • mutate: Crear nuevas columnas a partir de columnas (variables) ya existentes.

  • summarise: Resumir los valores de un dataframe.

Todas las funciones con las que trabajaremos en este capítulo tienen en común una serie de argumentos. En particular,

  • El primer argumento es el dataframe con el que queremos trabajar.

  • Los otros argumentos describen que hacer con el dataframe especificado en el primer argumento, podemos referirnos a las columnas en el dataframe directamente sin utilizar el operador $, es decir solo con el nombre de la columna/variable.

  • El valor de retorno es un nuevo dataframe. Esto es consistente con el modus operandi que hemos aprendido en la lección anterior, no sobreescribimos la información que ya teníamos.

Para que estas funciones realicen las tareas de forma correcta supondremos que el dataframe está correctamente ordenado siguiendo las normas anteriormente descritas. Referente a los dataframe, estas operaciones los transforman en un nuevo tipo de estructura llamada tibble. Para nosotros este cambio no tiene ninguna implicación, simplemente veremos que si mostramos los datos por pantalla se verán un poco “más bonito”.

Filter

Permite crear subconjuntos de observaciones en función de sus valores. El primer argumento es el nombre del dataframe. El segundo argumento y los subsiguientes son las expresiones que lo filtran. Por ejemplo, si queremos obtener todas aquellas observaciones que tienen una longitud del Sépalo mayor a 5 deberíamos ejecutar la siguiente expresión:

filter(iris, iris$Sepal.Length > 5.0)

Si queremos aquellas observaciones que tienen una longitud del Sépalo mayor a 5 y además son de la especie Setosa podríamos ejecutar la siguiente expresión:

filter(iris, Sepal.Length > 5.0, Species == "setosa")

Finalmente, si queremos aquellas observaciones que tienen una longitud del Sépalo mayor a 5 o son de la especie Setosa deberíamos ejecutar la siguiente expresión:

filter(iris, Sepal.Length > 5.0 | Species == "setosa") # Notad la expresión lógica

Cabe recordar que todas estas funciones devuelven un nuevo dataframe.

Arrange

Funciona de manera similar a filter excepto que en lugar de seleccionar filas, cambia su orden. Se necesita un dataframe y un conjunto de nombres de columna (o expresiones más complicadas) para ordenar. Si proporcionamos más de un nombre de columna, cada columna adicional se usará para desempatar los valores de las columnas anteriores.

Para ordenar el dataset Iris por el valor de Sepal.Length y luego por su Sepal.Width en orden creciente, lo podríamos hacer de la siguiente manera:

arrange(iris, Sepal.Length, Sepal.Width)

Podemos usar la función desc para conseguir que la ordenación sea decreciente:

arrange(iris, desc(Sepal.Length), Sepal.Width)

Select

Permite realizar selecciones de columnas en un dataframe. Podemos seleccionar columnas usando un rango, un vector de índices o su nombre

select(iris, 1:4) #nos proporciona las 4 primeras columnas del dataframe
select(iris, c(1,3)) #nos proporciona la primera y la tercera columna del dataframe
select(iris, Sepal.Width, Sepal.Length) # seleccionamos las 2 primeras columnas pero en distinto orden

Hay una serie de funciones auxiliares que podemos usar dentro de select:

  • starts_with(“abc”): selecciona los nombres de columna que comienzan con “abc”.

  • ends_with(“xyz”): selecciona los nombres de columna que terminan con “xyz”.

  • contains(“ijk”): selecciona los nombres de columna que contienen “ijk”.

  • num_range(“x”, 1:3): selecciona los nombres de columna: x1, x2 y x3.

La siguiente expresión selecciona las columnas que empiezan por Sepal:

select(iris, starts_with("Sepal"))

Mutate

Además de poder seleccionar conjuntos de columnas existentes, a menudo nos puede ser útil agregar nuevas columnas que son funciones de columnas existentes. Ese es el trabajo de la función mutate, hay que tener en cuenta que siempre agrega nuevas columnas al final del dataframe.

Si queremos añadir una columna llamada Sepal.Ratio que sea la relación entre la longitud y la anchura de un sépalo podríamos hacerlo de la siguiente manera:

mutate(iris, Sepal.Ratio = Sepal.Width / Sepal.Length)

Summarise

Esta función contrae un dataframe en una sola fila. Tal como podemos observar si lo aplicamos a los datos de Iris, por si sola no ofrece resultados útiles, pero en el futuro veremos que es de las funciones más útiles.

Pipelines

Además de las funciones que hemos comentado hasta ahora dplyr nos ofrece un nuevo operador %>% que permite concatenar operaciones. Es decir, el resultado de una operación (un dataframe) es el primer parámetro de la siguiente.

Por ejemplo podríamos concatenar la creación de la columna Sepal.Ratio y luego filtrar según si este valor es mayor a \(0.5\) . Finalmente, eliminaremos la columna Species.

resultado = mutate(iris, Sepal.Ratio= Sepal.Width / Sepal.Length) %>% filter(Sepal.Ratio > 0.5) %>% select(c(1:4,6))

Como podemos observar esto permite hacer las operaciones una por una pero usando el resultado para la siguiente.

Ejercicios

  1. Vamos a necesitar la librería dplyr:

install.packages("dplyr")
library(dplyr)
  1. Volvamos a un conjunto de datos ya conocido: Faculty Salary Data (ver enlace) y carga en R su versión de valores separados por comas (extensión .csv) en una variable con nombre salary.

  2. Intentaremos sacar algunas conclusiones de estos datos. Primero eliminaremos las columnas casey id, sirven para identificar las diferentes filas y nosotros no lo necesitaremos.

  3. De momento nos quedaremos con los datos del año 1995, así que construye un nuevo dataframe que solamente contenga estos datos, a continuación, usando el operador pipeline elimina la columna year.

  4. Transforma las columnas de tipo chr a tipo factor. No puedes hacerlo columna a columna, busca información de la función lapply en la ayuda de RStudio. ¿Qué diferencias observas con la función sapply?

  5. Ordena el conjunto de datos de manera que primero veremos aquellos sueldos mayores. Como posiblemente tendremos múltiples ocurrencias con el mismo salario, el segundo criterio de ordenación será el año de inicio en la universidad.

  6. En breve volveremos a trabajar con estos datos. Es recomendable que los guardes en un fichero en tu carpeta de trabajo.

Agrupaciones

Las secciones anteriores nos han mostrado como realizar operaciones sobre los dataframes que ya éramos capaces de realizar con las funciones básicas de R, ahora aprenderemos a usar funciones avanzadas de dplyr adquiriendo nuevas capacidades.

La función group_by de dplyr agrupa las filas en valores únicos en la columna especificada. Si se especifican varias columnas, las filas se agrupan por las combinaciones únicas de valores en las columnas. Cada valor único (o combinación de valores) constituye un grupo. Los cambios posteriores al conjunto de datos o los cálculos se pueden realizar dentro del contexto de cada grupo.

Hay que tener en cuenta que no hay ningún cambio perceptible en el conjunto de datos después de ejecutar esta función hasta que se aplica otro verbo dplyr. Sin embargo, podemos observar las agrupaciones imprimiendo el dataframe. Cuando imprimimos un dataframe agrupado, veremos que se ha transformado en un objeto de la clase tibble (muy similar a un dataframe estándar que, cuando se imprime, muestra qué agrupaciones se han aplicado y cuántos grupos hay, justo encima de la fila del encabezado).

Veamos el ejemplo con el conjunto de datos mtcars. En primer lugar, lo cargaremos y lo inspeccionaremos visualmente.

> data(mtcars)
> View(mtcars)
> ?mtcars # muestra la descripción del dataset en la ventana Help

En segundo lugar realizaremos una agrupación según la cilindrada del coche y si la transmisión es automática o manual (columna am):

> cars_grouped = group_by(mtcars, cyl, am)

Agrupaciones con otras funciones

Ahora podemos emplear la función summarise sobre nuestros grupos. Por ejemplo podemos calcular la media del peso de los coches y la desviación típica de cada grupo:

> summarise(cars_grouped, mean.weight=mean(wt), std.weight=sd(wt))

# A tibble: 6 × 4
# Groups:   cyl [3]
    cyl   am      mean.weight std.weight
  <dbl>  <dbl>       <dbl>      <dbl>
1     4     0        2.94      0.408
2     4     1        2.04      0.409
3     6     0        3.39      0.116
4     6     1        2.76      0.128
5     8     0        4.10      0.768
6     8     1        3.37      0.283

La agrupación es más útil junto con la función summarise, pero también puede ser interesante realizar operaciones de agrupación para luego poder usar la funciones mutate y filter. Por ejemplo podemos usar la expresión anterior junto al operador de pipeline para filtrar aquellos grupos con un peso medio inferior a 3 (1000 lbs):

> summarise(cars_grouped, mean.weight=mean(wt), std.weight=sd(wt)) %>% filter(mean.weight > 3)

   cyl    am mean.weight std.weight
  <dbl> <dbl>       <dbl>      <dbl>
1     6     0        3.39      0.116
2     8     0        4.10      0.768
3     8     1        3.37      0.283

Podemos usar la función mutate de la siguiente manera:

> summarise(cars_grouped, mean.weight=mean(wt), std.weight=sd(wt)) %>% mutate(norm=(mean.weight-std.weight)/mean.weight)

# A tibble: 6 × 5
# Groups:   cyl [3]
    cyl    am mean.weight std.weight  norm
  <dbl> <dbl>       <dbl>      <dbl> <dbl>
1     4     0        2.94      0.408 0.861
2     4     1        2.04      0.409 0.800
3     6     0        3.39      0.116 0.966
4     6     1        2.76      0.128 0.953
5     8     0        4.10      0.768 0.813
6     8     1        3.37      0.283 0.916

Contando elementos

Siempre que realizamos una agrupación, es una buena idea incluir un conteo de los elementos de cada grupo. Esto lo podemos hacer con la función tally o la expresión summarise(n = n()) .También puede ser interesante incorporar un conteo de valores faltantes summarise(not.available=sum(is.na(x))). De esta manera, puede verificar que no está sacando conclusiones basadas en cantidades muy pequeñas de datos.

En este primer ejemplo realizamos la operación de agrupación y luego añadimos el conteo del número de observaciones por grupo usando la función tally:

> mtcars %>% group_by(cyl, am) %>% tally()

En este segundo ejemplo realizamos un trabajo similar pero usando la función summarise, en ella además miramos is la columna am tiene algún valor NA:

> mtcars %>% group_by(cyl, am) %>% summarise(count=n(), not.available=sum(is.na(am)))

Los conteos son tan útiles que dplyr proporciona la función count que realiza la tarea de agrupar según las columnas que le indiquemos, realizar el conteo y desagrupar el dataframe para mostrarnos la solución. Veamos un ejemplo:

> mtcars %>% count(cyl,am)

Un-group

Los datos que se han agrupado permanecerán agrupados hasta que se desagrupan específicamente a través del uso de la función ungroup. Si nos olvidamos desagrupar un dataframe podemos obtener cálculos incorrectos, ya que las operaciones que hemos trabajado se realizan sobre los grupos.

Ejercicios

Vamos a usar un nuevo conjunto de datos que contiene datos sobre la esperanza de vida, la población y el PIB per cápita de diferentes países en diferentes años. Estos datos se encuentran en la librería gapminder. Podéis encontrar más información del proyecto gapminder en su página web, veréis que nosotros usaremos un pequeño conjunto de datos.

  1. Instala esta librería, carga los datos y realiza una primera exploración.

  2. ¿En qué fechas fueron tomadas estas mediciones? Realiza una expresión para obtener esta información.

  3. Obtén un vector con los países ordenados según su renta per capita en orden descendiente el año 1992. ¿Qué país tenía la renta menor?

  4. Obtén la renta per cápita media de cada continente.

  5. Obtén un nuevo dataframe con la esperanza de vida media (llamado vida) de cada continente para cada año en el que existen datos. Muestra por pantalla la evolución de esta información para europa.

  6. Obtén para un año determinado (por ejemplo 1952) un dataframe en el que obtenemos para cada uno de los países una columna con su esperanza de vida y una nueva columna con la esperanza de vida media de su continente.

  7. Extra: Obtener los 10 primeros países por encima del percentil 90 en esperanza de vida el año 2007) (consulta la ayuda de la función ntile).

Extra

  1. ¿Existe sesgo sexual en la universidad en el año más actual disponible (1995)?

  2. ¿Ha existido sesgo sexual en los salarios iniciales de los profesores? Es decir, salarios en el año de contratación.

  3. ¿Ha existido sesgo sexual al otorgar aumentos salariales entre 1976 y 1995?

  4. En general, ¿cómo responderías a la siguiente pregunta: ¿Existe sesgo sexual en salarios en la universidad? ¿Qué cuestiones están involucradas al intentar generalizar sus resultados?

Recursos

Existen múltiples fuentes que amplían los contenidos de esta lección, pero, sin duda el mejor recurso es el libro r4ds.