EFA, CFA, CB-SEM, and PLS-SEM syntax generation

The model is the third part of the research design. surveyframe stores the measurement and structural specification and writes the syntax for the modelling stage. It does not fit the models. This vignette builds the three models from the published Thailand study (Sharafuddin, Madhavan, and Wangtueai 2024). The syntax steps need no raw data, so every chunk below runs as written.

The constructs are digital marketing effectiveness (DME), built from relevance and engagement (DMRE), accessibility and usefulness (DMAU), ease of use (DMEU), and perceived value (DMPV); destination service quality (DSQ), built from accommodation (DSQA) and local transport (DSQT); destination sustainability quality (DSUQ); tourist satisfaction (TS); and behavioural intention (BI).

dmre <- paste0("dmre_", 1:4); dmau <- paste0("dmau_", 1:3)
dmeu <- paste0("dmeu_", 1:3); dmpv <- paste0("dmpv_", 1:4)
dsqa <- paste0("dsqa_", 1:3); dsqt <- paste0("dsqt_", 1:5)
dsuq <- paste0("dsuq_", 1:5); ts <- paste0("ts_", 1:3); bi <- paste0("bi_", 1:3)

EFA planning syntax

efa_syntax() writes a short R block that estimates an exploratory factor solution with the optional psych package. This screens the digital marketing items before the confirmatory model.

cat(efa_syntax(items = c(dmre, dmau, dmeu, dmpv), nfactors = 4,
               extraction = "minres", rotation = "oblimin"))
#> # EFA syntax generated by surveyframe
#> rlang::check_installed("psych", reason = "to estimate an EFA solution")
#> efa_items <- data[, c("dmre_1", "dmre_2", "dmre_3", "dmre_4", "dmau_1", "dmau_2", "dmau_3", "dmeu_1", "dmeu_2", "dmeu_3", "dmpv_1", "dmpv_2", "dmpv_3", "dmpv_4")]
#> psych::fa(efa_items, nfactors = 4, fm = "minres", rotate = "oblimin")

CFA syntax: the lower-order measurement model

The confirmatory measurement model has the nine lower-order constructs as reflective factors. cfa_lavaan_syntax() writes lavaan syntax from a model object, with no need for lavaan at this stage.

construct_def <- list(
  DMRE = dmre, DMAU = dmau, DMEU = dmeu, DMPV = dmpv,
  DSQA = dsqa, DSQT = dsqt, DSUQ = dsuq, TS = ts, BI = bi
)

cfa_model <- sf_model(
  id    = "lower_order_cfa",
  label = "Lower-order measurement model",
  type  = "cfa",
  constructs = Map(function(id, items) sf_construct(id, id, items),
                   names(construct_def), construct_def)
)

cat(cfa_lavaan_syntax(model = cfa_model))
#> # lavaan CFA syntax generated by surveyframe
#> # Model: Lower-order measurement model
#> # Recommended fitting option: std.lv = TRUE
#> # Fit with lavaan only when lavaan is installed.
#> 
#> # DMRE (reflective)
#> DMRE =~ dmre_1 + dmre_2 + dmre_3 + dmre_4
#> 
#> # DMAU (reflective)
#> DMAU =~ dmau_1 + dmau_2 + dmau_3
#> 
#> # DMEU (reflective)
#> DMEU =~ dmeu_1 + dmeu_2 + dmeu_3
#> 
#> # DMPV (reflective)
#> DMPV =~ dmpv_1 + dmpv_2 + dmpv_3 + dmpv_4
#> 
#> # DSQA (reflective)
#> DSQA =~ dsqa_1 + dsqa_2 + dsqa_3
#> 
#> # DSQT (reflective)
#> DSQT =~ dsqt_1 + dsqt_2 + dsqt_3 + dsqt_4 + dsqt_5
#> 
#> # DSUQ (reflective)
#> DSUQ =~ dsuq_1 + dsuq_2 + dsuq_3 + dsuq_4 + dsuq_5
#> 
#> # TS (reflective)
#> TS =~ ts_1 + ts_2 + ts_3
#> 
#> # BI (reflective)
#> BI =~ bi_1 + bi_2 + bi_3

CB-SEM syntax: the structural model

The covariance-based reading treats the five constructs as first-order reflective factors over their full item sets, with the nine direct paths and the four indirect effects from the hypotheses. sem_lavaan_syntax() writes the measurement, structural, and indirect-effect lines.

sem_model <- sf_model(
  id    = "tourism_structural",
  label = "Digital marketing structural model",
  type  = "cb_sem",
  constructs = list(
    sf_construct("DME",  "Digital marketing effectiveness", c(dmre, dmau, dmeu, dmpv)),
    sf_construct("DSQ",  "Destination service quality",     c(dsqa, dsqt)),
    sf_construct("DSUQ", "Destination sustainability quality", dsuq),
    sf_construct("TS",   "Tourist satisfaction", ts),
    sf_construct("BI",   "Behavioural intention", bi)
  ),
  paths = list(
    sf_path("DME", "DSQ", label = "h1"),  sf_path("DME", "DSUQ", label = "h2"),
    sf_path("DME", "TS",  label = "h3"),  sf_path("DME", "BI",   label = "h4"),
    sf_path("DSQ", "TS",  label = "h5"),  sf_path("DSQ", "BI",   label = "h6"),
    sf_path("DSUQ", "TS", label = "h7"),  sf_path("DSUQ", "BI",  label = "h8"),
    sf_path("TS", "BI",   label = "h9")
  ),
  indirect = list(
    sf_indirect("DME", "DSQ", "TS",  label = "h10"),
    sf_indirect("DME", "DSUQ", "TS", label = "h11"),
    sf_indirect("DME", c("DSQ", "TS"), "BI",  label = "h12"),
    sf_indirect("DME", c("DSUQ", "TS"), "BI", label = "h13")
  ),
  options = list(estimator = "MLR", missing = "fiml", standardised = TRUE)
)

validate_model(sem_model)
cat(sem_lavaan_syntax(sem_model))
#> # lavaan CB-SEM syntax generated by surveyframe
#> # Model: Digital marketing structural model
#> # Recommended summary option: standardized = TRUE
#> # Estimator: MLR
#> # Missing data method: fiml
#> 
#> DME =~ dmre_1 + dmre_2 + dmre_3 + dmre_4 + dmau_1 + dmau_2 + dmau_3 + dmeu_1 + dmeu_2 + dmeu_3 + dmpv_1 + dmpv_2 + dmpv_3 + dmpv_4
#> DSQ =~ dsqa_1 + dsqa_2 + dsqa_3 + dsqt_1 + dsqt_2 + dsqt_3 + dsqt_4 + dsqt_5
#> DSUQ =~ dsuq_1 + dsuq_2 + dsuq_3 + dsuq_4 + dsuq_5
#> TS =~ ts_1 + ts_2 + ts_3
#> BI =~ bi_1 + bi_2 + bi_3
#> 
#> # Structural paths
#> BI ~ h4*DME + h6*DSQ + h8*DSUQ + h9*TS
#> DSQ ~ h1*DME
#> DSUQ ~ h2*DME
#> TS ~ h3*DME + h5*DSQ + h7*DSUQ
#> 
#> # Indirect and total effects
#> h10 := h1*h5
#> total_DME_TS := h3 + h10
#> h11 := h2*h7
#> total_DME_TS := h3 + h11
#> h12 := h1*h5*h9
#> total_DME_BI := h4 + h12
#> h13 := h2*h7*h9
#> total_DME_BI := h4 + h13

PLS-SEM syntax: the two-stage higher-order model

The published study used PLS-SEM with a two-stage treatment of the higher-order constructs. The second stage models digital marketing effectiveness and service quality as composites of their lower-order construct scores, then estimates the structural paths. seminr_syntax() writes the seminr code, including the bootstrap, reliability, AVE, and HTMT calls.

pls_model <- sf_model(
  id    = "tourism_pls",
  label = "Two-stage higher-order PLS model",
  type  = "pls_sem",
  constructs = list(
    sf_construct("DME",  "Digital marketing effectiveness",
                 c("DMRE", "DMAU", "DMEU", "DMPV"), mode = "composite"),
    sf_construct("DSQ",  "Destination service quality",
                 c("DSQA", "DSQT"), mode = "composite"),
    sf_construct("DSUQ", "Destination sustainability quality", dsuq, mode = "composite"),
    sf_construct("TS",   "Tourist satisfaction", ts, mode = "composite"),
    sf_construct("BI",   "Behavioural intention", bi, mode = "composite")
  ),
  paths = list(
    sf_path("DME", "DSQ"), sf_path("DME", "DSUQ"),
    sf_path("DME", "TS"),  sf_path("DME", "BI"),
    sf_path("DSQ", "TS"),  sf_path("DSQ", "BI"),
    sf_path("DSUQ", "TS"), sf_path("DSUQ", "BI"),
    sf_path("TS", "BI")
  ),
  options = list(bootstrap = 1000)
)

cat(seminr_syntax(pls_model))
#> # seminr PLS-SEM syntax generated by surveyframe
#> rlang::check_installed("seminr", reason = "to fit PLS-SEM models")
#> measurement_model <- constructs(
#>   composite("DME", c("DMRE", "DMAU", "DMEU", "DMPV")),
#>   composite("DSQ", c("DSQA", "DSQT")),
#>   composite("DSUQ", multi_items("dsuq_", 1:5)),
#>   composite("TS", multi_items("ts_", 1:3)),
#>   composite("BI", multi_items("bi_", 1:3))
#> )
#> 
#> structural_model <- relationships(
#>   paths(from = "DME", to = c("DSQ", "DSUQ", "TS", "BI")),
#>   paths(from = "DSQ", to = c("TS", "BI")),
#>   paths(from = "DSUQ", to = c("TS", "BI")),
#>   paths(from = "TS", to = c("BI"))
#> )
#> 
#> pls_model <- estimate_pls(
#>   data = data,
#>   measurement_model = measurement_model,
#>   structural_model = structural_model
#> )
#> 
#> boot_model <- bootstrap_model(
#>   seminr_model = pls_model,
#>   nboot = 1000,
#>   cores = 1,
#>   seed = 123
#> )
#> 
#> reliability(pls_model)
#> ave(pls_model)
#> htmt(pls_model)

Construct modes

A construct’s mode is reflective, composite, formative, or single_item. lavaan syntax generation in v0.3 is intended for reflective measurement models, which is why the CFA and CB-SEM models use the default reflective mode. PLS-SEM syntax uses composite constructs, as above.

Model JSON and a reporting template

A model serialises to JSON for storage in a .sframe file, and model_report_template() writes a short reporting outline.

cat(model_report_template(sem_model, include_json = FALSE))
#> # Model: Digital marketing structural model
#> 
#> Type: cb_sem
#> Engine: lavaan
#> 
#> ## Constructs
#> - DME (reflective): dmre_1, dmre_2, dmre_3, dmre_4, dmau_1, dmau_2, dmau_3, dmeu_1, dmeu_2, dmeu_3, dmpv_1, dmpv_2, dmpv_3, dmpv_4
#> - DSQ (reflective): dsqa_1, dsqa_2, dsqa_3, dsqt_1, dsqt_2, dsqt_3, dsqt_4, dsqt_5
#> - DSUQ (reflective): dsuq_1, dsuq_2, dsuq_3, dsuq_4, dsuq_5
#> - TS (reflective): ts_1, ts_2, ts_3
#> - BI (reflective): bi_1, bi_2, bi_3
#> 
#> ## Structural Paths
#> - DME -> DSQ
#> - DME -> DSUQ
#> - DME -> TS
#> - DME -> BI
#> - DSQ -> TS
#> - DSQ -> BI
#> - DSUQ -> TS
#> - DSUQ -> BI
#> - TS -> BI
#> 
#> ## Reporting Notes
#> Report estimator, missing-data handling, fit indices, standardised path estimates, indirect effects, reliability, validity, and any item-retention decisions.

Where the syntax goes next

The generated lavaan syntax is copied into lavaan::cfa() or lavaan::sem() after choosing the estimator, ordered-item handling, and missing-data treatment. The generated seminr syntax is copied into a script where seminr is installed and the bootstrap settings and construct modes are set. The published study fitted the two-stage PLS model in seminr with 1000 bootstrap resamples.