Skip to content

Units

AlgebraOfGraphics supports input data with units, currently Unitful.jl and DynamicQuantities.jl have extensions implemented.

Let's first create a unitful version of the penguins dataset:

julia
using AlgebraOfGraphics
using CairoMakie
using Unitful
using DataFrames

df = DataFrame(AlgebraOfGraphics.penguins())
df.bill_length = df.bill_length_mm .* u"mm"
df.bill_depth = uconvert.(u"cm", df.bill_depth_mm .* u"mm")
df.flipper_length = df.flipper_length_mm .* u"mm"
df.body_mass = df.body_mass_g .* u"g"
select!(df, Not([:bill_length_mm, :bill_depth_mm, :flipper_length_mm, :body_mass_g]))

first(df, 5)
5×7 DataFrame
Rowspeciesislandsexbill_lengthbill_depthflipper_lengthbody_mass
StringStringStringQuantity…Quantity…Quantity…Quantity…
1AdelieTorgersenmale39.1 mm1.87 cm181 mm3750 g
2AdelieTorgersenfemale39.5 mm1.74 cm186 mm3800 g
3AdelieTorgersenfemale40.3 mm1.8 cm195 mm3250 g
4AdelieTorgersenfemale36.7 mm1.93 cm193 mm3450 g
5AdelieTorgersenmale39.3 mm2.06 cm190 mm3650 g

When we plot columns with units, the units are automatically appended to the respective labels:

julia
spec = data(df) *
    mapping(:bill_length, :bill_depth, color = :body_mass) *
    visual(Scatter)
draw(spec)

Labels are separate from units, so we can relabel without affecting the unit suffixes:

julia
spec = data(df) *
    mapping(:bill_length => "Bill length", :bill_depth => "Bill depth", color = :body_mass => "Body mass") *
    visual(Scatter)
draw(spec)

We can choose a different display unit per scale via the unit scale property:

julia
draw(
    spec,
    scales(
        X = (; unit = u"cm"),
        Y = (; unit = u"mm"),
        Color = (; unit = u"kg")
    )
)

If we plot different units on the same scale, all the units have to be dimensionally compatible and will be auto-converted to the same unit.

julia
layer1 = data(df) * mapping(:bill_length, color = direct("Bill length")) * visual(Density)
layer2 = data(df) * mapping(:bill_depth, color = direct("Bill depth")) * visual(Density)

draw(layer1 + layer2)

AlgebraOfGraphics will complain if we try to plot dimensionally incompatible units on the same scale:

julia
layer1 = data(df) * mapping(:bill_length, color = direct("Bill length")) * visual(Density)
layer2 = data(df) * mapping(:body_mass, color = direct("Body mass")) * visual(Density)

draw(layer1 + layer2)
┌ Warning: Assignment to `layer1` in soft scope is ambiguous because a global variable by the same name exists: `layer1` will be treated as a new local. Disambiguate by using `local layer1` to suppress this warning or `global layer1` to assign to the existing global variable.
└ @ units.md:72
┌ Warning: Assignment to `layer2` in soft scope is ambiguous because a global variable by the same name exists: `layer2` will be treated as a new local. Disambiguate by using `local layer2` to suppress this warning or `global layer2` to assign to the existing global variable.
└ @ units.md:73
DimensionError: 2700.0 g and 32.1 mm are not dimensionally compatible.

In the next example, we make a facet plot with wide data, and even though there are two y axes, there's just one underlying Y scale being fit. Therefore, both columns get unit-converted:

julia
spec_wide = data(df) *
    mapping(:sex, [:bill_length => "Bill length", :bill_depth => "Bill depth"], layout = dims(1)) *
    visual(Violin)

draw(spec_wide)

Again, we can force a different display unit via the scale options.

julia
draw(spec_wide, scales(Y = (; unit = u"cm"), Layout = (; show_labels = false)))