Getting Started with semrulesid

library(semrulesid)
#> semrulesid 0.4.2
#> Please report bugs or edge cases at:
#> https://github.com/zacharyvig/semrulesid/issues

Introduction

semrulesid allows the user to check a structural equation model (SEM) written in lavaan (Rosseel, 2012) syntax, or supplied as a lavaan parameter table or fit object, against a number of identification rules from the literature. Rules are specified as being necessary and/or sufficient and specific reasons are given when a rule is not satisfied or not applicable. Users should not treat the package output as the sole determinant of model identification. Instead, semrulesid should be used as a quick check for potential identification issues and outstanding model-specification concerns.

Basic workflow

The primary function is id(). Supply a model in lavaan syntax and specify the lavaan function whose defaults you intend to use through lav_fun.

model <- '
  L1 =~ Y1 + Y2 + Y3
  L2 =~ Y4 + Y5 + Y6
  L2 ~ L1
'

id(model, lav_fun = "sem")
#> semrulesid 0.4.2 Rule Check
#> 
#> Fitting function : lavaan::sem()
#> Model type       : General SEM 
#> 
#>                        Pass Necessary Sufficient Message 
#> N_theta Rule (t-Rule)   Yes       Yes         No 
#> Latent Scaling Rule     Yes       Yes         No 
#> Exogenous X Rule          -         -          -       1 
#> 2+ Emitted Paths Rule   Yes       Yes         No 
#> Three Indicator Rule      -         -          -       2 
#> 
#> Rules not applicable to general SEMs:
#>   Two Indicator Rule, Fully Recursive Rule, Null B_YY
#>   Rule, Recur/Corr Err Rule
#> 
#> ---
#> 
#> Rule not applicable to this model specification:
#> 
#> (1) This rule only applies when causal indicators or
#>     exogenous observed variables are in the model
#> (2) This rule only applies to confirmatory factor
#>     analysis models

The output table reports whether each implemented rule passes and whether the rule is necessary and/or sufficient (or neither) for the relevant model class, which is printed at the top of the output. Rules not applicable to the model type are printed directly below the table.

Interpreting messages

The id() function reports rules applicable to the specified model in the rule-check table. Rules that are not applicable to the detected model type are listed below the table.

When a rule has an associated message, the table’s Message column gives the number of the corresponding message. Messages are grouped into the following sections:

Thus, a rule with Pass = No should be interpreted together with its Necessary and Sufficient columns and any corresponding message. A failed necessary condition indicates an identification problem, whereas an unmet sufficient condition means only that the corresponding sufficient rule cannot certify identification.

Choosing lav_fun

When a model string is supplied, lav_fun determines which lavaan defaults are used when the model is converted to a parameter table. Current options are:

For example, use "cfa" for a confirmatory factor analysis model:

cfa_model <- '
  L1 =~ Y1 + Y2 + Y3
  L2 =~ Y4 + Y5 + Y6
  L1 ~~ L2
'

id(cfa_model, lav_fun = "cfa")

When a parameter table or fit lavaan object is supplied, lav_fun is ignored since the model defaults have already been applied.

Checking latent-variable scaling

Use scaling() to inspect whether each latent variable is scaled via a method used in the literature.

scaling(model, lav_fun = "sem")
#> semrulesid 0.4.2 Latent Variable Scaling
#> 
#> Fitting function : lavaan::sem() 
#> 
#> L1
#>   LV is scaled         : Yes
#>   No. of indicators    : 3
#>   Scaling indicator(s) : Y1
#>   Mean structure       : No 
#> 
#>   Scaling method(s):
#>   - Scaling indicator
#> 
#> 
#> L2
#>   LV is scaled         : Yes
#>   No. of indicators    : 3
#>   Scaling indicator(s) : Y4
#>   Mean structure       : No 
#> 
#>   Scaling method(s):
#>   - Scaling indicator

For models without a mean structure, the package checks whether each latent variable has assigned units through a fixed, nonzero loading (scaling indicator) or a fixed positive latent-variable variance.

For models with a mean structure, the package also checks whether the latent variable has an assigned origin, such as through a fixed latent-variable mean or a fixed intercept for a scaling indicator.

Working with a fit lavaan model

If a model has already been fit with lavaan, pass the fit object directly to id() or scaling(). Set lav_fun = NA to avoid warnings when lav_fun does not match the fitting function used to estimate the model.

library(lavaan)

fit <- sem(model, data = my_data)

id(fit, lav_fun = NA)
scaling(fit, lav_fun = NA)

Piping identification and scaling checks

semrulesid supports the use of a pipe operator to chain together identification and scaling checks, e.g., using the base R pipe |> or the pipe from the magrittr package:

id(model, lav_fun = "sem") |> scaling()
# or
library(magrittr)
id(model, lav_fun = "sem") %>% scaling

The reverse order is also supported:

scaling(model, lav_fun = "sem") |> id()
# or
library(magrittr)
scaling(model, lav_fun = "sem") %>% id

The two-step rule

For supported full SEMs (latent variables plus structural paths), id2() applies the two-step rule of identification (see Bollen, 2026). It first transforms the model into a confirmatory factor analysis (CFA) model and evaluates it for identification. It then transforms the model into a simultaneous equations model, and evaluates it for identification. If both steps are identified, the original model is identified. The outputs of both steps are printed via the id2() function.

id2(model, lav_fun = "sem")
#> semrulesid 0.4.2 Two-Step Rule Check
#> 
#> Fitting function : lavaan::sem()
#> Model type       : General SEM 
#> 
#> -------------------------------------------------------- 
#> 
#> Step 1: Measurement Model
#> 
#>                        Pass Necessary Sufficient Message 
#> N_theta Rule (t-Rule)   Yes       Yes         No 
#> Latent Scaling Rule     Yes       Yes         No 
#> Exogenous X Rule          -         -          -       1 
#> 2+ Emitted Paths Rule   Yes       Yes         No 
#> Three Indicator Rule    Yes        No        Yes 
#> Two Indicator Rule      Yes        No        Yes 
#> 
#> ---
#> 
#> Rule not applicable to this model specification:
#> 
#> (1) This rule only applies when causal indicators or
#>     exogenous observed variables are in the model
#> 
#> -------------------------------------------------------- 
#> 
#> Step 2: Latent Variable/Structural Model
#> 
#>                        Pass Necessary Sufficient Message 
#> N_theta Rule (t-Rule)   Yes       Yes         No 
#> Fully Recursive Rule    Yes        No        Yes 
#> Null B_YY Rule          Yes        No        Yes 
#> Recur/Corr Err Rule     Yes        No        Yes

Next steps

For details on individual rules, see:

?id
?scaling
?get_rules

You can retrieve implemented rule functions with get_rules():

cfa_rules <- get_rules(rule = "*", model_type = "cfa")
names(cfa_rules)
#> [1] "rule_cfa_three_indicator"   "rule_cfa_two_indicator"    
#> [3] "rule_sem_exogenous_x"       "rule_sem_latent_scaling"   
#> [5] "rule_sem_ntheta"            "rule_sem_two_emitted_paths"

For further theoretical background, see Ken Bollen’s book Elements of Structural Equation Models (2026).