<- lm(log(AirPassengers) ~ time(AirPassengers))
model
#> Função composta
mean(exp(fitted(model)))
#> Usando pipes
|> fitted() |> exp() |> mean()
model #> Usando objetos intermediários
<- fitted(model)
x1 <- exp(x1)
x2 <- mean(x2) x3
A filosofia do tidyverse
A filosofia geral do tidyverse
toma muito emprestado da gramática. As funções têm nomes de verbos que costumam ser intuitivos e são encadeados via “pipes”1 que funcionam como conectivos numa frase. Em tese, isto torna o código mais legível e até mais didático. A tarefa de renomear colunas, criar variáveis e calcular uma média nos grupos torna-se “linear” no mesmo sentido em que uma frase com sujeito-verbo-objeto é linear.
Pipes
O pipe, essencialmente, carrega o resultado de uma função adiante numa cadeia de comandos: objeto |> função1 |> função2 |> função3
. Isto tem duas vantagens: primeiro, evita que você use funções compostas que são lidas “de dentro para fora” como exp(mean(log(x)))
; e, segundo, dispensa a criação de objetos intermediários “inúteis” que estão ali somente para segurar um valor que não vai ser utilizado mais adiante.
Num contexto de manipulação de dados pode-se ter algo como o código abaixo.
<- dados |>
tab_vendas_cidade #> Renomeia colunas
rename(date = data, city = cidade, variable = vendas, value = valor) |>
#> Transforma colunas
mutate(
value = value / 1000,
date = readr::parse_date(date, format = "%Y-%b%-d", locale = readr::locale("pt")),
year = lubridate::year(date)
|>
) #> Agrupa pela coluna year e calcula algumas estatísticas
group_by(year) |>
summarise(
total = sum(value),
count = n()
)
Em base-R o mesmo código ficaria algo como o descrito abaixo.
names(dados) <- c("date", "city", "variable", "value")
$value <- dados$value / 1000
dados$date <- readr::parse_date(
dados$date, format = "%Y-%b%-d", locale = readr::locale("pt")
dados
)$year <- lubridate::year(dados$date)
dados
<- tapply(
tab_vendas_cidade $value,
dados$city,
dadosdata.frame(total = sum(x), count = length(x))}
\(x) { )
Há um tempo atrás argumentava-se contra o uso de “pipes”, pois estes dificultavam a tarefa de encontrar bugs no código. Isto continua sendo parcialmente verdade, mas as funções do tidyvserse
atualmente têm mensagens de erro bastante ricas e permitem encontrar a fonte do erro com relativa facilidade. Ainda assim, não se recomenda encadear funções em excesso, i.e., pipes com 10 funções ou mais2.
Funções
Outra filosofia do tidyverse
é de que tarefas rotineiras devem ser transformadas em funções específicas. Neste sentido, os pacotes dplyr
, tidyr
e afins são recheados de funções, às vezes com nomes muito semelhantes e com usos redundantes. As funções starts_with
e ends_with
, por exemplo, são casos específicos da função matches
. Há funções que permitem até duas formas de grafia como summarise
e summarize
. Outras como slice_min
e slice_max
são convenientes mas são literalmente: arrange + slice
.
Somando somente os dois principais pacotes, dplyr
e tidyr
, há 360 funções disponíveis. Contraste isto com o data.table
que permite fazer 95% das transformações de dados somente com dt[i, j, by = c(), .SDcols = cols]
.
Mesmo as funções base do R
costumam ser mais sucintas do que códigos em tidyverse
. No exemplo abaixo, a função tapply
consegue o mesmo resultado que o código mais extenso feito com dplyr
.
tapply(mtcars$mpg, mtcars$cyl, mean)
|>
mtcars group_by(cyl) |>
summarise(avg = mean(cyl))
As vantagens do tidyverse
se tornam mais evidentes com o tempo. De fato, o pacote permite abstrações muito poderosas, e eventualmente, pode-se fazer um código centenas de vezes mais sucinto combinando as suas funções. Em outros casos, as funções do tidyverse são simplesmente muito convenientes.
Tome a starts_with
, por exemplo, que seleciona as colunas que começam de uma certa forma. Suponha uma tabela simples em que há múltiplas colunas cujos nomes começam com a letra “x”. O código em tidyverse é muito mais limpo que o código em base-R.
<- df |>
df select(date, starts_with("x"))
<- df[, c("date", names(df)[grep("^x", names(df))])]
df <- df[, c("date", names(df)[stringr::str_detect(names(df), "^x")])] df
O exemplo abaixo é inspirado neste post, que mostra como calcular lags de uma série de tempo que esteja em um data.frame
. Calcular defasagens de uma série de tempo é uma tarefa um pouco árdua quando se usa somente funções base. O código abaixo mostra não somente a elegância do tidyverse mas também a facilidade em se criar funções a partir do tidyverse.
<- function(df, var, lags) {
calculate_lags
<- lags |> map(~partial(lag, n = .x))
map_lag <- df |>
out mutate(
across(.cols = {{var}},
.fns = map_lag,
.names = "{.col}_lag{lags}")
)
return(out)
}
<- data.frame(
df date = time(AirPassengers),
value = as.numeric(AirPassengers)
)
|> calculate_lags(value, 1:3) |> head()
df # date value value_lag1 value_lag2 value_lag3
# 1 1949.000 112 NA NA NA
# 2 1949.083 118 112 NA NA
# 3 1949.167 132 118 112 NA
# 4 1949.250 129 132 118 112
# 5 1949.333 121 129 132 118
# 6 1949.417 135 121 129 132
Desvantagens
O lado negativo da abordagem “gramatical” é que para não-falantes de inglês muitas destas vantagens são despercebidas3 e o resultado é somente um código “verborrágico”, cheio de funções. Além disso, pode-se argumentar que há ambiguidades inerentes na linguagem. A função filter
, por exemplo, é utilizada para filtrar as linhas de um data.frame
, mas podia, igualmente, chamar-se select
, que selecionaria as linhas de um data.frame
. A função select
, contudo, é usada para selecionar as colunas de um data.frame
.
Um fato particularmente irritante do tidyverse
é a frequência com que os pacotes mudam. Na maior parte das vezes, as mudanças são positivas, mas isto faz com que o código escrito em tidyverse
não seja sustentável ao longo do tempo.
Eu demorei um bom tempo para entender as funções tidyr::gather
e tidyr::spread
e, atualmente, ambas foram descontinuadas e substituídas pelas funções pivot_longer
e pivot_wider
4. As funções mutate_if
, mutate_at
e similares do dplyr
foram todas suprimidas pela sinataxe mais geral do across
. A função tidyr::separate
agora está sendo substituída por separate_wider_position
e separate_wider_delim
.
Mesmo um código bem escrito há poucos anos atrás tem grandes chances de não funcionar mais porque as funções foram alteradas ou descontinuadas. Em 2021, Wickham discutiu este problema abertamente numa palestra. Desde então, o tidyverse tem melhorado a sua política de manutenção de funções.
A velocidade e eficiência das funções do tidyverse pode ser um problema, mas atualmente existem diversas boas soluções como o já citado tidytable
. Particularmente, são raras as situações em que a velocidade do tidyverse me incomoda.
Atualmente, parece haver um consenso crescente de que a melhor forma de começar a aprender R é começando pelo tidyverse
; esta visão não é livre de críticos como de Norm Matloff, professor de estatística da UC Davis. Essencialmente, Matloff considera que o tidyverse
é muito complexo para iniciantes: há muitas funções para se aprender e o incentivo à programação funcional torna o código muito abstrato. O tidyverse também esconde o uso do base-R e não ensina operadores básicos como [[
e $
. Matloff também considera que “pipes” prejudicam o aprendizado pois dificultam a tarefa de encontrar a fonte dos erros no código.
Encontrando o equilíbrio
A realidade é que tanto os defensores quanto os críticos do tidyverse têm pontos válidos. O ideal não é escolher um “lado”, mas entender as forças e limitações de cada abordagem.
Quando usar tidyverse
O tidyverse brilha em:
- Análise exploratória de dados: A combinação dplyr + ggplot2 é imbatível para explorar dados rapidamente
- Relatórios e análises reprodutíveis: A legibilidade do código facilita a manutenção
- Ensino e aprendizado: A sintaxe intuitiva acelera a curva de aprendizado
- Trabalho colaborativo: Código mais padronizado facilita o trabalho em equipe
Quando considerar base-R
Base-R pode ser preferível para:
- Performance crítica: Operações simples em grandes datasets
- Pacotes e funções: Reduz dependências externas
- Estatística avançada: Muitos métodos estatísticos usam estruturas base-R
- Programação de sistemas: Controle fino sobre memória e performance
Conclusão: Uma ferramenta, não uma religião
O tidyverse
é um conjunto de pacotes excepcionalmente bem projetados que revolucionaram a forma como fazemos análise de dados em R. Sua filosofia gramatical, consistência sintática e foco na experiência do usuário o tornam uma ferramenta poderosa, especialmente para cientistas de dados e analistas.
As principais contribuições do tidyverse foram:
- Democratização: Tornou R mais acessível para não-programadores
- Padronização: Criou convenções consistentes em um ecossistema tradicionalmente fragmentado
- Produtividade: Permitiu análises complexas com menos código
- Comunidade: Fomentou uma comunidade vibrante de usuários e desenvolvedores
Contudo, é importante reconhecer que:
- Tidyverse não substitui completamente o conhecimento de base-R
- A escolha da ferramenta deve depender do contexto e objetivos
- Fluência em ambas as abordagens torna você um programador R mais completo
Minha recomendação
Para a maioria dos casos, especialmente em ciência de dados, o tidyverse oferece a melhor combinação de produtividade, legibilidade e facilidade de aprendizado. Mas invista tempo aprendendo base-R também - você será um programador R mais versátil e capaz.
Como disse o próprio Hadley Wickham: “O tidyverse não é uma tentativa de substituir R, mas de torná-lo mais consistente e expressivo”.
Próximos passos na série
- Post anterior: Tidyverse: o melhor caminho para começar? - Análise sobre por onde começar a aprender R
- Próximo post: O Tidyverse em Números - Dados que mostram a dominância do tidyverse no ecossistema R
Recursos para aprofundar
Para entender a filosofia do tidyverse: - Tidy Tools Manifesto - Os princípios oficiais - Design Principles - Guia de design detalhado
Para base-R: - Grolemund, G. Hands-on programming with R - Wickham, H. Advanced R (1ª ed.) - A primeira edição cobre mais base-R
Para críticas construtivas: - TidyverseSkeptic - Perspectiva alternativa de Norm Matloff
Footnotes
Para saber mais sobre pipes e a diferença entre o novo pipe nativo
|>
e o pipe|>
domagrittr
veja meu post sobre o assunto.↩︎Para mais sobre pipes consulte o meu post sobre o assunto.↩︎
No fundo, isto é ainda mais um incentivo para aprender inglês.↩︎
Tecnicamente, elas foram “superseded”, ou suplatandas. Isto significa que elas continuam existindo exatamente da forma como sempre existiram e que não receberão mais atualizações.↩︎