Los datos ordenados (en formato tidy) implican que:
Cada columna es una variable.
Cada fila es una observación.
Cada celda o casilla tiene un único valor.
tidyr
ofrece múltiples aplicaciones con el objetivo de principal de crear datos ordenados, lo que facilita la conexión del flujo de trabajo entre paquetes dentro del entorno tidyverse.
# install.packages("tidyr")
library(tidyr)
# o directamente
library(tidyverse)
#> -- Attaching core tidyverse packages ------------------------ tidyverse 2.0.0 --
#> v dplyr 1.1.4 v purrr 1.0.2
#> v forcats 1.0.0 v readr 2.1.5
#> v ggplot2 3.4.4 v stringr 1.5.1
#> v lubridate 1.9.3 v tibble 3.2.1
#> -- Conflicts ------------------------------------------ tidyverse_conflicts() --
#> x dplyr::filter() masks stats::filter()
#> x dplyr::lag() masks stats::lag()
#> i Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
“Pivotar” es reorganizar los datos colapsando o expandiendo las
columnas. Existen dos funciones principales para esto: pivot_longer()
y pivot_wider()
.
Colapsa muchas columnas en una sola ajustándose al formato tidy.
La siguiente base de datos no está en formato tidy, ya que el nivel de ingresos es una única variable y por tanto, debería estar en una única columna:
relig_income <- as_tibble(relig_income)
head(relig_income)
#> # A tibble: 6 x 11
#> religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Agnostic 27 34 60 81 76 137 122
#> 2 Atheist 12 27 37 52 35 70 73
#> 3 Buddhist 27 21 30 34 33 58 62
#> 4 Catholic 418 617 732 670 638 1116 949
#> 5 Don’t kn~ 15 14 15 11 10 35 21
#> 6 Evangeli~ 575 869 1064 982 881 1486 949
#> # i 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
#> # `Don't know/refused` <dbl>
Se agrupa toda la información usando tres variables: “religion”, “income” and “frequency”:
# tidyr
relig_income |>
pivot_longer(-religion, names_to = "income", values_to = "frequency") |>
head(n = 12L)
#> # A tibble: 12 x 3
#> religion income frequency
#> <chr> <chr> <dbl>
#> 1 Agnostic <$10k 27
#> 2 Agnostic $10-20k 34
#> 3 Agnostic $20-30k 60
#> 4 Agnostic $30-40k 81
#> 5 Agnostic $40-50k 76
#> 6 Agnostic $50-75k 137
#> 7 Agnostic $75-100k 122
#> 8 Agnostic $100-150k 109
#> 9 Agnostic >150k 84
#> 10 Agnostic Don't know/refused 96
#> 11 Atheist <$10k 12
#> 12 Atheist $10-20k 27
# R base
stacked_relig_income <- data.frame(relig_income$religion, stack(relig_income, select = -religion))
colnames(stacked_relig_income) <- c("religion", "frequency", "income")
stacked_relig_income <- stacked_relig_income[ ,c(1,3,2)]
head(stacked_relig_income, n = 20L)
#> religion income frequency
#> 1 Agnostic <$10k 27
#> 2 Atheist <$10k 12
#> 3 Buddhist <$10k 27
#> 4 Catholic <$10k 418
#> 5 Don’t know/refused <$10k 15
#> 6 Evangelical Prot <$10k 575
#> 7 Hindu <$10k 1
#> 8 Historically Black Prot <$10k 228
#> 9 Jehovah's Witness <$10k 20
#> 10 Jewish <$10k 19
#> 11 Mainline Prot <$10k 289
#> 12 Mormon <$10k 29
#> 13 Muslim <$10k 6
#> 14 Orthodox <$10k 13
#> 15 Other Christian <$10k 9
#> 16 Other Faiths <$10k 20
#> 17 Other World Religions <$10k 5
#> 18 Unaffiliated <$10k 217
#> 19 Agnostic $10-20k 34
#> 20 Atheist $10-20k 27
Para realizar la misma tarea en R base se puede usar la función
stack()
.
Funciona a la inversa de pivot_longer()
, expande los datos (hace la
base de datos más ancha) dividiendo la información de una variable en
varias columnas diferentes. Partiendo de la base da datos generada
anteriormente con nest()
, renombrándola como relig_income_tidy
.
# tidyr
relig_income_tidy |>
pivot_wider(names_from = "income", values_from = "frequency") |> head()
#> # A tibble: 6 x 11
#> religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k` `$75-100k`
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Agnostic 27 34 60 81 76 137 122
#> 2 Atheist 12 27 37 52 35 70 73
#> 3 Buddhist 27 21 30 34 33 58 62
#> 4 Catholic 418 617 732 670 638 1116 949
#> 5 Don’t kn~ 15 14 15 11 10 35 21
#> 6 Evangeli~ 575 869 1064 982 881 1486 949
#> # i 3 more variables: `$100-150k` <dbl>, `>150k` <dbl>,
#> # `Don't know/refused` <dbl>
# R base
unstacked_relig <- unstack(stacked_relig_income, frequency ~ income)
unstacked_relig_correct <- cbind(unique(stacked_relig_income$religion), unstacked_relig)
colnames(unstacked_relig_correct) <- colnames(relig_income)
head(unstacked_relig_correct)
#> religion <$10k $10-20k $20-30k $30-40k $40-50k $50-75k $75-100k
#> 1 Agnostic 27 34 60 81 76 137 122
#> 2 Atheist 12 27 37 52 35 70 73
#> 3 Buddhist 27 21 30 34 33 58 62
#> 4 Catholic 418 617 732 670 638 1116 949
#> 5 Don’t know/refused 15 14 15 11 10 35 21
#> 6 Evangelical Prot 575 869 1064 982 881 1486 949
#> $100-150k >150k Don't know/refused
#> 1 109 84 96
#> 2 59 74 76
#> 3 39 53 54
#> 4 792 633 1489
#> 5 17 18 116
#> 6 723 414 1529
Con R base
se podría usar la función unstack()
.
“Anidar” (Nesting) es agrupar los datos en una lista anidada (una lista
con otro tipo de objetos dentro). “Desanidar” (Rectangling) es ordenar
los datos de una lista anidada a un formato tidy. Para ello, se usan
dos funciones principales: nest()
& unnest()
.
Crea una lista anidada o un set de datos dentro de las celdas de una base de datos mayor.
Desde relig_income
se puede anidar “income” y “frequency” para cada
religión:
relig_income_tidy |> nest(data = c(income, frequency))
#> # A tibble: 18 x 2
#> religion data
#> <chr> <list>
#> 1 Agnostic <tibble [10 x 2]>
#> 2 Atheist <tibble [10 x 2]>
#> 3 Buddhist <tibble [10 x 2]>
#> 4 Catholic <tibble [10 x 2]>
#> 5 Don’t know/refused <tibble [10 x 2]>
#> 6 Evangelical Prot <tibble [10 x 2]>
#> 7 Hindu <tibble [10 x 2]>
#> 8 Historically Black Prot <tibble [10 x 2]>
#> 9 Jehovah's Witness <tibble [10 x 2]>
#> 10 Jewish <tibble [10 x 2]>
#> 11 Mainline Prot <tibble [10 x 2]>
#> 12 Mormon <tibble [10 x 2]>
#> 13 Muslim <tibble [10 x 2]>
#> 14 Orthodox <tibble [10 x 2]>
#> 15 Other Christian <tibble [10 x 2]>
#> 16 Other Faiths <tibble [10 x 2]>
#> 17 Other World Religions <tibble [10 x 2]>
#> 18 Unaffiliated <tibble [10 x 2]>
Cada valor del objeto creado “data” es un tibble que recoge información
de la siguiente forma: (ejemplo - relig_income_tidy[[2]][[1]]
):
income | frequency |
---|---|
<$10k | 27 |
$10-20k | 34 |
$20-30k | 60 |
$30-40k | 81 |
$40-50k | 76 |
$50-75k | 137 |
$75-100k | 122 |
$100-150k | 109 |
>150k | 84 |
Don’t know/refused | 96 |
Usando R base
se podría usar list()
, pero será necesario
tibble::tibble()
, ya que el formato data frame no permite trabajar con
este tipo de datos.
Para este ejemplo, simplemente usaremos las primeras 4 filas de
relig_income_tidy
:
Agnostic <- relig_income_tidy[relig_income_tidy$religion == "Agnostic", -1]
Atheist <- relig_income_tidy[relig_income_tidy$religion == "Atheist", -1]
Buddhist <- relig_income_tidy[relig_income_tidy$religion == "Buddhist", -1]
Catholic <- relig_income_tidy[relig_income_tidy$religion == "Catholic", -1]
Religion <- c("Agnostic", "Atheist", "Buddhist", "Catholic")
data <- list(Agnostic, Atheist, Buddhist, Catholic)
tibble(Religion, data)
#> # A tibble: 4 x 2
#> Religion data
#> <chr> <list>
#> 1 Agnostic <tibble [10 x 2]>
#> 2 Atheist <tibble [10 x 2]>
#> 3 Buddhist <tibble [10 x 2]>
#> 4 Catholic <tibble [10 x 2]>
La función inversa a nest()
. A partir de una base de datos con datos
anidados, se puede generar una base de datos en formato tidy.
Partiendo de la base da datos anidada que hemos generado anteriormente
con nest()
, renombrándola como relig_income_nested
:
relig_income_nested |> unnest(cols = "data") |> head(n = 20L)
#> # A tibble: 20 x 3
#> religion income frequency
#> <chr> <chr> <dbl>
#> 1 Agnostic <$10k 27
#> 2 Agnostic $10-20k 34
#> 3 Agnostic $20-30k 60
#> 4 Agnostic $30-40k 81
#> 5 Agnostic $40-50k 76
#> 6 Agnostic $50-75k 137
#> 7 Agnostic $75-100k 122
#> 8 Agnostic $100-150k 109
#> 9 Agnostic >150k 84
#> 10 Agnostic Don't know/refused 96
#> 11 Atheist <$10k 12
#> 12 Atheist $10-20k 27
#> 13 Atheist $20-30k 37
#> 14 Atheist $30-40k 52
#> 15 Atheist $40-50k 35
#> 16 Atheist $50-75k 70
#> 17 Atheist $75-100k 73
#> 18 Atheist $100-150k 59
#> 19 Atheist >150k 74
#> 20 Atheist Don't know/refused 76
En R base
necesitaremos acceder a los valores individuales de cada
tibble incluido en la variable “data”, de forma iterativa mediante un
bucle y el uso de dobles corchetes:
unnRbase <- data.frame() # generamos un dataframe vacio que iremos llenando
for(i in 1:nrow(relig_income_nested)) {
unnRbase <- rbind(unnRbase, data.frame(relig_income_nested[[1]][[i]], relig_income_nested[[2]][[i]]))
}
colnames(unnRbase)[1] <- "religion"
head(unnRbase, n = 20L)
#> religion income frequency
#> 1 Agnostic <$10k 27
#> 2 Agnostic $10-20k 34
#> 3 Agnostic $20-30k 60
#> 4 Agnostic $30-40k 81
#> 5 Agnostic $40-50k 76
#> 6 Agnostic $50-75k 137
#> 7 Agnostic $75-100k 122
#> 8 Agnostic $100-150k 109
#> 9 Agnostic >150k 84
#> 10 Agnostic Don't know/refused 96
#> 11 Atheist <$10k 12
#> 12 Atheist $10-20k 27
#> 13 Atheist $20-30k 37
#> 14 Atheist $30-40k 52
#> 15 Atheist $40-50k 35
#> 16 Atheist $50-75k 70
#> 17 Atheist $75-100k 73
#> 18 Atheist $100-150k 59
#> 19 Atheist >150k 74
#> 20 Atheist Don't know/refused 76
Durante el procesado de datos es común encontrar valores NA (not available) dentro de los objetos con los que se esté trabajando. Estos valores a veces pueden resultar problemáticos para realizar determinadas operaciones, por lo que existen funciones específicas para tratarlos.
Se trabajará con la base de datos palmerpenguins
.
library(palmerpenguins)
Vamos a intentar calcular el valor medio de la variable body mass
.
mean_bm <- mean(penguins$body_mass_g) # error NA
# si se indica el argumento na.rm (na remove), se puede realizar el cálculo
mean_bm <- mean(penguins$body_mass_g, na.rm = TRUE)
En tidyverse, existe esta función para eliminar los valores NA. En el
siguiente ejemplo, se calcula el valor medio de body mass
por cada
especie.
mean_bm_tidy <- penguins |>
tidyr::drop_na(body_mass_g) |>
group_by(species) |>
summarise(mean(body_mass_g)) |>
print()
#> # A tibble: 3 x 2
#> species `mean(body_mass_g)`
#> <fct> <dbl>
#> 1 Adelie 3701.
#> 2 Chinstrap 3733.
#> 3 Gentoo 5076.
Hay que destacar que la función drop_na()
, elimina los valores NA de
la variable que le indiquemos entre los paréntesis, como se ha realizado
en el ejemplo anterior. Pero si se aplica la misma función drop_na()
,
sin indicar nada como argumento, por defecto eliminará todas las filas
donde se encuentre algún valor NA.
mean_bm_tidy_2 <- penguins |>
tidyr::drop_na() |>
group_by(species) |>
summarise(mean(body_mass_g)) |>
print()
#> # A tibble: 3 x 2
#> species `mean(body_mass_g)`
#> <fct> <dbl>
#> 1 Adelie 3706.
#> 2 Chinstrap 3733.
#> 3 Gentoo 5092.
Los resultados son diferentes.
En otro caso, también podría resultar interesante cambiar los valores NA por 0, en función de la naturaleza de los datos con los que se esté trabajando.
# tidyr
penguins_mod_tidy <- penguins |>
mutate(body_mass_g = replace_na(body_mass_g, 0)) |>
print()
#> # A tibble: 344 x 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <fct> <fct> <dbl> <dbl> <int> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750
#> 2 Adelie Torgersen 39.5 17.4 186 3800
#> 3 Adelie Torgersen 40.3 18 195 3250
#> 4 Adelie Torgersen NA NA NA 0
#> 5 Adelie Torgersen 36.7 19.3 193 3450
#> 6 Adelie Torgersen 39.3 20.6 190 3650
#> 7 Adelie Torgersen 38.9 17.8 181 3625
#> 8 Adelie Torgersen 39.2 19.6 195 4675
#> 9 Adelie Torgersen 34.1 18.1 193 3475
#> 10 Adelie Torgersen 42 20.2 190 4250
#> # i 334 more rows
#> # i 2 more variables: sex <fct>, year <int>
# R base
penguins_mod <- penguins
penguins_mod[which(is.na(penguins_mod$body_mass_g) == TRUE), 6] <- 0
penguins_mod
#> # A tibble: 344 x 8
#> species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <fct> <fct> <dbl> <dbl> <int> <int>
#> 1 Adelie Torgersen 39.1 18.7 181 3750
#> 2 Adelie Torgersen 39.5 17.4 186 3800
#> 3 Adelie Torgersen 40.3 18 195 3250
#> 4 Adelie Torgersen NA NA NA 0
#> 5 Adelie Torgersen 36.7 19.3 193 3450
#> 6 Adelie Torgersen 39.3 20.6 190 3650
#> 7 Adelie Torgersen 38.9 17.8 181 3625
#> 8 Adelie Torgersen 39.2 19.6 195 4675
#> 9 Adelie Torgersen 34.1 18.1 193 3475
#> 10 Adelie Torgersen 42 20.2 190 4250
#> # i 334 more rows
#> # i 2 more variables: sex <fct>, year <int>