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 rowsAssim, 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 rowsTambé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 rowsEste é 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.