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 = Symbol.(replace.(dat.school, "-" => "_"))

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                                      gGr ⋯
       x2          ↔       x2    true                                      gGr ⋯
       x3          ↔       x3    true                                      gGr ⋯
       x4          ↔       x4    true                                      gGr ⋯
       x5          ↔       x5    true                                      gGr ⋯
       x6          ↔       x6    true                                      gGr ⋯
        ⋮          ⋮        ⋮       ⋮             ⋮         ⋮          ⋮       ⋱
 --------- ---------- -------- ------- ------------- --------- ---------- ------
                                                     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                                      gPa ⋯
       x2          ↔       x2    true                                      gPa ⋯
       x3          ↔       x3    true                                      gPa ⋯
       x4          ↔       x4    true                                      gPa ⋯
       x5          ↔       x5    true                                      gPa ⋯
       x6          ↔       x6    true                                      gPa ⋯
        ⋮          ⋮        ⋮       ⋮             ⋮         ⋮          ⋮       ⋱
 --------- ---------- -------- ------- ------------- --------- ---------- ------
                                                     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 multigroup Sem model:

model_ml_multigroup = Sem(
    specification = partable,
    data = dat,
    semterm_column = :school,
    groups = groups)
Structural Equation Model- Loss Functions 
  > SemML
    - id:          Grant_White 
    - observed:    SemObservedData 
    - implied:     RAM 
    - weight:      0.482 
  > SemML
    - id:          Pasteur 
    - observed:    SemObservedData 
    - implied:     RAM 
    - weight:      0.518 
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:

sem_fit = fit(model_ml_multigroup)
update_estimate!(partable, sem_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   value_fixed   start   free   from     label   relation

  x1   0.0        1.0                   0.0    visual   const   →
  x2   0.6                              1.0    visual   λ₂      →
  x3   0.78                             1.0    visual   λ₃      →


textual


  to   estimate   value_fixed   start   free   from      label   relation

  x4   0.0        1.0                   0.0    textual   const   →
  x5   1.08                             1.0    textual   λ₅      →
  x6   0.91                             1.0    textual   λ₆      →


speed


  to   estimate   value_fixed   start   free   from    label   relation

  x7   0.0        1.0                   0.0    speed   const   →
  x8   1.2                              1.0    speed   λ₈      →
  x9   1.04                             1.0    speed   λ₉      →


Directed Effects:


  from      to   estimate   value_fixed   start   free   label


Variances:


  from          to        estimate   value_fixed   start   free   label        ⋯

  x1        ↔   x1        0.65                             1.0    gGrant_White ⋯
  x2        ↔   x2        0.94                             1.0    gGrant_White ⋯
  x3        ↔   x3        0.61                             1.0    gGrant_White ⋯
  x4        ↔   x4        0.33                             1.0    gGrant_White ⋯
  x5        ↔   x5        0.39                             1.0    gGrant_White ⋯
  x6        ↔   x6        0.44                             1.0    gGrant_White ⋯
  x7        ↔   x7        0.6                              1.0    gGrant_White ⋯
  x8        ↔   x8        0.41                             1.0    gGrant_White ⋯
  x9        ↔   x9        0.54                             1.0    gGrant_White ⋯
  visual    ↔   visual    0.73                             1.0    gGrant_White ⋯
  textual   ↔   textual   0.91                             1.0    gGrant_White ⋯
  speed     ↔   speed     0.48                             1.0    gGrant_White ⋯

                                                                1 column omitted

Covariances:


  from          to        estimate   value_fixed   start   free   label        ⋯

  textual   ↔   visual    0.44                             1.0    gGrant_White ⋯
  speed     ↔   visual    0.32                             1.0    gGrant_White ⋯
  speed     ↔   textual   0.23                             1.0    gGrant_White ⋯

                                                                1 column omitted


 Group: Pasteur

---------------------------- Parameter Estimates -----------------------------

Loadings:

visual


  to   estimate   value_fixed   start   free   from     label   relation

  x1   0.0        1.0                   0.0    visual   const   →
  x2   0.6                              1.0    visual   λ₂      →
  x3   0.78                             1.0    visual   λ₃      →


textual


  to   estimate   value_fixed   start   free   from      label   relation

  x4   0.0        1.0                   0.0    textual   const   →
  x5   1.08                             1.0    textual   λ₅      →
  x6   0.91                             1.0    textual   λ₆      →


speed


  to   estimate   value_fixed   start   free   from    label   relation

  x7   0.0        1.0                   0.0    speed   const   →
  x8   1.2                              1.0    speed   λ₈      →
  x9   1.04                             1.0    speed   λ₉      →


Directed Effects:


  from      to   estimate   value_fixed   start   free   label


Variances:


  from          to        estimate   value_fixed   start   free   label

  x1        ↔   x1        0.55                             1.0    gPasteur_1
  x2        ↔   x2        1.27                             1.0    gPasteur_2
  x3        ↔   x3        0.89                             1.0    gPasteur_3
  x4        ↔   x4        0.44                             1.0    gPasteur_4
  x5        ↔   x5        0.51                             1.0    gPasteur_5
  x6        ↔   x6        0.27                             1.0    gPasteur_6
  x7        ↔   x7        0.85                             1.0    gPasteur_7
  x8        ↔   x8        0.52                             1.0    gPasteur_8
  x9        ↔   x9        0.66                             1.0    gPasteur_9
  visual    ↔   visual    0.81                             1.0    gPasteur_10
  textual   ↔   textual   0.92                             1.0    gPasteur_13
  speed     ↔   speed     0.31                             1.0    gPasteur_15


Covariances:


  from          to        estimate   value_fixed   start   free   label

  textual   ↔   visual    0.42                             1.0    gPasteur_11
  speed     ↔   visual    0.17                             1.0    gPasteur_12
  speed     ↔   textual   0.18                             1.0    gPasteur_14

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.