# 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.

There are five constructs. Digital marketing effectiveness (DME) is built from relevance and engagement (DMRE), accessibility and usefulness (DMAU), ease of use (DMEU), and perceived value (DMPV). Destination service quality (DSQ) is built from accommodation (DSQA) and local transport (DSQT). The remaining three are 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)
    #> <sframe validation>
    #>   Model:       tourism_structural (cb_sem)
    #>   Status:      valid
    #>   Checks:      10 run, 0 with problems
    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")
    #> library(seminr)
    #>
    #> 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
    #> )
    #>
    #> # Measurement assessment: reliability, AVE, and HTMT discriminant validity
    #> model_summary <- summary(pls_model)
    #> model_summary$reliability
    #> model_summary$validity$htmt
    #>
    #> # Bootstrapped structural paths
    #> boot_summary <- summary(boot_model)
    #> boot_summary$bootstrapped_paths

## Construct modes

A construct’s mode is `reflective`, `composite`, `formative`, or `single_item`. lavaan syntax generation 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.