Multi-tier spanners
The following example shows how layouts with several tiers of column spanners can be achieved.
The data
We will use the PalmerPenguins data.
julia
using DataFrames, Chain, StyledTables
using Statistics: mean
df = DataFrame(StyledTables.penguins())
describe(df)7×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Union… | Any | Union… | Any | Int64 | DataType | |
| 1 | species | Adelie | Gentoo | 0 | String | ||
| 2 | island | Biscoe | Torgersen | 0 | String | ||
| 3 | bill_length_mm | 43.9928 | 32.1 | 44.5 | 59.6 | 0 | Float64 |
| 4 | bill_depth_mm | 17.1649 | 13.1 | 17.3 | 21.5 | 0 | Float64 |
| 5 | flipper_length_mm | 200.967 | 172 | 197.0 | 231 | 0 | Int64 |
| 6 | body_mass_g | 4207.06 | 2700 | 4050.0 | 6300 | 0 | Int64 |
| 7 | sex | female | male | 0 | String |
Our goal is to summarise the data by island, species, and sex.
julia
bill_cols = [:bill_length_mm, :bill_depth_mm]
number_cols = [string.(bill_cols)..., "flipper_length_mm"] # , "body_mass_g"
male_ordered = ["male_" * colname for colname in number_cols]
female_ordered = ["female_" * colname for colname in number_cols]
summary = @chain df begin
select(_, :island, :species, :sex, number_cols...)
dropmissing(_)
groupby(_, [:island, :species, :sex])
combine(_, Cols(r"_mm$|_g$") .=> mean => identity)
stack(_, number_cols)
transform(_, [:sex, :variable] => ByRow((s, v) -> join([s, v], "_")) => :sex_variable)
select(_, Not(:sex, :variable))
unstack(_, :sex_variable, :value)
transform(_, :island => ByRow(x -> "$x Is.") => identity)
select(_, :island, :species, male_ordered..., female_ordered...)
sort(_, :island)
end5×8 DataFrame
| Row | island | species | male_bill_length_mm | male_bill_depth_mm | male_flipper_length_mm | female_bill_length_mm | female_bill_depth_mm | female_flipper_length_mm |
|---|---|---|---|---|---|---|---|---|
| String | String | Float64? | Float64? | Float64? | Float64? | Float64? | Float64? | |
| 1 | Biscoe Is. | Adelie | 40.5909 | 19.0364 | 190.409 | 37.3591 | 17.7045 | 187.182 |
| 2 | Biscoe Is. | Gentoo | 49.4738 | 15.718 | 221.541 | 45.5638 | 14.2379 | 212.707 |
| 3 | Dream Is. | Adelie | 40.0714 | 18.8393 | 191.929 | 36.9111 | 17.6185 | 187.852 |
| 4 | Dream Is. | Chinstrap | 51.0941 | 19.2529 | 199.912 | 46.5735 | 17.5882 | 191.735 |
| 5 | Torgersen Is. | Adelie | 40.587 | 19.3913 | 194.913 | 37.5542 | 17.55 | 188.292 |
The table we want to create will feature island as the row group, and the species present on each island are listed in each group. As some of the length measurements we summarised describe the bill, we will group these as a column spanner. Finally, a higher-order column spanner will indicate which measurements are from female and which from male penguins.
Step 1: Row groups
julia
tbl = StyledTable(summary)
tab_rowgroup!(tbl, :island)
cols_hide!(tbl, :island)
render(tbl)| species | male_bill_length_mm | male_bill_depth_mm | male_flipper_length_mm | female_bill_length_mm | female_bill_depth_mm | female_flipper_length_mm |
| Biscoe Is. | ||||||
| Adelie | 40.6 | 19 | 190 | 37.4 | 17.7 | 187 |
| Gentoo | 49.5 | 15.7 | 222 | 45.6 | 14.2 | 213 |
| Dream Is. | ||||||
| Adelie | 40.1 | 18.8 | 192 | 36.9 | 17.6 | 188 |
| Chinstrap | 51.1 | 19.3 | 200 | 46.6 | 17.6 | 192 |
| Torgersen Is. | ||||||
| Adelie | 40.6 | 19.4 | 195 | 37.6 | 17.6 | 188 |
Step 2: Level one spanner
julia
tab_spanner!(tbl, "Bill measures" => "male_" .* string.(bill_cols))
tab_spanner!(tbl, "Bill measures" => "female_" .* string.(bill_cols))
render(tbl)| Bill measures | Bill measures | |||||
| species | male_bill_length_mm | male_bill_depth_mm | male_flipper_length_mm | female_bill_length_mm | female_bill_depth_mm | female_flipper_length_mm |
| Biscoe Is. | ||||||
| Adelie | 40.6 | 19 | 190 | 37.4 | 17.7 | 187 |
| Gentoo | 49.5 | 15.7 | 222 | 45.6 | 14.2 | 213 |
| Dream Is. | ||||||
| Adelie | 40.1 | 18.8 | 192 | 36.9 | 17.6 | 188 |
| Chinstrap | 51.1 | 19.3 | 200 | 46.6 | 17.6 | 192 |
| Torgersen Is. | ||||||
| Adelie | 40.6 | 19.4 | 195 | 37.6 | 17.6 | 188 |
Step 3: Level two spanner
julia
tab_spanner!(tbl, "Male" => male_ordered, level = 2)
tab_spanner!(tbl, "Female" => female_ordered, level = 2)
render(tbl)| Male | Female | |||||
| Bill measures | Bill measures | |||||
| species | male_bill_length_mm | male_bill_depth_mm | male_flipper_length_mm | female_bill_length_mm | female_bill_depth_mm | female_flipper_length_mm |
| Biscoe Is. | ||||||
| Adelie | 40.6 | 19 | 190 | 37.4 | 17.7 | 187 |
| Gentoo | 49.5 | 15.7 | 222 | 45.6 | 14.2 | 213 |
| Dream Is. | ||||||
| Adelie | 40.1 | 18.8 | 192 | 36.9 | 17.6 | 188 |
| Chinstrap | 51.1 | 19.3 | 200 | 46.6 | 17.6 | 192 |
| Torgersen Is. | ||||||
| Adelie | 40.6 | 19.4 | 195 | 37.6 | 17.6 | 188 |
Step 4: Add column labels
julia
label_dict = Dict(
:species => "Species",
:male_bill_length_mm => "Length",
:male_bill_depth_mm => "Depth",
:male_flipper_length_mm => "Flipper length",
:female_bill_length_mm => "Length",
:female_bill_depth_mm => "Depth",
:female_flipper_length_mm => "Flipper length",
)
cols_label!(tbl, label_dict)
fmt_integer!(tbl, [male_ordered..., female_ordered...])
render(tbl)| Male | Female | |||||
| Bill measures | Bill measures | |||||
| Species | Length | Depth | Flipper length | Length | Depth | Flipper length |
| Biscoe Is. | ||||||
| Adelie | 41 | 19 | 190 | 37 | 18 | 187 |
| Gentoo | 49 | 16 | 222 | 46 | 14 | 213 |
| Dream Is. | ||||||
| Adelie | 40 | 19 | 192 | 37 | 18 | 188 |
| Chinstrap | 51 | 19 | 200 | 47 | 18 | 192 |
| Torgersen Is. | ||||||
| Adelie | 41 | 19 | 195 | 38 | 18 | 188 |