Multigroup models
As an example, we will fit the model from the lavaan
tutorial with loadings constrained to equality across groups.
We first load the example data. We have to make sure that the column indicating the group (here called school
) is a vector of Symbol
s, not strings - so we convert it.
dat = example_data("holzinger_swineford")
dat.school = ifelse.(dat.school .== "Pasteur", :Pasteur, :Grant_White)
We then specify our model via the graph interface:
latent_vars = [:visual, :textual, :speed]
observed_vars = Symbol.(:x, 1:9)
graph = @StenoGraph begin
# measurement model
visual → fixed(1, 1)*x1 + label(:λ₂, :λ₂)*x2 + label(:λ₃, :λ₃)*x3
textual → fixed(1, 1)*x4 + label(:λ₅, :λ₅)*x5 + label(:λ₆, :λ₆)*x6
speed → fixed(1, 1)*x7 + label(:λ₈, :λ₈)*x8 + label(:λ₉, :λ₉)*x9
# variances and covariances
_(observed_vars) ↔ _(observed_vars)
_(latent_vars) ⇔ _(latent_vars)
end
You can pass multiple arguments to fix()
and label()
for each group. Parameters with the same label (within and across groups) are constrained to be equal. To fix a parameter in one group, but estimate it freely in the other, you may write fix(NaN, 4.3)
.
You can then use the resulting graph to specify an EnsembleParameterTable
groups = [:Pasteur, :Grant_White]
partable = EnsembleParameterTable(
graph,
observed_vars = observed_vars,
latent_vars = latent_vars,
groups = groups)
EnsembleParameterTable with groups: |Grant_White||Pasteur|
Grant_White:
--------- ---------- --------- ------- ------------- --------- ---------- -----
from relation to free value_fixed start estimate ⋯
Symbol Symbol Symbol Bool Float64 Float64 Float64 ⋯
--------- ---------- --------- ------- ------------- --------- ---------- -----
visual → x1 false 1.0 ⋯
visual → x2 true ⋯
visual → x3 true ⋯
textual → x4 false 1.0 ⋯
textual → x5 true ⋯
textual → x6 true ⋯
speed → x7 false 1.0 ⋯
speed → x8 true ⋯
speed → x9 true ⋯
x1 ↔ x1 true g ⋯
x2 ↔ x2 true g ⋯
x3 ↔ x3 true g ⋯
x4 ↔ x4 true g ⋯
x5 ↔ x5 true g ⋯
x6 ↔ x6 true g ⋯
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱
--------- ---------- --------- ------- ------------- --------- ---------- -----
1 column and 9 rows omitted
Latent Variables: [:visual, :textual, :speed]
Observed Variables: [:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9]
Pasteur:
--------- ---------- --------- ------- ------------- --------- ---------- -----
from relation to free value_fixed start estimate ⋯
Symbol Symbol Symbol Bool Float64 Float64 Float64 ⋯
--------- ---------- --------- ------- ------------- --------- ---------- -----
visual → x1 false 1.0 ⋯
visual → x2 true ⋯
visual → x3 true ⋯
textual → x4 false 1.0 ⋯
textual → x5 true ⋯
textual → x6 true ⋯
speed → x7 false 1.0 ⋯
speed → x8 true ⋯
speed → x9 true ⋯
x1 ↔ x1 true g ⋯
x2 ↔ x2 true g ⋯
x3 ↔ x3 true g ⋯
x4 ↔ x4 true g ⋯
x5 ↔ x5 true g ⋯
x6 ↔ x6 true g ⋯
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱
--------- ---------- --------- ------- ------------- --------- ---------- -----
1 column and 9 rows omitted
Latent Variables: [:visual, :textual, :speed]
Observed Variables: [:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9]
The parameter table can be used to create a SemEnsemble
model:
model_ml_multigroup = SemEnsemble(
specification = partable,
data = dat,
column = :school,
groups = groups)
SemEnsemble
- Number of Models: 2
- Weights: [0.52, 0.48]
Models:
===============================================
---------------------- 1 ----------------------
Structural Equation Model
- Loss Functions
SemML
- Fields
observed: SemObservedData
implied: RAM
---------------------- 2 ----------------------
Structural Equation Model
- Loss Functions
SemML
- Fields
observed: SemObservedData
implied: RAM
Instead of choosing the workflow "Graph -> EnsembleParameterTable -> model", you may also directly specify RAMMatrices for each group (for an example see this test).
We now fit the model and inspect the parameter estimates:
fit = sem_fit(model_ml_multigroup)
update_estimate!(partable, fit)
details(partable)
--------------------------------- Variables ---------------------------------
Latent variables: visual textual speed
Observed variables: x1 x2 x3 x4 x5 x6 x7 x8 x9
Group: Grant_White
---------------------------- Parameter Estimates -----------------------------
Loadings:
visual
to estimate param value_fixed start free from relation
x1 0.0 const 1.0 0.0 visual →
x2 0.6 λ₂ 1.0 visual →
x3 0.78 λ₃ 1.0 visual →
textual
to estimate param value_fixed start free from relation
x4 0.0 const 1.0 0.0 textual →
x5 1.08 λ₅ 1.0 textual →
x6 0.91 λ₆ 1.0 textual →
speed
to estimate param value_fixed start free from relation
x7 0.0 const 1.0 0.0 speed →
x8 1.2 λ₈ 1.0 speed →
x9 1.04 λ₉ 1.0 speed →
Directed Effects:
from to estimate param value_fixed start free
Variances:
from to estimate param value_fixed start free
x1 ↔ x1 0.65 gGrant_White_1 1.0
x2 ↔ x2 0.94 gGrant_White_2 1.0
x3 ↔ x3 0.61 gGrant_White_3 1.0
x4 ↔ x4 0.33 gGrant_White_4 1.0
x5 ↔ x5 0.39 gGrant_White_5 1.0
x6 ↔ x6 0.44 gGrant_White_6 1.0
x7 ↔ x7 0.6 gGrant_White_7 1.0
x8 ↔ x8 0.41 gGrant_White_8 1.0
x9 ↔ x9 0.54 gGrant_White_9 1.0
visual ↔ visual 0.73 gGrant_White_10 1.0
textual ↔ textual 0.91 gGrant_White_13 1.0
speed ↔ speed 0.48 gGrant_White_15 1.0
Covariances:
from to estimate param value_fixed start free
textual ↔ visual 0.44 gGrant_White_11 1.0
speed ↔ visual 0.32 gGrant_White_12 1.0
speed ↔ textual 0.23 gGrant_White_14 1.0
Group: Pasteur
---------------------------- Parameter Estimates -----------------------------
Loadings:
visual
to estimate param value_fixed start free from relation
x1 0.0 const 1.0 0.0 visual →
x2 0.6 λ₂ 1.0 visual →
x3 0.78 λ₃ 1.0 visual →
textual
to estimate param value_fixed start free from relation
x4 0.0 const 1.0 0.0 textual →
x5 1.08 λ₅ 1.0 textual →
x6 0.91 λ₆ 1.0 textual →
speed
to estimate param value_fixed start free from relation
x7 0.0 const 1.0 0.0 speed →
x8 1.2 λ₈ 1.0 speed →
x9 1.04 λ₉ 1.0 speed →
Directed Effects:
from to estimate param value_fixed start free
Variances:
from to estimate param value_fixed start free
x1 ↔ x1 0.55 gPasteur_1 1.0
x2 ↔ x2 1.27 gPasteur_2 1.0
x3 ↔ x3 0.89 gPasteur_3 1.0
x4 ↔ x4 0.44 gPasteur_4 1.0
x5 ↔ x5 0.51 gPasteur_5 1.0
x6 ↔ x6 0.27 gPasteur_6 1.0
x7 ↔ x7 0.85 gPasteur_7 1.0
x8 ↔ x8 0.52 gPasteur_8 1.0
x9 ↔ x9 0.66 gPasteur_9 1.0
visual ↔ visual 0.81 gPasteur_10 1.0
textual ↔ textual 0.92 gPasteur_13 1.0
speed ↔ speed 0.31 gPasteur_15 1.0
Covariances:
from to estimate param value_fixed start free
textual ↔ visual 0.42 gPasteur_11 1.0
speed ↔ visual 0.17 gPasteur_12 1.0
speed ↔ textual 0.18 gPasteur_14 1.0
Other things you can query about your fitted model (fit measures, standard errors, etc.) are described in the section Model inspection and work the same way for multigroup models.