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 Symbols, 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 
A different way to specify

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.