Para a linguagem de programação R, Wickham (2011) popularizou a chamada estratégia _split-apply-combine (ou dividir-aplicar-combinar) para transformações de dados. Em essência, essa estratégia divide o dataset em grupos distintos, aplica uma ou mais funções para cada grupo e, depois, combina o resultado. DataFrames.jl suporta totalmente o método dividir-aplicar-combinar. Usaremos o exemplo das notas do aluno como antes. Suponha que queremos saber a nota média de cada aluno:
function all_grades()
    df1 = grades_2020()
    df1 = select(df1, :name, :grade_2020 => :grade)
    df2 = grades_2021()
    df2 = select(df2, :name, :grade_2021 => :grade)
    rename_bob2(data_col) = replace.(data_col, "Bob 2" => "Bob")
    df2 = transform(df2, :name => rename_bob2 => :name)
    return vcat(df1, df2)
end
all_grades()
| name | grade | 
|---|---|
| Sally | 1.0 | 
| Bob | 5.0 | 
| Alice | 8.5 | 
| Hank | 4.0 | 
| Bob | 9.5 | 
| Sally | 9.5 | 
| Hank | 6.0 | 
A estratégia é dividir o dataset em alunos distintos, aplicar a função média para cada aluno e combinar o resultado.
A divisão é chamada groupby e passamos como segundo argumento o ID da coluna em que queremos dividir o dataset:
groupby(all_grades(), :name)
GroupedDataFrame with 4 groups based on key: name
Group 1 (2 rows): name = "Sally"
 Row │ name    grade
     │ String  Float64
─────┼─────────────────
   1 │ Sally       1.0
   2 │ Sally       9.5
Group 2 (2 rows): name = "Bob"
 Row │ name    grade
     │ String  Float64
─────┼─────────────────
   1 │ Bob         5.0
   2 │ Bob         9.5
Group 3 (1 row): name = "Alice"
 Row │ name    grade
     │ String  Float64
─────┼─────────────────
   1 │ Alice       8.5
Group 4 (2 rows): name = "Hank"
 Row │ name    grade
     │ String  Float64
─────┼─────────────────
   1 │ Hank        4.0
   2 │ Hank        6.0
Nós aplicamos a função mean da biblioteca padrão de Julia no módulo Statistics:
using Statistics
Para aplicá-la, utilizamos a função combine:
gdf = groupby(all_grades(), :name)
combine(gdf, :grade => mean)
| name | grade_mean | 
|---|---|
| Sally | 5.25 | 
| Bob | 7.25 | 
| Alice | 8.5 | 
| Hank | 5.0 | 
Imagine ter que fazer isso sem as funções groupby e combine. Precisaríamos iterar sobre nossos dados para dividi-los em grupos, em seguida, iterar sobre os registros em cada divisão para aplicar a função e, finalmente, iterar sobre cada grupo para obter o resultado final. Vê-se assim que é muito bom conhecer a técnica dividir-aplicar-combinar.
Mas, e se quisermos aplicar uma função à várias colunas de nosso dataset?
group = [:A, :A, :B, :B]
X = 1:4
Y = 5:8
df = DataFrame(; group, X, Y)
| group | X | Y | 
|---|---|---|
| A | 1 | 5 | 
| A | 2 | 6 | 
| B | 3 | 7 | 
| B | 4 | 8 | 
Isso é feito de maneira semelhante:
gdf = groupby(df, :group)
combine(gdf, [:X, :Y] .=> mean; renamecols=false)
| group | X | Y | 
|---|---|---|
| A | 1.5 | 5.5 | 
| B | 3.5 | 7.5 | 
Perceba que usamos o operador dot . antes da seta à direita => para indicar que o mean tem que ser aplicado a múltiplas colunas de origem [:X, :Y].
Para usar funções compostas, uma maneira simples é criar uma função que faça as transformações compostas pretendidas. Por exemplo, para uma série de valores, vamos primeiro pegar o mean seguido de round para um número inteiro (também conhecido como um inteiro Int):
gdf = groupby(df, :group)
rounded_mean(data_col) = round(Int, mean(data_col))
combine(gdf, [:X, :Y] .=> rounded_mean; renamecols=false)
| group | X | Y | 
|---|---|---|
| A | 2 | 6 | 
| B | 4 | 8 |