Introdução ao pacote TidierData.jl

Manipulação de Dados
Tidy Data
Ferramentas
Pacotes

Um guia introdutório sobre o pacote TidierData.jl! Entenda como aplicar a gramática tidy para manipulação de dados de forma intuitiva, utilizando funções familiares para quem vem do tidyverse em R.

Autores
Afiliação

Universidade Estadual de Campinas

Universidade Estadual de Campinas

Data de Publicação

14 de junho de 2025

Introdução

O TidierData.jl é um pacote da linguagem julia que traz uma abordagem familiar aos usuários do R, inspirado diretamente nos pacotes dplyr e tidyr. Ele permite manipular e transformar conjuntos de dados com uma sintaxe concisa e legível no estilo tidy.

Neste post, vamos explorar como o TidierData.jl facilita tarefas comuns de manipulação de dados, como seleção, filtragem, agregações, junções e muito mais.

Por trás das cortinas, o TidierData.jl é construído sobre o robusto DataFrames.jl, combinando a performance de julia com a ergonomia do tidyverse.

Carregando o pacote

import Pkg

# Suprime mensagens e warnings durante a instalação dos pacotes
redirect_stdout(devnull) do
    redirect_stderr(devnull) do
        Pkg.add("TidierData")
        Pkg.add("DataFrames")
        Pkg.add("RDatasets") # Vamos usar o pacote RDatasets para gerar um banco de dados para análise  
    end
end

Funções

Carregando o dataset ‘mtcars’

using RDatasets

mtcars = dataset("datasets", "mtcars")
32×12 DataFrame
 Row │ Model              MPG      Cyl    Disp     HP     DRat     WT       QS ⋯
     │ String31           Float64  Int64  Float64  Int64  Float64  Float64  Fl ⋯
─────┼──────────────────────────────────────────────────────────────────────────
   1 │ Mazda RX4             21.0      6    160.0    110     3.9     2.62      ⋯
   2 │ Mazda RX4 Wag         21.0      6    160.0    110     3.9     2.875
   3 │ Datsun 710            22.8      4    108.0     93     3.85    2.32
   4 │ Hornet 4 Drive        21.4      6    258.0    110     3.08    3.215
   5 │ Hornet Sportabout     18.7      8    360.0    175     3.15    3.44      ⋯
   6 │ Valiant               18.1      6    225.0    105     2.76    3.46
   7 │ Duster 360            14.3      8    360.0    245     3.21    3.57
   8 │ Merc 240D             24.4      4    146.7     62     3.69    3.19
  ⋮  │         ⋮             ⋮       ⋮       ⋮       ⋮       ⋮        ⋮        ⋱
  26 │ Fiat X1-9             27.3      4     79.0     66     4.08    1.935     ⋯
  27 │ Porsche 914-2         26.0      4    120.3     91     4.43    2.14
  28 │ Lotus Europa          30.4      4     95.1    113     3.77    1.513
  29 │ Ford Pantera L        15.8      8    351.0    264     4.22    3.17
  30 │ Ferrari Dino          19.7      6    145.0    175     3.62    2.77      ⋯
  31 │ Maserati Bora         15.0      8    301.0    335     3.54    3.57
  32 │ Volvo 142E            21.4      4    121.0    109     4.11    2.78
                                                   5 columns and 17 rows omitted

first(mtcars, 5)
5×12 DataFrame
 Row │ Model              MPG      Cyl    Disp     HP     DRat     WT       QS ⋯
     │ String31           Float64  Int64  Float64  Int64  Float64  Float64  Fl ⋯
─────┼──────────────────────────────────────────────────────────────────────────
   1 │ Mazda RX4             21.0      6    160.0    110     3.9     2.62      ⋯
   2 │ Mazda RX4 Wag         21.0      6    160.0    110     3.9     2.875
   3 │ Datsun 710            22.8      4    108.0     93     3.85    2.32
   4 │ Hornet 4 Drive        21.4      6    258.0    110     3.08    3.215
   5 │ Hornet Sportabout     18.7      8    360.0    175     3.15    3.44      ⋯
                                                               5 columns omitted

Funções Macro

O pacote oferece mais de 40 funções macro, as quais podemos usar para manipular dados. A seguir, exploraremos algumas das principais funções e observaremos sua sintaxe de forma clara e concisa.

using TidierData, DataFrames

# Exemplo com o dataset mtcars

@chain mtcars begin
    @mutate(Eficiencia = MPG / WT)
    @filter(Cyl == 6)
    @arrange(desc("Eficiencia"))
    @select(Model, MPG, WT, Eficiencia)
    @slice(1:6)
end
6×4 DataFrame
 Row │ Model           MPG      WT       Eficiencia
     │ String31        Float64  Float64  Float64
─────┼──────────────────────────────────────────────
   1 │ Mazda RX4          21.0    2.62      8.01527
   2 │ Mazda RX4 Wag      21.0    2.875     7.30435
   3 │ Ferrari Dino       19.7    2.77      7.11191
   4 │ Hornet 4 Drive     21.4    3.215     6.6563
   5 │ Merc 280           19.2    3.44      5.5814
   6 │ Valiant            18.1    3.46      5.23121
  • Primeiramente, para quem já está familiarizado com a linguagem R, a função @chain() é similar ao pipeline %>% ou |>, usado para encadear várias operações em sequência no mesmo conjunto de dados.

  • Em seguida, criamos a nova coluna Eficiencia no nosso dataset, definida como a razão entre as colunas MPG e WT.

  • Além disso, aplicamos restrições à saída do dataset, filtrando apenas os carros que possuem exatamente 6 cilindros, usando @filter().

  • Selecionamos apenas as colunas Model, MPG, WT e Eficiencia, usando @select().

  • Ordenamos o dataset pelo valor da Eficiencia, em ordem decrescente, usando @arrange() para ordenar e desc() para determinar que era decrescente.

Obs.: desc() é uma função auxiliar.

  • Por fim, optamos por visualizar apenas da linha 1 até a linha 6 do dataset, utilizando a função @slice().

Agora, vejamos um exemplo de formatação dos dados e utilização das funções de join.

Vamos supor que possuímos dois dataframes: um com informações sobre clientes e outro com compras. Queremos combinar os dados para visualizar o nome dos clientes junto com seus respectivos valores de compra.

using TidierData, DataFrames

clientes = DataFrame(ID = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], Nome = ["Leonardo", "Arthur", "Carlos", "Esther", "Caio", "Guilherme", "Henrique", "Nicole", "Pedro", "Sarah"])
10×2 DataFrame
 Row │ ID     Nome
     │ Int64  String
─────┼──────────────────
   1 │     1  Leonardo
   2 │     2  Arthur
   3 │     3  Carlos
   4 │     4  Esther
   5 │     5  Caio
   6 │     6  Guilherme
   7 │     7  Henrique
   8 │     8  Nicole
   9 │     9  Pedro
  10 │    10  Sarah
compras = DataFrame(ID = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], Valor = [150, 200, 300, 100, 250, 220, 310, 420, 500, 50])
10×2 DataFrame
 Row │ ID     Valor
     │ Int64  Int64
─────┼──────────────
   1 │     1    150
   2 │     2    200
   3 │     3    300
   4 │     4    100
   5 │     5    250
   6 │     6    220
   7 │     7    310
   8 │     8    420
   9 │     9    500
  10 │    10     50

@left_join(compras, clientes)
10×3 DataFrame
 Row │ ID     Valor  Nome
     │ Int64  Int64  String?
─────┼─────────────────────────
   1 │     1    150  Leonardo
   2 │     2    200  Arthur
   3 │     3    300  Carlos
   4 │     4    100  Esther
   5 │     5    250  Caio
   6 │     6    220  Guilherme
   7 │     7    310  Henrique
   8 │     8    420  Nicole
   9 │     9    500  Pedro
  10 │    10     50  Sarah

Aqui, usamos a função @left_join() para unir nossos dados através da coluna ID. Assim, obtemos um dataframe alinhado por ID, com as informações dos clientes e o valor de suas respectivas compras.

Observe que o uso da função @left_join() é simples e intuitivo, pois ela une os dois dataframes automaticamente pela coluna em comum, sem necessidade de especificação explícita.

using TidierData, DataFrames

df = DataFrame(Nome = ["Thiago", "Vitor", "Gabriel", "Ana", "Gustavo", "Leticia"], Nota1 = [8, 7, 7.5, 6, 9, 10], Nota2 = [9, 10, 4, 5.5, 5, 9])
6×3 DataFrame
 Row │ Nome     Nota1    Nota2
     │ String   Float64  Float64
─────┼───────────────────────────
   1 │ Thiago       8.0      9.0
   2 │ Vitor        7.0     10.0
   3 │ Gabriel      7.5      4.0
   4 │ Ana          6.0      5.5
   5 │ Gustavo      9.0      5.0
   6 │ Leticia     10.0      9.0

print(df)
6×3 DataFrame
 Row │ Nome     Nota1    Nota2
     │ String   Float64  Float64
─────┼───────────────────────────
   1 │ Thiago       8.0      9.0
   2 │ Vitor        7.0     10.0
   3 │ Gabriel      7.5      4.0
   4 │ Ana          6.0      5.5
   5 │ Gustavo      9.0      5.0
   6 │ Leticia     10.0      9.0

@chain df begin
    @pivot_longer(cols = [:Nota1, :Nota2], names_to = :Avaliação, values_to = :Nota)
end
12×3 DataFrame
 Row │ Nome     Avaliação  Nota
     │ String   String     Float64
─────┼─────────────────────────────
   1 │ Thiago   Nota1          8.0
   2 │ Vitor    Nota1          7.0
   3 │ Gabriel  Nota1          7.5
   4 │ Ana      Nota1          6.0
   5 │ Gustavo  Nota1          9.0
   6 │ Leticia  Nota1         10.0
   7 │ Thiago   Nota2          9.0
   8 │ Vitor    Nota2         10.0
   9 │ Gabriel  Nota2          4.0
  10 │ Ana      Nota2          5.5
  11 │ Gustavo  Nota2          5.0
  12 │ Leticia  Nota2          9.0

Aqui, transformamos o dataframe do formato wide para long usando a função @pivot_longer(). A mesma lógica se aplica caso queiramos converter de long para wide, utilizando a função @pivot_wider().

Funções auxiliares

Além das macros principais como @mutate, @filter e @summarize, o TidierData.jl oferece funções auxiliares que tornam seu código mais expressivo, claro e eficiente. Vamos explorar algumas delas com exemplos práticos:

if_else()

Lógica condicional simples e vetorizada:

using TidierData, DataFrames

valores = DataFrame(Nome = ["A", "B", "C"], Salario = [3000, 5000, 4000])
3×2 DataFrame
 Row │ Nome    Salario
     │ String  Int64
─────┼─────────────────
   1 │ A          3000
   2 │ B          5000
   3 │ C          4000

@chain valores begin
    @mutate(Status = if_else(`Salario` .> 4000, "Alto", "Médio ou Baixo"))
end
3×3 DataFrame
 Row │ Nome    Salario  Status
     │ String  Int64    String
─────┼─────────────────────────────────
   1 │ A          3000  Médio ou Baixo
   2 │ B          5000  Alto
   3 │ C          4000  Médio ou Baixo

case_when()

Para múltiplas condições, substituindo vários if_else():

using TidierData, DataFrames

df2 = DataFrame(Nota = [10, 2, 6, 4, 7.1, 4.9, 9.7])
7×1 DataFrame
 Row │ Nota
     │ Float64
─────┼─────────
   1 │    10.0
   2 │     2.0
   3 │     6.0
   4 │     4.0
   5 │     7.1
   6 │     4.9
   7 │     9.7

@chain df2 begin
    @mutate(Conceito = case_when(
        `Nota` .>= 9 => "A",
        `Nota` .>= 7 => "B",
        `Nota` .>= 5 => "C",
        true        => "D"
    ))
end
7×2 DataFrame
 Row │ Nota     Conceito
     │ Float64  String
─────┼───────────────────
   1 │    10.0  A
   2 │     2.0  D
   3 │     6.0  C
   4 │     4.0  D
   5 │     7.1  B
   6 │     4.9  D
   7 │     9.7  A

starts_with(), ends_with()

Selecionando colunas com nomes que seguem um padrão:

using TidierData, DataFrames

df3 = DataFrame(
    Nome = ["José", "Carla", "Humberto"],
    Nota1 = [7, 9, 8],
    Nota2 = [8, 10, 10],
    Idade = [20, 22, 24]
)
3×4 DataFrame
 Row │ Nome      Nota1  Nota2  Idade
     │ String    Int64  Int64  Int64
─────┼───────────────────────────────
   1 │ José          7      8     20
   2 │ Carla         9     10     22
   3 │ Humberto      8     10     24

comeca = @chain df3 begin
    @select(`Nome`, starts_with("Nota"))
end
3×3 DataFrame
 Row │ Nome      Nota1  Nota2
     │ String    Int64  Int64
─────┼────────────────────────
   1 │ José          7      8
   2 │ Carla         9     10
   3 │ Humberto      8     10

termina = @chain df3 begin
  @select(`Nome`, ends_with("ade"))
end
3×2 DataFrame
 Row │ Nome      Idade
     │ String    Int64
─────┼─────────────────
   1 │ José         20
   2 │ Carla        22
   3 │ Humberto     24

Obs.: As funções endswith() da base do julia e ends_with() do pacote TidierData.jl tem uma pequena diferença: a função ends_with() é usada para nomes de colunas, enquanto endswith() é usada para valores dentro das colunas.

Referências

Blog inspirado na documentação disponibilizada pelos autores do pacote, a qual pode ser acessada pelo link https://tidierorg.github.io/TidierData.jl/latest/.

Nota

Ferramentas de IA foram utilizadas para correção ortográfica e aprimoramento do texto.