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

Módulo práctico de ITA
(Grupo M)

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)

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 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

COMBINANDO tablas (joining df’s)


Hasta ahora hemos trabajado con un único df, pero …

… muchas veces tenemos que trabajar con datos que están en varias tablas


  • así que, a veces tendremos que juntar o fusionar tablas

joining df’s

  • Hay diversos tipos de uniones de tablas: un buen recurso para estos temas es el capítulo dedicado a JOINS en R4DS2ed

  • Las uniones más comunes son las “mutating joins”, que añaden columnas de una tabla a otra. Lo vemos:


mutating joins (juntando df1 con df2)

  • Las mutating joins lo que hacen es añadir las variables de df2 a df1
  • Hay 3/4 tipos de mutating joins. Todas añaden las columnas de df2 al df1; PERO …


  • se diferencian en las filas que se seleccionan

  • Las filas que se seleccionan dependen del criterio para hacer el match. Lo vemos:

3/4 tipos de mutating joins

Recuerda que todas ellas añaden columnas (las columnas de df1 a df2) pero que se diferencian en las filas que se seleccionan

  • inner_join(df1,df2): retorna las filas de df1 que también existen en df2
  • left_join(df1,df2): retorna TODAS las filas de df1
  • full_join(df1,df2): retorna TODAS las filas de df1 y de df2; (osea, retorna TODAS las filas y TODAS las columnas de las 2 tablas)

mutating joins: un ejemplo

  • En el ejemplo usaremos estos 2 df’s:
df1 <- tibble(id = 1:3, x = paste0("x", 1:3))
df2 <- tibble(id = 1:4, y = paste0("y", 1:4)) %>% slice(-3)
df1
id x
1 x1
2 x2
3 x3
df2
id y
1 y1
2 y2
4 y4

mutating joins: un ejemplo


df_inner <- inner_join(df1, df2)             #- inner_join() only includes observations that match in df1 and df2

df_full_join <- full_join(df1, df2)             #- full_join() includes all observations from df1 and df2

df_left_join <- left_join(df1, df2)            #- left_join()  includes all observations in df1.

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

Extensión: select() junto a la función where() [🌶🌶🌶🌶]

  • select() y where() son dos funciones, sí, pero en la jerga del tidyverse, select() es un verbo y where() es un adverbio, cualifica/cambia lo que hace select().

Ejemplo de uso:

  • 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 que son numéricas. Podemos hacerlo por nombre o por posición pero mejor con select() y la función auxiliar where()

aa <- gapminder %>% select(is.numeric)        #- funciona, pero ...

aa <- gapminder %>% select(where(is.numeric)) #- es "preferible" esta segunda expresión
  • Si queremos seleccionar las variables que NO son numéricas haríamos:
aa <- gapminder %>% select(!where(is.numeric)) 

summarise() con across() : permite hacer “resúmenes” de muchas variables

  • Usar across() junto a summarise() permite calcular estadísticos de todas las variables, o de subconjuntos de estas, de manera más cómoda:
#- media de cada una de las 6 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) ) 

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


across() y where() con summarise()

  • Dentro de across() se puede utilizar where() para aplicar criterios lógicos en la selección variables [🌶🌶🌶🌶🌶🌶] 🌟
gapminder %>% summarise(across(where(is.numeric), mean))

Pipe nativa ( |> )

Mejoras en la pipe nativa

¿qué pasará en el futuro?