Programación y manejo de datos con R
(Web del curso aquí)
Aprendimos a cargar datos, pero es raro que los datos estén preparados para empezar nuestro análisis, así que hay que “arreglar/limpiar” los datos. Para ello, tenemos que:
Hacer nuestros datos TIDY
Arreglarlos/modificarlos para que sean útiles para nuestros propósitos
Arreglando los datos
¿qué es el tidyverse?
principales pkgs del Tidyverse
readr: para importar datos
tidyr: convertir a tidy data
dplyr: para manipular datos
ggplot2: para hacer gráficos
stringr: para manipular strings
… y algunos más
aquí nos centraremos principalmente en dos paquetes: dplyr y tidyr
“Filosofía” del Tidyverse
Programs must be written for people to read, and only incidentally for machines to execute – Hal Abelson
dos principios del tidyverse
Los scripts deben ser “fácilmente” legibles por las personas
Resolver problemas complejos encadenando funciones simples con el operador pipe (%>%)
El operador %>% es crucial en el tidyverse
|> (la veremos en una extensión)El operador %>% es crucial en el tidyverse. Hay que saber usarlo y entenderlo
Es fácil. Pronto os sentiréis cómodos con él.
The pipe
Igual os resulta más fácil entenderlo con ejemplos
The pipe: ¿lo has entendido?
¿Qué hace la siguiente expresión?
Pipe nativa (|>)
|>Mejoras en la pipe nativa
En la versión 4.2.0 se mejora la pipe nativa introduciendo un “placeholder”: el guion bajo _
iris |> head(x = _, n = 4)Aun así, la pipe nativa tiene algunas limitaciones: aquí, aquí y en este vídeo
¿qué pasará en el futuro?
|>%>% versus |>: ¿en qué se diferencian?
%>% en código “antiguo” (y en muchas de nuestras transparencias) y |> en el código más modernoEn la mayoría de los casos son intercambiables, pero hay algunas diferencias:
El placeholder en %>%: es el punto .; mientas que en |>: es el guion bajo _
El placeholder de |> necesita que los argumentos tengan nombre
Paréntesis: %>% deja escribir la función sin paréntesis; mientras que |> exige ponerlos
Paquetes: %>% necesita cargar un paquete (magrittr, o tidyverse); |> no, ya está incluido en R-base
Truquillo: el atajo de teclado Ctrl + Shift + M escribe la pipe por ti
En RStudio puedes elegir que pipe escribe el atajo.
Para cambiarlo tienes que seguir esta ruta de menús: Tools > Global Options > Code > Use native pipe operator, |>
tidyrUn aspecto importante del tidyverse es hacer los datos tidy
Parece fácil, y lo es: realmente es la situación a la que estamos acostumbrados, pero …
… mejor desarrollarlo con unos ejemplos
Tidy data … ejemplo I
¿Estos datos son tidy?
Pedro, Carla, María) son valores de una variable (“persona”)Tidy data … ejemplo II
¿Estos datos son tidy?
w_2014, …), pero son valores de la variable “año”Tidy data … ejemplo III
¿Estos datos son tidy?
Sí, estos sí son datos tidy
Son más difíciles de leer por las personas, pero es que los datos los leen las máquinas!!
Los datos tidy suelen estar en formato largo o long
Para trabajar à la tidyverse es crucial que los datos sean “tidy” (en formato LONG)
Es importante aprender a pasar de datos WIDE a LONG y viceversa
Con el pkg tidyr es sencillo pero …
De wide a LONG format con … tidyr::pivot_longer()
Tarea: convierte “df_wide” a formato LARGO
Aquí tienes un df en formato ANCHO, pásalo a formato LARGO
Está en formato ancho:
| students | w_2014 | w_2015 | w_2016 |
|---|---|---|---|
| Pedro | 100 | 500 | 200 |
| Carla | 400 | 600 | 250 |
| María | 200 | 700 | 900 |
Lo quiero en formato largo:
| students | name | value |
|---|---|---|
| Pedro | w_2014 | 100 |
| Pedro | w_2015 | 500 |
| Pedro | w_2016 | 200 |
| Carla | w_2014 | 400 |
| Carla | w_2015 | 600 |
| Carla | w_2016 | 250 |
| María | w_2014 | 200 |
| María | w_2015 | 700 |
| María | w_2016 | 900 |
df_wide y ver qué columnas contienen valores de la misma variabletidyr::pivot_longer() y en su primer argumento le tienes que decir qué columnas hay que convertir a formato “largo”El df original en formato ancho:
| students | w_2014 | w_2015 | w_2016 |
|---|---|---|---|
| Pedro | 100 | 500 | 200 |
| Carla | 400 | 600 | 250 |
| María | 200 | 700 | 900 |
#- la función pivot_longer() transforma los datos de formato ancho(wide) a formato largo(long)
df_long <- df_wide %>%
tidyr::pivot_longer(cols = 2:4)
gt::gt(df_long)| students | name | value |
|---|---|---|
| Pedro | w_2014 | 100 |
| Pedro | w_2015 | 500 |
| Pedro | w_2016 | 200 |
| Carla | w_2014 | 400 |
| Carla | w_2015 | 600 |
| Carla | w_2016 | 250 |
| María | w_2014 | 200 |
| María | w_2015 | 700 |
| María | w_2016 | 900 |
#- con names_to y values_to elegimos el nombre de las dos columnas nuevas
df_long <- df_wide %>%
tidyr::pivot_longer(cols = 2:4,
names_to = "periodo",
values_to = "salario")
gt::gt(df_long)| students | periodo | salario |
|---|---|---|
| Pedro | w_2014 | 100 |
| Pedro | w_2015 | 500 |
| Pedro | w_2016 | 200 |
| Carla | w_2014 | 400 |
| Carla | w_2015 | 600 |
| Carla | w_2016 | 250 |
| María | w_2014 | 200 |
| María | w_2015 | 700 |
| María | w_2016 | 900 |
df_long <- df_wide %>%
1 tidyr::pivot_longer(cols = starts_with("w_"),
2 names_to = "year",
3 values_to = "salario",
4 names_prefix = "w_",
5 names_transform = as.integer)cols = -students
w_ de los nombres: "w_2014" pasa a "2014"
| students | year | salario |
|---|---|---|
| Pedro | 2014 | 100 |
| Pedro | 2015 | 500 |
| Pedro | 2016 | 200 |
| Carla | 2014 | 400 |
| Carla | 2015 | 600 |
| Carla | 2016 | 250 |
| María | 2014 | 200 |
| María | 2015 | 700 |
| María | 2016 | 900 |
De long a WIDE format con tidyr::pivot_wider()
¿Por qué querríamos convertirlo a formato ANCHO?
Tarea: convierte “df_long” a formato ANCHO
df_long está en formato LARGO, pásalo a formato ANCHO
Tienes que distribuir los datos de salario en columnas: una columna para cada año
Partimos del df_long de la “Solución ampliada” de la tarea anterior (con las columnas year y salario)
Está en formato largo:
| students | year | salario |
|---|---|---|
| Pedro | 2014 | 100 |
| Pedro | 2015 | 500 |
| Pedro | 2016 | 200 |
| Carla | 2014 | 400 |
| Carla | 2015 | 600 |
| Carla | 2016 | 250 |
| María | 2014 | 200 |
| María | 2015 | 700 |
| María | 2016 | 900 |
Lo quiero en formato ancho: (con una columna para cada año)
| students | 2014 | 2015 | 2016 |
|---|---|---|---|
| Pedro | 100 | 500 | 200 |
| Carla | 400 | 600 | 250 |
| María | 200 | 700 | 900 |
tidyr::pivot_wider()values_from le tienes que decir qué columna quieres que sus valores se distribuyan entre varias columnasnames_from le tienes que decir qué columna quieres que sus valores aparezcan como los títulos de las nuevas columnas que se crearánTarea: convierte OTRA VEZ “df_long” a formato ANCHO
df_long está en formato LARGO, pásalo a formato ANCHO
Tienes que distribuir los datos de salario en columnas: PERO esta vez quiero que haya una columna para cada persona (Pedro, Carla, …)
Está en formato largo:
Lo quiero en formato ancho:
(con una columna para cada PERSONA)
| year | Pedro | Carla | María |
|---|---|---|---|
| 2014 | 100 | 400 | 200 |
| 2015 | 500 | 600 | 700 |
| 2016 | 200 | 250 | 900 |
tidyr::pivot_wider()values_from le tienes que decir qué columna quieres que sus valores se distribuyan entre varias columnasnames_from le tienes que decir qué columna quieres que sus valores aparezcan como los títulos de las nuevas columnas que se crearántidyrtidyr tiene muchas funciones. Veremos dos: separate() y unite()las funciones separate() y unite()
separate() y unite() nos facilitarán poder separar y unir columnas| personajes | year |
|---|---|
| Pedro Navaja | 1978 |
| Bob Dylan | 1941 |
| Peter Pan | 1904 |
personajesTarea: Utilizando tidyr::separate()
personajes en dos columnas: nombre y apellidotidyr::separate()Tendrás que darle los valores de 3 argumentos: col, into y sep
en el argumento col tienes que decir qué columna quieres separar
en into tienes que decir qué nombres quieres que tengan las nuevas columnas
en sep tienes que decir qué carácter separa los dos elementos
df_3 <- data.frame(personajes = c("Pedro Navaja", "Pedro de la Vega"),
1 year = c(1978, 2000))| personajes | year |
|---|---|
| Pedro Navaja | 1978 |
| Pedro de la Vega | 2000 |
1df_3 %>%
tidyr::separate(col = personajes, into = c("nombre", "apellido"), sep = " ")| nombre | apellido | year |
|---|---|---|
| Pedro | Navaja | 1978 |
| Pedro | de | 2000 |
df_3 %>%
tidyr::separate(col = personajes, into = c("nombre", "apellido"), sep = " ",
1 extra = "merge")extra = "merge" el resto de trozos se quedan juntos en la última columna: “de la Vega”
| nombre | apellido | year |
|---|---|---|
| Pedro | Navaja | 1978 |
| Pedro | de la Vega | 2000 |
Tarea: Utilizando tidyr::unite()
Partimos de df_1, el resultado de la tarea anterior (con las columnas nombre y apellido)
Une las columnas nombre y apellido en una sola columna llamada nombre_y_apellido, con el formato "apellido, nombre"
Esto es lo que tenemos (df_1):
| nombre | apellido | year |
|---|---|---|
| Pedro | Navaja | 1978 |
| Bob | Dylan | 1941 |
| Peter | Pan | 1904 |
Esto es lo que quiero:
| nombre_y_apellido | year |
|---|---|
| Navaja, Pedro | 1978 |
| Dylan, Bob | 1941 |
| Pan, Peter | 1904 |
tidyr::unite()Tendrás que darle los valores de “3” argumentos:
en el argumento col tienes que decir el nombre de la nueva columna que se va a crear
después tienes que decir qué columnas quieres unir
en sep tienes que decir qué carácter separará los dos elementos que vamos a unir
Extensión: las nuevas funciones para separar (y unir) columnas
tidyr (2023), separate() está superseded: sigue funcionando, pero ya no se desarrolla. Sus sustitutas son la familia separate_wider_*() y separate_longer_*()Hace lo mismo que separate(): separa una columna en varias columnas.
Cambian los nombres de los argumentos: cols, delim y names
Funciones hermanas:
separate_wider_position() (separa por posición)
separate_wider_regex() (con expresiones regulares)
separate() solo avisaba; separate_wider_delim() da un error, y hay que decirle qué hacer con too_many (y too_few)df_3 %>%
tidyr::separate_wider_delim(
cols = personajes,
delim = " ",
names = c("nombre", "apellido"),
1 too_many = "merge")too_many = "merge" deja los trozos que sobran en la última columna. Con too_many = "debug" añade columnas para ver dónde está el problema
| nombre | apellido | year |
|---|---|---|
| Pedro | Navaja | 1978 |
| Pedro | de la Vega | 2000 |
separate_rows())| student | cursos |
|---|---|
| Pedro | R, Python |
| Carla | R, SQL, Excel |
unite() no ha cambiado: no está superseded y no tiene una función nueva que la sustituya
La alternativa habitual es crear la columna con dplyr::mutate() (lo veremos en el siguiente tema) y una función que pegue textos, como paste() o stringr::str_c():
1df_1 %>% dplyr::mutate(nombre_y_apellido = paste(apellido, nombre, sep = ", "))unite(), mutate() no elimina las columnas originales
nombre apellido year nombre_y_apellido
1 Pedro Navaja 1978 Navaja, Pedro
2 Bob Dylan 1941 Dylan, Bob
3 Peter Pan 1904 Pan, Peter
NA) con tidyrNA). tidyr tiene varias funciones para tratarlosAlgunas funciones de tidyr para los NA
NA:| students | year | salario |
|---|---|---|
| Pedro | 2014 | NA |
| NA | 2015 | 500 |
| Carla | 2014 | 400 |
NA …NA en las columnas que le digamosNA con el valor anterior (muy útil con ficheros Excel con celdas combinadas)NA por el valor que le digamos| students | year | salario |
|---|---|---|
| Pedro | 2014 | NA |
| desconocido | 2015 | 500 |
| Carla | 2014 | 400 |
NA numérico con 0 (p. ej. un salario desconocido) cambia los resultados (medias, totales, …). Un salario desconocido no es un salario 0Hace explícitos los NA “ocultos”: filas que faltan en los datos. En df_na no hay ninguna fila para Carla en 2015
complete() crea las combinaciones de students y year que faltan (antes rellenamos students con fill())
Práctica: datos reales
tidyr trae el data.frame billboard: la posición de canciones en la lista Billboard del año 2000, semana a semana| artist | track | date.entered | wk1 | wk2 | wk3 | wk4 | wk5 |
|---|---|---|---|---|---|---|---|
| 2 Pac | Baby Don't Cry (Keep... | 2000-02-26 | 87 | 82 | 72 | 77 | 87 |
| 2Ge+her | The Hardest Part Of ... | 2000-09-02 | 91 | 87 | 92 | NA | NA |
| 3 Doors Down | Kryptonite | 2000-04-08 | 81 | 70 | 68 | 67 | 66 |
| 3 Doors Down | Loser | 2000-10-21 | 76 | 76 | 72 | 69 | 67 |
¿Son datos tidy? ¿Por qué?
Pásalos a formato largo, con una columna week (que sea un número) y otra rank
[🌶] Elimina las filas con NA (las semanas en las que la canción ya no estaba en la lista)
Las columnas wk1, wk2, … wk76 son valores de la variable “semana”
En pivot_longer(): cols = starts_with("wk"), y fíjate en los argumentos names_prefix y names_transform
Para los NA: la función drop_na()
No son tidy: las columnas wk1, wk2, … no son variables, son valores de la variable “semana”
Con pivot_longer(). Para que week sea un número: names_prefix quita el "wk" y as.integer() lo convierte
[🌶] Para quitar los NA: la función drop_na() o el argumento values_drop_na = TRUE
Con funciones:
| artist | track | date.entered | week | rank |
|---|---|---|---|---|
| 2 Pac | Baby Don't Cry (Keep... | 2000-02-26 | 1 | 87 |
| 2 Pac | Baby Don't Cry (Keep... | 2000-02-26 | 2 | 82 |
| 2 Pac | Baby Don't Cry (Keep... | 2000-02-26 | 3 | 72 |
| 2 Pac | Baby Don't Cry (Keep... | 2000-02-26 | 4 | 77 |
| 2 Pac | Baby Don't Cry (Keep... | 2000-02-26 | 5 | 87 |
| Para … | usamos … |
|---|---|
| encadenar funciones | la pipe: %>% o |> |
| pasar de ancho a largo | tidyr::pivot_longer() |
| pasar de largo a ancho | tidyr::pivot_wider() |
| separar una columna en varias | tidyr::separate() (o la nueva separate_wider_delim()) |
| unir varias columnas en una | tidyr::unite() |
tratar los NA |
tidyr::drop_na(), fill(), replace_na(), complete() |
tidyrEn el siguiente tema: manipular datos con dplyr