7.6 O pacote forcats
Se você já utilizava o R antes do surgimento do tidyverse
, provavelmente já escreveu a expressão stringsAsFactors = F
quando importava dados usando as funções read.csv
ou read.table
. É muito frustrante quando uma coluna de strings
é lida como um fator, pois fatores não podem ser manipulados do mesmo jeito que manipulamos vetores de strings
.
Felizmente, depois de aprender a usar o readr
você não terá mais esse problema, pois as funções do pacote não leem colunas de caracteres como fatores por padrão. Mas isso não significa que fatores são inúteis. Eles representam uma forma muito prática de lidar com variáveis categorizadas, tanto para fins de modelagem quanto para fins de visualização.
Grande parte da frustração associada ao uso de fatores no R existe por conta da falta de algumas ferramentas úteis no pacote base
. Para resolver esse problema, Hadley Wickham ajudou a comunidade R (de novo) desenvolvendo o pacote forcats
(for categorial variables), que implementa algumas dessas ferramentas.
As principais funções do forcats
servem para alterar a ordem e modificar os níveis de um fator. Para exemplificar a utilidade dessas funções, nesta seção vamos utilizá-las em situações corriqueiras.
Se você não tem o pacote {forcats}
, instalado rode o código abaixo antes de utilizá-lo:
install.packages("forcats")
Nos exemplos a seguir, utilizarmos os seguintes pacotes:
library(forcats)
library(ggplot2)
library(dplyr)
7.6.1 O que são fatores?
Como vimos na Seção 3.13.2, fatores são uma classe de objetos no R criada para representar as variáveis categóricas numericamente.
Eles são necessários pois muitas vezes precisamos representar variáveis categóricas como números. Quando estamos fazendo um gráfico, por exemplo, só podemos mapear variáveis numéricas em seus eixos, pois o plano cartesiano é formado por duas retas de números reais.
O que fazemos então quando plotamos uma variável categórica? Nós a transformamos em fatores.
Mas como a manipulação de fatores é diferente da manipulação de números e strings (graças aos famosos levels), tarefas que parecem simples, como ordenar as barras de um gráfico de barras, acabam se tornando grandes desafios quando não sabemos lidar com essa classe de valores.
Nos exemplos a seguir, vamos utilizar a base starwars
(do pacote {dplyr}
) para aprendermos a fazer as principais operações com fatores utilizando o pacote {forcats}
.
7.6.2 Modificando níveis de um fator
Vamos trabalhar primeiro com a coluna sex
, que diz qual é o sexo de cada personagem. As possibilidades são:
%>%
starwars pull(sex) %>%
unique()
## [1] "male" "none" "female" "hermaphroditic"
## [5] NA
Veja que se transformarmos a coluna em fator, esses serão os levels da variável, não importa se o sub-conjunto que estivermos observando possua ou não todas as categorias.
%>%
starwars mutate(sex = as.factor(sex)) %>%
pull(sex) %>%
head()
## [1] male none none male female male
## Levels: female hermaphroditic male none
Vamos criar um objeto com os 16 primeiros valores da coluna sex
já transformados em fator.
<- starwars %>%
fator_sex pull(sex) %>%
as.factor() %>%
head(16)
fator_sex## [1] male none none male female
## [6] male female none male male
## [11] male male male male male
## [16] hermaphroditic
## Levels: female hermaphroditic male none
Para mudar os níveis de um fator, podemos utilizar a função lvls_revalue()
. Veja que, ao mudarmos os níveis de um fator, o label de cada valor também muda. Os novos valores precisam ser passados conforme a ordem dos níveis antigos.
lvls_revalue(
fator_sex, new_levels = c("Fêmea", "Hermafrodita", "Macho", "Nenhum")
)## [1] Macho Nenhum Nenhum Macho Fêmea
## [6] Macho Fêmea Nenhum Macho Macho
## [11] Macho Macho Macho Macho Macho
## [16] Hermafrodita
## Levels: Fêmea Hermafrodita Macho Nenhum
Essa função é uma boa alternativa para mudar o nome das categorias de uma variável antes de construir um gráfico.
%>%
starwars filter(!is.na(sex)) %>%
count(sex) %>%
mutate(
sex = lvls_revalue(sex, c("Fêmea", "Hermafrodita", "Macho", "Nenhum"))
%>%
) ggplot() +
geom_col(aes(x = sex, y = n))
Como as colunas no gráfico respeitam a ordem dos níveis do fator, não importa a ordem que as linhas aparecem na tabela, o gráfico sempre será gerado com as colunas na mesma ordem. Assim, se quiséssemos alterar a ordem das barras do gráfico anterior, precisaríamos mudar a ordem dos níveis do fator sex
.
7.6.3 Mudando a ordem dos níveis de um fator
Para mudar a ordem dos níveis de um fator, podemos utilizar a função lvls_reorder()
. Basta passarmos qual a nova ordem dos fatores, com relação à ordem anterior. No exemplo abaixo definimos que, na nova ordem,
a primeira posição terá o nível que estava na terceira posição na ordem antiga;
a segunda posição terá o nível que estava na primeira posição na ordem antiga;
a terceira posição terá o nível que estava na quarta posição na ordem antiga;
a quarta posição terá o nível que estava na segunda posição na ordem antiga.
lvls_reorder(fator_sex, c(3, 1, 4, 2))
## [1] male none none male female
## [6] male female none male male
## [11] male male male male male
## [16] hermaphroditic
## Levels: male female none hermaphroditic
Assim, poderíamos usar essa nova ordem para ordenar as colunas do nosso gráfico.
%>%
starwars filter(!is.na(sex)) %>%
count(sex) %>%
mutate(
sex = lvls_revalue(sex, c("Fêmea", "Hermafrodita", "Macho", "Nenhum")),
sex = lvls_reorder(sex, c(3, 1, 4, 2))
%>%
) ggplot() +
geom_col(aes(x = sex, y = n))
Repare que precisamos passar a nova ordem na mão, o que pode deixar de funcionar se a nossa base mudar (recebermos mais linhas ou fizermos um filtro anteriormente).
Anterior ao mutate()
temos a seguinte tabela:
%>%
starwars filter(!is.na(sex)) %>%
count(sex)
## # A tibble: 4 × 2
## sex n
## <chr> <int>
## 1 female 16
## 2 hermaphroditic 1
## 3 male 60
## 4 none 6
O que queremos é que os níveis da coluna sex
sejam ordenados segundo os valores da coluna n
, isto é, quem tiver o maior valor de n
deve ser o primeiro nível, o segundo maior valor de n
seja o segundo nível e assim por diante.
Podemos melhorar esse código utilizando a função fct_reorder()
. Com ela, em vez de definirmos na mão a ordem dos níveis do fator, podemos ordená-lo segundo valores de uma segunda variável.
%>%
starwars filter(!is.na(sex)) %>%
count(sex) %>%
mutate(
sex = lvls_revalue(sex, c("Fêmea", "Hermafrodita", "Macho", "Nenhum")),
sex = fct_reorder(sex, n)
%>%
) ggplot() +
geom_col(aes(x = sex, y = n))
É quase o que queríamos! O problema é que os níveis estão sendo ordenados de forma crescente e gostaríamos de ordenar na ordem decrescente. Para isso, basta utilizarmos o parâmetro .desc
.
%>%
starwars filter(!is.na(sex)) %>%
count(sex) %>%
mutate(
sex = lvls_revalue(sex, c("Fêmea", "Hermafrodita", "Macho", "Nenhum")),
sex = fct_reorder(sex, n, .desc = TRUE)
%>%
) ggplot() +
geom_col(aes(x = sex, y = n))
Agora sim! Com esse código, as colunas estarão sendo ordenadas pela frequência, independentemente dos valores de n
e sex
que chegarem no mutate()
.
Se olharmos a documentação da função fct_reorder()
vamos descobrir que esse exemplo é apenas um caso particular de como podemos utilizá-la. No contexto geral, ela ordena os níveis de um fator segundo uma função dos valores de uma segunda variável.
Se em vez de construirmos um gráfico de barras da frequência da variável sex
, construíssemos boxplots da altura para cada sexo diferente, teríamos o gráfico a seguir.
%>%
starwars filter(!is.na(sex)) %>%
ggplot() +
geom_boxplot(aes(x = sex, y = height))
## Warning: Removed 5 rows containing non-finite values (stat_boxplot).
Se quiséssemos ordenar cada boxplot (pela mediana, por exemplo), continuamos podendo usar a função fct_reorder()
. Veja que ela possui o argumento .fun
, que indica qual função será utilizada na variável secundária para determinar a ordem dos níveis.
No exemplo abaixo, utilizamos .fun = median
, o que significa que, para cada nível da variável sex
, vamos calcular a mediana da variável height
e ordenaremos os níveis de sex
conforme a ordem dessas medianas. Assim, o primeiro nível será o sexo com menor altura mediana, o segundo nível será o sexo com a segunda menor algura media e assim por diante. Se quiséssemos ordenar de forma decrescente, bastaria utilizar o argumento .desc = TRUE
.
%>%
starwars filter(!is.na(sex)) %>%
mutate(
sex = lvls_revalue(sex, c("Fêmea", "Hermafrodita", "Macho", "Nenhum")),
sex = fct_reorder(sex, height, .fun = median, na.rm = TRUE)
%>%
) ggplot() +
geom_boxplot(aes(x = sex, y = height))
## Warning: Removed 5 rows containing non-finite values (stat_boxplot).
Também poderíamos ordenar pelo máximo, utilizando .fun = max
. Neste argumento, podemos usar qualquer função sumarizadora: min()
, mean()
, median()
, max()
, sd()
, var()
etc.
%>%
starwars filter(!is.na(sex)) %>%
mutate(
sex = lvls_revalue(sex, c("Fêmea", "Hermafrodita", "Macho", "Nenhum")),
sex = fct_reorder(sex, height, .fun = max, na.rm = TRUE)
%>%
) ggplot() +
geom_boxplot(aes(x = sex, y = height))
## Warning: Removed 5 rows containing non-finite values (stat_boxplot).
7.6.4 Colapsando níveis de um fator
Imagine que queremos fazer um gráfico de barras com a frequência de personagens por espécie.
%>%
starwars ggplot(aes(x = species)) +
geom_bar()
O gráfico resultante é horrível, pois temos muitas espécies diferentes. Uma solução seria agrupar as espécies menos frequentes, criando uma nova categoria (outras, por exemplo).
Para isso, podemos usar a função fct_lump()
. Vamos fazer isso primeiro com o vetor de espécies.
<- as.factor(starwars$species)
fator_especies
fator_especies## [1] Human Droid Droid Human Human
## [6] Human Human Droid Human Human
## [11] Human Human Wookiee Human Rodian
## [16] Hutt Human Human Yoda's species Human
## [21] Human Droid Trandoshan Human Human
## [26] Mon Calamari Human Human Ewok Sullustan
## [31] Human Neimodian Human Gungan Gungan
## [36] Gungan <NA> Toydarian Dug <NA>
## [41] Human Zabrak Twi'lek Twi'lek Vulptereen
## [46] Xexto Toong Human Cerean Nautolan
## [51] Zabrak Tholothian Iktotchi Quermian Kel Dor
## [56] Chagrian Human Human Human Geonosian
## [61] Mirialan Mirialan Human Human Human
## [66] Human Clawdite Besalisk Kaminoan Kaminoan
## [71] Human Aleena Droid Skakoan Muun
## [76] Togruta Kaleesh Wookiee Human <NA>
## [81] Pau'an Human Human Human Droid
## [86] <NA> Human
## 37 Levels: Aleena Besalisk Cerean Chagrian Clawdite Droid Dug ... Zabrak
Temos 37 espécies diferentes na base. Podemos deixar apenas as 3 mais frequentes da seguinte forma:
fct_lump(fator_especies, n = 3)
## [1] Human Droid Droid Human Human Human Human Droid Human Human
## [11] Human Human Other Human Other Other Human Human Other Human
## [21] Human Droid Other Human Human Other Human Human Other Other
## [31] Human Other Human Gungan Gungan Gungan <NA> Other Other <NA>
## [41] Human Other Other Other Other Other Other Human Other Other
## [51] Other Other Other Other Other Other Human Human Human Other
## [61] Other Other Human Human Human Human Other Other Other Other
## [71] Human Other Droid Other Other Other Other Other Human <NA>
## [81] Other Human Human Human Droid <NA> Human
## Levels: Droid Gungan Human Other
O fator resultante possui 4 níveis: Droid
, Gungan
, Human
e Other
. Os 3 primeiros níveis são os mais frequentes, enquanto o nível Other
foi atribuído a todos os outros 34 níveis que existiam anteriormente.
Poderíamos definir o nome do nível Others
usando o argumento other_level
.
fct_lump(fator_especies, n = 3, other_level = "Outras espécies")
## [1] Human Droid Droid Human
## [5] Human Human Human Droid
## [9] Human Human Human Human
## [13] Outras espécies Human Outras espécies Outras espécies
## [17] Human Human Outras espécies Human
## [21] Human Droid Outras espécies Human
## [25] Human Outras espécies Human Human
## [29] Outras espécies Outras espécies Human Outras espécies
## [33] Human Gungan Gungan Gungan
## [37] <NA> Outras espécies Outras espécies <NA>
## [41] Human Outras espécies Outras espécies Outras espécies
## [45] Outras espécies Outras espécies Outras espécies Human
## [49] Outras espécies Outras espécies Outras espécies Outras espécies
## [53] Outras espécies Outras espécies Outras espécies Outras espécies
## [57] Human Human Human Outras espécies
## [61] Outras espécies Outras espécies Human Human
## [65] Human Human Outras espécies Outras espécies
## [69] Outras espécies Outras espécies Human Outras espécies
## [73] Droid Outras espécies Outras espécies Outras espécies
## [77] Outras espécies Outras espécies Human <NA>
## [81] Outras espécies Human Human Human
## [85] Droid <NA> Human
## Levels: Droid Gungan Human Outras espécies
Também podemos transformar em Outras
os níveis cuja frequência relativa é menor que um determinado limite, por exemplo, 2%. No exemplo abaixo, apenas espécies que representam mais de 2% dos personagem na base são mantidas. As demais foram transformadas em Outras
.
fct_lump(fator_especies, p = 0.02, other_level = "Outras")
## [1] Human Droid Droid Human Human Human Human Droid
## [9] Human Human Human Human Wookiee Human Outras Outras
## [17] Human Human Outras Human Human Droid Outras Human
## [25] Human Outras Human Human Outras Outras Human Outras
## [33] Human Gungan Gungan Gungan <NA> Outras Outras <NA>
## [41] Human Zabrak Twi'lek Twi'lek Outras Outras Outras Human
## [49] Outras Outras Zabrak Outras Outras Outras Outras Outras
## [57] Human Human Human Outras Mirialan Mirialan Human Human
## [65] Human Human Outras Outras Kaminoan Kaminoan Human Outras
## [73] Droid Outras Outras Outras Outras Wookiee Human <NA>
## [81] Outras Human Human Human Droid <NA> Human
## 9 Levels: Droid Gungan Human Kaminoan Mirialan Twi'lek Wookiee ... Outras
Com isso, já conseguimos fazer um gráfico de barras mais agradável.
%>%
starwars filter(!is.na(species)) %>%
mutate(species = fct_lump(species, n = 2)) %>%
ggplot(aes(x = species)) +
geom_bar()
%>%
starwars filter(!is.na(species)) %>%
mutate(
species = fct_lump(species, p = 0.02),
species = fct_infreq(species) # Ordena pela frequência de cada nível
%>%
) ggplot(aes(x = species)) +
geom_bar()
Também é possível colapsar níveis criando manualmente os grupos. Para isso, utilizamos a função fct_collapse()
. No exemplo a seguir, reclassificamos os níveis da variável eye_color
. Os níveis não listados são reclassificados como "outros"
.
<- as.factor(starwars$eye_color)
fator_cor_olhos
fct_collapse(
fator_cor_olhos,preto = "black",
castanho = c("brown", "hazel"),
azul_verde = c("blue", "green"),
exotico = c("pink", "red", "white"),
colorido = c("red, blue", "green, yellow"),
other_level = "outros"
)## Warning: Unknown levels in `f`: green
## [1] azul_verde outros exotico outros castanho azul_verde
## [7] azul_verde exotico castanho outros azul_verde azul_verde
## [13] azul_verde castanho preto outros castanho azul_verde
## [19] castanho outros castanho exotico exotico castanho
## [25] azul_verde outros azul_verde castanho castanho preto
## [31] azul_verde exotico azul_verde outros outros outros
## [37] azul_verde outros outros castanho castanho outros
## [43] exotico castanho outros preto outros castanho
## [49] outros preto castanho azul_verde outros outros
## [55] preto azul_verde castanho castanho azul_verde outros
## [61] azul_verde azul_verde castanho castanho castanho castanho
## [67] outros outros preto preto azul_verde outros
## [73] colorido outros outros preto colorido azul_verde
## [79] castanho exotico preto outros castanho castanho
## [85] preto outros castanho
## Levels: preto azul_verde castanho colorido exotico outros
7.6.5 Outras funções úteis
A seguir, listamos outras funções úteis do pacote {forcats}
, apresentando exemplos simples de como usá-las.
<- factor(c("a", "b", "a", "c", "c", "a"))
fator
fator## [1] a b a c c a
## Levels: a b c
fct_c()
Junta dois fatores (e seus níveis).
<- factor(c("d", "e"))
fator2 fct_c(fator, fator2)
## [1] a b a c c a d e
## Levels: a b c d e
fct_count()
Devolve a frequência dos níveis de um vetor.
fct_count(fator)
## # A tibble: 3 × 2
## f n
## <fct> <int>
## 1 a 3
## 2 b 1
## 3 c 2
7.6.6 Exercícios
A base casas
nos exercícios abaixo está no pacote dados
:
::install_github("cienciadedatos/dados") remotes
1. Qual a diferença nos fatores criados com os códigos abaixo?
<- as.factor(c("c", "a", "z", "B"))
fator1 <- forcats::as_factor(c("c", "a", "z", "B")) fator2
2. Ordene os níveis do fator frutas
conforme a sua preferência, isto é, as que você mais gosta primeiro e as que você menos gosta por último.
<- as.factor(c("maçã", "banana", "mamão", "laranja", "melancia")) frutas
3. Com base no vetor series
, resolva os itens a seguir.
<- as.factor(c("Game of Thrones", "How I Met your Mother", "Friends", "Lost", "The Office", "Breaking Bad")) series
a. Ordene os níveis do vetor
series
conforme a sua preferência, isto é, as que você mais gosta primeiro e as que você menos gosta por último.b. Junte ao vetor
series
o vetornovas_series
a seguir, reordenando os níveis para manter a sua ordem de preferência.
<- as.factor(c("The Boys", "Stranger Things", "Queen's Gambit")) novas_series
- c. Renomeie o níveis do vetor criado no item (b) para os nomes em Português das séries. Mantenha o mesmo nome caso não haja tradução.
4. Ordene as categorias do eixo y do gráfico abaixo para que os pontos no eixo x fique em ordem crescente.
library(dplyr)
library(ggplot2)
%>%
mtcars ::rownames_to_column("modelo") %>%
tibbleggplot(aes(x = mpg, y = modelo)) +
geom_point()
5. Utilize a base dados::casas
para fazer um gráfico de barras mostrando as vizinhanças (coluna vizinhanca
) com casas mais caras (segundo a coluna venda_valor
). O gráfico deve conter as 9 vizinhanças mais frequentes e as demais devem ser agrupadas em uma categoria chamada Outras vizinhanças
.