Tidyverse: manipulación de filas y columnas

Cuadernos prácticos de Software II del Grado en Ciencia de Datos Aplicada (curso 2024-2025)

Author

Javier Álvarez Liébana

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.

datos |>
  limpio(...) |>
  filtro(...) |>
  selecciono(...) |>
  ordeno(...) |>
  modifico(...) |>
  renombro(...) |>
  agrupo(...) |>
  cuento(...) |>
  resumo(...) |>
  pinto(...)

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)

Importante

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
starwars |> filter(between(mass, 65, 90))

📝 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)) |
           eye_color %in% c("brown", "red"))

📝 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
starwars |> filter(str_detect(name, "Skywalker"))

1.3.1 Rebanadas de datos: slice()

starwars |> slice(posiciones)

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 = ...) y slice_tail(n = ...) podemos obtener la cabecera y cola de la tabla
starwars |> slice_head(n = 2)
# 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_tail(n = 2)
# 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() y slice_min() obtenemos la filas con menor/mayor valor de una variable (si empate, todas salvo que with_ties = FALSE) que indicamos en order_by = ...
starwars |> slice_min(mass, n = 2)
# 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>
starwars |> slice_max(height, n = 2)
# 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).

starwars |> slice_sample(n = 2)
# 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 Palpatine    170    75 grey       pale       yellow            82 male  mascu…
2 Shaak Ti     178    57 none       red, blue… black             NA fema… femin…
# ℹ 5 more variables: homeworld <chr>, species <chr>, films <list>,
#   vehicles <list>, starships <list>
Importante…

«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 Beru Whi…    165    75 brown      light      blue              47 fema… femin…
2 Zam Wese…    168    55 blonde     fair, gre… yellow            NA fema… femin…
3 Cordé        157    NA brown      light      brown             NA <NA>  <NA>  
4 Tion Med…    206    80 none       grey       black             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 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>

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] 4 5 2 3 3 4 4 2 2 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 2 1 1 4 1 5 2 1 2 4 1 1 1 3 3 1 1 2 2 3 2 2 1 4 1 2 1 1 1 4 1 2 2 6 1 1 1
[39] 1 2 3 4 1 3 1 1 1 5 1 2

¿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()

starwars |> arrange(var1, var2, ...)

También podemos ordenar filas en función de alguna variable con arrange()

starwars |> arrange(mass)
# 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()

starwars |> arrange(desc(height))
# 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>
starwars |> arrange(mass, desc(height))
# 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()

starwars |> distinct(var1, var2, ...)

Muchas veces necesitaremos asegurarnos que no hay duplicados en alguna variable (DNI) y podemos eliminar filas duplicadas con distinct().

starwars |> distinct(sex)
# 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.

starwars |> distinct(sex, .keep_all = TRUE)
# 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()

tibble1 |> bind_rows(tibble2)

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
starwars |> slice_sample(n = 3)

📝 Extrae el 10% de los registros aleatoriamente.

Code
starwars |> slice_sample(prop = 0.1)

📝 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
starwars |> slice_max(birth_year, n = 3)

📝 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)
airquality <- as_tibble(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 en airquality_sin_NA

Code
# tidyverse
airquality_sin_NA <-
  airquality |> 
  drop_na()

# R base
airquality_sin_NA <-
  airquality[!is.na(airquality$Ozone) & !is.na(airquality$Solar.R) &
             !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
aux_data <- 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)), ]

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
airquality_sin_NA[sample(1:nrow(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()

datos |> selecciono(var1, var2, ...)
starwars |> select(var1, var2, ...)

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.

starwars |> select(name, hair_color)
# 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 :

starwars |> select(name:eye_color) 
# 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)

starwars |>  select(-mass, -(eye_color:starships))
# 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

starwars |> select(mass, homeworld, everything())
# 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.

starwars |> select(name:mass, homeworld, last_col())
# 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
starwars |> select(ends_with("color"), matches("sex|gender"))
# 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
datos |> select(num_range("semana", 1:4))
# 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
starwars |> select(where(is.numeric) | where(is.character))
# 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.

starwars |> relocate(species, .before = name)
# 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()

datos |> renombrar(nuevo = antiguo)
starwars |> rename(nuevo = antiguo)

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.

starwars |> rename(nombre = name, altura = height, peso = mass)
# 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()

datos |> retirar(var)
starwars |> pull(var)

Si observas la salida de los select() sigue siendo una tabla tibble, ya que nos preserva la naturaleza de nuestros datos.

starwars |> select(name)
# 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()

starwars |> pull(name)
 [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()

datos |> modificar(nueva = funcion())
starwars |> mutate(nueva = funcion())

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.

starwars |> mutate(height_m = height / 100)
# 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>
Importante…

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",
                            height < 160 ~ "bajito",
                            height < 180 ~ "normal",
                            height < 200 ~ "alto",
                            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,
                               IMC < 18 ~ "delgadez",
                               IMC < 30 ~ "normal",
                               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 llamada id_bebe y otra llamada sexo. 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 de chico y 0.5 de chica.

Code
n <- 20
datos <- tibble("id_bebe" = 1:n,
                "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.514, 0.495)))

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
taylor_album_songs |> nrow()
taylor_album_songs |> ncol()

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
taylor_album_songs |> distinct(album_name)
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 digamos

  • con .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 variables album_name, album_release, track_name, featuring y duration_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

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) |> 
  janitor::clean_names()
lotr

6.3 Pregunta 3

Los números que hay guardados en female y male 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()

datos |> contar(var1, var2)
starwars |> count(var1, var2)

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.

starwars |> count(sex)
# 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).

starwars |> count(sex, gender, sort = TRUE)
# 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().

starwars |> slice_max(height)
# 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>
Importante

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

starwars |> slice_max(height, by = sex)
# 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.

notas <- tibble("mates" = c(7.5, 8, 9.1, 3),
                "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()

datos |> resumir()
starwars |> summarise()

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

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

datos |> resumir()
starwars |> reframe()

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
starwars |> count(species, sort = TRUE)

📝 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

datos <- read_csv(file = "./datos/futbol.csv")
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 liga
  • player: nombre del jugador
  • country, 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).
  • goals, assist: goles y asistencias totales
  • pk, 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 variable goals_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",
                         matches > 20 ~ "recurrente",
                         matches > 7 ~ "suplente",
                         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
  janitor::clean_names() |> 
  # 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)
datos <- read_csv(file = "./datos/messy_covid_data.csv")
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 como 80-Inf_cosa, otras como NC-NC_cosa. Intenta separar dicha columna para generar tres columnas nuevas edad_inf, edad_sup y sexo de manera adecuada. Recuerda que NC es un ausente. Por ejemplo, si 10-19_H tendré que mandar el 10 a una columna, el 19 a otra y H a otra; si tengo 80-Inf_H deberá mandar 80 a una, NA a otra (no existe la cota superior) y H a otra; si tengo NC-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
provincias_permitidas <- c("M", "B", "SE", "V", "Z")

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 vector provincias_permitidas del ejercicio anterior y comprueba que te da lo mismo que antes (debería…)

Code
provincias_permitidas <- c("M", "B", "SE", "V", "Z")

resumen_por_fecha_provincia <- function(prov_iso, datos) {
  
  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 <- resumen_por_fecha_provincia(datos = tidy_covid, prov_iso = provincias_permitidas)

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)