library(tidyverse)
penguins <- palmerpenguins::penguins
penguins |>
summarise(
mean_bill_length = mean(bill_length_mm, na.rm = TRUE),
mean_bill_depth = mean(bill_depth_mm, na.rm = TRUE),
mean_flipper_length = mean(flipper_length_mm, na.rm = TRUE),
mean_body_mass = mean(body_mass_g, na.rm = TRUE)
)
#> # A tibble: 1 × 4
#> mean_bill_length mean_bill_depth mean_flipper_length mean_body_mass
#> <dbl> <dbl> <dbl> <dbl>
#> 1 43.9 17.2 201. 4202.
Dentre as inúmeras ferramentas disponíveis no tidyverse, a função across()
pode ser um recurso de grande utilidade para simplificar transformações aplicadas em múltiplas variáveis.
Usando across( )
Quando estamos inicando na ciência de dados em R e conhecendo a sintaxe do tidyverse, é muito comum escrevermos códigos parecidos como estes:
Neste caso, estamos computando a mesma função mean()
em diversas colunas. Porém, há uma maneira mais eficiente de fazer esta mesma operação usando a função across()
, onde apenas devemos selecionar as variáveis nas quais desejamos trabalhar no parâmetro .cols
e a função desejada no parâmetro .fns
que, neste caso, é a função de média, mean()
.
Note que a função de média é aplicada em cada variável através da função anônima \(.x) mean(.x, na.rm = TRUE)
, onde \()
equivale a function()
e o .x
corresponde as colunas listadas em .cols
que serão utilizadas para calcular suas médias com mean()
.
across()
também nos permite aplicar diversas funções ao mesmo tempo para as variáveis escolhidas em .cols
. Para aplicar mais de uma função em across()
, apenas é necessário inserir as funções num vetor, como vemos abaixo, calculamos a variâcia das variáveis em conjunto com suas médias no parâmetro .fns
:
penguins |>
summarise(
across(
.cols = c(
bill_length_mm, bill_depth_mm,
flipper_length_mm, body_mass_g
),
.fns = c(
mean = \(.x) mean(.x, na.rm = TRUE),
var = \(.x) mean(.x, na.rm = TRUE)
)
)
)
#> # A tibble: 1 × 8
#> bill_length_mm_mean bill_length_mm_var bill_depth_mm_mean bill_depth_mm_var
#> <dbl> <dbl> <dbl> <dbl>
#> 1 43.9 43.9 17.2 17.2
#> # ℹ 4 more variables: flipper_length_mm_mean <dbl>,
#> # flipper_length_mm_var <dbl>, body_mass_g_mean <dbl>, …
Obverse o output do código, onde cada variável resultou em duas variáveis, uma para cada estatística: média (mean()
) e variância (var()
). Infelizmente, o output deste código gera uma tibble em um formato largo (wide tibble), com oito variáveis e apenas uma observação, o que não é ideal para podermos trabalhar com ela. Felizmente, podemos usar mais um parâmetro do across()
para facilitar nossas vidas, .names
:
penguins |>
summarise(
across(
.cols = c(
bill_length_mm, bill_depth_mm,
flipper_length_mm, body_mass_g
),
.fns = c(
mean = \(.x) mean(.x, na.rm = TRUE),
var = \(.x) mean(.x, na.rm = TRUE)
),
.names = "{.col}----{.fn}"
)
)
#> # A tibble: 1 × 8
#> `bill_length_mm----mean` `bill_length_mm----var` `bill_depth_mm----mean`
#> <dbl> <dbl> <dbl>
#> 1 43.9 43.9 17.2
#> # ℹ 5 more variables: `bill_depth_mm----var` <dbl>,
#> # `flipper_length_mm----mean` <dbl>, `flipper_length_mm----var` <dbl>, …
.names
entra em cena para auxiliar nosso trabalho ao nos permitir modificar os nomes das variáveis inclusas em .cols
. O parâmetro requer uma string, onde é possível adicionar {.col}
para sinalizar o uso do nome da variável e {.fn}
que indica o uso do nome da função (neste caso, ou mean
, ou var
). Entre eles, adicionei quatro traços ----
, que servirá como separador e irá facilitar nossas vidas na transformação de uma wide tibble para uma long tibble, através do pivot_longer()
:
penguins |>
summarise(
across(
.cols = c(
bill_length_mm, bill_depth_mm,
flipper_length_mm, body_mass_g
),
.fns = c(
mean = \(.x) mean(.x, na.rm = TRUE),
var = \(.x) mean(.x, na.rm = TRUE)
),
.names = "{.col}----{.fn}"
)
) |>
pivot_longer(
cols = everything(),
names_sep = "----",
names_to = c("variable", "stat")
)
#> # A tibble: 8 × 3
#> variable stat value
#> <chr> <chr> <dbl>
#> 1 bill_length_mm mean 43.9
#> 2 bill_length_mm var 43.9
#> 3 bill_depth_mm mean 17.2
#> 4 bill_depth_mm var 17.2
#> 5 flipper_length_mm mean 201.
#> 6 flipper_length_mm var 201.
#> # ℹ 2 more rows
Assim, a tibble fica muito mais amigável para criação de novas variáveis e para trabalhar com ela.
Usando tidyselect helpers em conjunto com across( )
Os helpers tidyselect são um grupo de verbos do tidyverse disponibilizadas para a seleção de variáveis. São úteis para podermos lidar com tibbles que possuem um número expressivo de variáveis, evitando digitação de muito código. Por exemplo, a tibble abaixo possui 74 variáveis:
ames <- modeldata::ames
ames
#> # A tibble: 2,930 × 74
#> MS_SubClass MS_Zoning Lot_Frontage Lot_Area Street
#> * <fct> <fct> <dbl> <int> <fct>
#> 1 One_Story_1946_and_Newer_All_… Residential_Lo… 141 31770 Pave
#> 2 One_Story_1946_and_Newer_All_… Residential_Hi… 80 11622 Pave
#> 3 One_Story_1946_and_Newer_All_… Residential_Lo… 81 14267 Pave
#> 4 One_Story_1946_and_Newer_All_… Residential_Lo… 93 11160 Pave
#> 5 Two_Story_1946_and_Newer Residential_Lo… 74 13830 Pave
#> 6 Two_Story_1946_and_Newer Residential_Lo… 78 9978 Pave
#> # ℹ 2,924 more rows
#> # ℹ 69 more variables: Alley <fct>, Lot_Shape <fct>, Land_Contour <fct>, …
Digamos que queremos trabalhar com todas as variáveis numéricas do dataset ames
. Ao invés de digitarmos todas as colunas, podemos utilizar os helpers tidyselect, como o where()
em conjunto com is.numeric
para selecionar todas as variáveis onde os resultados de is.numeric
é igual a TRUE
.
ames |>
summarise(
across(
.cols = where(is.numeric),
.fns = c(
mean = \(.x) mean(.x, na.rm = TRUE),
var = \(.x) mean(.x, na.rm = TRUE)
),
.names = "{.col}----{.fn}"
)
) |>
pivot_longer(
cols = everything(),
names_sep = "----",
names_to = c("variable", "stat")
)
#> # A tibble: 68 × 3
#> variable stat value
#> <chr> <chr> <dbl>
#> 1 Lot_Frontage mean 57.6
#> 2 Lot_Frontage var 57.6
#> 3 Lot_Area mean 10148.
#> 4 Lot_Area var 10148.
#> 5 Year_Built mean 1971.
#> 6 Year_Built var 1971.
#> # ℹ 62 more rows
Também é possível usar mais helpers do tidyselect para filtrar ainda mais as colunas desejadas. Por exemplo, podemos combinar where(is.numeric)
e starts_with("Lot")
para aplicar transformações apenas para as variáveis numéricas que iniciam com o padrão “Lot”.
ames |>
summarise(
across(
.cols = where(is.numeric) & starts_with("Lot"),
.fns = c(
mean = \(.x) mean(.x, na.rm = TRUE),
var = \(.x) mean(.x, na.rm = TRUE)
),
.names = "{.col}----{.fn}"
)
) |>
pivot_longer(
cols = everything(),
names_sep = "----",
names_to = c("variable", "stat")
)
#> # A tibble: 4 × 3
#> variable stat value
#> <chr> <chr> <dbl>
#> 1 Lot_Frontage mean 57.6
#> 2 Lot_Frontage var 57.6
#> 3 Lot_Area mean 10148.
#> 4 Lot_Area var 10148.
Outras dicas
Podemos reescrever a função across()
de diversas maneiras:
# com tidyselect helper ends_with()
# operador lógico |
penguins |>
summarise(
across(
.cols = ends_with("_mm") | ends_with("_g"),
.fns = \(.x) mean(.x, na.rm = TRUE)
)
)
#> # A tibble: 1 × 4
#> bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <dbl> <dbl> <dbl> <dbl>
#> 1 43.9 17.2 201. 4202.
# com tidyselect helper ends_with()
# operador lógico |
# expressão anônima reduzida ~
penguins |>
summarise(
across(
.cols = ends_with("_mm") | ends_with("_g"),
.fns = ~ mean(.x, na.rm = TRUE)
)
)
#> # A tibble: 1 × 4
#> bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <dbl> <dbl> <dbl> <dbl>
#> 1 43.9 17.2 201. 4202.
# com tidyselect helper ends_with()
# operador lógico |
# expressão anônima reduzida ~
# parâmetros implícitos
penguins |>
summarise(across(ends_with("_mm") | ends_with("_g"), ~ mean(.x, na.rm = T)))
#> # A tibble: 1 × 4
#> bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
#> <dbl> <dbl> <dbl> <dbl>
#> 1 43.9 17.2 201. 4202.
Também é possível utilizar o across()
com mutate()
:
modeldata::ames |>
mutate(across(matches("(Y|y)ear"), ~ make_date(year = .x))) |>
select(where(is.Date))
#> # A tibble: 2,930 × 3
#> Year_Built Year_Remod_Add Year_Sold
#> <date> <date> <date>
#> 1 1960-01-01 1960-01-01 2010-01-01
#> 2 1961-01-01 1961-01-01 2010-01-01
#> 3 1958-01-01 1958-01-01 2010-01-01
#> 4 1968-01-01 1968-01-01 2010-01-01
#> 5 1997-01-01 1998-01-01 2010-01-01
#> 6 1998-01-01 1998-01-01 2010-01-01
#> # ℹ 2,924 more rows
Este é somente um exemplo dos mais diversos de como é possível combinar across()
com mutate()
ou summarise()
para fazer bastante progresso em uma análise de dados, digitando bem menos e otimizando consideravelmente o código.