Partially-within subjects designs

Published

2026-04-10

Begin by loading the packages to be used.

Code
using AlgebraOfGraphics
using CairoMakie
using DataFrames
using MixedModels
using MixedModelsMakie
using MixedModelsSim
using ProgressMeter
using Random
using StableRNGs

const progress = isinteractive()
Code
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(StableRNG(42), n_subj, n_item;
                        item_btwn = item_btwn)
design = DataFrame(design)
120×4 DataFrame
95 rows omitted
Row subj item frequency dv
String String String Float64
1 S01 I1 high -0.670252
2 S02 I1 high 0.447122
3 S03 I1 high 1.37363
4 S04 I1 high 1.30954
5 S05 I1 high 0.12607
6 S06 I1 high 0.683948
7 S07 I1 high -1.0192
8 S08 I1 high -0.793513
9 S09 I1 high 1.77472
10 S10 I1 high 1.29735
11 S11 I1 high -1.64385
12 S12 I1 high 0.794439
13 S13 I1 high -1.30967
109 S29 I3 low 1.23469
110 S30 I3 low -1.12075
111 S31 I3 low -1.39684
112 S32 I3 low -0.658154
113 S33 I3 low -1.51584
114 S34 I3 low -0.711622
115 S35 I3 low -0.411443
116 S36 I3 low -1.25361
117 S37 I3 low 2.08165
118 S38 I3 low -0.530435
119 S39 I3 low -1.63993
120 S40 I3 low -0.768712
Code
unique!(select(design, :item, :frequency))
3×2 DataFrame
Row item frequency
String String
1 I1 high
2 I2 medium
3 I3 low
Code
m0 = let contrasts, form
    contrasts = Dict(:frequency => HelmertCoding(base="high"))
    form = @formula(dv ~ 1 + frequency +
                    (1 + frequency | subj))
    fit(MixedModel, form, design; contrasts, progress)
end
Code
corrmat = [ 1    0.1 -0.2
            0.1  1    0.1
           -0.2  0.1  1 ]
re_subj = create_re(1.2, 1.5, 1.5; corrmat)
3×3 LinearAlgebra.LowerTriangular{Float64, Matrix{Float64}}:
  1.2    ⋅         ⋅ 
  0.15  1.49248    ⋅ 
 -0.3   0.180907  1.45852
Code
θ = createθ(m0; subj=re_subj)
6-element Vector{Float64}:
  1.2
  0.15000000000000002
 -0.30000000000000004
  1.49248115565993
  0.18090680674665818
  1.4585173044131932
Code
σ = 1;
β = [1.0, -3, -2];
Code
fit!(simulate!(m0; θ, β, σ))
Est. SE z p σ_subj
(Intercept) 0.8952 0.1665 5.38 <1e-07 0.4164
frequency: low -2.8729 0.2885 -9.96 <1e-22 1.3882
frequency: medium -2.1634 0.2839 -7.62 <1e-13 1.6600
Residual 1.6747
Code
shrinkageplot(m0)
Code
caterpillar(m0; orderby=nothing, vline_at_zero=true)
Code
design[!, :dv] .= response(m0)
120-element Vector{Float64}:
  4.870223383535779
  5.378703641645242
  1.4318457937610516
  7.321822220205691
  5.719183420980856
  6.131033643992091
  4.8779823742704815
  1.5104205514921665
  7.1549055451106645
  4.943687153958377
  ⋮
  1.3493426312325476
 -0.8868909915180834
  0.3148588574207105
  3.2465536632714738
  1.1841538083169167
 -4.579969499686759
  1.4889884818635712
  3.5909977349003714
 -2.0702695592556264
Code
design_partial = filter(design) do row
    subj = parse(Int, row.subj[2:end])
    item = parse(Int, row.item[2:end])
    # for even-numbered subjects, we keep all conditions
    # for odd-numbered subjects, we keep only the two "odd" items,
    # i.e. the first and last conditions
    return iseven(subj) || isodd(item)
end
sort!(unique!(select(design_partial, :subj, :frequency)), :subj)
100×2 DataFrame
75 rows omitted
Row subj frequency
String String
1 S01 high
2 S01 low
3 S02 high
4 S02 medium
5 S02 low
6 S03 high
7 S03 low
8 S04 high
9 S04 medium
10 S04 low
11 S05 high
12 S05 low
13 S06 high
89 S36 medium
90 S36 low
91 S37 high
92 S37 low
93 S38 high
94 S38 medium
95 S38 low
96 S39 high
97 S39 low
98 S40 high
99 S40 medium
100 S40 low
Code
m1 = let contrasts, form
    contrasts = Dict(:frequency => HelmertCoding(base="high"))
    form = @formula(dv ~ 1 + frequency +
                    (1 + frequency | subj))
    fit(MixedModel, form, design_partial; contrasts, progress)
end
Code
shrinkageplot(m1)
Code
caterpillar(m1; orderby=nothing, vline_at_zero=true)

This page was rendered from git revision 0e399c4 using Quarto 1.9.36.

Back to top