Data Analysis with DataFrames
Prerequisites: Packages and environments
DataFrames.jl is Julia's table library, similar to pandas in Python
or data frames in R, and CSV.jl reads and writes CSV files quickly.
Install both with ] add DataFrames CSV (the examples use DataFrames
1.8 and CSV 1.1).
using DataFrames
df = DataFrame(name = ["Ada", "Alan", "Grace", "Edsger"],
dept = ["math", "cs", "cs", "cs"],
salary = [120, 95, 130, 110])
println(df)
println(size(df), " ", names(df))
println(df.salary)
println(df[2, :name], " ", df[df.salary .> 100, :name])
4×3 DataFrame
Row │ name dept salary
│ String String Int64
─────┼────────────────────────
1 │ Ada math 120
2 │ Alan cs 95
3 │ Grace cs 130
4 │ Edsger cs 110
(4, 3) ["name", "dept", "salary"]
[120, 95, 130, 110]
Alan ["Ada", "Grace", "Edsger"]
- Each column is an ordinary Julia vector —
df.salaryis aVector{Int64}. df[rows, cols]indexes like a matrix, with column names as symbols.
Transforming and filtering
using DataFrames
df = DataFrame(name = ["Ada", "Alan", "Grace", "Edsger"],
dept = ["math", "cs", "cs", "cs"],
salary = [120, 95, 130, 110])
df.bonus = df.salary .* 0.1
high = filter(:salary => >(100), df)
println(high)
println(sort(df, :salary, rev = true)[:, [:name, :salary]])
println(select(df, :name, :salary => (s -> s .* 1000) => :salary_usd))
3×4 DataFrame
Row │ name dept salary bonus
│ String String Int64 Float64
─────┼─────────────────────────────────
1 │ Ada math 120 12.0
2 │ Grace cs 130 13.0
3 │ Edsger cs 110 11.0
4×2 DataFrame
Row │ name salary
│ String Int64
─────┼────────────────
1 │ Grace 130
2 │ Ada 120
3 │ Edsger 110
4 │ Alan 95
4×2 DataFrame
Row │ name salary_usd
│ String Int64
─────┼────────────────────
1 │ Ada 120000
2 │ Alan 95000
3 │ Grace 130000
4 │ Edsger 110000
source => function => new_name is DataFrames' mini-language for
transformations — used by select, transform, and combine.
Group, then combine
using DataFrames, Statistics
df = DataFrame(name = ["Ada", "Alan", "Grace", "Edsger"],
dept = ["math", "cs", "cs", "cs"],
salary = [120, 95, 130, 110])
summary = combine(groupby(df, :dept),
nrow => :people,
:salary => mean => :avg_salary,
:salary => maximum => :top)
println(summary)
2×4 DataFrame
Row │ dept people avg_salary top
│ String Int64 Float64 Int64
─────┼───────────────────────────────────
1 │ math 1 120.0 120
2 │ cs 3 111.667 130
This is the split–apply–combine pattern: groupby splits the rows,
and combine computes one row of results per group.
Reading and writing CSV
using CSV, DataFrames
write("people.csv", """
name,age,city
Ada,36,London
Alan,,Wilmslow
Grace,85,Arlington
""")
people = CSV.read("people.csv", DataFrame; stringtype = String)
println(people)
println(eltype(people.age))
dropped = dropmissing(people, :age)
CSV.write("adults.csv", dropped)
print(read("adults.csv", String))
3×3 DataFrame
Row │ name age city
│ String Int64? String
─────┼────────────────────────────
1 │ Ada 36 London
2 │ Alan missing Wilmslow
3 │ Grace 85 Arlington
Union{Missing, Int64}
name,age,city
Ada,36,London
Grace,85,Arlington
CSV.jl detects column types and turns empty cells into missing.
(stringtype = String asks for ordinary String columns; by default
CSV.jl 1.1 uses a compact string type from DataStrings.jl.)
Other useful functions: leftjoin/innerjoin to join tables, stack
and unstack to reshape, describe(df) for a quick summary. Generate
test data with the random CSV generator.