Capítulo 21 Cómo cambiar el formato de datos
Como hemos visto a través del libro, tener datos en formato tidy es lo que hace que el tidyverse fluya. Después del primer paso en el proceso de análisis de datos, la importación de datos, un siguiente paso común es cambiar la forma de los datos a una que facilite el resto del análisis. El paquete tidyr incluye varias funciones útiles para poner los datos en formato tidy.
Utilizaremos el set de datos en formato ancho fertility, descrito en la Sección 4.1, como ejemplo en esta sección.
library(tidyverse)
library(dslabs)
<- system.file("extdata", package="dslabs")
path <- file.path(path, "fertility-two-countries-example.csv")
filename <- read_csv(filename) wide_data
21.1 pivot_longer
Una de las funciones más usadas del paquete tidyr es pivot_longer
, que nos permite convertir datos anchos (wide data en inglés) en datos tidy.
Igual que con la mayoría de las funciones de tidyverse, el primer argumento de la función pivot_longer
es el data frame que será procesado. Aquí queremos cambiar la forma del set de datos wide_data
para que cada fila represente una observación de fertilidad, que implica que necesitamos tres columnas para almacenar el año, el país y el valor observado. En su forma actual, los datos de diferentes años están en diferentes columnas con los valores de año almacenados en los nombres de las columnas. A través de los argumentos names_to
y values_to
, le daremos a pivot_longer
los nombres de columna que le queremos asignar a las columnas que contienen los nombres de columna y las observaciones actuales, respectivamente. Por defecto, estos nombres son name
(nombre) y value
(valor), los cuales son buenas opciones en general. En este caso, una mejor opción para estos dos argumentos serían year
y fertility
. Noten que ninguna parte del archivo nos dice que se trata de datos de fertilidad. En cambio, desciframos esto del nombre del archivo. A través cols
, el segundo argumento, especificamos las columnas que contienen los valores observados; estas son las columnas que serán pivotadas. La acción por defecto es recopilar todas las columnas, por lo que, en la mayoría de los casos, tenemos que especificar las columnas. En nuestro ejemplo queremos las columnas 1960
, 1961
hasta 2015
.
El código para recopilar los datos de fertilidad se ve así:
<- pivot_longer(wide_data, `1960`:`2015`,
new_tidy_data names_to = "year", values_to = "fertility")
También podemos usar el pipe de esta manera:
<- wide_data |>
new_tidy_data pivot_longer(`1960`:`2015`, names_to = "year", values_to = "fertility")
Podemos ver que los datos se han convertido al formato tidy con columnas year
y fertility
:
head(new_tidy_data)
#> # A tibble: 6 × 3
#> country year fertility
#> <chr> <chr> <dbl>
#> 1 Germany 1960 2.41
#> 2 Germany 1961 2.44
#> 3 Germany 1962 2.47
#> 4 Germany 1963 2.49
#> 5 Germany 1964 2.49
#> # … with 1 more row
y que cada año resultó en dos filas ya que tenemos dos países y la columna de los países no se recopiló. Una forma un poco más rápida de escribir este código es especificar qué columna no se recopilará, en lugar de todas las columnas que se recopilarán:
<- wide_data |>
new_tidy_data pivot_longer(-country, names_to = "year", values_to = "fertility")
El objeto new_tidy_data
se parece al original tidy_data
que definimos de esta manera:
data("gapminder")
<- gapminder |>
tidy_data filter(country %in% c("South Korea", "Germany") & !is.na(fertility)) |>
select(country, year, fertility)
con solo una pequeña diferencia. ¿La pueden ver? Miren el tipo de datos de la columna del año:
class(tidy_data$year)
#> [1] "integer"
class(new_tidy_data$year)
#> [1] "character"
La función pivot_longer
supone que los nombres de columna son caracteres. Así que necesitamos un poco más de wrangling antes de poder graficar. Necesitamos convertir la columna con los años en números. La función pivot_longer
incluye el argumento convert
para este propósito:
<- wide_data |>
new_tidy_data pivot_longer(-country, names_to = "year", values_to = "fertility") |>
mutate(year = as.integer(year))
Tengan en cuenta que también podríamos haber utilizado mutate
y as.numeric
.
Ahora que los datos están tidy, podemos usar este código relativamente sencillo de ggplot2:
|> ggplot(aes(year, fertility, color = country)) +
new_tidy_data geom_point()
21.2 pivot_wider
Como veremos en ejemplos posteriores, a veces es útil convertir datos tidy en datos anchos para fines de wrangling de datos. A menudo usamos esto como un paso intermedio para convertir los datos en formato tidy. La función pivot_wider
es básicamente la inversa de pivot_longer
. El primer argumento es para los datos, pero como estamos usando el pipe, no lo mostramos. El argumento names_from
le dice a pivot_longer
qué variable usar como nombre de columna. El argumento names_to
especifica qué variable usar para completar las celdas:
<- new_tidy_data |>
new_wide_data pivot_wider(names_from = year, values_from = fertility)
select(new_wide_data, country, `1960`:`1967`)
#> # A tibble: 2 × 9
#> country `1960` `1961` `1962` `1963` `1964` `1965` `1966` `1967`
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Germany 2.41 2.44 2.47 2.49 2.49 2.48 2.44 2.37
#> 2 South Korea 6.16 5.99 5.79 5.57 5.36 5.16 4.99 4.85
Similar a pivot_wider
, names_from
y values_from
son name
and value
por defecto.
21.3 separate
El wrangling de datos que mostramos arriba es sencillo en comparación con lo que generalmente se requiere. En nuestros archivos de hoja de cálculo que usamos como ejemplo, incluimos una ilustración que es un poco más complicada. Contiene dos variables: esperanza de vida y fertilidad. Sin embargo, la forma en que se almacena no es tidy y, como explicaremos, no es óptima.
<- system.file("extdata", package = "dslabs")
path
<- "life-expectancy-and-fertility-two-countries-example.csv"
filename <- file.path(path, filename)
filename
<- read_csv(filename)
raw_dat select(raw_dat, 1:5)
#> # A tibble: 2 × 5
#> country `1960_fertility` `1960_life_expectancy` `1961_fertility`
#> <chr> <dbl> <dbl> <dbl>
#> 1 Germany 2.41 69.3 2.44
#> 2 South Korea 6.16 53.0 5.99
#> # … with 1 more variable: `1961_life_expectancy` <dbl>
Primero, tengan en cuenta que los datos están en formato ancho. Además, observen que esta tabla incluye valores para dos variables, fertilidad y esperanza de vida, con el nombre (en inglés) de la columna codificando qué columna representa qué variable. No recomendamos codificar la información en los nombres de las columnas, pero, desafortunadamente, es algo bastante común. Usaremos nuestras habilidades de wrangling para extraer esta información y almacenarla de manera tidy.
Podemos comenzar el wrangling de datos con la función pivot_longer
, pero ya no deberíamos usar el nombre de la columna year
para la nueva columna, dado que también contiene el tipo de variable. La nombraremos name
, el valor predeterminado, por ahora:
<- raw_dat |> pivot_longer(-country)
dat head(dat)
#> # A tibble: 6 × 3
#> country name value
#> <chr> <chr> <dbl>
#> 1 Germany 1960_fertility 2.41
#> 2 Germany 1960_life_expectancy 69.3
#> 3 Germany 1961_fertility 2.44
#> 4 Germany 1961_life_expectancy 69.8
#> 5 Germany 1962_fertility 2.47
#> # … with 1 more row
El resultado no es exactamente lo que llamamos tidy ya que cada observación está asociada con dos filas en vez de una. Queremos tener los valores de las dos variables, fertility
y life_expectancy
, en dos columnas separadas. El primer reto para lograr esto es separar la columna name
en año y tipo de variable. Observen que las entradas en esta columna separan el año del nombre de la variable con una barra baja:
$name[1:5]
dat#> [1] "1960_fertility" "1960_life_expectancy" "1961_fertility"
#> [4] "1961_life_expectancy" "1962_fertility"
Codificar múltiples variables en el nombre de una columna es un problema tan común que el paquete readr incluye una función para separar estas columnas en dos o más. Aparte de los datos, la función separate
toma tres argumentos: el nombre de la columna que se separará, los nombres que se utilizarán para las nuevas columnas y el carácter que separa las variables. Entonces, un primer intento de hacer esto es:
|> separate(name, c("year", "name"), "_") dat
separate
supone por defecto que_
es el separador y, por eso, no tenemos que incluirlo en el código:
|> separate(name, c("year", "name"))
dat #> Warning: Expected 2 pieces. Additional pieces discarded in 112 rows [2,
#> 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38,
#> 40, ...].
#> # A tibble: 224 × 4
#> country year name value
#> <chr> <chr> <chr> <dbl>
#> 1 Germany 1960 fertility 2.41
#> 2 Germany 1960 life 69.3
#> 3 Germany 1961 fertility 2.44
#> 4 Germany 1961 life 69.8
#> 5 Germany 1962 fertility 2.47
#> # … with 219 more rows
La función separa los valores, pero nos encontramos con un nuevo problema. Recibimos la advertencia Too many values at 112 locations:
y la variable life_expectancy
se corta a life
. Esto es porque el _
se usa para separar life
y expectancy
, no solo el año y el nombre de la variable. Podríamos añadir una tercera columna para guardar esto y dejar que la función separate
sepa cual columna llenar con los valores faltantes, NA
, cuando no hay un tercer valor. Aquí le decimos que llene la columna de la derecha:
<- c("year", "first_variable_name", "second_variable_name")
var_names |> separate(name, var_names, fill = "right")
dat #> # A tibble: 224 × 5
#> country year first_variable_name second_variable_name value
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 Germany 1960 fertility <NA> 2.41
#> 2 Germany 1960 life expectancy 69.3
#> 3 Germany 1961 fertility <NA> 2.44
#> 4 Germany 1961 life expectancy 69.8
#> 5 Germany 1962 fertility <NA> 2.47
#> # … with 219 more rows
Sin embargo, si leemos el archivo de ayuda de separate
, encontramos que un mejor enfoque es fusionar las dos últimas variables cuando hay una separación adicional:
|> separate(name, c("year", "name"), extra = "merge")
dat #> # A tibble: 224 × 4
#> country year name value
#> <chr> <chr> <chr> <dbl>
#> 1 Germany 1960 fertility 2.41
#> 2 Germany 1960 life_expectancy 69.3
#> 3 Germany 1961 fertility 2.44
#> 4 Germany 1961 life_expectancy 69.8
#> 5 Germany 1962 fertility 2.47
#> # … with 219 more rows
Esto logra la separación que queríamos. Sin embargo, aún no hemos terminado. Necesitamos crear una columna para cada variable. Como aprendimos, la función pivot_wider
hace eso:
|>
dat separate(name, c("year", "name"), extra = "merge") |>
pivot_wider()
#> # A tibble: 112 × 4
#> country year fertility life_expectancy
#> <chr> <chr> <dbl> <dbl>
#> 1 Germany 1960 2.41 69.3
#> 2 Germany 1961 2.44 69.8
#> 3 Germany 1962 2.47 70.0
#> 4 Germany 1963 2.49 70.1
#> 5 Germany 1964 2.49 70.7
#> # … with 107 more rows
Los datos ahora están en formato tidy con una fila para cada observación con tres variables: año, fertilidad y esperanza de vida.
21.4 unite
A veces es útil hacer el inverso de separate
, es decir, unir dos columnas en una. Para demostrar cómo usar unite
, mostramos un código que, aunque no es el acercamiento óptimo, sirve como ilustración. Supongan que no supiéramos sobre extra
y usáramos este comando para separar:
<- c("year", "first_variable_name", "second_variable_name")
var_names |>
dat separate(name, var_names, fill = "right")
#> # A tibble: 224 × 5
#> country year first_variable_name second_variable_name value
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 Germany 1960 fertility <NA> 2.41
#> 2 Germany 1960 life expectancy 69.3
#> 3 Germany 1961 fertility <NA> 2.44
#> 4 Germany 1961 life expectancy 69.8
#> 5 Germany 1962 fertility <NA> 2.47
#> # … with 219 more rows
Podemos lograr el mismo resultado final uniendo las segunda y tercera columnas, luego esparciendo las columnas usando pivot_wider
y renombrando fertility_NA
a fertility
:
|>
dat separate(name, var_names, fill = "right") |>
unite(name, first_variable_name, second_variable_name) |>
pivot_wider() |>
rename(fertility = fertility_NA)
#> # A tibble: 112 × 4
#> country year fertility life_expectancy
#> <chr> <chr> <dbl> <dbl>
#> 1 Germany 1960 2.41 69.3
#> 2 Germany 1961 2.44 69.8
#> 3 Germany 1962 2.47 70.0
#> 4 Germany 1963 2.49 70.1
#> 5 Germany 1964 2.49 70.7
#> # … with 107 more rows
21.5 Ejercicios
1. Ejecute el siguiente comando para definir el objeto co2_wide
:
<- data.frame(matrix(co2, ncol = 12, byrow = TRUE)) |>
co2_wide setNames(1:12) |>
mutate(year = as.character(1959:1997))
Utilice la función pivot_longer
para wrangle esto en un set de datos tidy. Nombre a la columna con las mediciones de CO2 co2
y nombre a la columna de mes month
. Nombre al objeto resultante co2_tidy
.
2. Grafique CO2 versus mes con una curva diferente para cada año usando este código:
|> ggplot(aes(month, co2, color = year)) + geom_line() co2_tidy
Si no se realiza el gráfico esperado, probablemente es porque co2_tidy$month
no es numérico:
class(co2_tidy$month)
Reescriba el código y que asegúrese que la columna de mes será numérica. Luego haga el gráfico
3. ¿Qué aprendemos de este gráfico?
- Las medidas de CO2 aumentan monotónicamente de 1959 a 1997.
- Las medidas de CO2 son más altas en el verano y el promedio anual aumentó de 1959 a 1997.
- Las medidas de CO2 parecen constantes y la variabilidad aleatoria explica las diferencias.
- Las medidas de CO2 no tienen una tendencia estacional.
4. Ahora cargue el set de datos admissions
, que contiene información de admisión para hombres y mujeres en seis concentraciones y mantenga solo la columna de porcentaje admitido:
load(admissions)
<- admissions |> select(-applicants) dat
Si pensamos en una observación como una concentración, y que cada observación tiene dos variables (porcentaje de hombres admitidos y porcentaje de mujeres admitidas), entonces esto no es tidy. Utilice la función pivot_wider
para wrangle en la forma tidy que queremos: una fila para cada concentración.
5. Ahora intentaremos un reto más avanzado de wrangling. Queremos wrangle los datos de admisión para cada concentración para tener 4 observaciones: admitted_men
, admitted_women
, applicants_men
y applicants_women
. El “truco” que hacemos aquí es realmente bastante común: primero usamos pivot_longer
para generar un data frame intermedio y luego usamos pivot_wider
para obtener los datos tidy que queremos. Iremos paso a paso en este y en los próximos dos ejercicios.
Utilice la función pivot_longer
para crear un data frame tmp
con una columna que contiene el tipo de observación admitted
o applicants
. Nombre a las nuevas columnas name
y value
.
6. Ahora tiene un objeto tmp
con columnas major
, gender
, name
y value
. Tenga en cuenta que si combina name
y gender
, se obtienen los nombres de columna que queremos: admitted_men
, admitted_women
, applicants_men
y applicants_women
. Use la función unite
para crear una nueva columna llamada column_name
.
7. Ahora use la función pivot_wider
para generar los datos tidy con cuatro variables para cada concentración.
8. Ahora use el pipe para escribir una línea de código que convierta admissions
en la tabla producida en el ejercicio anterior.