Creating multi-panel plots

Author

Phillip Alday, Douglas Bates, and Reinhold Kliegl

Published

2025-09-13

This notebook shows creating a multi-panel plot similar to Figure 2 of Fühner et al. (2021).

The data are available from the SMLP2026 example datasets.

Code
using Arrow
using AlgebraOfGraphics
using CairoMakie   # for displaying static plots
using DataFrames
using Statistics
using StatsBase
using SMLP2026: dataset
Precompiling packages...
Info Given Arrow was explicitly requested, output will be shown live 
WARNING: Constructor for type "Type" was extended in `Flatbuf` without explicit qualification or import.
  NOTE: Assumed "Type" refers to `Base.Type`. This behavior is deprecated and may differ in future versions.`
  NOTE: This behavior may have differed in Julia versions prior to 1.12.
  Hint: If you intended to create a new generic function of the same name, use `function Type end`.
  Hint: To silence the warning, qualify `Type` as `Base.Type` in the method signature or explicitly `import Base: Type`.
   4823.8 msArrow
  1 dependency successfully precompiled in 5 seconds. 49 already precompiled.
  1 dependency had output during precompilation:Arrow[Output was shown above]
Precompiling packages...
    945.0 msQuartoNotebookWorkerTablesExt (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
   1255.8 msBzip2_jll
   1329.9 msRmath_jll
   1337.7 msXorg_libXau_jll
   1333.7 mslibpng_jll
   1360.8 msLibmount_jll
   1374.4 msLLVMOpenMP_jll
   1387.3 mslibfdk_aac_jll
   1599.4 msGraphite2_jll
   1789.5 msImath_jll
   1481.2 msGiflib_jll
   1461.1 msLAME_jll
   1462.9 msLERC_jll
   1539.4 msCRlibm_jll
   1540.6 msXZ_jll
   1410.4 msOgg_jll
   1686.2 msEarCut_jll
   1904.8 msJpegTurbo_jll
   1828.9 msXorg_libXdmcp_jll
   1245.7 msx265_jll
   1233.7 msOpus_jll
   1456.9 mslibaom_jll
   1239.8 msXorg_xtrans_jll
   1423.1 msLZO_jll
   1627.5 msx264_jll
   1536.4 msExpat_jll
   1414.6 msLibffi_jll
   1339.9 msisoband_jll
   1526.8 msFFTW_jll
   1311.8 msOpenSpecFun_jll
   1379.6 msOpenBLASConsistentFPCSR_jll
   1487.9 msLibuuid_jll
   1488.2 msFriBidi_jll
   1606.9 msGettextRuntime_jll
   1433.6 msFreeType2_jll
   2558.7 msIntelOpenMP_jll
   2805.6 msoneTBB_jll
   1648.9 msRmath
   1601.6 msPixman_jll
   1422.3 msCRlibm
   2017.1 msOpenEXR_jll
   1624.5 msLibtiff_jll
    691.5 msIsoband
   2390.8 mslibvorbis_jll
   1514.8 mslibsixel_jll
   2113.3 msFreeType
   2812.6 msGlib_jll
   9750.6 msColorSchemes
   5620.2 msXorg_libxcb_jll
   2524.3 msFontconfig_jll
   2758.7 msMKL_jll
  11398.7 msJSON
   7002.8 msSpecialFunctions
   2266.7 msXorg_libX11_jll
   3822.3 msOpenEXR
   3076.7 msColorBrewer
   2481.2 msHypergeometricFunctions
   1219.3 msColorVectorSpace → SpecialFunctionsExt
   5180.0 msSpecialFunctions → SpecialFunctionsChainRulesCoreExt
   2017.7 msXorg_libXext_jll
   2478.6 msXorg_libXrender_jll
  10072.4 msIntervalArithmetic
  21089.5 msStaticArrays
   1448.8 msIntervalArithmetic → IntervalArithmeticSparseArraysExt
   1060.9 msIntervalArithmetic → IntervalArithmeticIntervalSetsExt
   2354.3 msCairo_jll
   2710.3 msLibglvnd_jll
   1233.7 msIntervalArithmetic → IntervalArithmeticLinearAlgebraExt
   1224.1 msStaticArrays → StaticArraysStatisticsExt
   1340.6 msConstructionBase → ConstructionBaseStaticArraysExt
   1709.2 msAdapt → AdaptStaticArraysExt
   1857.2 msAccessors → StaticArraysExt
   7242.7 msStatsFuns
   5522.7 msStaticArrays → StaticArraysChainRulesCoreExt
   2297.4 msHarfBuzz_jll
    895.9 msStatsFuns → StatsFunsInverseFunctionsExt
  29814.0 msSIMD
   3736.0 mslibwebp_jll
   6757.7 msStructArrays → StructArraysStaticArraysExt
   2235.8 msLoess
   3330.4 msStatsFuns → StatsFunsChainRulesCoreExt
   2889.1 mslibass_jll
   5841.6 msStatsModels
  28240.7 msFFTW
  13764.1 msExactPredicates
  11483.0 msInterpolations
   3996.5 msFFMPEG_jll
   3952.5 msInterpolations → InterpolationsUnitfulExt
  35061.0 msPlotUtils
  16612.7 msDistributions
   4184.2 msDistributions → DistributionsTestExt
   5146.6 msGLM
   6760.2 msDistributions → DistributionsChainRulesCoreExt
  13077.9 msDelaunayTriangulation
  55359.5 msImageCore
   3409.6 msKernelDensity
   6558.6 msImageBase
   6953.5 msWebP
   8762.2 msJpegTurbo
  39842.6 msGeometryBasics
  10166.7 msSixel
   5146.4 msImageAxes
   2779.2 msPacking
   3732.6 msGeometryBasics → GeometryBasicsGeoInterfaceExt
  12983.4 msPNGFiles
   5072.0 msShaderAbstractions
   2443.0 msImageMetadata
  34396.3 msAutoma
   4416.1 msFreeTypeAbstraction
   2649.9 msNetpbm
   9375.2 msMakieCore
  11708.5 msGridLayoutBase
   7704.5 msMathTeXEngine
  52627.1 msTiffImages
 116565.3 msMakie
  40641.2 msAlgebraOfGraphics
   4586.6 msAlgebraOfGraphics → AlgebraOfGraphicsUnitfulExt
  116 dependencies successfully precompiled in 256 seconds. 179 already precompiled.
Precompiling packages...
   1673.8 msQuartoNotebookWorkerLaTeXStringsExt (serial)
  1 dependency successfully precompiled in 2 seconds
Precompiling packages...
    363.5 msInlineStrings → ParsersExt
  1 dependency successfully precompiled in 0 seconds. 9 already precompiled.
Precompiling packages...
   1039.0 msQuartoNotebookWorkerJSONExt (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    424.8 msJSON → JSONArrowExt
  1 dependency successfully precompiled in 0 seconds. 12 already precompiled.
Precompiling packages...
   5227.8 msQuartoNotebookWorkerMakieExt (serial)
  1 dependency successfully precompiled in 5 seconds
Precompiling packages...
    761.6 msPango_jll
   1956.8 msCairo
  37689.5 msCairoMakie
  3 dependencies successfully precompiled in 41 seconds. 272 already precompiled.
Precompiling packages...
   4861.4 msQuartoNotebookWorkerCairoMakieExt (serial)
  1 dependency successfully precompiled in 5 seconds
Precompiling packages...
   1493.4 msStringManipulation
  20561.2 msPrettyTables
  39541.3 msDataFrames
  3 dependencies successfully precompiled in 62 seconds. 33 already precompiled.
Precompiling packages...
   1156.9 msCategoricalArrays → CategoricalArraysJSONExt
   1199.2 msJSON3 → JSON3ArrowExt
   1445.9 msNLopt_jll
   1561.2 msChunkCodecLibZstd
   2008.9 msWeakRefStrings
    910.6 msIntervalArithmetic → IntervalArithmeticDiffRulesExt
   3032.9 msFastGaussQuadrature
   1932.7 msConda
   2720.2 msCategoricalArrays → CategoricalArraysArrowExt
   3182.2 msMixedModelsDatasets
   2236.2 msRegressionFormulae
   2573.9 msStandardizedPredictors
   1949.4 msNLopt
   6462.0 msDataFrameMacros
   8642.2 msStatic
   9050.2 msForwardDiff
   1583.8 msUnitful → ForwardDiffExt
   1671.6 msForwardDiff → ForwardDiffStaticArraysExt
   7436.9 msBoxCox
   1969.1 msBoxCox → BoxCoxStatsModelsExt
   4239.0 msIntervalArithmetic → IntervalArithmeticForwardDiffExt
   4758.5 msEffects
  19034.0 msRCall → RCallAxisArraysExt
   2531.7 msEffects → EffectsGLMExt
  23401.0 msArrayLayouts
   2445.5 msArrayLayouts → ArrayLayoutsSparseArraysExt
  26248.3 msCSV
  28251.4 msBoxCox → BoxCoxMakieExt
  25484.1 msBandedMatrices
  45406.2 msJLD2
   1688.1 msBandedMatrices → BandedMatricesSparseArraysExt
  10559.2 msBSplineKit
  52217.6 msMixedModels
   4623.6 msBoxCox → BoxCoxMixedModelsExt
   4843.8 msMixedModels → MixedModelsForwardDiffExt
   5824.5 msEffects → EffectsMixedModelsExt
   5504.8 msMixedModelsExtras
   5621.4 msMixedModelsSim
   5912.6 msMixedModelsSerialization
   5007.4 msMixedModelsSerialization → MixedModelsSerializationEffectsExt
  41601.8 msMixedModelsMakie
   7193.3 msSMLP2026
  42 dependencies successfully precompiled in 167 seconds. 349 already precompiled.
Precompiling packages...
   1045.1 msQuartoNotebookWorkerJSON3Ext (serial)
  1 dependency successfully precompiled in 1 seconds
tbl = dataset("fggk21")
Arrow.Table with 525126 rows, 7 columns, and schema:
 :Cohort  String
 :School  String
 :Child   String
 :Sex     String
 :age     Float64
 :Test    String
 :score   Float64
typeof(tbl)
Arrow.Table
df = DataFrame(tbl)
typeof(df)
DataFrame

1 Creating a summary data frame

The response to be plotted is the mean score by Test and Sex and age, rounded to the nearest 0.1 years.

The first task is to round the age to 1 digit after the decimal place, which can be done with select applied to a DataFrame. In some ways this is the most complicated expression in creating the plot so we will break it down. select is applied to DataFrame(dat), which is the conversion of the Arrow.Table, dat, to a DataFrame. This is necessary because an Arrow.Table is immutable but a DataFrame can be modified.

The arguments after the DataFrame describe how to modify the contents. The first : indicates that all the existing columns should be included. The other expression can be pairs (created with the => operator) of the form :col => function or of the form :col => function => :newname. (See the documentation of the DataFrames package for details.)

In this case the function is an anonymous function of the form round.(x, digits=1) where “dot-broadcasting” is used to apply to the entire column (see this documentation for details).

transform!(df, :age, :age => (x -> x .- 8.5) => :a1) # centered age (linear)
select!(groupby(df, :Test), :, :score => zscore => :zScore) # z-score
tlabels = [     # establish order and labels of tbl.Test
  "Run" => "Endurance",
  "Star_r" => "Coordination",
  "S20_r" => "Speed",
  "SLJ" => "PowerLOW",
  "BPT" => "PowerUP",
];

The next stage is a group-apply-combine operation to group the rows by Sex, Test and rnd_age then apply mean to the zScore and also apply length to zScore to record the number in each group.

df2 = combine(
  groupby(
    select(df, :, :age => ByRow(x -> round(x; digits=1)) => :age),
    [:Sex, :Test, :age],
  ),
  :zScore => mean => :zScore,
  :zScore => length => :n,
)
120×5 DataFrame
95 rows omitted
Row Sex Test age zScore n
String String Float64 Float64 Int64
1 male S20_r 8.0 -0.0265138 1223
2 male BPT 8.0 0.026973 1227
3 male SLJ 8.0 0.121609 1227
4 male Star_r 8.0 -0.0571726 1186
5 male Run 8.0 0.292695 1210
6 female S20_r 8.0 -0.35164 1411
7 female BPT 8.0 -0.610355 1417
8 female SLJ 8.0 -0.279872 1418
9 female Star_r 8.0 -0.268221 1381
10 female Run 8.0 -0.245573 1387
11 male S20_r 8.1 0.0608397 3042
12 male BPT 8.1 0.0955413 3069
13 male SLJ 8.1 0.123099 3069
109 male Star_r 9.0 0.254973 4049
110 male Run 9.0 0.258082 4034
111 female S20_r 9.1 -0.0286172 1154
112 female BPT 9.1 -0.0752301 1186
113 female SLJ 9.1 -0.094587 1174
114 female Star_r 9.1 0.00276252 1162
115 female Run 9.1 -0.235591 1150
116 male S20_r 9.1 0.325745 1303
117 male BPT 9.1 0.616416 1320
118 male SLJ 9.1 0.267577 1310
119 male Star_r 9.1 0.254342 1297
120 male Run 9.1 0.251045 1294

2 Creating the plot

The AlgebraOfGraphics package applies operators to the results of functions such as data (specify the data table to be used), mapping (designate the roles of columns), and visual (type of visual presentation).

let
  design = mapping(:age, :zScore; color=:Sex, col=:Test)
  lines = design * linear()
  means = design * visual(Scatter; markersize=5)
  draw(data(df2) * means + data(df) * lines)
end
  • TBD: Relabel factor levels (Boys, Girls; fitness components for Test)
  • TBD: Relevel factors; why not levels from Tables?
  • TBD: Set range (7.8 to 9.2 and tick marks (8, 8.5, 9) of axes.
  • TBD: Move legend in plot?
Fühner, T., Granacher, U., Golle, K., & Kliegl, R. (2021). Age and sex effects in physical fitness components of 108,295 third graders including 515 primary schools and 9 cohorts. Scientific Reports, 11(1). https://doi.org/10.1038/s41598-021-97000-4

This page was rendered from git revision f875caa using Quarto 1.8.26.

Back to top