Manejo de datos con R (II):
the tidyverse way

Programación y manejo de datos con R
(Web del curso aquí)

Data munging: the tidyverse way



Aprendimos a cargar datos y a hacerlos TIDY, pero es raro que los datos estén preparados para empezar nuestro análisis, así que hay que “arreglar/limpiar” los datos

Arreglando los datos

  • Aprenderemos a limpiar y transformar datos en R. Priorizaremos la nueva forma de hacer las cosas en R (o workflow) conocido como tidyverse.


  • El procesado/limpieza de los datos suele ocupar un 80% del tiempo de un análisis de datos; así que el workflow sería más bien así:

DPLYR


  • el paquete dplyr es el paquete más importante a la hora de manipular datos.

dplyr

  • dplyr es un paquete que permite manipular datos de forma intuitiva


Tiene 6-7 funciones (o verbos) principales

  • Cada función hace “una sola cosa”, así que para realizar transformaciones complejas hay que ir concatenando instrucciones sencillas con el operador pipe (%>%)

sintaxis de dplyr

Todas las funciones tienen una estructura o comportamiento similar:

  • el primer argumento siempre es un df

  • los siguientes argumentos describen que hacer con los datos.

  • el resultado es siempre un nuevo df


Las siguientes 3 expresiones hacen exactamente lo mismo:

filter(df, X1 >= 10)

df %>% filter(. , X1 >= 10)

df %>% filter(X1 >= 10)

principales funciones de dplyr

  • select() : selecciona columnas
  • filter() : filtra/selecciona filas (que cumplen una o varias condiciones)


  • mutate() : crea nuevas columnas (variables)
  • arrange(): reordena las filas
  • rename() : cambia los nombres de las columnas


  • summarise() : resume (colapsa) los valores de una columna a un solo valor. Por ejemplo, calcula la media, máximo, mínimo, etc…

hay una séptima función (que da mucha potencia a dplyr)

  • group_by() : “agrupa” filas (en función de una o varias condiciones)

Extensión: across() y where()


  • Con dplyr 1.0.0, en mayo de 2020, aparecieron dos funciones importantes más: across() y where()


  • Estas funciones son un poco diferentes, solo se usan en combinación de otra función/verbo

  • Son 2 funciones que en la jerga del tidyverse no son verbos sino adverbios.


  • Lo vemos (en las extensiones)

Vamos a aprender a usar dplyr con ejemplos



trabajaremos con datos del pkg gapminder

  • ¿Supongo que ya sabéis que hace el siguiente código?

    • Claro que sí: hacemos accesibles en memoria de R el fichero de datos gapminder que está en el paquete gapminder
gapminder <- gapminder::gapminder   

select() se utiliza para seleccionar variables

  • Seleccionar variables por nombre
aa <- gapminder %>% select(year, lifeExp) 
  • Eliminar variables por nombre
aa <- gapminder %>% select(-year, -lifeExp)


  • Seleccionar variables por posición
aa <- gapminder %>% select(1:3, 5)
  • Eliminar variables por posición
aa <- gapminder %>% select(-c(1:3, 5))

A veces queremos reordenar las columnas/variables

  • Podemos hacerlo con relocate()
aa <- gapminder %>% dplyr::relocate(country, .after = lifeExp)

aa <- gapminder %>% dplyr::relocate(country, .before = lifeExp)

A veces queremos renombrar las columnas

  • Podemos hacerlo rename()
gapminder %>% rename(life_exp = lifeExp)



la función names() de R-base es útil

aa <- gapminder

names(aa)
[1] "country"   "continent" "year"      "lifeExp"   "pop"       "gdpPercap"

A veces queremos CREAR nuevas columnas

  • Podemos hacerlo con mutate()

  • Por ejemplo, podemos crear la variable: GDP = pop * gdpperCap

aa <- gapminder %>% mutate(GDP = pop * gdpPercap)


Extensión:

Por defecto, la nueva variable creada se situará al final del df, a no ser que usemos los argumentos .after y .before

aa <- gapminder %>% mutate(GDP = pop * gdpPercap, .after = pop)

filter(): permite seleccionar filas

filas que cumplan una determinadas condiciones o criterios lógicos

gapminder <- gapminder::gapminder  #- cargamos los datos

#- Observaciones de España (country == "Spain")
aa <- gapminder %>% filter(country == "Spain") 

#- filas con valores de "lifeExp" < 29
aa <- gapminder %>% filter(lifeExp < 29)       

#- filas con valores de "lifeExp" entre [29, 32]
aa <- gapminder %>% filter(lifeExp >=  29 &  lifeExp <= 32)  
aa <- gapminder %>% filter(between(lifeExp, 29, 32))       

#- observaciones de países de África con lifeExp > 32
aa <- gapminder %>% filter(lifeExp > 72 &  continent == "Africa") 

#- observaciones de países de África o Asia con lifeExp > 32
aa <- gapminder %>% filter(lifeExp > 72 &  continent %in% c("Africa", "Asia") )  
aa <- gapminder %>% filter(lifeExp > 72 & (continent == "Africa" | continent == "Asia") ) 

slice() es una variante de filter()

permite seleccionar filas pero por posición


  • filter() y slice() ambas seleccionan filas, la primera por condiciones y la segunda por posición:


#- selecciona las observaciones de la décima a la quinceava
aa <- gapminder %>% slice(c(10:15)) 

#- selecciona las 4 primeras observaciones, de la 41 a a la 43, y las 4 últimas 
aa <- gapminder %>%
  slice( c(1:4, 41:43, (n()-3):n() ) ) 

variantes de slice()

  • slice_max() y slice_min(): seleccionan filas con valor máximo (o mínimo) de una variable:
#- selecciona las 3 filas con mayor valor de lifeExp
aa <- gapminder %>% slice_max(lifeExp, n = 3)

#- selecciona las 4 filas con MENOR valor de pop
aa <- gapminder %>% slice_min(pop, n = 4)

#- observaciones en el primer décil en cuanto a esperanza de vida, 10% con menor esperanza de vida
aa <- gapminder %>% slice_min(lifeExp, prop = 0.1)

#- 1% de observaciones con mayor población. Imagino que estarán China e India
aa <- gapminder %>% slice_max(pop, prop = 0.01)

variantes de slice()

  • slice_sample(): permite obtener una muestra aleatoria de los datos


#- selecciona (aleatoriamente) 100 filas de los datos
aa <- gapminder %>% slice_sample(n = 100)

#- selecciona (aleatoriamente) un 5% de los datos
aa <- gapminder %>% slice_sample(prop = 0.05)

arrange(): permite reordenar las filas de un df


#- ordena las filas de MENOR a mayor según los valores de la v. lifeExp 
aa <- gapminder %>% arrange(lifeExp)

#- ordena las filas de MAYOR a menor según los valores de la v. lifeExp
aa <- gapminder %>% arrange(desc(lifeExp))  

#- ordena las filas de MENOR a mayor según los valores de la v. lifeExp. 
#- Si hubiesen empates se resuelve con la variable "pop"
aa <- gapminder %>% arrange(lifeExp, pop) 

summarize() para “resumir” variables

  • summarize(): coge una variable como input y devuelve un solo valor; por ejemplo, calcula la media aritmética (o el mínimo, o el máximo …) de una columna/variable


  • Empezamos “resumiendo” una sola variable:


aa <- gapminder %>% summarise(maximo = max(pop))  #- el valor máximo de la variable "pop"

aa <- gapminder %>% summarise(NN = n())           #- el número de observaciones

aa <- gapminder %>% summarise(media = mean(lifeExp))  #- la media de la variable "lifeExp"

aa <- gapminder %>% summarise(desviacion_tipica = sd(lifeExp))  #- os lo explicarán en Estadística 

summarize(): “resumimos” dos variables


#- retornará 2 valores: las medias de "lifeExp" y "gdpPercap"
aa <- gapminder %>% summarise(mean(lifeExp), mean(gdpPercap))  



summarize(): hacemos 2 resúmenes de una variable


#- retornará 2 valores: la media y máximo de la v. "lifeExp"
aa <- gapminder %>% summarise(mean(lifeExp), max(lifeExp))

group_by(): con está función ya se puede ver la potencia de dplyr

En análisis de datos muchas operaciones queremos calcularlas para distintos grupos (p. ej. mujer/hombre, diferentes países, provincias …). group_by() permite hacerlo.

  • group_by() coge un df y lo convierte en un “df agrupado”. En ese nuevo “df agrupado”, las operaciones que hagamos, se harán por separado para cada uno de los grupos que hayamos definido. Ahora lo vemos.
  • Por ejemplo: queremos calcular el nº de observaciones en el df y el nº de observaciones de cada continente
# calculamos el nº de observaciones en el df
aa <- gapminder %>% summarise(NN = n()) 
gt::gt(aa)
NN
1704
# ahora queremos calcular el nº de observaciones de cada continente
# cogemos df y lo (des)agrupamos por grupos definidos por la variable "continent"
# o sea, habrá 5 grupos (5 continentes)
# después con summarise() calcularemos el nº de observaciones en cada grupo;
# es decir, nos retornará un df con una fila por cada continente
# con el nº de observaciones de cada continente

bb <- gapminder %>% group_by(continent) %>% summarise(NN = n())
gt::gt(bb)
continent NN
Africa 624
Americas 300
Asia 396
Europe 360
Oceania 24

Extensiones




con dplyr 1.0.0, en 2020, aparecieron dos funciones importantes: across() y where()

  • across() y where(): estas funciones son un poco diferentes, solo se usan en combinación de otra función/verbo. Son 2 funciones que en la jerga del tidyverse no son verbos sino adverbios, cualifican/cambian lo que hacen otras funciones como select()

Ejemplo de uso: select() junto a la función where() [🌶🌶🌶🌶]

  • En gapminder las 2 primeras variables (country y continent) son factores y las 4 siguientes son variable numéricas.


  • Imagina que queremos seleccionar sólo las variables numéricas. Podemos hacerlo por nombre o por posición pero mejor hacerlo con select() y where()
aa <- gapminder %>% select(is.numeric)        #- funciona, pero ...

aa <- gapminder %>% select(where(is.numeric)) #- es "preferible" esta segunda expresión


  • Si quisiseramos seleccionar las variables que NO son numéricas haríamos:
aa <- gapminder %>% select(!where(is.numeric)) 

más ejemplos de across() y where()


  • Imagina que queremos calcular la media de todas las variables de gapminder. Tendríamos que repetir código 6 veces … o usar across()
#- media de todas (everything()) las variables. 
#- Devuelve 2 warnings porque las 2 primeras son textuales. No se puede calcular la media de continent y country
gapminder %>% summarise(across(everything(), mean) )


  • Ahora calculamos la media de la tercera a la sexta variable:
gapminder %>% summarise(across(3:6, mean) )

across() y where() con summarise()       😱


  • Dentro de across() se puede utilizar where()

  • Con esto conseguimos poder aplicar criterios lógicos en la selección de variables: [🌶] [ 🌟 ]


Si quisiéramos calcular la media de las variables numéricas

gapminder %>% summarise(across(where(is.numeric), mean))



Ahora no os asustéis de lo que salga: es lo mismo que lo de arriba pero con los nombres de los argumentos

#- con los nombres de los argumentos (más largo pero conviene verlo de vez en cuando)
gapminder %>% summarise(across(.cols = where(is.numeric), .fns = mean))

summarise() con across() y varias funciones         😱😱😱😱

  • Si quieres calcular varios estadísticos de varias variables; por ejemplo, la media y la desviación típica de un grupo de variables

  • Tendrás que seguir utilizando summarise() con across() pero, además, tendrás que poner la lista de funciones dentro de list(). [🌶] [ 🌟 ]

Si quisiéramos calcular la media y la desviación típica de las variables 3 a 6

gapminder %>% summarise(across(3:6, list(media = mean, desv = sd)))


Otra vez, no os asustéis de lo que salga: es lo mismo que lo de arriba pero con los nombres de los argumentos

#- lo mismo, pero explicitando los nombres de los argumentos [🌶] 
gapminder %>% summarise(across(.cols = 3:6, .fns = list(media = mean, desv = sd) ))


Ya lo último: añadimos un argumento más .names, que nos permite controlar el nombre de las nuevas variables

#- lo mismo otra vez, pero eligiendo el nombre de las variables que se van a crear con .names [🌶] [🌶] 
gapminder %>% summarise(across(3:6, list(media = mean, desv = sd), .names = "{fn}_{col}"))

Más ejemplos para afianzar el uso de across()


Creamos un nuevo df: “gapminder_gr” o “gapminder agrupado”

gapminder_gr <- gapminder %>% filter(year %in% c(1952, 2007)) %>%
                 group_by(continent, year) 

Hacemos cálculos partiendo de “gapminder_gr”. Recuerda que esta “agrupado” por continente y año

#- si queremos calcular la media de varias variables tenemos que usar across()
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), mean))

#- si queremos calcular la media de todas las variables numéricas tenemos que usar across() y where()
gapminder_gr %>% summarise(across(where(is.numeric), mean))

#- si queremos calcular la media y la mediana, hay que usar list()
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), 
                            list (media = mean, mediana = median) ))

#- si ponemos los nombres de los argumentos quedaría como
gapminder_gr %>% summarise(across(.cols = c(lifeExp, gdpPercap), 
                                  .fns = list (media = mean, mediana = median)))

#- además, podemos controlar el nombre de las variables creadas con el argumento .names
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), 
                        list (media = mean, mediana = median), 
                        .names = "{fn}_{col}"))

Seguimos con dplyr



  • Seguimos con más ejemplos para afianzar nuestros conocimientos

  • Empezamos con ejemplos sencillos; pronto llegaremos a “preguntas de verdad”

vimos ya la la “teoría” del tidyverse

  • El tidyverse es un conjunto de paquetes de R que facilitan la manipulación de datos, su visualización y modelización


  • dplyr es el paquete más importante a la hora de manipular datos.


  • Sus funciones más importantes son: select(), filter(), mutate(), arrange(), rename, summarise() y group_by().


  • Cada función hace “una sola cosa”, así que para realizar transformaciones complejas hay que ir concatenando instrucciones sencillas con el operador pipe (%>%)

Ejemplos


para aprender a manipular datos con dplyr

CONTAR (observaciones)


  • Es importante saber cómo contar las observaciones que tenemos en distintos grupos

  • Por ejemplo, contar cuantas mujeres hay, o cuantas observaciones hay en cada continente, o cuantas empresas en diferentes sectores, …

… vamos a CONTAR

tenemos 3 formas distintas de contar el nº de observaciones (o filas)

1. Usando mutate() con n()

#- fíjate q con mutate() se mantienen todas las filas y todas las columnas
aa <- gapminder %>% mutate(NN = n())  

nrow(aa)  #- se mantienen las 1704 observaciones
[1] 1704

1. mutate() mantiene el nº de filas originales (1704)

bb <- head(aa, n = 3)
gt::gt(bb)
country continent year lifeExp pop gdpPercap NN
Afghanistan Asia 1952 28.801 8425333 779.4453 1704
Afghanistan Asia 1957 30.332 9240934 820.8530 1704
Afghanistan Asia 1962 31.997 10267083 853.1007 1704

2. Usando summarise() con n()

#- fíjate q  summarise() solo devuelve una fila y una columna
aa <- gapminder %>% summarise(NN = n())

2. summarise() devuelve una fila (por grupo)

gt::gt(aa)
NN
1704

3. Usando count()

aa <- gapminder %>% count()

3. count() devuelve una fila (por grupo)

gt::gt(aa)
n
1704

… seguimos CONTANDO

Pero ahora contamos el nº de observaciones de distintos grupos (por ejemplo el nº de observaciones de cada continente)

  • También podríamos usar mutate() pero esta vez solo usaremos summarise() y count()
# fíjate q  summarise() devuelve una fila por cada grupo
# una fila por cada continente
aa <- gapminder %>% 
  group_by(continent) %>% summarise(NN = n())
gt::gt(aa)
continent NN
Africa 624
Americas 300
Asia 396
Europe 360
Oceania 24
aa <- gapminder %>% count(continent)
gt::gt(aa)
continent n
Africa 624
Americas 300
Asia 396
Europe 360
Oceania 24

… aún seguimos CONTANDO

Contamos el nº de observaciones de distintos grupos, pero ahora definidos por dos variables (por ejemplo el nº de observaciones de cada continente y año)

# fíjate q hay 60 grupos (5 continentes x 12 periodos)
# por lo que devuelve 60 filas, una por grupo

aa <- gapminder %>% 
  group_by(continent, year) %>% summarise(NN = n())


aa <- gapminder %>% count(year, continent)
bb <- head(aa, n = 14)
gt::gt(bb) 
year continent n
1952 Africa 52
1952 Americas 25
1952 Asia 33
1952 Europe 30
1952 Oceania 2
1957 Africa 52
1957 Americas 25
1957 Asia 33
1957 Europe 30
1957 Oceania 2
1962 Africa 52
1962 Americas 25
1962 Asia 33
1962 Europe 30

Extensión: n() versus nrow()

  • n() es una función auxiliar en el tidyverse. Devuelve en nº de filas “in the current group”. Solo funciona en el tidyverse, concretamente en funciones como mutate() y summarise()
#- como agrupamos por año y continente, saldrán 60 grupos (12 x 5)

aa <- gapminder %>%
  group_by(continent, year) %>% 
  summarise(NN = n()) %>% 
  ungroup()
aa %>% slice(1, 2, 12, 13) %>% 
  gt::gt() %>% 
  gtExtras::gt_theme_guardian()