|>
datos limpio(...) |>
filtro(...) |>
selecciono(...) |>
ordeno(...) |>
modifico(...) |>
renombro(...) |>
agrupo(...) |>
cuento(...) |>
resumo(...) |>
pinto(...)
Tidyverse: manipulación de filas y columnas
Cuadernos prácticos de Software II del Grado en Ciencia de Datos Aplicada (curso 2024-2025)
1 Tidyverse: manipulación de filas
1.1 Introducción
1.1.1 ¿Qué es tidyverse?
{tibble}
: optimizando data.frame{tidyr}
: limpieza de datos{readr}
: carga datos rectangulares (.csv){dplyr}
: gramática para depurar{stringr}
: manejo de textos{ggplot2}
: visualización de datos{tidymodels}
: modelización/predicción
Dentro de {tidyverse}
usaremos el paquete {dplyr}
para el preprocesamiento y depuración de los datos.
La idea es que el código sea legible, como si fuese una lista de instrucciones que al leerla nos diga de manera muy evidente lo que está haciendo.
1.1.2 Hipótesis: tidydata
Toda la depuración que vamos a realizar es sobre la hipótesis de que nuestros datos están en tidydata
Recuerda que en {tidyverse}
será clave el operador pipe (tubería) definido como |>
(ctrl+shift+M): será una tubería que recorre los datos y los transforma.
Vamos a practicar con el dataset starwars
del paquete cargado {dplyr}
library(tidyverse)
starwars
1.2 Muestreo (filas/individuos)
Una de las operaciones más comunes es lo que se conoce en estadística como muestreo: una selección o filtrado de registros (una submuestra)
No aleatorio (por cuotas): en base a condiciones lógicas sobre los registros (
filter()
)No aleatorio (intencional/discreccional): en base a posición (
slice()
)Aleatorio simple (
slice_sample()
)Aleatorio estratificado (
group_by()
+slice_sample()
)
1.2.1 Filtrar filas: filter()
|>
starwars filter(condicion)
El más simple es cuando filtramos registros en base a alguna condición lógica: con filter()
se seleccionarán solo individuos que cumplan ciertas condiciones (muestreo no aleatorio por condiciones)
==
,!=
: igual o distinto que (|> filter(variable == "a")
)>
,<
: mayor o menor que (|> filter(variable < 3)
)>=
,<=
: mayor o igual o menor o igual que (|> filter(variable >= 5)
)%in%
: valores pertenencen a un listado de opciones (|> filter(variable %in% c("azul", "verde"))
)between(variable, val1, val2)
: si los valores (continuos) caen dentro de un rango de valores (|> filter(between(variable, 160, 180))
)
Dichas condiciones lógicas las podemos combinar de diferentes maneras (y, o, o excluyente)
Recuerda que dentro de filter()
debe ir siempre algo que devuelva un vector de valores lógicos.
¿Cómo harías para… filtrar los personajes de ojos marrones?
¿Qué tipo de variable es? –> La variable eye_color
es cualitativa así que está representada por textos
|>
starwars filter(eye_color == "brown")
# A tibble: 21 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Leia Or… 150 49 brown light brown 19 fema… femin…
2 Biggs D… 183 84 black light brown 24 male mascu…
3 Han Solo 180 80 brown fair brown 29 male mascu…
4 Yoda 66 17 white green brown 896 male mascu…
5 Boba Fe… 183 78.2 black fair brown 31.5 male mascu…
6 Lando C… 177 79 black dark brown 31 male mascu…
7 Arvel C… NA NA brown fair brown NA male mascu…
8 Wicket … 88 20 brown brown brown 8 male mascu…
9 Padmé A… 185 45 brown light brown 46 fema… femin…
10 Quarsh … 183 NA black dark brown 62 male mascu…
# ℹ 11 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para… filtrar los personajes que no tienen ojos marrones?
|>
starwars filter(eye_color != "brown")
# A tibble: 66 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Owen La… 178 120 brown, gr… light blue 52 male mascu…
6 Beru Wh… 165 75 brown light blue 47 fema… femin…
7 R5-D4 97 32 <NA> white, red red NA none mascu…
8 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
9 Anakin … 188 84 blond fair blue 41.9 male mascu…
10 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
# ℹ 56 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para … filtrar los personajes que tengan los ojos marrones o azules?
|>
starwars filter(eye_color %in% c("blue", "brown"))
# A tibble: 40 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 Leia Or… 150 49 brown light brown 19 fema… femin…
3 Owen La… 178 120 brown, gr… light blue 52 male mascu…
4 Beru Wh… 165 75 brown light blue 47 fema… femin…
5 Biggs D… 183 84 black light brown 24 male mascu…
6 Anakin … 188 84 blond fair blue 41.9 male mascu…
7 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
8 Chewbac… 228 112 brown unknown blue 200 male mascu…
9 Han Solo 180 80 brown fair brown 29 male mascu…
10 Jek Ton… 180 110 brown fair blue NA <NA> <NA>
# ℹ 30 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Fíjate que %in%
es equivalente a concatenar varios ==
con una conjunción o (|
)
|>
starwars filter(eye_color == "blue" | eye_color == "brown")
# A tibble: 40 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 Leia Or… 150 49 brown light brown 19 fema… femin…
3 Owen La… 178 120 brown, gr… light blue 52 male mascu…
4 Beru Wh… 165 75 brown light blue 47 fema… femin…
5 Biggs D… 183 84 black light brown 24 male mascu…
6 Anakin … 188 84 blond fair blue 41.9 male mascu…
7 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
8 Chewbac… 228 112 brown unknown blue 200 male mascu…
9 Han Solo 180 80 brown fair brown 29 male mascu…
10 Jek Ton… 180 110 brown fair blue NA <NA> <NA>
# ℹ 30 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para … filtrar los personajes que midan entre 120 y 160 cm?
¿Qué tipo de variable es? –> La variable height
es cuantitativa continua así que deberemos filtrar por rangos de valores (intervalos) –> usaremos between()
|>
starwars filter(between(height, 120, 160))
# A tibble: 6 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Leia Org… 150 49 brown light brown 19 fema… femin…
2 Mon Moth… 150 NA auburn fair blue 48 fema… femin…
3 Nien Nunb 160 68 none grey black NA male mascu…
4 Watto 137 NA black blue, grey yellow NA male mascu…
5 Gasgano 122 NA none white, bl… black NA male mascu…
6 Cordé 157 NA brown light brown NA <NA> <NA>
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para… filtrar los personajes que tengan ojos y no sean humanos?
|>
starwars filter(eye_color == "brown" & species != "Human")
# A tibble: 3 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Yoda 66 17 white green brown 896 male mascu…
2 Wicket S… 88 20 brown brown brown 8 male mascu…
3 Eeth Koth 171 NA black brown brown NA male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Cómo harías para… filtrar los personajes que tengan ojos y no sean humanos, o que tengan más de 60 años? Piénsalo bien: los paréntesis son importantes: no es lo mismo \((a+b)*c\) que \(a+(b*c)\)
|>
starwars filter((eye_color == "brown" & species != "Human") | birth_year > 60)
# A tibble: 18 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 C-3PO 167 75 <NA> gold yellow 112 none mascu…
2 Wilhuff… 180 NA auburn, g… fair blue 64 male mascu…
3 Chewbac… 228 112 brown unknown blue 200 male mascu…
4 Jabba D… 175 1358 <NA> green-tan… orange 600 herm… mascu…
5 Yoda 66 17 white green brown 896 male mascu…
6 Palpati… 170 75 grey pale yellow 82 male mascu…
7 Wicket … 88 20 brown brown brown 8 male mascu…
8 Qui-Gon… 193 89 brown fair blue 92 male mascu…
9 Finis V… 170 NA blond fair blue 91 male mascu…
10 Quarsh … 183 NA black dark brown 62 male mascu…
11 Shmi Sk… 163 NA black fair brown 72 fema… femin…
12 Mace Wi… 188 84 none dark brown 72 male mascu…
13 Ki-Adi-… 198 82 white pale yellow 92 male mascu…
14 Eeth Ko… 171 NA black brown brown NA male mascu…
15 Cliegg … 183 NA brown fair blue 82 male mascu…
16 Dooku 193 80 white fair brown 102 male mascu…
17 Bail Pr… 191 NA black tan brown 67 male mascu…
18 Jango F… 183 79 black tan brown 66 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
1.2.2 Eliminar ausentes: drop_na()
|>
starwars drop_na(var1, var2, ...)
Hay un filtro especial para una de las operaciones más habituales en depuración: retirar los ausentes. Para ello podemos usar dentro de un filtro is.na()
, que nos devuelve TRUE/FALSE
en función de si es ausente, o bien …
Usar drop_na()
: si no indicamos variable, elimina registros con ausente en cualquier variable. Más adelante veremos como imputar esos ausentes
|>
starwars drop_na(mass, height)
# A tibble: 59 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 49 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
|>
starwars drop_na()
# A tibble: 29 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 Darth V… 202 136 none white yellow 41.9 male mascu…
3 Leia Or… 150 49 brown light brown 19 fema… femin…
4 Owen La… 178 120 brown, gr… light blue 52 male mascu…
5 Beru Wh… 165 75 brown light blue 47 fema… femin…
6 Biggs D… 183 84 black light brown 24 male mascu…
7 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
8 Anakin … 188 84 blond fair blue 41.9 male mascu…
9 Chewbac… 228 112 brown unknown blue 200 male mascu…
10 Han Solo 180 80 brown fair brown 29 male mascu…
# ℹ 19 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
1.3 💻 Tu turno
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Selecciona del conjunto de starwars solo los personajes que sean androides o cuyo valor en species
sea desconocido
Code
|>
starwars filter(species == "Droid" | is.na(species))
📝 Selecciona del conjunto de starwars solo los personajes cuyo peso esté entre 65 y 90 kg.
Code
|> filter(between(mass, 65, 90)) starwars
📝 Tras limpiar de ausentes en todas las variables, selecciona del conjunto de starwars solo los personajes que sean humanos y que vengan de Tatooine
Code
|>
starwars drop_na() |>
filter(species == "Human" & homeworld == "Tatooine")
📝 Selecciona del conjunto original de starwars los personajes no humanos, male
en el sexo y que midan entre 120 y 170 cm, o los personajes con ojos marrones o rojos.
Code
|>
starwars filter((species != "Human" & sex == "male" &
between(height, 120, 170)) |
%in% c("brown", "red")) eye_color
📝 Busca información en la ayuda de la función str_detect()
del paquete {stringr}
(cargado en {tidyverse}
). Consejo: prueba antes las funciones que vayas a usar con algún vector de prueba para poder comprobar su funcionamiento. Tras saber lo que hace, filtra solo aquellos personajes con apellido Skywalker
Code
|> filter(str_detect(name, "Skywalker")) starwars
1.3.1 Rebanadas de datos: slice()
|> slice(posiciones) starwars
A veces nos puede interesar realizar un muestreo no aleatorio discreccional, o lo que es lo mismo, filtrar por posición: con slice(posiciones)
podremos seleccionar filas concretas pasando como argumento un vector de índices
# fila 1
|>
starwars slice(1)
# A tibble: 1 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sky… 172 77 blond fair blue 19 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
# filas de la 7 a la 9
|>
starwars slice(7:9)
# A tibble: 3 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Beru Whi… 165 75 brown light blue 47 fema… femin…
2 R5-D4 97 32 <NA> white, red red NA none mascu…
3 Biggs Da… 183 84 black light brown 24 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
# filas 2, 7, 10 y 31
|>
starwars slice(c(2, 7, 10, 31))
# A tibble: 4 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 C-3PO 167 75 <NA> gold yellow 112 none mascu…
2 Beru Whi… 165 75 brown light blue 47 fema… femin…
3 Obi-Wan … 182 77 auburn, w… fair blue-gray 57 male mascu…
4 Qui-Gon … 193 89 brown fair blue 92 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Disponemos de opciones por defecto:
- con
slice_head(n = ...)
yslice_tail(n = ...)
podemos obtener la cabecera y cola de la tabla
|> slice_head(n = 2) starwars
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sky… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
|> slice_tail(n = 2) starwars
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 BB8 NA NA none none black NA none mascu…
2 Captain … NA NA none none unknown NA fema… femin…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
- con
slice_max()
yslice_min()
obtenemos la filas con menor/mayor valor de una variable (si empate, todas salvo quewith_ties = FALSE
) que indicamos enorder_by = ...
|> slice_min(mass, n = 2) starwars
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Ratts Ty… 79 15 none grey, blue unknown NA male mascu…
2 Yoda 66 17 white green brown 896 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
|> slice_max(height, n = 2) starwars
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Yarael P… 264 NA none white yellow NA male mascu…
2 Tarfful 234 136 brown brown blue NA male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
1.3.2 Muestreo aleatorio: slice_sample()
|>
starwars slice_sample(posiciones)
El conocido como muestreo aleatorio simple se basa en seleccionar individuos aleatoriamente, de forma que cada uno tenga ciertas probabilidades de ser seleccionado. Con slice_sample(n = ...)
podemos extraer n registros aleatoriamente (a priori equiprobables).
|> slice_sample(n = 2) starwars
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Jek Tono… 180 110 brown fair blue NA <NA> <NA>
2 Dooku 193 80 white fair brown 102 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
«Aleatorio» no implica equiprobable: es igual de aleatorio un dado normal que uno trucado. No hay cosas «más aleatorias» que otras, simplemente tienen subyacente distintas leyes de probabilidad.
También podremos indicarle la proporción de datos a samplear (en lugar del número) y si queremos que sea con reemplazamiento (que se puedan repetir).
# 5% de registros aleatorios con reemplazamiento
|>
starwars slice_sample(prop = 0.05, replace = TRUE)
# A tibble: 4 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Finis Va… 170 NA blond fair blue 91 male mascu…
2 Shmi Sky… 163 NA black fair brown 72 fema… femin…
3 R4-P17 96 NA none silver, r… red, blue NA none femin…
4 Finn NA NA black dark dark NA male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Como decíamos, «aleatorio» no es igual que «equiprobable», así que podemos pasarle un vector de probabilidades. Por ejemplo, vamos a forzar que sea muy improbable sacar una fila que no sean las dos primeras
|>
starwars slice_sample(n = 2, weight_by = c(0.495, 0.495, rep(0.01/85, 85)))
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sky… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
|>
starwars slice_sample(n = 2, weight_by = c(0.495, 0.495, rep(0.01/85, 85)))
# A tibble: 2 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 C-3PO 167 75 <NA> gold yellow 112 none mascu…
2 Luke Sky… 172 77 blond fair blue 19 male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
1.4 Paréntesis: sample()
La función slice_sample()
es simplemente una integración de {tidyverse}
de la función básica de R
conocida como sample()
que nos permite muestrear elementos
Por ejemplo, vamos a muestrear 10 tiradas de un dado, indicándole
- soporte de nuestra variable aleatorio (valores permitidos en
x
) - tamaño muestral (
size
) - reemplazamiento (si
TRUE
entonces pueden salir repetidas, como en el caso del dado)
sample(x = 1:6, size = 10, replace = TRUE)
[1] 2 4 1 5 3 3 2 1 5 3
La opción anterior lo que genera son sucesos de una variable aleatoria equiprobable pero al igual que antes, podemos asignarle un vector de probabilidades o función de masa concreta con el argumento prob = ...
sample(x = 1:6, size = 50, replace = TRUE,
prob = c(0.5, 0.2, 0.1, 0.1, 0.05, 0.05))
[1] 1 1 1 2 1 6 3 1 5 4 3 2 1 1 1 3 1 1 1 2 1 1 6 3 4 1 4 1 2 2 3 4 3 1 1 1 1 1
[39] 1 1 3 4 2 1 1 1 4 3 1 1
¿Cómo harías el siguiente enunciado?
Supongamos que en una ciudad se han estudiado episodios de gripe estacional. Sean las variables aleatorias \(X_m\) y \(X_p\) tal que \(X_m=1\) si la madre tiene gripe, \(X_m=0\) si la madre no tiene gripe, \(X_p=1\) si el padre tiene gripe y \(X_p=0\) si el padre no tiene gripe. El modelo teórico asociado a este tipo de epidemias indica que la distribución conjunta viene dada por \(P(X_m = 1, X_p=1)=0.02\), \(P(X_m = 1, X_p=0)=0.08\), \(P(X_m = 1, X_p=0)=0.1\) y \(P(X_m = 0, X_p=0)=0.8\)
Genera una muestra de tamaño \(n = 1000\) (soporte "10"
, "01"
, "00"
y "11"
) haciendo uso de runif()
y haciendo uso de sample()
1.5 Reordenar filas: arrange()
|> arrange(var1, var2, ...) starwars
También podemos ordenar filas en función de alguna variable con arrange()
|> arrange(mass) starwars
# A tibble: 87 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Ratts T… 79 15 none grey, blue unknown NA male mascu…
2 Yoda 66 17 white green brown 896 male mascu…
3 Wicket … 88 20 brown brown brown 8 male mascu…
4 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
5 R5-D4 97 32 <NA> white, red red NA none mascu…
6 Sebulba 112 40 none grey, red orange NA male mascu…
7 Padmé A… 185 45 brown light brown 46 fema… femin…
8 Dud Bolt 94 45 none blue, grey yellow NA male mascu…
9 Wat Tam… 193 48 none green, gr… unknown NA male mascu…
10 Sly Moo… 178 48 none pale white NA <NA> <NA>
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Por defecto de menor a mayor pero podemos invertir el orden con desc()
|> arrange(desc(height)) starwars
# A tibble: 87 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Yarael … 264 NA none white yellow NA male mascu…
2 Tarfful 234 136 brown brown blue NA male mascu…
3 Lama Su 229 88 none grey black NA male mascu…
4 Chewbac… 228 112 brown unknown blue 200 male mascu…
5 Roos Ta… 224 82 none grey orange NA male mascu…
6 Grievous 216 159 none brown, wh… green, y… NA male mascu…
7 Taun We 213 NA none grey black NA fema… femin…
8 Rugor N… 206 NA none green orange NA male mascu…
9 Tion Me… 206 80 none grey black NA male mascu…
10 Darth V… 202 136 none white yellow 41.9 male mascu…
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
|> arrange(mass, desc(height)) starwars
# A tibble: 87 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Ratts T… 79 15 none grey, blue unknown NA male mascu…
2 Yoda 66 17 white green brown 896 male mascu…
3 Wicket … 88 20 brown brown brown 8 male mascu…
4 R5-D4 97 32 <NA> white, red red NA none mascu…
5 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
6 Sebulba 112 40 none grey, red orange NA male mascu…
7 Padmé A… 185 45 brown light brown 46 fema… femin…
8 Dud Bolt 94 45 none blue, grey yellow NA male mascu…
9 Wat Tam… 193 48 none green, gr… unknown NA male mascu…
10 Sly Moo… 178 48 none pale white NA <NA> <NA>
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
1.6 Eliminar duplicados: distinct()
|> distinct(var1, var2, ...) starwars
Muchas veces necesitaremos asegurarnos que no hay duplicados en alguna variable (DNI) y podemos eliminar filas duplicadas con distinct()
.
|> distinct(sex) starwars
# A tibble: 5 × 1
sex
<chr>
1 male
2 none
3 female
4 hermaphroditic
5 <NA>
Para mantener todas las columnas de la tabla usaremos .keep_all = TRUE
.
|> distinct(sex, .keep_all = TRUE) starwars
# A tibble: 5 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sky… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 Leia Org… 150 49 brown light brown 19 fema… femin…
4 Jabba De… 175 1358 <NA> green-tan… orange 600 herm… mascu…
5 Jek Tono… 180 110 brown fair blue NA <NA> <NA>
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
1.7 Añadir filas: bind_rows()
|> bind_rows(tibble2) tibble1
Por último, podemos concatenar nuevas filas con bind_rows()
con las nuevas observaciones en tabla (si no cuadran columnas rellena con ausentes)
<-
datos tibble("nombre" = c("javi", "laura"), "edad" = c(33, 50))
datos
# A tibble: 2 × 2
nombre edad
<chr> <dbl>
1 javi 33
2 laura 50
|>
datos bind_rows(tibble("nombre" = c("carlos", NA), "cp" = c(28045, 28019)))
# A tibble: 4 × 3
nombre edad cp
<chr> <dbl> <dbl>
1 javi 33 NA
2 laura 50 NA
3 carlos NA 28045
4 <NA> NA 28019
1.8 💻 Tu turno
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Selecciona solo los personajes que sean humanos y de ojos marrones, para después ordernarlos en altura descendente y peso ascendente.
Code
|>
starwars filter(eye_color == "brown" & species == "Human") |>
arrange(height, desc(mass))
📝 Extrae 3 registros aleatoriamente.
Code
|> slice_sample(n = 3) starwars
📝 Extrae el 10% de los registros aleatoriamente.
Code
|> slice_sample(prop = 0.1) starwars
📝 Extrae aleatoriamente 10 personajes pero de forma que la probabilidad de que salga cada uno sea proporcional a su peso (más pesados, más probable)
Code
|>
starwars drop_na(mass) |>
slice_sample(n = 10, weight_by = mass)
📝 Selecciona los 3 personajes más mayores.
Code
|> slice_max(birth_year, n = 3) starwars
📝 Para saber que valores únicos hay en el color de pelo, elimina duplicados de la variable hair_color
, eliminando antes los ausentes de dicha variable.
Code
|>
starwars drop_na(hair_color) |>
distinct(hair_color)
📝 De los personajes que son humanos y miden más de 160 cm, elimina duplicados en color de ojos, elimina ausentes en peso, selecciona los 3 más altos, y orden de mayor a menor peso. Devuelve la tabla.
Code
|>
starwars filter(species == "Human" & height > 160) |>
distinct(eye_color, .keep_all = TRUE) |>
drop_na(mass) |>
slice_max(height, n = 3) |>
arrange(desc(mass))
2 🐣 Caso práctico I: airquality tidyverse
Vamos a volver aun viejon conocido: en el paquete {datasets}
(ya instalado por defecto) teníamos diversos conjuntos de datos y uno de ellos era airquality
con el que ya trabajamos. Los datos capturan medidas diarias (n = 153 observaciones) de la calidad del aire en Nueva York, de mayo a septiembre de 1973. Se midieron 6 variables: niveles de ozono, radiación solar, viento, temperatura, mes y día.
En ese momento lo trabajamos desde la perspectiva de R base y extrayendo algunas variables del mismo. El objetivo ahora será trabajarlo desde la perspectiva de {tidyverse}
fijándonos en las diferencias de una y otra forma de trabajar.
library(datasets)
airquality
Ozone Solar.R Wind Temp Month Day
1 41 190 7.4 67 5 1
2 36 118 8.0 72 5 2
3 12 149 12.6 74 5 3
4 18 313 11.5 62 5 4
5 NA NA 14.3 56 5 5
6 28 NA 14.9 66 5 6
7 23 299 8.6 65 5 7
8 19 99 13.8 59 5 8
9 8 19 20.1 61 5 9
10 NA 194 8.6 69 5 10
11 7 NA 6.9 74 5 11
12 16 256 9.7 69 5 12
13 11 290 9.2 66 5 13
14 14 274 10.9 68 5 14
15 18 65 13.2 58 5 15
16 14 334 11.5 64 5 16
17 34 307 12.0 66 5 17
18 6 78 18.4 57 5 18
19 30 322 11.5 68 5 19
20 11 44 9.7 62 5 20
21 1 8 9.7 59 5 21
22 11 320 16.6 73 5 22
23 4 25 9.7 61 5 23
24 32 92 12.0 61 5 24
25 NA 66 16.6 57 5 25
26 NA 266 14.9 58 5 26
27 NA NA 8.0 57 5 27
28 23 13 12.0 67 5 28
29 45 252 14.9 81 5 29
30 115 223 5.7 79 5 30
31 37 279 7.4 76 5 31
32 NA 286 8.6 78 6 1
33 NA 287 9.7 74 6 2
34 NA 242 16.1 67 6 3
35 NA 186 9.2 84 6 4
36 NA 220 8.6 85 6 5
37 NA 264 14.3 79 6 6
38 29 127 9.7 82 6 7
39 NA 273 6.9 87 6 8
40 71 291 13.8 90 6 9
41 39 323 11.5 87 6 10
42 NA 259 10.9 93 6 11
43 NA 250 9.2 92 6 12
44 23 148 8.0 82 6 13
45 NA 332 13.8 80 6 14
46 NA 322 11.5 79 6 15
47 21 191 14.9 77 6 16
48 37 284 20.7 72 6 17
49 20 37 9.2 65 6 18
50 12 120 11.5 73 6 19
51 13 137 10.3 76 6 20
52 NA 150 6.3 77 6 21
53 NA 59 1.7 76 6 22
54 NA 91 4.6 76 6 23
55 NA 250 6.3 76 6 24
56 NA 135 8.0 75 6 25
57 NA 127 8.0 78 6 26
58 NA 47 10.3 73 6 27
59 NA 98 11.5 80 6 28
60 NA 31 14.9 77 6 29
61 NA 138 8.0 83 6 30
62 135 269 4.1 84 7 1
63 49 248 9.2 85 7 2
64 32 236 9.2 81 7 3
65 NA 101 10.9 84 7 4
66 64 175 4.6 83 7 5
67 40 314 10.9 83 7 6
68 77 276 5.1 88 7 7
69 97 267 6.3 92 7 8
70 97 272 5.7 92 7 9
71 85 175 7.4 89 7 10
72 NA 139 8.6 82 7 11
73 10 264 14.3 73 7 12
74 27 175 14.9 81 7 13
75 NA 291 14.9 91 7 14
76 7 48 14.3 80 7 15
77 48 260 6.9 81 7 16
78 35 274 10.3 82 7 17
79 61 285 6.3 84 7 18
80 79 187 5.1 87 7 19
81 63 220 11.5 85 7 20
82 16 7 6.9 74 7 21
83 NA 258 9.7 81 7 22
84 NA 295 11.5 82 7 23
85 80 294 8.6 86 7 24
86 108 223 8.0 85 7 25
87 20 81 8.6 82 7 26
88 52 82 12.0 86 7 27
89 82 213 7.4 88 7 28
90 50 275 7.4 86 7 29
91 64 253 7.4 83 7 30
92 59 254 9.2 81 7 31
93 39 83 6.9 81 8 1
94 9 24 13.8 81 8 2
95 16 77 7.4 82 8 3
96 78 NA 6.9 86 8 4
97 35 NA 7.4 85 8 5
98 66 NA 4.6 87 8 6
99 122 255 4.0 89 8 7
100 89 229 10.3 90 8 8
101 110 207 8.0 90 8 9
102 NA 222 8.6 92 8 10
103 NA 137 11.5 86 8 11
104 44 192 11.5 86 8 12
105 28 273 11.5 82 8 13
106 65 157 9.7 80 8 14
107 NA 64 11.5 79 8 15
108 22 71 10.3 77 8 16
109 59 51 6.3 79 8 17
110 23 115 7.4 76 8 18
111 31 244 10.9 78 8 19
112 44 190 10.3 78 8 20
113 21 259 15.5 77 8 21
114 9 36 14.3 72 8 22
115 NA 255 12.6 75 8 23
116 45 212 9.7 79 8 24
117 168 238 3.4 81 8 25
118 73 215 8.0 86 8 26
119 NA 153 5.7 88 8 27
120 76 203 9.7 97 8 28
121 118 225 2.3 94 8 29
122 84 237 6.3 96 8 30
123 85 188 6.3 94 8 31
124 96 167 6.9 91 9 1
125 78 197 5.1 92 9 2
126 73 183 2.8 93 9 3
127 91 189 4.6 93 9 4
128 47 95 7.4 87 9 5
129 32 92 15.5 84 9 6
130 20 252 10.9 80 9 7
131 23 220 10.3 78 9 8
132 21 230 10.9 75 9 9
133 24 259 9.7 73 9 10
134 44 236 14.9 81 9 11
135 21 259 15.5 76 9 12
136 28 238 6.3 77 9 13
137 9 24 10.9 71 9 14
138 13 112 11.5 71 9 15
139 46 237 6.9 78 9 16
140 18 224 13.8 67 9 17
141 13 27 10.3 76 9 18
142 24 238 10.3 68 9 19
143 16 201 8.0 82 9 20
144 13 238 12.6 64 9 21
145 23 14 9.2 71 9 22
146 36 139 10.3 81 9 23
147 7 49 10.3 69 9 24
148 14 20 16.6 63 9 25
149 30 193 6.9 70 9 26
150 NA 145 13.2 77 9 27
151 14 191 14.3 75 9 28
152 18 131 8.0 76 9 29
153 20 223 11.5 68 9 30
2.1 Pregunta 1
Convierte a tibble. Accede solo a los 5 primeros registros. Después accede al primero, segundo, quinto y décimo
Code
library(tidyverse)
<- as_tibble(airquality)
airquality
# secuencia de 1 a 5
|>
airquality slice(1:5)
# otra forma
|>
airquality slice(c(1, 2, 3, 4, 5))
# primero, segundo, quinto y décimo
|>
airquality slice(c(1, 2, 5, 10))
# Fíjate que la principal diferencia respecto a R base
# es que ahora no necesitamos [...] y lo más importante:
# el propio código es legible ya que dice casi literal
# lo que quieres hacer
2.2 Pregunta 2
Accede solo a los registros de temperaturas de mayo. Después accede a los elementos de mayo, abril y junio
Code
# mayo
|>
airquality filter(Month == 5)
# abril, mayo, junio
|>
airquality filter(Month %in% c(4, 5, 6))
2.3 Pregunta 3
¿Cuántos registros tenemos de mayo? ¿Y de abril?
Code
# Número de registros de mayo
|>
airquality filter(Month == 5) |>
nrow()
# Número de registros de abril
|>
airquality filter(Month == 4) |>
nrow()
2.4 Pregunta 5
Solo con los datos de agosto, ordena el dataset resultante en función las temperaturas (los más fríos primero, los más cálidos después). Luego ordénalo al revés
Code
# de frios a cálidos
|>
airquality filter(Month == 8) |>
arrange(temp)
# de cálidos a fríos
|>
airquality filter(Month == 8) |>
arrange(desc(temp))
2.5 Pregunta 6
Elimina los ausentes (
NA
) de todas las variables (no puede quedar ningún registro que tenga ausente en alguna de las columnas). Hazlo tanto en R Base (prohibido|>
,filter
, etc) y en tidyverse. En ambos casos guarda el resultado enairquality_sin_NA
Code
# tidyverse
<-
airquality_sin_NA |>
airquality drop_na()
# R base
<-
airquality_sin_NA !is.na(airquality$Ozone) & !is.na(airquality$Solar.R) &
airquality[!is.na(airquality$Wind) & !is.na(airquality$Temp) &
!is.na(airquality$Month) & !is.na(airquality$Day), ]
2.6 Pregunta 7
Con los datos sin
NA
, quédate solo con los datos de verano (junio, julio, agosto y septiembre) y, con esos datos, ordena los registros de menor a mayor temperatura y, en caso de empate, de mayor a menor ozono. Hazlo tanto en R Base (prohibido|>
,filter
, etc) y en tidyverse.
Code
# tidyverse
|>
airquality_sin_NA filter(Month %in% c(6, 7, 8, 9)) |>
arrange(Temp, desc(Ozone))
# r base
<- airquality_sin_NA[airquality_sin_NA$Month %in% c(6, 7, 8, 9), ]
aux_data order(aux_data$Temp, aux_data$Ozone, decreasing = c(FALSE, TRUE)), ] aux_data[
2.7 Pregunta 8
Con los datos sin
NA
, selecciona de manera aleatoria el 10% de los registros de manera que tengan más peso (más probabilidades de salir) los registros con temperatura más alta. Hazlo tanto en R Base (prohibido|>
,filter
, etc) y en tidyverse.
Code
# tidyverse
|>
airquality_sin_NA slice_sample(prop = 0.1, weight_by = Temp)
# r base
sample(1:nrow(airquality_sin_NA),
airquality_sin_NA[size = nrow(airquality_sin_NA)*0.1,
prob = airquality_sin_NA$Temp), ]
3 Tidyverse: manipulación de columnas
3.1 Selección columnas: select()
|> selecciono(var1, var2, ...) datos
|> select(var1, var2, ...) starwars
Hasta ahora todas las operaciones realizadas (aunque usásemos info de columnas) eran por filas. En elc aso de columnas, la acción más sencilla es seleccionar variables por nombre con select()
, dando como argumentos los nombres de columnas sin comillas.
|> select(name, hair_color) starwars
# A tibble: 87 × 2
name hair_color
<chr> <chr>
1 Luke Skywalker blond
2 C-3PO <NA>
3 R2-D2 <NA>
4 Darth Vader none
5 Leia Organa brown
6 Owen Lars brown, grey
7 Beru Whitesun Lars brown
8 R5-D4 <NA>
9 Biggs Darklighter black
10 Obi-Wan Kenobi auburn, white
# ℹ 77 more rows
La función select()
nos permite seleccionar varias variables a la vez, incluso concatenando sus nombres como si fuesen índices numéricos haciendo uso del iterador :
|> select(name:eye_color) starwars
# A tibble: 87 × 6
name height mass hair_color skin_color eye_color
<chr> <int> <dbl> <chr> <chr> <chr>
1 Luke Skywalker 172 77 blond fair blue
2 C-3PO 167 75 <NA> gold yellow
3 R2-D2 96 32 <NA> white, blue red
4 Darth Vader 202 136 none white yellow
5 Leia Organa 150 49 brown light brown
6 Owen Lars 178 120 brown, grey light blue
7 Beru Whitesun Lars 165 75 brown light blue
8 R5-D4 97 32 <NA> white, red red
9 Biggs Darklighter 183 84 black light brown
10 Obi-Wan Kenobi 182 77 auburn, white fair blue-gray
# ℹ 77 more rows
Y podemos deseleccionar columnas con -
delante (recuerda: -
cuando queremos excluir índices o columnas, !
para negar una condición lógica)
|> select(-mass, -(eye_color:starships)) starwars
# A tibble: 87 × 4
name height hair_color skin_color
<chr> <int> <chr> <chr>
1 Luke Skywalker 172 blond fair
2 C-3PO 167 <NA> gold
3 R2-D2 96 <NA> white, blue
4 Darth Vader 202 none white
5 Leia Organa 150 brown light
6 Owen Lars 178 brown, grey light
7 Beru Whitesun Lars 165 brown light
8 R5-D4 97 <NA> white, red
9 Biggs Darklighter 183 black light
10 Obi-Wan Kenobi 182 auburn, white fair
# ℹ 77 more rows
Tenemos además palabras reservadas: everything()
todas las variables…
|> select(mass, homeworld, everything()) starwars
# A tibble: 87 × 14
mass homeworld name height hair_color skin_color eye_color birth_year sex
<dbl> <chr> <chr> <int> <chr> <chr> <chr> <dbl> <chr>
1 77 Tatooine Luke… 172 blond fair blue 19 male
2 75 Tatooine C-3PO 167 <NA> gold yellow 112 none
3 32 Naboo R2-D2 96 <NA> white, bl… red 33 none
4 136 Tatooine Dart… 202 none white yellow 41.9 male
5 49 Alderaan Leia… 150 brown light brown 19 fema…
6 120 Tatooine Owen… 178 brown, gr… light blue 52 male
7 75 Tatooine Beru… 165 brown light blue 47 fema…
8 32 Tatooine R5-D4 97 <NA> white, red red NA none
9 84 Tatooine Bigg… 183 black light brown 24 male
10 77 Stewjon Obi-… 182 auburn, w… fair blue-gray 57 male
# ℹ 77 more rows
# ℹ 5 more variables: gender <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
…y last_col()
para referirnos a la última columna.
|> select(name:mass, homeworld, last_col()) starwars
# A tibble: 87 × 5
name height mass homeworld starships
<chr> <int> <dbl> <chr> <list>
1 Luke Skywalker 172 77 Tatooine <chr [2]>
2 C-3PO 167 75 Tatooine <chr [0]>
3 R2-D2 96 32 Naboo <chr [0]>
4 Darth Vader 202 136 Tatooine <chr [1]>
5 Leia Organa 150 49 Alderaan <chr [0]>
6 Owen Lars 178 120 Tatooine <chr [0]>
7 Beru Whitesun Lars 165 75 Tatooine <chr [0]>
8 R5-D4 97 32 Tatooine <chr [0]>
9 Biggs Darklighter 183 84 Tatooine <chr [1]>
10 Obi-Wan Kenobi 182 77 Stewjon <chr [5]>
# ℹ 77 more rows
También podemos jugar con patrones en el nombre, aquellas que comiencen por un prefijo (starts_with()
), terminen con un sufijo (ends_with()
), contengan un texto (contains()
) o cumplan una expresión regular (matches()
).
# variables cuyo nombre acaba en "color" y contengan sexo o género
|> select(ends_with("color"), matches("sex|gender")) starwars
# A tibble: 87 × 5
hair_color skin_color eye_color sex gender
<chr> <chr> <chr> <chr> <chr>
1 blond fair blue male masculine
2 <NA> gold yellow none masculine
3 <NA> white, blue red none masculine
4 none white yellow male masculine
5 brown light brown female feminine
6 brown, grey light blue male masculine
7 brown light blue female feminine
8 <NA> white, red red none masculine
9 black light brown male masculine
10 auburn, white fair blue-gray male masculine
# ℹ 77 more rows
Incluso podemos seleccionar por rango numérico si tenemos variables con un prefijo y números. Con num_range()
podemos seleccionar con un prefijo y una secuencia numérica.
<-
datos tibble("semana1" = c(115, 141, 232), "semana2" = c(7, NA, 17),
"semana3" = c(95, 162, NA), "semana4" = c(11, 19, 15),
"semana5" = c(NA, 262, 190), "semana6" = c(21, 15, 23))
datos
# A tibble: 3 × 6
semana1 semana2 semana3 semana4 semana5 semana6
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 115 7 95 11 NA 21
2 141 NA 162 19 262 15
3 232 17 NA 15 190 23
|> select(num_range("semana", 1:4)) datos
# A tibble: 3 × 4
semana1 semana2 semana3 semana4
<dbl> <dbl> <dbl> <dbl>
1 115 7 95 11
2 141 NA 162 19
3 232 17 NA 15
Por último, podemos seleccionar columnas por tipo de dato haciendo uso de where()
y dentro una función que devuelva un valor lógico de tipo de dato.
# Solo columnas numéricas o de texto
|> select(where(is.numeric) | where(is.character)) starwars
# A tibble: 87 × 11
height mass birth_year name hair_color skin_color eye_color sex gender
<int> <dbl> <dbl> <chr> <chr> <chr> <chr> <chr> <chr>
1 172 77 19 Luke Sk… blond fair blue male mascu…
2 167 75 112 C-3PO <NA> gold yellow none mascu…
3 96 32 33 R2-D2 <NA> white, bl… red none mascu…
4 202 136 41.9 Darth V… none white yellow male mascu…
5 150 49 19 Leia Or… brown light brown fema… femin…
6 178 120 52 Owen La… brown, gr… light blue male mascu…
7 165 75 47 Beru Wh… brown light blue fema… femin…
8 97 32 NA R5-D4 <NA> white, red red none mascu…
9 183 84 24 Biggs D… black light brown male mascu…
10 182 77 57 Obi-Wan… auburn, w… fair blue-gray male mascu…
# ℹ 77 more rows
# ℹ 2 more variables: homeworld <chr>, species <chr>
3.2 Mover columnas: relocate()
|>
datos recolocar(var1, despues_de = var2)
|>
starwars relocate(var1, .after = var2)
Para facilitar la recolocación de variables tenemos una función para ello, relocate()
, indicándole en .after
o .before
detrás o delante de qué columnas queremos moverlas.
|> relocate(species, .before = name) starwars
# A tibble: 87 × 14
species name height mass hair_color skin_color eye_color birth_year sex
<chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 Human Luke S… 172 77 blond fair blue 19 male
2 Droid C-3PO 167 75 <NA> gold yellow 112 none
3 Droid R2-D2 96 32 <NA> white, bl… red 33 none
4 Human Darth … 202 136 none white yellow 41.9 male
5 Human Leia O… 150 49 brown light brown 19 fema…
6 Human Owen L… 178 120 brown, gr… light blue 52 male
7 Human Beru W… 165 75 brown light blue 47 fema…
8 Droid R5-D4 97 32 <NA> white, red red NA none
9 Human Biggs … 183 84 black light brown 24 male
10 Human Obi-Wa… 182 77 auburn, w… fair blue-gray 57 male
# ℹ 77 more rows
# ℹ 5 more variables: gender <chr>, homeworld <chr>, films <list>,
# vehicles <list>, starships <list>
3.3 Renombrar: rename()
|> renombrar(nuevo = antiguo) datos
|> rename(nuevo = antiguo) starwars
A veces también podemos querer modificar la «metainformación» de los datos, renombrando columnas. Para ello usaremos de rename()
poniendo primero el nombre nuevo y luego el antiguo.
|> rename(nombre = name, altura = height, peso = mass) starwars
# A tibble: 87 × 14
nombre altura peso hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 77 more rows
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
3.4 Extraer columnas: pull()
|> retirar(var) datos
|> pull(var) starwars
Si observas la salida de los select()
sigue siendo una tabla tibble, ya que nos preserva la naturaleza de nuestros datos.
|> select(name) starwars
# A tibble: 87 × 1
name
<chr>
1 Luke Skywalker
2 C-3PO
3 R2-D2
4 Darth Vader
5 Leia Organa
6 Owen Lars
7 Beru Whitesun Lars
8 R5-D4
9 Biggs Darklighter
10 Obi-Wan Kenobi
# ℹ 77 more rows
A veces no querremos dicha estructura sino extraer literalmente la columna en un VECTOR, algo que podemos hacer con pull()
|> pull(name) starwars
[1] "Luke Skywalker" "C-3PO" "R2-D2"
[4] "Darth Vader" "Leia Organa" "Owen Lars"
[7] "Beru Whitesun Lars" "R5-D4" "Biggs Darklighter"
[10] "Obi-Wan Kenobi" "Anakin Skywalker" "Wilhuff Tarkin"
[13] "Chewbacca" "Han Solo" "Greedo"
[16] "Jabba Desilijic Tiure" "Wedge Antilles" "Jek Tono Porkins"
[19] "Yoda" "Palpatine" "Boba Fett"
[22] "IG-88" "Bossk" "Lando Calrissian"
[25] "Lobot" "Ackbar" "Mon Mothma"
[28] "Arvel Crynyd" "Wicket Systri Warrick" "Nien Nunb"
[31] "Qui-Gon Jinn" "Nute Gunray" "Finis Valorum"
[34] "Padmé Amidala" "Jar Jar Binks" "Roos Tarpals"
[37] "Rugor Nass" "Ric Olié" "Watto"
[40] "Sebulba" "Quarsh Panaka" "Shmi Skywalker"
[43] "Darth Maul" "Bib Fortuna" "Ayla Secura"
[46] "Ratts Tyerel" "Dud Bolt" "Gasgano"
[49] "Ben Quadinaros" "Mace Windu" "Ki-Adi-Mundi"
[52] "Kit Fisto" "Eeth Koth" "Adi Gallia"
[55] "Saesee Tiin" "Yarael Poof" "Plo Koon"
[58] "Mas Amedda" "Gregar Typho" "Cordé"
[61] "Cliegg Lars" "Poggle the Lesser" "Luminara Unduli"
[64] "Barriss Offee" "Dormé" "Dooku"
[67] "Bail Prestor Organa" "Jango Fett" "Zam Wesell"
[70] "Dexter Jettster" "Lama Su" "Taun We"
[73] "Jocasta Nu" "R4-P17" "Wat Tambor"
[76] "San Hill" "Shaak Ti" "Grievous"
[79] "Tarfful" "Raymus Antilles" "Sly Moore"
[82] "Tion Medon" "Finn" "Rey"
[85] "Poe Dameron" "BB8" "Captain Phasma"
3.5 💻 Tu turno
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Filtra el conjunto de personajes y quédate solo con aquellos que en la variable height
no tengan un dato ausente. Con los datos obtenidos del filtro anterior, selecciona solo las variables name, height, así como todas aquellas variables que CONTENGAN la palabra color en su nombre.
📝 Con los datos obtenidos del ejercicio anterior, traduce el nombre de las columnas a castellano
📝 Con los datos obtenidos del ejercicio anterior, coloca la variable de color de pelo justo detrás de la variable de nombres.
📝 Con los datos obtenidos del ejercicio anterior, comprueba cuántas modalidades únicas hay en la variable de color de pelo (sin usar unique()
).
📝 Del conjunto de datos originales, elimina las columnas de tipo lista, y tras ello elimina duplicados en la variable eye_color
. Tras eliminar duplicados extrae dicha columna en un vector.
📝 Del conjunto de datos original de starwars, con solo los personajes cuya altura es conocida, extrae en un vector con dicha variable.
📝 Tras obtener el vector del ejercicio anterior, usa dicho vector para realizar un muestreo aleatorio del 50% de los datos de manera que la probabilidad de cada personaje de ser elegido sea inversamente proporcional a su altura (más bajitos, más opciones).
3.6 Modificar columnas: mutate()
|> modificar(nueva = funcion()) datos
|> mutate(nueva = funcion()) starwars
En muchas ocasiones querremos modificar o crear variables con mutate()
. Vamos a crear por ejemplo una nueva variable height_m
con la altura en metros.
|> mutate(height_m = height / 100) starwars
# A tibble: 87 × 15
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Luke Sk… 172 77 blond fair blue 19 male mascu…
2 C-3PO 167 75 <NA> gold yellow 112 none mascu…
3 R2-D2 96 32 <NA> white, bl… red 33 none mascu…
4 Darth V… 202 136 none white yellow 41.9 male mascu…
5 Leia Or… 150 49 brown light brown 19 fema… femin…
6 Owen La… 178 120 brown, gr… light blue 52 male mascu…
7 Beru Wh… 165 75 brown light blue 47 fema… femin…
8 R5-D4 97 32 <NA> white, red red NA none mascu…
9 Biggs D… 183 84 black light brown 24 male mascu…
10 Obi-Wan… 182 77 auburn, w… fair blue-gray 57 male mascu…
# ℹ 77 more rows
# ℹ 6 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>, height_m <dbl>
Además con los argumentos opcionales podemos recolocar la columna modificada
|>
starwars mutate(height_m = height / 100,
IMC = mass / (height_m^2), .before = name)
# A tibble: 87 × 16
height_m IMC name height mass hair_color skin_color eye_color birth_year
<dbl> <dbl> <chr> <int> <dbl> <chr> <chr> <chr> <dbl>
1 1.72 26.0 Luke … 172 77 blond fair blue 19
2 1.67 26.9 C-3PO 167 75 <NA> gold yellow 112
3 0.96 34.7 R2-D2 96 32 <NA> white, bl… red 33
4 2.02 33.3 Darth… 202 136 none white yellow 41.9
5 1.5 21.8 Leia … 150 49 brown light brown 19
6 1.78 37.9 Owen … 178 120 brown, gr… light blue 52
7 1.65 27.5 Beru … 165 75 brown light blue 47
8 0.97 34.0 R5-D4 97 32 <NA> white, red red NA
9 1.83 25.1 Biggs… 183 84 black light brown 24
10 1.82 23.2 Obi-W… 182 77 auburn, w… fair blue-gray 57
# ℹ 77 more rows
# ℹ 7 more variables: sex <chr>, gender <chr>, homeworld <chr>, species <chr>,
# films <list>, vehicles <list>, starships <list>
Cuando aplicamos mutate()
, debemos de acordarnos que las operaciones se realizan de manera vectorial, elemento a elemento, por lo que la función que usemos dentro debe devolver un vector de igual longitud. En caso contrario, devolverá una constante
|>
starwars mutate(constante = mean(mass, na.rm = TRUE), .before = name)
# A tibble: 87 × 15
constante name height mass hair_color skin_color eye_color birth_year sex
<dbl> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 97.3 Luke… 172 77 blond fair blue 19 male
2 97.3 C-3PO 167 75 <NA> gold yellow 112 none
3 97.3 R2-D2 96 32 <NA> white, bl… red 33 none
4 97.3 Dart… 202 136 none white yellow 41.9 male
5 97.3 Leia… 150 49 brown light brown 19 fema…
6 97.3 Owen… 178 120 brown, gr… light blue 52 male
7 97.3 Beru… 165 75 brown light blue 47 fema…
8 97.3 R5-D4 97 32 <NA> white, red red NA none
9 97.3 Bigg… 183 84 black light brown 24 male
10 97.3 Obi-… 182 77 auburn, w… fair blue-gray 57 male
# ℹ 77 more rows
# ℹ 6 more variables: gender <chr>, homeworld <chr>, species <chr>,
# films <list>, vehicles <list>, starships <list>
3.6.1 Recategorizar: if_else()
También podemos combinar mutate()
con la expresión de control if_else()
para recategorizar la variable: si se cumple una condición, hace una cosa, en caso contrario otra.
|>
starwars mutate(human = if_else(species == "Human", "Human", "Not Human"),
.after = name) |>
select(name:mass)
# A tibble: 87 × 4
name human height mass
<chr> <chr> <int> <dbl>
1 Luke Skywalker Human 172 77
2 C-3PO Not Human 167 75
3 R2-D2 Not Human 96 32
4 Darth Vader Human 202 136
5 Leia Organa Human 150 49
6 Owen Lars Human 178 120
7 Beru Whitesun Lars Human 165 75
8 R5-D4 Not Human 97 32
9 Biggs Darklighter Human 183 84
10 Obi-Wan Kenobi Human 182 77
# ℹ 77 more rows
3.6.2 Recategorizar: case_when()
Para recategorizaciones más complejas tenemos case_when()
, por ejemplo, para crear una categoría de los personajes en función de su altura.
|>
starwars drop_na(height) |>
mutate(altura = case_when(height < 120 ~ "enanos",
< 160 ~ "bajito",
height < 180 ~ "normal",
height < 200 ~ "alto",
height TRUE ~ "gigante"), .before = name)
# A tibble: 81 × 15
altura name height mass hair_color skin_color eye_color birth_year sex
<chr> <chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr>
1 normal Luke S… 172 77 blond fair blue 19 male
2 normal C-3PO 167 75 <NA> gold yellow 112 none
3 enanos R2-D2 96 32 <NA> white, bl… red 33 none
4 gigante Darth … 202 136 none white yellow 41.9 male
5 bajito Leia O… 150 49 brown light brown 19 fema…
6 normal Owen L… 178 120 brown, gr… light blue 52 male
7 normal Beru W… 165 75 brown light blue 47 fema…
8 enanos R5-D4 97 32 <NA> white, red red NA none
9 alto Biggs … 183 84 black light brown 24 male
10 alto Obi-Wa… 182 77 auburn, w… fair blue-gray 57 male
# ℹ 71 more rows
# ℹ 6 more variables: gender <chr>, homeworld <chr>, species <chr>,
# films <list>, vehicles <list>, starships <list>
3.7 💻 Tu turno
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Selecciona solo las variables nombre, altura y así como todas aquellas variables relacionadas con el color, a la vez que te quedas solo con aquellos que no tengan ausente en la altura.
Code
|>
starwars select(name, height, contains("color")) |>
drop_na(height)
📝 Con los datos obtenidos del ejercicio anterior, traduce el nombre de las columnas a castellano.
Code
|>
starwars select(name, height, contains("color")) |>
drop_na(height) |>
rename(nombre = name, altura = height,
color_pelo = eye_color, color_piel = skin_color,
color_pelo = hair_color)
📝 Con los datos obtenidos del ejercicio anterior, coloca la variable de color de pelo justo detrás de la variable de nombres.
Code
|>
starwars select(name, height, contains("color")) |>
drop_na(height) |>
rename(nombre = name, altura = height,
color_pelo = eye_color, color_piel = skin_color,
color_pelo = hair_color) |>
relocate(color_pelo, .after = nombre)
📝 Con los datos originales, comprueba cuántas modalidades únicas hay en la variable de color de pelo.
Code
|>
starwars distinct(hair_color) |>
nrow()
📝 Del dataset original, selecciona solo las variables numéricas y de tipo texto. Tras ello define una nueva variable llamada under_18
que nos recategorice la variable de edad: TRUE
si es menor de edad y FALSE
en caso contrario
Code
|>
starwars select(where(is.numeric) | where(is.character)) |>
mutate(under_18 = birth_year < 18)
📝 Del dataset original, crea una nueva columna llamada auburn
(cobrizo/caoba) que nos diga TRUE si el color de pelo contiene dicha palabra y FALSE en caso contrario (reminder str_detect()
).
Code
|>
starwars mutate(auburn = str_detect(hair_color, "auburn"))
📝 Del dataset original, incluye una columna que calcule el IMC. Tras ello, crea una nueva variable que valga NA
si no es humano, delgadez
por debajo de 18, normal
entre 18 y 30, sobrepeso
por encima de 30.
Code
|>
starwars mutate(IMC = mass / ((height/100)^2),
IMC_recat = case_when(species != "Human" ~ NA,
< 18 ~ "delgadez",
IMC < 30 ~ "normal",
IMC TRUE ~ "sobrepeso"),
.after = name)
4 🐣 Caso práctico I: simulación
Haciendo uso de todo lo aprendido, vamos a proceder a crear una tabla con datos de bebés de tamaño n = 20
en donde simulemos el sexo de los bebés y su peso
4.1 Pregunta 1
Crea un
tibble
con dos columnas, una llamadaid_bebe
y otra llamadasexo
. En el primer caso debe ir de 1 a 20. En el segundo caso, simula su sexo de manera que haya un 0.5 de probabilidad dechico
y 0.5 dechica
.
Code
<- 20
n <- tibble("id_bebe" = 1:n,
datos "sexo" = sample(x = c("chico", "chica"), size = n, replace = TRUE))
4.2 Pregunta 2
Conocido el sexo, crea una tercera columna llamada
peso
en la que simules dicho valor. Supondremos que para los chicos el peso sigue una distribución \(N(\mu = 3.266kg, \sigma = 0.514)\) y que para las chicas sigue una distribución \(N(\mu = 3.155kg, \sigma = 0.495)\).
Code
<-
datos |>
datos mutate(peso = rnorm(n, mean = if_else(sexo == "chico", 3.266, 3.155),
sd = if_else(sexo == "chica", 0.495, 0.514)))
5 🐣 Caso práctico II: Taylor Swift
Vamos a volver al análisis de Taylor Swift pero esta vez desde una perspectiva tidyverse
library(taylor)
taylor_album_songs
# A tibble: 240 × 29
album_name ep album_release track_number track_name artist featuring
<chr> <lgl> <date> <int> <chr> <chr> <chr>
1 Taylor Swift FALSE 2006-10-24 1 Tim McGraw Taylo… <NA>
2 Taylor Swift FALSE 2006-10-24 2 Picture To Bu… Taylo… <NA>
3 Taylor Swift FALSE 2006-10-24 3 Teardrops On … Taylo… <NA>
4 Taylor Swift FALSE 2006-10-24 4 A Place In Th… Taylo… <NA>
5 Taylor Swift FALSE 2006-10-24 5 Cold As You Taylo… <NA>
6 Taylor Swift FALSE 2006-10-24 6 The Outside Taylo… <NA>
7 Taylor Swift FALSE 2006-10-24 7 Tied Together… Taylo… <NA>
8 Taylor Swift FALSE 2006-10-24 8 Stay Beautiful Taylo… <NA>
9 Taylor Swift FALSE 2006-10-24 9 Should've Sai… Taylo… <NA>
10 Taylor Swift FALSE 2006-10-24 10 Mary's Song (… Taylo… <NA>
# ℹ 230 more rows
# ℹ 22 more variables: bonus_track <lgl>, promotional_release <date>,
# single_release <date>, track_release <date>, danceability <dbl>,
# energy <dbl>, key <int>, loudness <dbl>, mode <int>, speechiness <dbl>,
# acousticness <dbl>, instrumentalness <dbl>, liveness <dbl>, valence <dbl>,
# tempo <dbl>, time_signature <int>, duration_ms <int>, explicit <lgl>,
# key_name <chr>, mode_name <chr>, key_mode <chr>, lyrics <list>
5.1 Pregunta 1
¿Cuántas canciones hay guardadas? ¿Cuántas características de cada una?
Code
|> nrow()
taylor_album_songs |> ncol() taylor_album_songs
Fíjate que ahora separamos el dato de la acción con datos
+ tubería + acción
5.2 Pregunta 2
Obtén los álbumes únicos del dataset. ¿Cuántos hay?
Code
|> distinct(album_name)
taylor_album_songs |>
taylor_album_songs distinct(album_name, keep) |>
nrow()
Fíjate que distinct()
tiene 2 usos:
sin nada (
.keep_all = FALSE
): devuelve en una tabla los valores únicos de las variables que le digamoscon
.keep_all = TRUE
: devuelve TODAS las variables, y sirve para eliminar filas duplicadas de una tabla.
5.3 Pregunta 3
¿En cuántas canciones tuvo colaboración con otro artista? ¿Cuántos artistas (únicos) han colaborado con ella?
Code
# nº canciones con colaboración
|>
taylor_album_songs drop_na(featuring) |>
nrow()
# nº de colaboradores únicos
|>
taylor_album_songs drop_na(featuring) |>
summarise(n_collabs = n_distinct(featuring))
5.4 Pregunta 4
Crea un nuevo
tibble
solo con las variablesalbum_name
,album_release
,track_name
,featuring
yduration_ms
. Después ordena dicho tibble de más reciente a más antiguo
Code
<-
nuevo_tb |>
taylor_album_songs select(album_name, album_release, track_name, featuring, duration_ms)
|>
nuevo_tb arrange(desc(album_release))
5.5 Pregunta 5
Añade al dataset anterior 2 variables con el mes y año de la variable de fecha
album_release
. Piensa cómo determinar el mes con más canciones
Code
library(lubridate)
<-
nuevo_tb |>
nuevo_tb mutate(month = month(album_release), year = year(album_release))
|>
nuevo_tb count(month, sort = TRUE)
5.6 Pregunta 6
Obtén la duración media de las canciones en minutos (variable
duration_ms
en milisegundos). Extrae la info de la canción que más dura
Code
|>
nuevo_tb drop_na(duration_ms) |>
summarise(avg_dur = mean(duration_ms/60000))
|>
nuevo_tb slice_max(duration_ms)
6 🐣 Caso práctico III: el señor de los anillos
Para practicar algunas funciones de {dplyr}
vamos a usar datos de las películas de la trilogía El Señor de los Anillos. Los datos los cargaremos directamente desde la web (Github en este caso), sin pasar por el ordenador antes, simplemente indicando como ruta la web donde está el archivo
La comunidad del anillo -> https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Fellowship_Of_The_Ring.csv
Las 2 torres -> https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Two_Towers.csv
El Retorno del Rey -> https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Return_Of_The_King.csv
library(readr)
<-
lotr_1 read_csv(file = "https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Fellowship_Of_The_Ring.csv")
Rows: 3 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): Film, Race
dbl (2): Female, Male
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
<-
lotr_2 read_csv(file = "https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Two_Towers.csv")
Rows: 3 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): Film, Race
dbl (2): Female, Male
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
<-
lotr_3 read_csv(file = "https://raw.githubusercontent.com/jennybc/lotr-tidy/master/data/The_Return_Of_The_King.csv")
Rows: 3 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): Film, Race
dbl (2): Female, Male
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
6.1 Pregunta 1
Analiza cada archivo. ¿Cuántas filas tenemos? ¿Cuántas columnas?
Code
nrow(lotr_1)
ncol(lotr_1)
nrow(lotr_2)
ncol(lotr_2)
nrow(lotr_3)
ncol(lotr_3)
6.2 Pregunta 2
Junta los 3 tibble en uno solo. Tras juntarlos usa la función
clean_names()
del paquete{janitor}
(nos sirve para estandarizar nombres de columnas siempre igual)
Code
<-
lotr bind_rows(lotr_1, lotr_2, lotr_3) |>
::clean_names()
janitor lotr
6.3 Pregunta 3
Los números que hay guardados en
female
ymale
en realidad representan la cantidad de palabras que, en cada saga y en cada raza, dice cada uno de los sexos (la cantidad de diálogo de cada tipo de personaje). ¿Es tidy data? En caso negativo convierte a dicho formato.
Code
# no lo es ya que female, male debería ser dato, no nombre de variable
# ahora mismo tenemos una misma variable (count_word, por ejemplo)
# separado en dos columnas (una por sexo). Para convertirlo
# a tidy data basta con pivotar para hacer la tabla más larga
# creando una variable que lleve la información del género
<-
lotr_tidy |>
lotr pivot_longer(cols = c("female", "male"), names_to = "gender",
values_to = "word_count")
lotr_tidy
6.4 Pregunta 4
Guarda el dataset unificado en formato tidy en un
.csv
Code
write_csv(lotr_tidy, file = "./datos/lotr_tidy.csv")
6.5 Pregunta 5
Pasa a minúscula los títulos de las películas y machaca la variable
film
con el nuevo valor
Code
<-
lotr_tidy |>
lotr_tidy mutate(film = str_to_lower(film))
lotr_tidy
6.6 Pregunta 6
Filtra solo los datos para Hobbits hombres y ordena por número de palabras (de más a menos). ¿En cuál de las sagas hablarón más?
Code
|>
lotr_tidy filter(race == "Hobbit" & gender == "male") |>
arrange(desc(word_count))
6.7 Pregunta 7
Separa cada uno de los datasets de manera que solo exista un objeto llamado
lotr_nest
con los datasets anidados en el interior (un tibble de dos columnas, una que identifique el dataset, es decir la película, y otra con el dataset anidado)
Code
<-
lotr_nest |>
lotr_tidy nest(data = c(race, gender, word_count))
lotr_nest
7 Tidyverse: resúmenes
7.1 Contar: count()
|> contar(var1, var2) datos
|> count(var1, var2) starwars
Hasta ahora solo hemos transformado o consultado los datos pero no hemos generado estadísticas.
Empecemos por lo sencillo: ¿cómo contar (frecuencias)?
Cuando lo usamos en solitario count()
nos devolverá simplemente el número de registros , pero cuando lo usamos con variables count()
calcula lo que se conoce como frecuencias: número de elementos de cada modalidad.
|> count(sex) starwars
# A tibble: 5 × 2
sex n
<chr> <int>
1 female 16
2 hermaphroditic 1
3 male 60
4 none 6
5 <NA> 4
Además si pasamos varias variables nos calcula lo que se conoce como una tabla de contigencia. Con sort = TRUE
nos devolverá el conteo ordenado (más frecuentes primero).
|> count(sex, gender, sort = TRUE) starwars
# A tibble: 6 × 3
sex gender n
<chr> <chr> <int>
1 male masculine 60
2 female feminine 16
3 none masculine 5
4 <NA> <NA> 4
5 hermaphroditic masculine 1
6 none feminine 1
7.2 Agrupar: group_by()
|>
datos agrupar(var1, var2) |>
accion() |>
desagrupar()
|>
starwars group_by(var1, var2) |>
accion() |>
ungroup()
Una de las funciones más potentes a combinar con las acciones vistas es group_by()
, que nos permitirá agrupar nuestros registros previamente
|>
starwars group_by(sex) |>
count() |>
ungroup()
# A tibble: 5 × 2
sex n
<chr> <int>
1 female 16
2 hermaphroditic 1
3 male 60
4 none 6
5 <NA> 4
Cuando apliquemos group_by()
es importante entender que NO MODIFICA los datos, sino que nos crea una variable de grupo (subtablas por cada grupo) que modificará las acciones futuras: las operaciones se aplicarán a cada subtabla por separado
Por ejemplo, imaginemos que queremos extraer el personaje más alto con slice_max()
.
|> slice_max(height) starwars
# A tibble: 1 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Yarael P… 264 NA none white yellow NA male mascu…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
¿Y si queremos extraer el personaje más alto pero…de cada uno de los sexos?
|>
starwars group_by(sex) |>
slice_max(height) |>
ungroup()
# A tibble: 5 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Taun We 213 NA none grey black NA fema… femin…
2 Jabba De… 175 1358 <NA> green-tan… orange 600 herm… mascu…
3 Yarael P… 264 NA none white yellow NA male mascu…
4 IG-88 200 140 none metal red 15 none mascu…
5 Gregar T… 185 85 black dark brown NA <NA> <NA>
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
Recuerda siempre hacer ungroup para eliminar la variable de grupo creada
En la nueva versión de {dplyr}
ahora se permite incluir la variable de grupo en la llamada a muchas funciones con el argumento by = ...
o .by = ...
|> slice_max(height, by = sex) starwars
# A tibble: 5 × 14
name height mass hair_color skin_color eye_color birth_year sex gender
<chr> <int> <dbl> <chr> <chr> <chr> <dbl> <chr> <chr>
1 Yarael P… 264 NA none white yellow NA male mascu…
2 IG-88 200 140 none metal red 15 none mascu…
3 Taun We 213 NA none grey black NA fema… femin…
4 Jabba De… 175 1358 <NA> green-tan… orange 600 herm… mascu…
5 Gregar T… 185 85 black dark brown NA <NA> <NA>
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
# vehicles <list>, starships <list>
7.3 Fila-a-fila: rowwise()
Una opción muy útil usada antes de una operación también es rowwise()
: toda operación que venga después se aplicará en cada fila por separado. Por ejemplo, vamos a definir un conjunto dummy de notas.
<- tibble("mates" = c(7.5, 8, 9.1, 3),
notas "lengua" = c(8, 6, 6.5, 9.2))
Si aplicamos la media directamente el valor será idéntico ya que nos ha hecho la media global, pero nos gustaría sacar una media por registro. Para eso usaremos rowwise()
|>
notas rowwise() |>
mutate(media_curso = mean(c(mates, lengua)))
# A tibble: 4 × 3
# Rowwise:
mates lengua media_curso
<dbl> <dbl> <dbl>
1 7.5 8 7.75
2 8 6 7
3 9.1 6.5 7.8
4 3 9.2 6.1
7.4 Resumir: summarise()
|> resumir() datos
|> summarise() starwars
Por último tenemos summarise()
, que nos permitirá sacar resúmenes estadísticos. Por ejemplo, vamos a calcular la media de las alturas.
|>
starwars drop_na(height) |>
summarise(media_altura = mean(height))
# A tibble: 1 × 1
media_altura
<dbl>
1 175.
Fíjate que mutate()
devuelve tantas filas como registros originales, mientras que con summarise()
calcula un nuevo dataset de resumen, solo incluyendo aquello que esté indicado.
Si además esto lo combinamos con la agrupación de group_by()
o .by = ...
, en pocas líneas de código puedes obtener estadísticas desagreagadas
|>
starwars drop_na(sex, height, mass) |>
summarise(media_altura = mean(height),
media_peso = mean(mass),
.by = sex)
# A tibble: 4 × 3
sex media_altura media_peso
<chr> <dbl> <dbl>
1 male 178. 80.2
2 none 140 69.8
3 female 172. 54.7
4 hermaphroditic 175 1358
7.5 Resumir: reframe()
|> resumir() datos
|> reframe() starwars
En el nuevo {dplyr}
han incluido reframe()
para evitar problemas de summarise()
cuando devolvemos más de un valor por variable.
|>
starwars drop_na(mass) |>
summarise(quantile(mass))
Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
dplyr 1.1.0.
ℹ Please use `reframe()` instead.
ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
always returns an ungrouped data frame and adjust accordingly.
# A tibble: 5 × 1
`quantile(mass)`
<dbl>
1 15
2 55.6
3 79
4 84.5
5 1358
|>
starwars drop_na(mass) |>
reframe(quantile(mass))
# A tibble: 5 × 1
`quantile(mass)`
<dbl>
1 15
2 55.6
3 79
4 84.5
5 1358
7.6 Selectores: across()
Un truco es hacer uso de selectores across()
y where()
. El primero nos permite actuar sobre varias columnas por nombre (con mutate()
o summarise()
)
|>
starwars summarise(medias = across(height:mass, mean, na.rm = TRUE),
.by = sex)
Warning: There was 1 warning in `summarise()`.
ℹ In argument: `medias = across(height:mass, mean, na.rm = TRUE)`.
ℹ In group 1: `sex = "male"`.
Caused by warning:
! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
Supply arguments directly to `.fns` through an anonymous function instead.
# Previously
across(a:b, mean, na.rm = TRUE)
# Now
across(a:b, \(x) mean(x, na.rm = TRUE))
# A tibble: 5 × 2
sex medias$height $mass
<chr> <dbl> <dbl>
1 male 179. 80.2
2 none 131. 69.8
3 female 172. 54.7
4 hermaphroditic 175 1358
5 <NA> 175 81
El segundo, where()
, nos permite hacer lo mismo pero seleccionando por tipo.
|>
starwars summarise(across(where(is.numeric), mean, na.rm = TRUE),
.by = c(sex, gender))
# A tibble: 6 × 5
sex gender height mass birth_year
<chr> <chr> <dbl> <dbl> <dbl>
1 male masculine 179. 80.2 84.8
2 none masculine 140 69.8 53.3
3 female feminine 172. 54.7 47.2
4 hermaphroditic masculine 175 1358 600
5 <NA> <NA> 175 81 NaN
6 none feminine 96 NaN NaN
7.7 💻 Tu turno
Intenta realizar los siguientes ejercicios sin mirar las soluciones
📝 Calcula cuántos personajes hay de cada especie, ordenados de más a menor frecuencia.
Code
|> count(species, sort = TRUE) starwars
📝 Tras eliminar ausentes en las variables de peso y estatura, añade una nueva variable que nos calcule el IMC de cada personaje, y determina el IMC medio de nuestros personajes desagregada por sexo
Code
|>
starwars drop_na(mass, height) |>
mutate(IMC = mass / ((height/100)^2)) |>
summarise(IMC_medio = mean(IMC), .by = sex)
📝 Obtén el personaje más joven por cada sexo.
Code
|>
starwars slice_min(birth_year, by = sex)
📝 Obtén la edad del personaje más joven y más viejo de cada sexo.
Code
|>
starwars drop_na(birth_year) |>
summarise(min(birth_year), max(birth_year), .by = sex)
📝 Determina la cantidad de personajes en cada década (echa un vistazo a round()
, primero sin desagregar y luego desagregado por sexo.
Code
|>
starwars count(birth_decade = round(birth_year, -1))
8 🐣 Caso práctico I: billboard
Vamos a hacer un repaso de lo aprendido en {tidyverse}
con la tabla billboard del paquete {tidyr}
.
billboard
# A tibble: 317 × 79
artist track date.entered wk1 wk2 wk3 wk4 wk5 wk6 wk7 wk8
<chr> <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2 Pac Baby… 2000-02-26 87 82 72 77 87 94 99 NA
2 2Ge+her The … 2000-09-02 91 87 92 NA NA NA NA NA
3 3 Doors D… Kryp… 2000-04-08 81 70 68 67 66 57 54 53
4 3 Doors D… Loser 2000-10-21 76 76 72 69 67 65 55 59
5 504 Boyz Wobb… 2000-04-15 57 34 25 17 17 31 36 49
6 98^0 Give… 2000-08-19 51 39 34 26 26 19 2 2
7 A*Teens Danc… 2000-07-08 97 97 96 95 100 NA NA NA
8 Aaliyah I Do… 2000-01-29 84 62 51 41 38 35 35 38
9 Aaliyah Try … 2000-03-18 59 53 38 28 21 18 16 14
10 Adams, Yo… Open… 2000-08-26 76 76 74 69 68 67 61 58
# ℹ 307 more rows
# ℹ 68 more variables: wk9 <dbl>, wk10 <dbl>, wk11 <dbl>, wk12 <dbl>,
# wk13 <dbl>, wk14 <dbl>, wk15 <dbl>, wk16 <dbl>, wk17 <dbl>, wk18 <dbl>,
# wk19 <dbl>, wk20 <dbl>, wk21 <dbl>, wk22 <dbl>, wk23 <dbl>, wk24 <dbl>,
# wk25 <dbl>, wk26 <dbl>, wk27 <dbl>, wk28 <dbl>, wk29 <dbl>, wk30 <dbl>,
# wk31 <dbl>, wk32 <dbl>, wk33 <dbl>, wk34 <dbl>, wk35 <dbl>, wk36 <dbl>,
# wk37 <dbl>, wk38 <dbl>, wk39 <dbl>, wk40 <dbl>, wk41 <dbl>, wk42 <dbl>, …
El dataset representa algo parecido a Los 40 principales (pero versión americana y un top 100 en lugar de 40): para cada artista y canción se guarda la fecha en la que entró en el ranking, y la posición que ocupaba en el ranking en cada una de las semanas (wk1
, wk2
, …)
8.1 Pregunta 1
Selecciona solo las primeras 52 semanas.
Code
<-
billboard_year |>
billboard select(artist:date.entered, num_range("wk", 1:52))
8.2 Pregunta 2
Tras ello convierte el dataset a tidydata con los formatos y tipos adecuados para cada variable (por ejemplo, la variable semana resultante debería ser un número; busca opciones en
pivot_longer()
) sin datos ausentes.
Code
<-
billboard_tidy |>
billboard_year pivot_longer(cols = "wk1":"wk52", names_to = "week",
values_to = "rank", names_prefix = "wk",
values_drop_na = TRUE) |>
mutate(week = as.numeric(week))
8.3 Pregunta 3
Extrae los artistas (distintos) que aparecen en la tabla, incluyendo cuántas veces aparece cada uno.
Code
|>
billboard_tidy count(artist, sort = TRUE)
8.4 Pregunta 4
Determina cuántas canciones diferentes tiene cada artista en el ranking (una canción que aparece muchas semanas solo cuenta como una canción)
Code
|>
billboard_tidy distinct(artist, track) |>
count(artist, sort = TRUE)
8.5 Pregunta 5
Determina las 5 canciones que más semanas aparecen en la lista de éxitos.
Code
|>
billboard_tidy count(track) |>
slice_max(n, n = 5)
8.6 Pregunta 6
Determina para cada artista la canción que más semanas aparece en la lista de éxitos.
Code
|>
billboard_tidy group_by(artist) |>
count(track) |>
slice_max(n, n = 1) |>
ungroup()
8.7 Pregunta 7
Calcula la posición más alta en la que ha estado cada canción. Calcula la posición más alta en la que ha estado un artista
Code
|>
billboard_tidy slice_min(rank, by = track, with_ties = FALSE) |>
select(artist, track, rank)
|>
billboard_tidy slice_min(rank, by = artist, with_ties = FALSE) |>
select(artist, rank)
8.8 Pregunta 8
Obtén una tabla resumen con el ranking medio de cada artista (contando solo el ranking más alto alcanzado por sus canciones), así como el número de canciones (distintas) que ha colocado en el top 100.
Code
|>
billboard_tidy slice_min(rank, by = c(artist, track), with_ties = FALSE) |>
summarise(ave_rank = mean(rank), n_songs = n(), .by = artist)
8.9 Pregunta 9
Realiza un muestreo aleatorio estratificado, extrayendo el 50% de los datos
Code
|>
billboard_tidy slice_sample(prop = 0.5)
9 🐣 Caso práctico II: Messi vs Ronaldo
Vamos a seguir practicando lo aprendido en {tidyverse}
con el fichero futbol.csv
, donde tenemos datos de los jugadores de las 5 principales ligas de futbol masculinas, desde 2005 hasta 2019, recopilando diferentes estadísticas. Los datos se han extraído directamente haciendo uso del paquete {worldfootballR}
, que nos permite extraer datos de https://www.fbref.com
<- read_csv(file = "./datos/futbol.csv") datos
Rows: 40393 Columns: 16
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (5): team, league, player, country, position
dbl (11): season, date_birth, minutes_playing, matches, goals, assist, goals...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
datos
# A tibble: 40,393 × 16
season team league player country position date_birth minutes_playing
<dbl> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
1 2005 Ajaccio Ligue 1 Djamel Ab… ALG MF 1986 30
2 2005 Ajaccio Ligue 1 Gaspar Az… POR DF 1975 1224
3 2005 Ajaccio Ligue 1 Yacine Be… ALG MF 1981 140
4 2005 Ajaccio Ligue 1 Nicolas B… FRA MF 1976 892
5 2005 Ajaccio Ligue 1 Marcelinh… BRA MF 1971 704
6 2005 Ajaccio Ligue 1 Cyril Cha… FRA FW 1979 1308
7 2005 Ajaccio Ligue 1 Xavier Co… FRA DF 1974 2734
8 2005 Ajaccio Ligue 1 Renaud Co… FRA MF 1980 896
9 2005 Ajaccio Ligue 1 Yohan Dem… FRA DF,MF 1978 2658
10 2005 Ajaccio Ligue 1 Christoph… FRA DF 1973 74
# ℹ 40,383 more rows
# ℹ 8 more variables: matches <dbl>, goals <dbl>, assist <dbl>,
# goals_minus_pk <dbl>, pk <dbl>, pk_attemp <dbl>, yellow_card <dbl>,
# red_card <dbl>
Las variables capturan la siguiente información:
season
,team
,league
: temporada, equipo y ligaplayer
: nombre del jugadorcountry
,position
,date_birth
: país, posición y año de nacimiento.minutes_playing
,matches
: minutos totales jugados y partidos jugados en media (es decir, cuantos partidos de 90 minutos ha jugado con los minutos jugados). Esta última la vamos a crear nosotros
<-
datos |>
datos mutate("matches" = minutes_playing / 90)
datos
# A tibble: 40,393 × 16
season team league player country position date_birth minutes_playing
<dbl> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl>
1 2005 Ajaccio Ligue 1 Djamel Ab… ALG MF 1986 30
2 2005 Ajaccio Ligue 1 Gaspar Az… POR DF 1975 1224
3 2005 Ajaccio Ligue 1 Yacine Be… ALG MF 1981 140
4 2005 Ajaccio Ligue 1 Nicolas B… FRA MF 1976 892
5 2005 Ajaccio Ligue 1 Marcelinh… BRA MF 1971 704
6 2005 Ajaccio Ligue 1 Cyril Cha… FRA FW 1979 1308
7 2005 Ajaccio Ligue 1 Xavier Co… FRA DF 1974 2734
8 2005 Ajaccio Ligue 1 Renaud Co… FRA MF 1980 896
9 2005 Ajaccio Ligue 1 Yohan Dem… FRA DF,MF 1978 2658
10 2005 Ajaccio Ligue 1 Christoph… FRA DF 1973 74
# ℹ 40,383 more rows
# ℹ 8 more variables: matches <dbl>, goals <dbl>, assist <dbl>,
# goals_minus_pk <dbl>, pk <dbl>, pk_attemp <dbl>, yellow_card <dbl>,
# red_card <dbl>
goals
,assist
: goles y asistencias totalespk
,pk_attemp
,goals_minus_pk
: penalties marcados, penalties tirados y goles marcados sin contar los penalties.yellow_card
,red_card
: tarjetas amarillas/rojas.
9.1 Pregunta 1
Incluye una nueva variable que indique la edad de cada jugador en cada temporada (por ejemplo si nació en 1989 y la temporada es la de 2017, se le pone 18 años de edad). Coloca dicha columna detrás de la fecha de nacimiento
Code
<-
datos |>
datos mutate(age = season - date_birth, .after = "date_birth")
datos
9.2 Pregunta 2
Calcula los goles y asistencias (por separado) marcados cada 90 minutos (e inclúyelo como variables). Para ello solo deberás considerar los jugadores que han jugado más de 30 minutos a lo largo de la temporada y han jugado más de 5 partidos de media.
Code
<-
datos |>
datos filter(minutes_playing > 30 & matches > 5) |>
mutate(goals_per_90 = 90 * goals / minutes_playing,
assist_per_90 = 90 * assist / minutes_playing)
datos
9.3 Pregunta 3
Incluye un nueva variable
pk_fails
que calcule el número de penalties fallados. Además incluye una nueva variablegoals_minus_pk_per_90
que calcule los goles por cada 90 minutos pero excluyendo los goles por penalti.
Code
<-
datos |>
datos mutate(pk_fails = pk_attemp - pk,
goals_minus_pk_per_90 = 90 * goals_minus_pk / minutes_playing)
datos
9.4 Pregunta 4
Crea una nueva variable que nos codifique en
rol
si un jugador es “titular” (más de 30 slots de 90’s jugados de media), “recurrente” (entre 20 y 30 minutos), “suplente” (entre 7 y 20 minutos) o “esporádico” (menos de 7).
Code
<-
datos |>
datos mutate(rol = case_when(matches > 30 ~ "titular",
> 20 ~ "recurrente",
matches > 7 ~ "suplente",
matches TRUE ~ "esporádico"))
datos
9.5 Pregunta 5
Calcula la media de goles y la media de goles cada 90 minutos de cada categoría de jugador (la creada en
rol
). ¿Qué rol tiene mejor rendimiento (es decir, mayor cantidad de goles por cada 90 minutos)?
Code
|>
datos summarise(mean_goals = mean(goals, na.rm = TRUE),
mean_goals_per_90 = mean(goals_per_90, na.rm = TRUE),
.by = "rol")
9.6 Pregunta 6
Determina en todo el dataset los 10 jugadores con mejor promedio goleador por cada 90 minutos. ¿Qué jugadores españoles en dicho top 10?
Code
# top 10
|>
datos slice_max(goals_per_90, n = 10)
# solo paco alcácer
|>
datos slice_max(goals_per_90, n = 10) |>
filter(country == "ESP")
9.7 Pregunta 7
Calcula la cantidad total de goles de cada una de las grandes ligas. ¿En cuál se marcaron más durante todos estos años? Obtén la tabla resumen ordenada de más a menos goles
Code
# la española la más ofensiva
# la alemana la menos
|>
datos summarise(total_goals = sum(goals, na.rm = TRUE), .by = league) |>
arrange(desc(total_goals))
Obtén el total de goles por liga y temporada ordenados de más a menos
Code
# la española 2017
# la italiana 2017
# la española 2019
# la española 2013
|>
datos summarise(total_goals = sum(goals, na.rm = TRUE), .by = c(season, league)) |>
arrange(desc(total_goals))
9.8 Pregunta 8
Calcula para cada liga y cada año, el jugador más efectivo (más goles promedio por partido). ¿En cuántas temporadas lo fue Ronaldo? ¿En cuántas Messi?
Code
|>
datos slice_max(goals_per_90, by = c(season, league))
|>
datos slice_max(goals_per_90, by = c(season, league)) |>
count(player == "Cristiano Ronaldo")
|>
datos slice_max(goals_per_90, by = c(season, league)) |>
count(player == "Lionel Messi")
9.9 Pregunta 9
Calcula el número de temporadas que Cristiano Ronaldo promedió más goles por partido que Messi (siempre que ambos jugasen en esa temporada). Piensa como rediseñar el dataset para poder hacer el cálculo (simplifica la tabla primero solo a lo que te interesa)
Code
# 6 cr, 8 messi de las 14 temporadas que jugaron (del dataset al menos)
|>
datos # nos quedamos solo con los dos jugadores
filter(player %in% c("Cristiano Ronaldo", "Lionel Messi")) |>
# solo nos interesa season, su nombre y su dato (lo demás sobra)
select(season, player, goals_per_90) |>
# pivotamos para tener los registros de ambos jugadores en la misma fila
pivot_wider(names_from = "player", values_from = "goals_per_90") |>
# eliminamos ausentes (años donde alguno de los dos no jugó)
drop_na() |>
# normalizamos nombres de columnas
::clean_names() |>
janitor# contamos
count(cristiano_ronaldo > lionel_messi)
9.10 Pregunta 10
Determina que posición juega de media más minutos
Code
# porteros (goalkeepers) por supuesto
|>
datos summarise(ave_min = mean(minutes_playing), .by = position) |>
arrange(desc(ave_min))
9.11 Pregunta 11
Determina que jugador con rol de titular, y que no sea portero ni defensa (contienen GK o DF), promedio menos goles por partido en cada temporada y liga. Determina el que más.
Code
|>
datos filter(rol == "titular" & !str_detect(position, "GK|DF")) |>
slice_min(goals_per_90, by = c(season, league), with_ties = FALSE)
|>
datos filter(rol == "titular" & !str_detect(position, "GK|DF")) |>
slice_max(goals_per_90, by = c(season, league), with_ties = FALSE)
10 🐣 Caso práctico III: discursos
Volvamos al dataset discursos
donde teníamos guardados los discursos navideños de los «jefes» de Estado desde 1946 hasta 2021.
load(file = "./datos/discursos.RData")
Piensa como responder a las mismas preguntas que hicimos en R base pero en modo tidyverse.
10.1 Pregunta 1
Convierte todos los discursos a minúscula.
Code
library(stringr)
<-
discursos |>
discursos mutate("texto" = str_to_lower(texto))
10.2 Pregunta 2
Elimina signos de puntuación tales como “:”, “,”, “.”, “;”, “¡”, “!”, “¿” and “?”. Elimina después espacios al inicio o final, y si hay en medio deja solo uno.
Code
<-
discursos |>
discursos mutate("texto" =
str_remove_all(texto, pattern = "\\:|\\,|\\.|\\;|\\¡|\\!|\\¿|\\?"),
"texto" = str_squish(texto))
10.3 Pregunta 3
Crea una nueva variable
long
con la longitud de cada discurso.
Code
<-
discursos |>
discursos mutate("long" = str_length(texto))
10.4 Pregunta 4
Añade una nueva variable
n_words
con el número de palabras de cada discurso.
Code
<-
discursos |>
discursos # ojo: length para vectores, lengths para listas
# haz un ejemplo sencillo para ver como funciona str_split
mutate("n_words" = lengths(str_split(texto, boundary("word"))))
10.5 Pregunta 5
Determina los 5 años con discursos más largos y los 5 con menos palabras
Code
|>
discursos slice_max(long, n = 5)
|>
discursos slice_min(n_words, n = 5)
10.6 Pregunta 6
Añade una nueva variable llamada
spain
que calcule el número de veces que se dice “españoles”, “españolas” o “españa”. Determina los 5 años que menos se mencione dichas palabras.
Code
|>
discursos mutate("spain" = str_count(texto, pattern = "españoles|españolas|españa")) |>
slice_min(spain, n = 5)
10.7 Pregunta 7
De los 76 años de discursos, calcula el número de discursos en los que las palabras “mujer” o “mujeres” son más mencionadas que “hombre” u “hombres”
Code
|>
discursos reframe("year" = year,
"n_mujer" = str_count(texto, pattern = "mujer|mujeres"),
"n_hombre" = str_count(texto, pattern = "hombre|hombres")) |>
count(n_mujer > n_hombre)
10.8 Pregunta 8
Detecta los discursos donde se mencione “cataluña”, “catalanes”, “catalanas” o “catalán” y guarda solo esos discursos.
Code
<-
discursos_cat |>
discursos filter(str_detect(texto, pattern = "cataluña|catalanes|catalán|catalanas"))
11 🐣 Caso práctico IV: R base vs tidyverse
Vamos a volver al dataset de covid de la entrega II pero esta vez desde una perspectiva tidyverse
library(readr)
<- read_csv(file = "./datos/messy_covid_data.csv") datos
Rows: 9486 Columns: 31
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (1): provincia_iso
dbl (29): 0-9_H, 10-19_H, 20-29_H, 30-39_H, 40-49_H, 50-59_H, 60-69_H, 70-7...
dttm (1): fecha
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
datos
# A tibble: 9,486 × 31
provincia_iso fecha `0-9_H` `10-19_H` `20-29_H` `30-39_H`
<chr> <dttm> <dbl> <dbl> <dbl> <dbl>
1 A 2020-03-01 00:00:00 0 0 0 0
2 AL 2020-03-01 00:00:00 0 0 0 0
3 B 2020-03-01 00:00:00 0 0 0 0
4 BA 2020-03-01 00:00:00 0 1 0 0
5 BI 2020-03-01 00:00:00 0 0 0 0
6 C 2020-03-01 00:00:00 0 0 0 0
7 CA 2020-03-01 00:00:00 0 0 0 0
8 CO 2020-03-01 00:00:00 0 0 0 0
9 GI 2020-03-01 00:00:00 0 0 0 0
10 GR 2020-03-01 00:00:00 0 0 0 0
# ℹ 9,476 more rows
# ℹ 25 more variables: `40-49_H` <dbl>, `50-59_H` <dbl>, `60-69_H` <dbl>,
# `70-79_H` <dbl>, `80-Inf_H` <dbl>, `NC-NC_H` <dbl>, `0-9_M` <dbl>,
# `10-19_M` <dbl>, `20-29_M` <dbl>, `30-39_M` <dbl>, `40-49_M` <dbl>,
# `50-59_M` <dbl>, `60-69_M` <dbl>, `70-79_M` <dbl>, `80-Inf_M` <dbl>,
# `NC-NC_M` <dbl>, `0-9_NC` <dbl>, `10-19_NC` <dbl>, `20-29_NC` <dbl>,
# `30-39_NC` <dbl>, `40-49_NC` <dbl>, `50-59_NC` <dbl>, `60-69_NC` <dbl>, …
11.1 Pregunta 1
El nombre de las columnas codifica el sexo (H hombre, M mujer, NC no consta) y el grupo etario (0-9, 10-19, 20-29, 30-39, 40-49, 50-59, 60-69, 70-79, ≥80 años y NC no consta). Salvo en las dos primeras columnas, convierte cada 0 casos que encuentres en la fila por un
NA
La ventaja de tidyverse es que podemos evitar bucles por filas (por naturaleza del propio tidyverse) y por columnas (piensa que selectores hemos aprendido para poder aplicar la misma acción a varias columnas)
Code
<-
datos |>
datos mutate(across(where(is.numeric),
function(x) { if_else(x == 0, NA, x) }))
11.2 Pregunta 2
Razona por qué no es tidydata y conviértelo a tidy data. Si te fijas hay muchísimas filas que nos sobran (por tener ausentes) así que haz que al pivotar las elimine.
Code
<-
tidy_covid |>
datos pivot_longer(cols = -c(provincia_iso, fecha),
names_to = "grupo", values_to = "casos",
values_drop_na = TRUE)
11.3 Pregunta 3
Una de las columnas la tenemos codificada a veces como
cosa-cosa_cosa
, otras como80-Inf_cosa
, otras comoNC-NC_cosa
. Intenta separar dicha columna para generar tres columnas nuevasedad_inf
,edad_sup
ysexo
de manera adecuada. Recuerda queNC
es un ausente. Por ejemplo, si10-19_H
tendré que mandar el 10 a una columna, el 19 a otra y H a otra; si tengo80-Inf_H
deberá mandar 80 a una, NA a otra (no existe la cota superior) y H a otra; si tengoNC-NC_H
deberás tener NA, NA y H. A lo mejor debes preprocesar los datos antes para luego los NC o los Inf los entienda como un ausente al convertirlo a número. Prueba si quieres a hacer primero la separación sin hacer nada antes y observa que te queda para ver que tienes que hacer antes
Code
library(stringr)
<-
tidy_covid |>
tidy_covid mutate(grupo = str_replace_all(grupo, "Inf|NC", "NA")) |>
separate(col = "grupo", into = c("edad_inf", "edad_sup", "sexo"),
convert = TRUE)
11.4 Pregunta 4
Incorpora una nueva variable a la tabla que codifice el mes y el año (por ejemplo, cualquier día de enero de 2020 será algo similar a “1-2020” y cualquier día de febrero del 2021 será “2-2021”).
Code
library(lubridate)
library(glue)
<-
tidy_covid |>
tidy_covid mutate(mes = month(fecha),
year = year(fecha),
mes_year = glue("{mes}-{year}"))
11.5 Pregunta 5
Haciendo uso de esa variable de grupo
mes_year
y del vector de provincias permitidas que aparece debajo (busca en https://es.wikipedia.org/wiki/ISO_3166-2:ES#Provincias cómo están codificados los códigos ISO en la tabla) obtén un resumen que nos devuelva en un tibble, por cada provincia permitida y cada mes-año, la media de casos (sin importar edad ni sexo)
Code
<- c("M", "B", "SE", "V", "Z")
provincias_permitidas
<-
resumen_mes_provincia |>
tidy_covid filter(provincia_iso %in% provincias_permitidas) |>
summarise("mean_casos" = mean(casos), .by = c(provincia_iso, mes_year))
11.6 Pregunta 6
Diseña una función
resumen_por_fecha_provincia()
que dada un vector de códigos ISO de provincias permitidas, y un tabla con una columna id de provincia, otra de fecha (nos da igual la de mes-año porque la vamos a crear dentro de la función) y otra de casos, nos devuelva la media de casos diarios que hubo cada mes-año (sin importar sexo ni edad) en las provincias permitidas. Es decir: debes “adaptar” el código anterior a una función para que podamos aplicarlo cada vez que queramos sin necesidad de programarlo de nuevo. Aplica después de la función a los códigos ISO del vectorprovincias_permitidas
del ejercicio anterior y comprueba que te da lo mismo que antes (debería…)
Code
<- c("M", "B", "SE", "V", "Z")
provincias_permitidas
<- function(prov_iso, datos) {
resumen_por_fecha_provincia
<-
datos |>
datos mutate(mes = month(fecha), year = year(fecha),
mes_year = glue("{mes}-{year}"))
<-
resumen_mes_provincia |>
datos filter(provincia_iso %in% prov_iso) |>
summarise("mean_casos" = mean(casos), .by = c(provincia_iso, mes_year))
return(resumen_mes_provincia)
}<- resumen_por_fecha_provincia(datos = tidy_covid, prov_iso = provincias_permitidas) resumen
11.7 Pregunta 7
¿Cuántos casos diarios hubo de media en abril de 2020 en Madrid? ¿Y en Barcelona? ¿Cuál fue la provincia española (de las permitidas) con menos caos de media en dicha fecha?
Code
|>
resumen filter(provincia_iso == "M" & mes_year == "4-2020")
|>
resumen filter(provincia_iso == "B" & mes_year == "4-2020")
|>
resumen filter(mes_year == "4-2020") |>
slice_min(mean_casos)