diff --git a/.gitignore b/.gitignore index 7ec82e1..87a97da 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,8 @@ site_libs/ **/*.quarto_ipynb +.claude/settings.local.json +data/emotikon.rds +data/fggk21_Child.arrow +data/fggk21_Score.arrow +data/fggk21.arrow diff --git a/Project.toml b/Project.toml index 8624414..b32821b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SMLP2026" uuid = "2d4e6da2-0493-4b51-b329-ae5cd97e516e" -authors = ["Phillip Alday ", "Douglas Bates ", "Reinhold Kliegl "] version = "1.0.0" +authors = ["Phillip Alday ", "Douglas Bates ", "Reinhold Kliegl "] [deps] AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67" @@ -34,6 +34,7 @@ MixedModelsSim = "d5ae56c5-23ca-4a1f-b505-9fc4796fc1fe" PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" RCall = "6f49c342-dc21-5d91-9882-a32aef131414" +RData = "df47a6cb-8c03-5eed-afd8-b6050d6c41da" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" RegressionFormulae = "545c379f-4ec2-4339-9aea-38f2fb6a8ba2" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" @@ -66,6 +67,7 @@ MixedModelsMakie = "0.4" MixedModelsSerialization = "0.2" MixedModelsSim = "0.2.7" RCall = "0.14" +RData = "1.1.0" Random = "1" RegressionFormulae = "0.1.3" StableRNGs = "1.0.3" diff --git a/_metadata.yml b/_metadata.yml index 880ed50..9fab5f9 100644 --- a/_metadata.yml +++ b/_metadata.yml @@ -1,4 +1,7 @@ -author: "Phillip Alday, Douglas Bates, and Reinhold Kliegl" +author: + - "Phillip Alday" + - "Douglas Bates" + - "Reinhold Kliegl" format: html: toc: true diff --git a/_quarto.yml b/_quarto.yml index 49e9a3a..788b6b5 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -18,31 +18,31 @@ project: execute-dir: project output-dir: _build/ render: - - about.qmd - - AoGPlots.qmd - - arrow.qmd - - bootstrap.qmd - - check_emotikon_transform.qmd - - contrasts_fggk21.qmd - - contrasts_kwdyz11.qmd - - fggk21.qmd - - glmm.qmd - index.qmd - - kb07.qmd - - kkl15.qmd - - kwdyz11.qmd - - largescaledesigned.qmd - - mrk17.qmd - - partial_within.qmd - - pkg.qmd - - profiling.qmd - - selection.qmd - - shrinkageplot.qmd - - singularity.qmd - - sleepstudy.qmd - - sleepstudy_speed.qmd - - transformation.qmd - - useful_packages.qmd + - getting-started/about.qmd + - getting-started/useful_packages.qmd + - getting-started/pkg.qmd + - getting-started/arrow.qmd + - lmm-intro/sleepstudy.qmd + - lmm-intro/singularity.qmd + - model-specification/partial_within.qmd + - model-specification/mrk17.qmd + - model-specification/largescaledesigned.qmd + - contrasts/transformation.qmd + - contrasts/contrasts_fggk21.qmd + - contrasts/contrasts_kwdyz11.qmd + - contrasts/sleepstudy_speed.qmd + - inference/bootstrap.qmd + - inference/profiling.qmd + - inference/kb07.qmd + - inference/power_simulation.qmd + - glmm/glmm.qmd + - visualization/shrinkageplot.qmd + - visualization/AoGPlots.qmd + - examples/emotikon/fggk21.qmd + - examples/emotikon/check_emotikon_transform.qmd + - examples/visual-attention/kwdyz11.qmd + - examples/visual-attention/kkl15.qmd # editor_options: # chunk_output_type: console @@ -84,33 +84,48 @@ website: search: true collapse-level: 1 contents: - - section: "Getting started with Julia" + - section: "Getting Started" contents: - - useful_packages.qmd - - pkg.qmd - - arrow.qmd - - section: "Worked examples" + - index.qmd + - getting-started/about.qmd + - getting-started/useful_packages.qmd + - getting-started/pkg.qmd + - getting-started/arrow.qmd + - section: "Introduction to Linear Mixed Models" contents: - - sleepstudy.qmd - - sleepstudy_speed.qmd - - kwdyz11.qmd - - kkl15.qmd - - largescaledesigned.qmd - - mrk17.qmd - - partial_within.qmd - - section: "Contrast coding and transformations" + - lmm-intro/sleepstudy.qmd + - lmm-intro/singularity.qmd + - section: "Model Specification and Random Effects" contents: - - contrasts_fggk21.qmd - - contrasts_kwdyz11.qmd - - transformation.qmd - - glmm.qmd - - section: "Bootstrap and profiling" + - model-specification/partial_within.qmd + - model-specification/mrk17.qmd + - model-specification/largescaledesigned.qmd + - section: "Contrast Coding and Transformations" contents: - - bootstrap.qmd - - profiling.qmd - - kb07.qmd - - section: "Visualizations and diagnostics" + - contrasts/transformation.qmd + - contrasts/contrasts_fggk21.qmd + - contrasts/contrasts_kwdyz11.qmd + - contrasts/sleepstudy_speed.qmd + - section: "Inference: Uncertainty Quantification" contents: - - singularity.qmd - - shrinkageplot.qmd - - AoGPlots.qmd + - inference/bootstrap.qmd + - inference/profiling.qmd + - inference/kb07.qmd + - inference/power_simulation.qmd + - section: "Generalized Linear Mixed Models" + contents: + - glmm/glmm.qmd + - section: "Visualization and Diagnostics" + contents: + - visualization/shrinkageplot.qmd + - visualization/AoGPlots.qmd + - section: "Extended Worked Examples" + contents: + - section: "The Emotikon Project" + contents: + - examples/emotikon/fggk21.qmd + - examples/emotikon/check_emotikon_transform.qmd + - section: "Visual Attention Experiments" + contents: + - examples/visual-attention/kwdyz11.qmd + - examples/visual-attention/kkl15.qmd diff --git a/about.qmd b/about.qmd deleted file mode 100644 index 07c5e7f..0000000 --- a/about.qmd +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "About" ---- - -About this site diff --git a/contrasts_fggk21.qmd b/contrasts/contrasts_fggk21.qmd similarity index 99% rename from contrasts_fggk21.qmd rename to contrasts/contrasts_fggk21.qmd index cfc5305..e435974 100644 --- a/contrasts_fggk21.qmd +++ b/contrasts/contrasts_fggk21.qmd @@ -50,11 +50,10 @@ using ProgressMeter using StableRNGs using Statistics using StatsBase - using MixedModels: likelihoodratiotest using SMLP2026: dataset -progress = isinteractive() +const progress = isinteractive() ``` ```{julia} diff --git a/contrasts_kwdyz11.qmd b/contrasts/contrasts_kwdyz11.qmd similarity index 100% rename from contrasts_kwdyz11.qmd rename to contrasts/contrasts_kwdyz11.qmd diff --git a/sleepstudy_speed.qmd b/contrasts/sleepstudy_speed.qmd similarity index 99% rename from sleepstudy_speed.qmd rename to contrasts/sleepstudy_speed.qmd index f20b85e..8024272 100644 --- a/sleepstudy_speed.qmd +++ b/contrasts/sleepstudy_speed.qmd @@ -1,5 +1,5 @@ --- -title: "The sleepstudy: Speed - for a change ..." +title: "The Belenky Sleep Study: Speed Metrics and Between-Group Comparisons" engine: julia julia: exeflags: ["--project", "--threads=auto"] diff --git a/transformation.qmd b/contrasts/transformation.qmd similarity index 100% rename from transformation.qmd rename to contrasts/transformation.qmd diff --git a/data/fggk21.arrow b/data/fggk21.arrow deleted file mode 100644 index 31c9c75..0000000 --- a/data/fggk21.arrow +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bedf7f75ad1362700800bc0de31c2a5776b2ced858289b64c5d5aa36409709c -size 3077850 diff --git a/data/fggk21_Child.arrow b/data/fggk21_Child.arrow deleted file mode 100644 index 3892b03..0000000 --- a/data/fggk21_Child.arrow +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b87440aeee48ac5940488e3a265e57e3f89f27ffff9aa2ca635758106aa62d86 -size 1774946 diff --git a/data/fggk21_Score.arrow b/data/fggk21_Score.arrow deleted file mode 100644 index 9a119f5..0000000 --- a/data/fggk21_Score.arrow +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43953e81d7ee44df90dd9b812702e2945a087113b667bc5fa8bff9395f1bb980 -size 2794058 diff --git a/check_emotikon_transform.qmd b/examples/emotikon/check_emotikon_transform.qmd similarity index 80% rename from check_emotikon_transform.qmd rename to examples/emotikon/check_emotikon_transform.qmd index e364813..b3866ba 100644 --- a/check_emotikon_transform.qmd +++ b/examples/emotikon/check_emotikon_transform.qmd @@ -1,5 +1,5 @@ --- -title: Transformed and original metrics in Emotikon +title: "Emotikon: Comparing Original and Transformed Metrics" engine: julia julia: exeflags: ["--project", "--threads=auto"] @@ -25,7 +25,7 @@ using DataFrameMacros using MixedModels using MixedModelsMakie using RCall -using SMLP2026: dataset +using SMLP2026: dataset, FITSDIR using Serialization using StatsBase ``` @@ -44,13 +44,6 @@ describe(dat) ### Data and figure with z-scores based on original metric -```{julia} -# dat_om = rcopy(R"readRDS('./data/fggk21_om.rds')"); #Don't know what the _om is -# @transform!(dat_om, :a1 = :age - 8.5); -# select!(groupby(dat_om, :Test), :, :score => zscore => :zScore); -# describe(dat_om) -``` - ## LMMs ### Contrasts @@ -80,7 +73,7 @@ f1 = @formula zScore ~ ```{julia} m1x = LinearMixedModel(f1, dat; contrasts) -restoreoptsum!(m1x, "./fits/fggk21_m1_optsum.json") +restoreoptsum!(m1x, joinpath(FITSDIR, "fggk21_m1_optsum.json")) ``` ```{julia} @@ -91,22 +84,22 @@ VarCorr(m1x) - Command for fitting LMM m1_om = fit(MixedModel, f1, dat_om, contrasts=contr) - Minimizing 10502 Time: 0 Time: 2:09:40 ( 0.74 s/it) - - Store with: julia> saveoptsum("./fits/fggk21_m1_om_optsum.json", m1_om) - - Only for short-term and when desperate: julia> serialize("./fits/m1_om.jls", m1_om); + - Store with: julia> saveoptsum(joinpath(FITSDIR, "fggk21_m1_om_optsum.json"), m1_om) + - Only for short-term and when desperate: julia> serialize(joinpath(FITSDIR, "m1_om.jls"), m1_om); #### ... restoreoptsum!() ```{julia} #| eval: false m1_om = LinearMixedModel(f1, dat; contrasts); -restoreoptsum!(m1_om, "./fits/fggk21_m1_om_optsum.json"); +restoreoptsum!(m1_om, joinpath(FITSDIR, "fggk21_m1_om_optsum.json")); ``` #### ... deserialize() ```{julia} #| eval: false -m1x_om = deserialize("./fits/m1_om.jls") +m1x_om = deserialize(joinpath(FITSDIR, "m1_om.jls")) ``` ```{julia} diff --git a/fggk21.qmd b/examples/emotikon/fggk21.qmd similarity index 97% rename from fggk21.qmd rename to examples/emotikon/fggk21.qmd index c5d09d5..ce37008 100644 --- a/fggk21.qmd +++ b/examples/emotikon/fggk21.qmd @@ -1,5 +1,5 @@ --- -title: Basics with Emotikon Project +title: "The Emotikon Project: Model Complexification and PCA" engine: julia julia: exeflags: ["--project", "--threads=auto"] @@ -34,9 +34,9 @@ using MixedModelsMakie: simplelinreg using Random using Statistics using StatsBase -using SMLP2026: dataset, fit_or_restore +using SMLP2026: dataset, fit_or_restore, DATADIR -const progress=false +const progress = isinteractive() ``` # Readme @@ -849,7 +849,7 @@ bic(m1) # objective(m1) + dof(m1)*log(nobs(m1)) ``` + julia> coeftable(m1) # StatsBase.coeftable: fixed-effects statiscs; default level=0.95 -+ julia> Arrow.write("./data/m_cpx_fe.arrow", DataFrame(coeftable(m1))); ++ julia> Arrow.write(joinpath(DATADIR, "m_cpx_fe.arrow"), DataFrame(coeftable(m1))); + julia> coef(m1) # StatsBase.coef - parts of the table + julia> fixef(m1) # MixedModels.fixef: not the same as coef() for rank-deficient case @@ -865,7 +865,7 @@ coeftable(m1) # StatsBase.coeftable: fixed-effects statiscs; default level=0.95 ``` ```{julia} -#Arrow.write("./data/m_cpx_fe.arrow", DataFrame(coeftable(m1))); +#Arrow.write(joinpath(DATADIR, "m_cpx_fe.arrow"), DataFrame(coeftable(m1))); ``` ```{julia} @@ -1009,6 +1009,12 @@ shrinkageplot!(Figure(; size=(800, 800)), m1, :Cohort) shrinkageplot!(Figure(; size=(800, 800)), m2, :Cohort) ``` +## See Also + +- [Mixed Models Tutorial: Contrast Coding](../../contrasts/contrasts_fggk21.qmd) — applies a full range of contrast schemes (effect, Helmert, hypothesis, PCA-based) to the same Emotikon dataset. +- [Emotikon: Comparing Original and Transformed Metrics](check_emotikon_transform.qmd) — investigates whether the age × Sex × Test interaction changes when tasks are analyzed in their original time metric rather than speed. +- [Creating Multi-Panel Plots](../../visualization/AoGPlots.qmd) — reproduces the publication figure from @Fuehner2021 using AlgebraOfGraphics. + ::: {#refs} ::: diff --git a/kkl15.qmd b/examples/visual-attention/kkl15.qmd similarity index 99% rename from kkl15.qmd rename to examples/visual-attention/kkl15.qmd index b269498..74d7da7 100644 --- a/kkl15.qmd +++ b/examples/visual-attention/kkl15.qmd @@ -1,5 +1,5 @@ --- -title: 'RePsychLing Kliegl, Kuschela, & Laubrock (2015)- Reduction of Model Complexity' +title: "Visual Attention: Replication and Model Reduction (Kliegl et al., 2015)" engine: julia julia: exeflags: ["--project", "--threads=auto"] diff --git a/kwdyz11.qmd b/examples/visual-attention/kwdyz11.qmd similarity index 96% rename from kwdyz11.qmd rename to examples/visual-attention/kwdyz11.qmd index beb6d23..b9bd050 100644 --- a/kwdyz11.qmd +++ b/examples/visual-attention/kwdyz11.qmd @@ -1,5 +1,5 @@ --- -title: "RePsychLing Kliegl et al. (2011)" +title: "Visual Attention Effects: Kliegl et al. (2011)" engine: julia julia: exeflags: ["--project", "--threads=auto"] @@ -398,6 +398,11 @@ First, the correlation between GM and the spatial effect is well defined. Second, as discussed throughout this script, the CP between spatial and attraction effect is close to the 1.0 border and clearly not well defined. Therefore, this CP will be replicated with a larger sample in script `kkl15.jl` [@Kliegl2015]. +# See Also + +- [Visual Attention: Replication and Model Reduction (Kliegl et al., 2015)](kkl15.qmd) — direct replication and follow-up of this experiment, with an additional focus on reducing model complexity. +- [Contrast Coding of Visual Attention Effects](../../contrasts/contrasts_kwdyz11.qmd) — applies _a priori_ contrast coding to this same dataset to test theoretically motivated hypotheses. + # References ::: {#refs} diff --git a/fits/partial_within_m0.json.zip b/fits/partial_within_m0.json.zip index 9d635fb..2e28dc0 100644 --- a/fits/partial_within_m0.json.zip +++ b/fits/partial_within_m0.json.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eef25195246d8cd5d2f740b5c7bbb20a1e63e966e798efaf4b44be7abd04a816 -size 4287 +oid sha256:a6508dda046435daee862a052b683f678147f37f63a7926d272cd943a05e0a0b +size 5080 diff --git a/fits/partial_within_m1.json.zip b/fits/partial_within_m1.json.zip index ab9c42e..fe66701 100644 --- a/fits/partial_within_m1.json.zip +++ b/fits/partial_within_m1.json.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62b350608be138a6e9a2633c2f0e5d98f229f98bd2b6d317277754240367f933 -size 4883 +oid sha256:d719c924a9f972ac1a743f7fb28ba9a1454401048d96bbad9c93d2931d76118d +size 6291 diff --git a/fits/power_simulation_fm1.json.zip b/fits/power_simulation_fm1.json.zip new file mode 100644 index 0000000..55a450f --- /dev/null +++ b/fits/power_simulation_fm1.json.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5094d389388e8a5a3cd3487fc7d85d4fa8d194082a4349297ee6b3732006d42f +size 1420 diff --git a/getting-started/about.qmd b/getting-started/about.qmd new file mode 100644 index 0000000..f0e739e --- /dev/null +++ b/getting-started/about.qmd @@ -0,0 +1,36 @@ +--- +title: "About" +--- + +These materials support the **Advanced Frequentist Methods** stream of the [Summer School on Statistical Methods for Linguistics and Psychology (SMLP)](https://vasishth.github.io/SMLP2026/), held at the University of Potsdam, 24--28 August 2026. +The stream focuses on fitting, evaluating, and interpreting linear and generalized linear mixed-effects models using the [Julia programming language](https://julialang.org) and the [MixedModels.jl](https://juliastats.org/MixedModels.jl/stable/) package. + +# Instructors + +## Phillip Alday + +Phillip Alday is an applied statistician and cognitive neuroscientist whose research spans mixed-effects modelling, EEG/M-EEG signal analysis, and the neuroscience of language processing. +Over the course of his career, he's moved from mathematics through linguistics to cognitive neuroscience, with a sustained interest in developing better statistical tools for behavioural and neural data. + +He is a core developer of [MixedModels.jl](https://github.com/JuliaStats/MixedModels.jl) — the Julia successor to the widely used R package `lme4` — and has authored or co-authored a family of related packages including [MixedModelsMakie.jl](https://github.com/palday/MixedModelsMakie.jl) (model visualisation), [Effects.jl](https://github.com/beacon-biosignals/Effects.jl) (effect estimation and plotting), and [MixedModelsExtras.jl](https://github.com/palday/MixedModelsExtras.jl). +Much of the course content has also served as a testing ground for new MixedModels.jl features and the broader Julia data-science ecosystem. + +Phillip has taught the Advanced Frequentist Track at SMLP since 2020, previously together with Douglas Bates (the original author of `lme4` and MixedModels.jl) and Reinhold Kliegl. +He has held positions at the University of Marburg, University of South Australia, and the Max Planck Institute for Psycholinguistics, and is currently at [Beacon Biosignals](https://beacon.bio). + +- Website: [phillipalday.com](https://phillipalday.com) +- GitHub: [github.com/palday](https://github.com/palday) + +## Reinhold Kliegl + +Reinhold Kliegl is Senior Professor of Psychology in the Division of Cognitive Psychology at the University of Potsdam. +He received his doctorate from the University of Colorado at Boulder and worked as a research scientist at the Max Planck Institute for Human Development, Berlin, before coming to Potsdam, where he has been a central figure in applying and teaching modern mixed-model methods in the cognitive sciences. + +His research examines eye movements during reading, parafoveal and foveal processing, attentional control, and cognitive ageing, with a recurring emphasis on simultaneous modelling of experimental effects and individual differences. +Several datasets collected in his lab — including the visual-attention experiments (kwdyz11, kkl15) and the large-scale Emotikon fitness study (fggk21) — appear throughout these course materials as worked examples. + +On the methodological side, Reinhold is a co-author of two widely cited papers on mixed-model practice: "Parsimonious mixed models" [@Bates2015] and "Balancing Type I error and power in linear mixed models" [@Matuschek2017], both of which inform the model-specification and model-selection material in this course. +He is a founding contributor to SMLP and has taught the Advanced Frequentist Track since the school's first year. + +- University page: [uni-potsdam.de](https://www.uni-potsdam.de/en/cognitive-psychology/staff/kliegl-reinhold) +- Google Scholar: [scholar.google.com](https://scholar.google.com/citations?user=nxD9IfcAAAAJ) diff --git a/arrow.qmd b/getting-started/arrow.qmd similarity index 91% rename from arrow.qmd rename to getting-started/arrow.qmd index d220323..33bfc15 100644 --- a/arrow.qmd +++ b/getting-started/arrow.qmd @@ -40,7 +40,8 @@ using DataFrameMacros using DataFrames using Downloads using KernelDensity -using RCall # access R from within Julia +using RData # load RDS files +using SMLP2026 using StatsBase CairoMakie.activate!(; type="svg") @@ -50,20 +51,25 @@ using AlgebraOfGraphics: density # Downloading and importing the RDS file This is similar to some of the code shown by Julius Krumbiegel on Monday. -In the data directory of the emotikon project on osf.io under Data, the url for the rds data file is found to be [https://osf.io/xawdb/]. Note that we want version 2 of this file. +In the data directory of the Emotikon project on osf.io under Data, the url for the rds data file is found to be [https://osf.io/xawdb/]. Note that we want version 2 of this file. ```{julia} -fn = Downloads.download("https://osf.io/xawdb/download?version=2"); +# downloads the file to a temporary directory +fn = joinpath(DATADIR, "emotikon.rds") +if !isfile(fn) + @info "Downloading Emotikon data" + Downloads.download("https://osf.io/xawdb/download?version=2", fn) +end ``` ```{julia} -dfrm = rcopy(reval("""readRDS("$(fn)")""")) +dfrm = load(fn) ``` Now write this file as a Arrow file and read it back in. ```{julia} -arrowfn = joinpath("data", "fggk21.arrow") +arrowfn = joinpath(DATADIR, "fggk21.arrow") Arrow.write(arrowfn, dfrm; compress=:lz4) tbl = Arrow.Table(arrowfn) ``` @@ -106,14 +112,14 @@ length(unique(Child.Child)) # should be 108295 ```{julia} filesize( - Arrow.write("./data/fggk21_Child.arrow", Child; compress=:lz4) + Arrow.write(joinpath(DATADIR, "fggk21_Child.arrow"), Child; compress=:lz4) ) ``` ```{julia} filesize( Arrow.write( - "./data/fggk21_Score.arrow", + joinpath(DATADIR, "fggk21_Score.arrow"), select(df, :Child, :Test, :score); compress=:lz4, ), @@ -127,7 +133,7 @@ A careful examination of the file sizes versus that of `./data/fggk21.arrow` wil Now read the Arrow tables in and reassemble the original table. ```{julia} -Score = DataFrame(Arrow.Table("./data/fggk21_Score.arrow")) +Score = DataFrame(Arrow.Table(joinpath(DATADIR, "fggk21_Score.arrow"))) ``` At this point we can create the z-score column by standardizing the scores for each `Test`. @@ -138,7 +144,7 @@ The code to do this follows Julius's presentation on Monday. ``` ```{julia} -Child = DataFrame(Arrow.Table("./data/fggk21_Child.arrow")) +Child = DataFrame(Arrow.Table(joinpath(DATADIR, "fggk21_Child.arrow"))) ``` ```{julia} @@ -194,13 +200,13 @@ There are Arrow implementations for R (the `arrow` package) and for Python (`pya ```{python} #| eval: false import pyarrow.feather: read_table -read_table("./data/fggk21.arrow") +read_table("fggk21.arrow") ``` ```{r} #| eval: false library(arrow) -read_ipc_file("./data/fggk21.arrow") +read_ipc_file("fggk21.arrow") ``` diff --git a/pkg.qmd b/getting-started/pkg.qmd similarity index 100% rename from pkg.qmd rename to getting-started/pkg.qmd diff --git a/useful_packages.qmd b/getting-started/useful_packages.qmd similarity index 100% rename from useful_packages.qmd rename to getting-started/useful_packages.qmd diff --git a/glmm.qmd b/glmm/glmm.qmd similarity index 99% rename from glmm.qmd rename to glmm/glmm.qmd index ecb8467..d05b776 100644 --- a/glmm.qmd +++ b/glmm/glmm.qmd @@ -21,7 +21,7 @@ using MixedModels using MixedModelsMakie using SMLP2026: dataset, fit_or_restore -const progress=false +const progress = isinteractive() ``` - A GLMM (Generalized Linear Mixed Model) is used instead of a LMM (Linear Mixed Model) when the response is binary or, perhaps, a count with a low expected count. diff --git a/index.qmd b/index.qmd index cdcaffb..c4346ec 100644 --- a/index.qmd +++ b/index.qmd @@ -1,7 +1,9 @@ --- title: "Tenth Summer School on Statistical Methods for Linguistics and Psychology" subtitle: "Advanced methods in frequentist statistics with Julia" -author: "Phillip Alday and Reinhold Kliegl" +author: + - "Phillip Alday" + - "Reinhold Kliegl" engine: julia julia: exeflags: ["--project", "--threads=auto"] @@ -44,6 +46,38 @@ There are resources available online for new Julia users and several specialize The web site and other documents for this course are rendered using a `knitr`-like system called Quarto. You can download the version appropriate for your setup from here: [quarto](https://quarto.org/docs/get-started/) +### Command line + +From the root of the cloned repository, two commands cover most workflows: + +```sh +quarto preview +``` + +`quarto preview` renders the site and opens it in a browser, then watches for changes and re-renders automatically. It is the most convenient way to work on materials locally. + +```sh +quarto render +``` + +`quarto render` renders the entire site once to the `_build/` output directory. Use this to do a clean full build, e.g. before checking the final output. + +Both commands accept a single file as an argument to work on just that document: + +```sh +quarto preview lmm-intro/sleepstudy.qmd +quarto render lmm-intro/sleepstudy.qmd +``` + +### VS Code Quarto extension + +Install the [Quarto extension for VS Code](https://marketplace.visualstudio.com/items?itemName=quarto.quarto) (`quarto.quarto`) from the Extensions panel. It provides: + +- **Preview** (`Ctrl+Shift+K` / `Cmd+Shift+K`): renders the current `.qmd` file and shows a live preview in a side panel, updating on save. +- **Render** (`Ctrl+Shift+P` → *Quarto: Render*): renders the current file to its output format. +- **Render Project** (`Ctrl+Shift+P` → *Quarto: Render Project*): equivalent to `quarto render` from the command line. +- Syntax highlighting, code completion, and cell execution for Julia code chunks directly in the editor. + ## Integrated Development Environment (IDE) An integrated development environment (IDE) makes editing and running Julia and Quarto source files easier. diff --git a/bootstrap.qmd b/inference/bootstrap.qmd similarity index 96% rename from bootstrap.qmd rename to inference/bootstrap.qmd index fca1686..f5c426f 100644 --- a/bootstrap.qmd +++ b/inference/bootstrap.qmd @@ -41,7 +41,7 @@ using Random using SMLP2026: dataset, fit_or_restore using AlgebraOfGraphics: AlgebraOfGraphics as AoG -const progress=false +const progress = isinteractive() ``` Note that the precise stream of random numbers generated for a given seed can change between Julia versions. @@ -287,6 +287,11 @@ This operation is encapsulated in a method for the `issingular` function that wo count(issingular(sampsleep)) ``` +# See Also + +- [Confidence Intervals from Profiled Objective](profiling.qmd) — an alternative approach to uncertainty quantification via likelihood profiling rather than simulation. +- [Bootstrapping a Fitted Model](kb07.qmd) — a practical worked example applying parametric bootstrap to the Kronmueller & Barr (2007) dataset. + # References ::: {#refs} diff --git a/kb07.qmd b/inference/kb07.qmd similarity index 91% rename from kb07.qmd rename to inference/kb07.qmd index 74abf72..a0aa78f 100644 --- a/kb07.qmd +++ b/inference/kb07.qmd @@ -17,7 +17,7 @@ using MixedModels using Random using SMLP2026: dataset -const progress=false +const progress = isinteractive() ``` # Data set and model @@ -146,6 +146,11 @@ draw( ) ``` +# See Also + +- [More on Shrinkage Plots](../visualization/shrinkageplot.qmd) — uses the same kb07 dataset to visualize how the model shrinks random effects toward zero. +- [Parametric Bootstrap for Mixed-Effects Models](bootstrap.qmd) — conceptual introduction to the bootstrap method applied here. + # References ::: {#refs} diff --git a/power_simulation.qmd b/inference/power_simulation.qmd similarity index 99% rename from power_simulation.qmd rename to inference/power_simulation.qmd index a45b4f0..bf2dfc2 100644 --- a/power_simulation.qmd +++ b/inference/power_simulation.qmd @@ -4,10 +4,6 @@ subtitle: "Or how I learned how to stop worrying and love the bootstrap" engine: julia julia: exeflags: ["--project", "--threads=auto"] -format: - html: - embed-resources: true - css: styles.css --- ```{julia} @@ -23,7 +19,7 @@ using StatsBase using SMLP2026: dataset, fit_or_restore using Random -const progress=false +const progress = isinteractive() ``` # The Parametric Bootstrap diff --git a/profiling.qmd b/inference/profiling.qmd similarity index 99% rename from profiling.qmd rename to inference/profiling.qmd index cbaddc3..9ab9eed 100644 --- a/profiling.qmd +++ b/inference/profiling.qmd @@ -36,7 +36,7 @@ using MixedModelsMakie using Random using SMLP2026: dataset, fit_or_restore -const progress=false +const progress = isinteractive() ``` Load the data and define the contrasts so that the coefficients for each of the experimental variables, `load`, `spkr` and `prec`, are positive. diff --git a/singularity.qmd b/lmm-intro/singularity.qmd similarity index 100% rename from singularity.qmd rename to lmm-intro/singularity.qmd diff --git a/sleepstudy.qmd b/lmm-intro/sleepstudy.qmd similarity index 97% rename from sleepstudy.qmd rename to lmm-intro/sleepstudy.qmd index 8a5b7ea..dd388dd 100644 --- a/sleepstudy.qmd +++ b/lmm-intro/sleepstudy.qmd @@ -368,6 +368,11 @@ let end ``` +# See Also + +- [The Belenky Sleep Study: Speed Metrics and Between-Group Comparisons](../contrasts/sleepstudy_speed.qmd) — re-analyzes a related sleep-deprivation dataset using a speed (1/RT) transformation of the response. +- [Convergence, Singularity, and All That](singularity.qmd) — uses the sleepstudy data to illustrate what happens when a model's random-effects covariance structure is degenerate. + # References ::: {#refs} diff --git a/largescaledesigned.qmd b/model-specification/largescaledesigned.qmd similarity index 98% rename from largescaledesigned.qmd rename to model-specification/largescaledesigned.qmd index 8d80f1f..df2d7e8 100644 --- a/largescaledesigned.qmd +++ b/model-specification/largescaledesigned.qmd @@ -1,4 +1,5 @@ --- +title: "Large-Scale Designed Experiments: The English Lexicon Project" engine: julia julia: exeflags: ["--project", "--threads=auto"] @@ -19,11 +20,16 @@ using DataFrames using Effects using MixedModels using MixedModelsMakie -using SMLP2026: dataset, fit_or_restore +using SMLP2026: dataset, FITSDIR using StandardizedPredictors using StatsBase ``` +```{julia} +#| echo: false +using SMLP2026: fit_or_restore +``` + The English Lexicon Project [@Balota_2007] was a large-scale multicenter study to examine properties of English words. It incorporated both a lexical decision task and a word recognition task. Different groups of subjects participated in the different tasks. @@ -546,8 +552,8 @@ The amount of time to fit more complex models will be much greater so we may wan We can split the tasks of fitting and analyzing a model between computers by saving the optimization summary after the model fit and later creating the `MixedModel` object followed by restoring the `optsum` object. ```{julia} -if !isfile("./fits/elm01.json") - saveoptsum("./fits/elm01.json", elm01); +if !isfile(joinpath(FITSDIR, "elm01.json")) + saveoptsum(joinpath(FITSDIR, "elm01.json"), elm01); end ``` @@ -559,7 +565,7 @@ elm01a = restoreoptsum!( ) MixedModel(form, pruned; contrasts) end, - "./fits/elm01.json", + joinpath(FITSDIR, "elm01.json"), ) ``` diff --git a/mrk17.qmd b/model-specification/mrk17.qmd similarity index 99% rename from mrk17.qmd rename to model-specification/mrk17.qmd index b38a799..8ddfd94 100644 --- a/mrk17.qmd +++ b/model-specification/mrk17.qmd @@ -1,5 +1,5 @@ --- -title: "RePsychLing Masson, Rabe, & Kliegl, 2017) with Julia: Specification and selection" +title: "Model Specification and Selection: Masson, Rabe & Kliegl (2017)" engine: julia julia: exeflags: ["--project", "--threads=auto"] diff --git a/partial_within.qmd b/model-specification/partial_within.qmd similarity index 95% rename from partial_within.qmd rename to model-specification/partial_within.qmd index 20113a3..0186fa2 100644 --- a/partial_within.qmd +++ b/model-specification/partial_within.qmd @@ -18,18 +18,24 @@ using MixedModelsMakie using MixedModelsSim using ProgressMeter using Random -using SMLP2026: fit_or_restore +using StableRNGs -const progress=false +const progress = isinteractive() ``` +```{julia} +#| include: false +using SMLP2026: fit_or_restore +``` + + ```{julia} #| code-fold: true n_subj = 40 n_item = 3 # things are expressed as "between", so "within subjects" is "between items" item_btwn = Dict(:frequency => ["high", "medium", "low"]) -design = simdat_crossed(MersenneTwister(42), n_subj, n_item; +design = simdat_crossed(StableRNG(42), n_subj, n_item; item_btwn = item_btwn) design = DataFrame(design) ``` diff --git a/selection.qmd b/selection.qmd deleted file mode 100644 index df3e6e0..0000000 --- a/selection.qmd +++ /dev/null @@ -1,53 +0,0 @@ ---- -engine: julia -julia: - exeflags: ["--project", "--threads=auto"] ---- - -```{julia} -#| code-fold: true -using Arrow -using CairoMakie -using DataFrames -``` - -```{julia} -tbl = Arrow.Table("./data/fggk21.arrow") -``` - -```{julia} -df = DataFrame(tbl) -describe(df) -``` - -## Raw score density - -```{julia} -#| eval: false -let - fdensity = Figure(; size=(1000, 500)) - axs = Axis(fdensity[1, 1]) - tdf = filter(:Test => ==(test), df) - colors = Makie.cgrad(:PuOr_4, 2; categorical=true, alpha=0.6) - if by_sex - density!( - axs, - filter(:Sex => ==("female"), tdf).score; - color=colors[1], - label="Girls", - ) - density!( - axs, - filter(:Sex => ==("male"), tdf).score; - color=colors[2], - label="Boys", - ) - axislegend(axs; position=:lt) - else - density!(axs, tdf.score) - end - fdensity -end -``` - -*This page was rendered from git revision {{< git-rev short=true >}} using Quarto {{< version >}}.* diff --git a/src/SMLP2026.jl b/src/SMLP2026.jl index c8a2c92..e5fedcc 100644 --- a/src/SMLP2026.jl +++ b/src/SMLP2026.jl @@ -18,6 +18,10 @@ using ZipFile const CACHE = Ref("") const MMDS = String[] +const DATADIR = joinpath(dirname(@__DIR__), "data") +const FITSDIR = joinpath(dirname(@__DIR__), "fits") +export DATADIR, FITSDIR + function __init__() CACHE[] = @get_scratch!("data") mkpath(CACHE[]) @@ -48,7 +52,7 @@ export GENRES, function _normalize_cache_path(path) dir, file = splitdir(path) if isempty(dir) - path = joinpath(dirname(@__DIR__), "fits", file) + path = joinpath(FITSDIR, file) end return path diff --git a/AoGPlots.qmd b/visualization/AoGPlots.qmd similarity index 100% rename from AoGPlots.qmd rename to visualization/AoGPlots.qmd diff --git a/shrinkageplot.qmd b/visualization/shrinkageplot.qmd similarity index 99% rename from shrinkageplot.qmd rename to visualization/shrinkageplot.qmd index 6edfe6e..0a45a1f 100644 --- a/shrinkageplot.qmd +++ b/visualization/shrinkageplot.qmd @@ -20,11 +20,16 @@ using MixedModels using MixedModelsMakie using Random using ProgressMeter -using SMLP2026: fit_or_restore -const progress = false +const progress = isinteractive() +``` + +```{julia} +#| echo: false +using SMLP2026: fit_or_restore ``` + Load the kb07 data set (don't tell Reinhold that I used these data). ```{julia}