(Syntax) Equivalence second-order model and bifactor model with proportionality constraints

57 views
Skip to first unread message

Pedro Ribeiro

unread,
Jul 3, 2023, 2:23:43 AM7/3/23
to lavaan
Hi all,

There is a well-known equivalence between the second-order factor model and the bi-factor model with proportionality constraints:

Yung, Y. F., Thissen, D., & McLeod, L. D. (1999). On the relationship between the higher-order factor model and the hierarchical factor model. Psychometrika64, 113-128.

In this website, it is demonstrated how to display this equivalence using SAS syntax:


However, I have been unsure of how to impose these proportionality constraints using lavaan syntax and have not been able to find this information elsewhere. My bifactor model syntax is:

model_bi <- '
 general =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9
  specific1 =~ x1 + x2 + x3
  specific2 =~ x4 + x5 + x6
  specific3 =~ x7 + x8 + x9
 
  general ~~ 0*specific1
  general ~~ 0*specific2
  general ~~ 0*specific3
  specific1 ~~ 0*specific2
  specific1 ~~ 0*specific3
  specific2 ~~ 0*specific3
'
Does anyone know how to write this bifactor model with proportionality constraints so it is equivalent to the second-order factor model shown below?

second_model <- '
  second =~ specific1 + specific2 + specific3
  specific1 =~ x1 + x2 + x3
  specific2 =~ x4 + x5 + x6
  specific3 =~ x7 + x8 + x9
'
I tried the syntax below but it did not work:

model_cons <- '
  general =~ (p1*X11)*x1 + (p1*X21)*x2 + (p1*X31)*x3 + (p2*X42)*x4 + (p2*X52)*x5 + (p2*X62)*x6 + (p3*X73)*x7 + (p3*X83)*x8 + (p3*X93)*x9
  specific1 =~ X11*x1 + X21*x2 + X31*x3
  specific2 =~ X42*x4 + X52*x5 + X62*x6
  specific3 =~ X73*x7 + X83*x8 + X93*x9
 
  general ~~ 0*specific1
  general ~~ 0*specific2
  general ~~ 0*specific3
  specific1 ~~ 0*specific2
  specific1 ~~ 0*specific3
  specific2 ~~ 0*specific3
'

Thanks in advance, any help is appreciated!

Cheers,
Pedro

Shu Fai Cheung (張樹輝)

unread,
Jul 3, 2023, 7:34:04 AM7/3/23
to lavaan
Dear Pedro,

I happened to be examining something similar a while ago. This is what I found in my computer, adapted to your case:

``` r
library(lavaan)
#> This is lavaan 0.6-15
#> lavaan is FREE software! Please report any bugs.

# Bifactor with proportionalitu constraints

model_bf <- '
visual  =~ a1*x1 + a2*x2 + a3*x3
textual =~ a4*x4 + a5*x5 + a6*x6
speed   =~ a7*x7 + a8*x8 + a9*x9
g =~ b1*x1 + b2*x2 + b3*x3 + b4*x4 + b5*x5 + b6*x6 + b7*x7 + b8*x8 + b9*x9
g ~~ 0*visual + 0*textual + 0*speed
visual ~~ 0*textual + 0*speed
textual ~~0*speed
lr1 := a1 / b1
lr2 := a2 / b2
lr3 := a3 / b3
lr4 := a4 / b4
lr5 := a5 / b5
lr6 := a6 / b6
lr7 := a7 / b7
lr8 := a8 / b8
lr9 := a9 / b9
lr1 == lr2
lr2 == lr3
lr4 == lr5
lr5 == lr6
lr7 == lr8
lr8 == lr9
'

fit_bf <- cfa(model_bf,
              HolzingerSwineford1939,
              std.lv = TRUE)

parameterEstimates(fit_bf)
#>        lhs op     rhs label    est    se      z pvalue ci.lower ci.upper
#> 1   visual =~      x1    a1 -0.438 0.194 -2.256  0.024   -0.819   -0.058
#> 2   visual =~      x2    a2 -0.243 0.108 -2.252  0.024   -0.454   -0.031
#> 3   visual =~      x3    a3 -0.320 0.138 -2.325  0.020   -0.589   -0.050
#> 4  textual =~      x4    a4  0.842 0.064 13.251  0.000    0.718    0.967
#> 5  textual =~      x5    a5  0.938 0.071 13.293  0.000    0.799    1.076
#> 6  textual =~      x6    a6  0.780 0.060 13.084  0.000    0.663    0.897
#> 7    speed =~      x7    a7  0.522 0.066  7.908  0.000    0.393    0.651
#> 8    speed =~      x8    a8  0.616 0.067  9.129  0.000    0.484    0.748
#> 9    speed =~      x9    a9  0.564 0.064  8.808  0.000    0.439    0.690
#> 10       g =~      x1    b1  0.786 0.116  6.744  0.000    0.557    1.014
#> 11       g =~      x2    b2  0.435 0.090  4.827  0.000    0.258    0.611
#> 12       g =~      x3    b3  0.573 0.102  5.605  0.000    0.373    0.773
#> 13       g =~      x4    b4  0.520 0.088  5.936  0.000    0.348    0.691
#> 14       g =~      x5    b5  0.578 0.097  5.940  0.000    0.388    0.769
#> 15       g =~      x6    b6  0.481 0.081  5.918  0.000    0.322    0.641
#> 16       g =~      x7    b7  0.334 0.066  5.026  0.000    0.204    0.464
#> 17       g =~      x8    b8  0.394 0.073  5.432  0.000    0.252    0.536
#> 18       g =~      x9    b9  0.361 0.069  5.259  0.000    0.226    0.496
#> 19  visual ~~       g        0.000 0.000     NA     NA    0.000    0.000
#> 20 textual ~~       g        0.000 0.000     NA     NA    0.000    0.000
#> 21   speed ~~       g        0.000 0.000     NA     NA    0.000    0.000
#> 22  visual ~~ textual        0.000 0.000     NA     NA    0.000    0.000
#> 23  visual ~~   speed        0.000 0.000     NA     NA    0.000    0.000
#> 24 textual ~~   speed        0.000 0.000     NA     NA    0.000    0.000
#> 25      x1 ~~      x1        0.549 0.114  4.833  0.000    0.326    0.772
#> 26      x2 ~~      x2        1.134 0.102 11.146  0.000    0.934    1.333
#> 27      x3 ~~      x3        0.844 0.091  9.317  0.000    0.667    1.022
#> 28      x4 ~~      x4        0.371 0.048  7.779  0.000    0.278    0.465
#> 29      x5 ~~      x5        0.446 0.058  7.642  0.000    0.332    0.561
#> 30      x6 ~~      x6        0.356 0.043  8.277  0.000    0.272    0.441
#> 31      x7 ~~      x7        0.799 0.081  9.823  0.000    0.640    0.959
#> 32      x8 ~~      x8        0.488 0.074  6.573  0.000    0.342    0.633
#> 33      x9 ~~      x9        0.566 0.071  8.003  0.000    0.427    0.705
#> 34  visual ~~  visual        1.000 0.000     NA     NA    1.000    1.000
#> 35 textual ~~ textual        1.000 0.000     NA     NA    1.000    1.000
#> 36   speed ~~   speed        1.000 0.000     NA     NA    1.000    1.000
#> 37       g ~~       g        1.000 0.000     NA     NA    1.000    1.000
#> 38     lr1 :=   a1/b1   lr1 -0.558 0.309 -1.809  0.070   -1.163    0.047
#> 39     lr2 :=   a2/b2   lr2 -0.558 0.309 -1.809  0.070   -1.163    0.047
#> 40     lr3 :=   a3/b3   lr3 -0.558 0.309 -1.809  0.070   -1.163    0.047
#> 41     lr4 :=   a4/b4   lr4  1.621 0.338  4.798  0.000    0.959    2.283
#> 42     lr5 :=   a5/b5   lr5  1.621 0.338  4.798  0.000    0.959    2.283
#> 43     lr6 :=   a6/b6   lr6  1.621 0.338  4.798  0.000    0.959    2.283
#> 44     lr7 :=   a7/b7   lr7  1.563 0.348  4.489  0.000    0.881    2.246
#> 45     lr8 :=   a8/b8   lr8  1.563 0.348  4.489  0.000    0.881    2.246
#> 46     lr9 :=   a9/b9   lr9  1.563 0.348  4.489  0.000    0.881    2.246

# 2nd Order CFA

model_2nd <- '
visual  =~ a1*x1 + a2*x2 + a3*x3
textual =~ a4*x4 + a5*x5 + a6*x6
speed   =~ a7*x7 + a8*x8 + a9*x9
g =~ visual + textual + speed
'

fit_2nd <- cfa(model_2nd,
               HolzingerSwineford1939,
               std.lv = TRUE)

# Check if they are equivalent

fit_bf
#> lavaan 0.6.15 ended normally after 234 iterations
#>
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        27
#>
#>   Number of observations                           301
#>
#> Model Test User Model:
#>                                                      
#>   Test statistic                                85.306
#>   Degrees of freedom                                24
#>   P-value (Chi-square)                           0.000
fit_2nd
#> lavaan 0.6.15 ended normally after 36 iterations
#>
#>   Estimator                                         ML
#>   Optimization method                           NLMINB
#>   Number of model parameters                        21
#>
#>   Number of observations                           301
#>
#> Model Test User Model:
#>                                                      
#>   Test statistic                                85.306
#>   Degrees of freedom                                24
#>   P-value (Chi-square)                           0.000
all.equal(lavInspect(fit_bf, "implied")$cov,
          lavInspect(fit_2nd, "implied")$cov,
          tolerance = 1e-4)
#> [1] TRUE
semTools::net(fit_bf, fit_2nd)
#>
#>         If cell [R, C] is TRUE, the model in row R is nested within column C.
#>
#>         If the models also have the same degrees of freedom, they are equivalent.
#>
#>         NA indicates the model in column C did not converge when fit to the
#>         implied means and covariance matrix from the model in row R.
#>
#>         The hidden diagonal is TRUE because any model is equivalent to itself.
#>         The upper triangle is hidden because for models with the same degrees
#>         of freedom, cell [C, R] == cell [R, C].  For all models with different
#>         degrees of freedom, the upper diagonal is all FALSE because models with
#>         fewer degrees of freedom (i.e., more parameters) cannot be nested
#>         within models with more degrees of freedom (i.e., fewer parameters).
#>        
#>                   fit_bf fit_2nd
#> fit_bf (df = 24)                
#> fit_2nd (df = 24) TRUE
```

<sup>Created on 2023-07-03 with [reprex v2.0.2](https://reprex.tidyverse.org)</sup>


I did that when exploring something. I haven't fully tested it. You can take a look and see whether this is what you need.

Hope this helps.

-- Shu Fai

Pedro Ribeiro

unread,
Jul 3, 2023, 7:43:23 PM7/3/23
to lavaan
Dear Shu Fai,

Thanks so much, I adapted the syntax and it worked perfectly!

Cheers,
Pedro

Reply all
Reply to author
Forward
0 new messages