DataFrames, CSV and tabular data in Julia

Build and reshape tables, filter and transform columns, aggregate by group, join tables, and handle missing values without surprises.

Building and inspecting

using DataFrames, CSV

df = DataFrame(
    id = 1:5,
    region = ["north", "south", "north", "east", "south"],
    amount = [100.0, 250.0, missing, 400.0, 50.0],
    shipped = [true, false, true, true, false],
)

names(df)
size(df)
first(df, 3)
describe(df)                 # per-column summary, including missing counts

df.amount                     # a column as a vector
df[!, :amount]                # same, no copy
df[:, :amount]                # a copy
VerbDoes
selectChoose and rename columns
filterKeep rows matching a predicate
transformAdd or replace columns, keeping the rest
select with a functionAdd columns and drop the others
subsetKeep rows, treating missing as false
groupby + combineAggregate per group
sortOrder rows by columns
innerjoin / leftjoinJoin on a key

Transforming and grouping

using DataFramesMeta          # optional, for @chain and friends

transform(df, :amount => (x -> coalesce(x, 0.0)) => :amount_clean)
transform(df, :amount => ByRow(x -> x === missing ? 0.0 : x) => :amount2)

# note the difference between the two arrow forms
transform(df, :amount => sum => :total)                       # whole column
transform(df, :amount => ByRow(abs) => :abs_amount)           # element-wise

filter(:shipped => ==(true), df)
subset(df, :amount => ByRow(>(100)), skipmissing = true)

g = groupby(df, :region)
combine(g, :amount => sum => :revenue,
           :amount => mean => :avg,
           nrow => :count)

sort(df, :amount; rev = true, nullsfirst = false)

ByRow wraps a function so it is applied per element instead of to the whole column. Forgetting it is the most common source of confusing errors in transform.

Missing values and joins

sum(df.amount)                    # missing, because one value is missing
sum(skipmissing(df.amount))
sum(coalesce.(df.amount, 0.0))

eltype(df.amount)                 # Union{Missing, Float64}
dropmissing(df)
disallowmissing(df)

# joins: check the key type and uniqueness first
left = DataFrame(id = [1, 2, 3], name = ["a", "b", "c"])
right = DataFrame(id = [2, 3, 4], score = [10, 20, 30])

innerjoin(left, right, on = :id)
leftjoin(left, right, on = :id)
outerjoin(left, right, on = :id, source = :origin => ["left", "right"])
⚠️
A join where the key has duplicates on both sides produces a cross product for those keys and silently multiplies your row count. Check uniqueness with nrow(df) == length(unique(df.id)) before joining, and compare nrow before and after.

FAQ

Why is my sum missing when only one value is absent?
Arithmetic on missing propagates, by design: a sum that silently ignored unknowns would be wrong. Use skipmissing to drop them deliberately, or coalesce to substitute a value.
How do I add a column computed from several columns?
Use transform with a function that takes the frame or a ByRow function over a tuple: ByRow((a, b) -> a + b) applied to [:x, :y].

Strings, IO and working with files Plotting with Plots.jl and Makie

Last refreshed 2026-09-18.