Build by parts

You can always build a model by parts - that is, you construct the observed, imply, loss and optimizer part seperately.

As an example on how this works, we will build A first model in parts.

First, we specify the model just as usual:

using StructuralEquationModels

data = example_data("political_democracy")

observed_vars = [:x1, :x2, :x3, :y1, :y2, :y3, :y4, :y5, :y6, :y7, :y8]
latent_vars = [:ind60, :dem60, :dem65]

graph = @StenoGraph begin

    # loadings
    ind60 → fixed(1)*x1 + x2 + x3
    dem60 → fixed(1)*y1 + y2 + y3 + y4
    dem65 → fixed(1)*y5 + y6 + y7 + y8

    # latent regressions
    ind60 → dem60
    dem60 → dem65
    ind60 → dem65

    # variances
    _(observed_vars) ↔ _(observed_vars)
    _(latent_vars) ↔ _(latent_vars)

    # covariances
    y1 ↔ y5
    y2 ↔ y4 + y6
    y3 ↔ y7
    y8 ↔ y4 + y6

end

partable = ParameterTable(
    latent_vars = latent_vars,
    observed_vars = observed_vars,
    graph = graph)
 -------- ---------------- -------- ------- ------------- --------- ------------
    from   parameter_type       to    free   value_fixed     start   estimate  Symbol           Symbol   Symbol    Bool       Float64   Float64    Float64  ⋯
 -------- ---------------- -------- ------- ------------- --------- ------------
   ind60                →       x1   false           1.0       0.0        0.0  ⋯
   ind60                →       x2    true           0.0       0.0        0.0  ⋯
   ind60                →       x3    true           0.0       0.0        0.0  ⋯
   dem60                →       y1   false           1.0       0.0        0.0  ⋯
   dem60                →       y2    true           0.0       0.0        0.0  ⋯
   dem60                →       y3    true           0.0       0.0        0.0  ⋯
   dem60                →       y4    true           0.0       0.0        0.0  ⋯
   dem65                →       y5   false           1.0       0.0        0.0  ⋯
   dem65                →       y6    true           0.0       0.0        0.0  ⋯
   dem65                →       y7    true           0.0       0.0        0.0  ⋯
   dem65                →       y8    true           0.0       0.0        0.0  ⋯
   ind60                →    dem60    true           0.0       0.0        0.0  ⋯
   dem60                →    dem65    true           0.0       0.0        0.0  ⋯
   ind60                →    dem65    true           0.0       0.0        0.0  ⋯
      x1                ↔       x1    true           0.0       0.0        0.0  ⋯
    ⋮            ⋮            ⋮        ⋮          ⋮           ⋮         ⋮      ⋱
 -------- ---------------- -------- ------- ------------- --------- ------------
                                                    1 column and 19 rows omitted
Latent Variables:    [:ind60, :dem60, :dem65] 
Observed Variables:  [:x1, :x2, :x3, :y1, :y2, :y3, :y4, :y5, :y6, :y7, :y8] 

Now, we construct the different parts:

# observed ---------------------------------------------------------------------------------
observed = SemObservedData(specification = partable, data = data)

# imply ------------------------------------------------------------------------------------
imply_ram = RAM(specification = partable)

# loss -------------------------------------------------------------------------------------
ml = SemML(observed = observed)

loss_ml = SemLoss(ml)

# optimizer -------------------------------------------------------------------------------------
optimizer = SemOptimizerOptim()

# model ------------------------------------------------------------------------------------

model_ml = Sem(observed, imply_ram, loss_ml, optimizer)
Structural Equation Model 
- Loss Functions 
   SemML
- Fields 
   observed:    SemObservedData 
   imply:       RAM 
   optimizer:   SemOptimizerOptim