# Scale reliability and validity

This vignette focuses on measurement-quality checks. The examples use the tourism-services demo because it has coherent Likert constructs.

    demo <- sframe_demo_data()
    instr <- demo$instrument
    responses <- demo$responses

## Construct-to-item mapping

Scale definitions are stored in the instrument. They define which items form each construct and how scores should be computed.

    lapply(instr$scales, function(scale) {
      list(
        id = scale$id,
        label = scale$label,
        items = scale$items,
        method = scale$method,
        min_valid = scale$min_valid,
        reverse_items = scale$reverse_items
      )
    })
    #> [[1]]
    #> [[1]]$id
    #> [1] "digital_marketing"
    #>
    #> [[1]]$label
    #> [1] "Digital marketing effectiveness"
    #>
    #> [[1]]$items
    #> [1] "dm_1" "dm_2" "dm_3"
    #>
    #> [[1]]$method
    #> [1] "mean"
    #>
    #> [[1]]$min_valid
    #> NULL
    #>
    #> [[1]]$reverse_items
    #> NULL
    #>
    #>
    #> [[2]]
    #> [[2]]$id
    #> [1] "service_quality"
    #>
    #> [[2]]$label
    #> [1] "Service quality"
    #>
    #> [[2]]$items
    #> [1] "sq_1" "sq_2" "sq_3"
    #>
    #> [[2]]$method
    #> [1] "mean"
    #>
    #> [[2]]$min_valid
    #> NULL
    #>
    #> [[2]]$reverse_items
    #> NULL
    #>
    #>
    #> [[3]]
    #> [[3]]$id
    #> [1] "sustainability"
    #>
    #> [[3]]$label
    #> [1] "Sustainability perception"
    #>
    #> [[3]]$items
    #> [1] "sus_1" "sus_2"
    #>
    #> [[3]]$method
    #> [1] "mean"
    #>
    #> [[3]]$min_valid
    #> NULL
    #>
    #> [[3]]$reverse_items
    #> NULL
    #>
    #>
    #> [[4]]
    #> [[4]]$id
    #> [1] "satisfaction"
    #>
    #> [[4]]$label
    #> [1] "Tourist satisfaction"
    #>
    #> [[4]]$items
    #> [1] "sat_1" "sat_2"
    #>
    #> [[4]]$method
    #> [1] "mean"
    #>
    #> [[4]]$min_valid
    #> NULL
    #>
    #> [[4]]$reverse_items
    #> NULL
    #>
    #>
    #> [[5]]
    #> [[5]]$id
    #> [1] "behavioural_intention"
    #>
    #> [[5]]$label
    #> [1] "Behavioural intention"
    #>
    #> [[5]]$items
    #> [1] "bi_1" "bi_2"
    #>
    #> [[5]]$method
    #> [1] "mean"
    #>
    #> [[5]]$min_valid
    #> NULL
    #>
    #> [[5]]$reverse_items
    #> NULL

## Reverse coding and minimum valid item rules

`score_scales()` applies reverse coding and minimum valid item rules before returning scale scores.

    scored <- score_scales(
      responses,
      instr,
      keep_items = TRUE,
      keep_meta = TRUE
    )

    scale_ids <- vapply(instr$scales, function(scale) scale$id, character(1))
    head(scored[, intersect(scale_ids, names(scored)), drop = FALSE])
    #>   digital_marketing service_quality sustainability satisfaction
    #> 1          2.666667        3.666667            5.0          3.5
    #> 2          3.000000        2.666667            3.0          1.5
    #> 3          4.666667        3.333333            3.5          4.5
    #> 4          4.333333        4.000000            5.0          4.5
    #> 5          3.000000        3.666667            4.0          3.0
    #> 6          3.666667        3.666667            2.5          3.5
    #>   behavioural_intention
    #> 1                   4.5
    #> 2                   2.5
    #> 3                   2.5
    #> 4                   5.0
    #> 5                   3.5
    #> 6                   3.0

## Reliability

Cronbach’s alpha and omega use the optional `psych` package. Interpret these statistics together with item wording, dimensionality, and study design.

    alpha_only <- reliability_report(
      responses,
      instr,
      omega = FALSE
    )

    alpha_only
    #> Reliability Report
    #>
    #> Scale: digital_marketing (Digital marketing effectiveness)
    #>   Items: 3   N: 120
    #>   Alpha:   0.837
    #>
    #> Scale: service_quality (Service quality)
    #>   Items: 3   N: 120
    #>   Alpha:   0.844
    #>
    #> Scale: sustainability (Sustainability perception)
    #>   Items: 2   N: 120
    #>   Alpha:   0.772
    #>
    #> Scale: satisfaction (Tourist satisfaction)
    #>   Items: 2   N: 120
    #>   Alpha:   0.816
    #>
    #> Scale: behavioural_intention (Behavioural intention)
    #>   Items: 2   N: 120
    #>   Alpha:   0.844

    omega_report <- reliability_report(
      responses,
      instr,
      alpha = FALSE,
      omega = TRUE
    )
    #> Loading required namespace: GPArotation
    #> Omega_h for 1 factor is not meaningful, just omega_t
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> Omega_h and Omega_asymptotic are not meaningful with one factor
    #> Warning in cov2cor(t(w) %*% r %*% w): diag(V) had non-positive or NA entries;
    #> the non-finite result may be dubious
    #> Omega_h for 1 factor is not meaningful, just omega_t
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> Omega_h and Omega_asymptotic are not meaningful with one factor
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> diag(V) had non-positive or NA entries; the non-finite result may be dubious
    #> Omega_h for 1 factor is not meaningful, just omega_t
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> Omega_h and Omega_asymptotic are not meaningful with one factor
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> diag(V) had non-positive or NA entries; the non-finite result may be dubious
    #> Omega_h for 1 factor is not meaningful, just omega_t
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> Omega_h and Omega_asymptotic are not meaningful with one factor
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> diag(V) had non-positive or NA entries; the non-finite result may be dubious
    #> Omega_h for 1 factor is not meaningful, just omega_t
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> Omega_h and Omega_asymptotic are not meaningful with one factor
    #> Warning in schmid(m, nfactors, fm, digits, rotate = rotate, n.obs = n.obs, :
    #> diag(V) had non-positive or NA entries; the non-finite result may be dubious

    omega_report
    #> Reliability Report
    #>
    #> Scale: digital_marketing (Digital marketing effectiveness)
    #>   Items: 3   N: 120
    #>   Omega h: 0.837
    #>   Omega t: 0.837
    #>
    #> Scale: service_quality (Service quality)
    #>   Items: 3   N: 120
    #>   Omega h: 0.845
    #>   Omega t: 0.845
    #>
    #> Scale: sustainability (Sustainability perception)
    #>   Items: 2   N: 120
    #>   Omega h: 0.773
    #>   Omega t: 0.773
    #>
    #> Scale: satisfaction (Tourist satisfaction)
    #>   Items: 2   N: 120
    #>   Omega h: 0.817
    #>   Omega t: 0.817
    #>
    #> Scale: behavioural_intention (Behavioural intention)
    #>   Items: 2   N: 120
    #>   Omega h: 0.844
    #>   Omega t: 0.844

## Item diagnostics

Item diagnostics help identify sparse items, poor item-total relationships, and floor or ceiling issues.

    items <- item_report(responses, instr)

    names(items)
    #> [1] "digital_marketing"     "service_quality"       "sustainability"
    #> [4] "satisfaction"          "behavioural_intention"
    items[[1]]
    #> $scale_id
    #> [1] "digital_marketing"
    #>
    #> $label
    #> [1] "Digital marketing effectiveness"
    #>
    #> $diagnostics
    #>   item_id     mean        sd item_rest_r  floor_pct ceiling_pct n_missing
    #> 1    dm_1 3.141667 0.9982828  -0.5118417 0.05000000  0.10000000         0
    #> 2    dm_2 3.125000 0.9663455  -0.4619588 0.05000000  0.07500000         0
    #> 3    dm_3 3.191667 0.9982828  -0.5122414 0.05833333  0.08333333         0

## EFA readiness

`efa_report()` is a screening step. Interpret it alongside item wording, sample size, theory, and the study context.

    efa_report(responses, instr)
    #> R was not square, finding R from data
    #> Parallel analysis suggests that the number of factors =  4  and the number of components =  4
    #> EFA Readiness Diagnostics
    #>
    #>   Items:          12
    #>   Complete cases: 120
    #>   KMO overall:    0.761
    #>   Bartlett chi-sq: 665.84  df: 66  p: 0.0000
    #>   Suggested factors (parallel analysis): 4
    #>   Planned rotation: oblimin
    #>
    #> Note: estimate the EFA solution with a dedicated modelling package.

## Validity summary from supplied loadings

When standardised loadings are available from a CFA or PLS-SEM workflow, they can be summarised with `validity_report()`.

    loadings <- list(
      digital_marketing = c(dm_1 = .72, dm_2 = .78, dm_3 = .81),
      service_quality = c(sq_1 = .75, sq_2 = .80, sq_3 = .77),
      satisfaction = c(sat_1 = .76, sat_2 = .82)
    )

    validity <- validity_report(loadings)

    validity
    #> $method
    #> [1] "validity"
    #>
    #> $loading_summary
    #>           construct  item loading
    #> 1 digital_marketing  dm_1    0.72
    #> 2 digital_marketing  dm_2    0.78
    #> 3 digital_marketing  dm_3    0.81
    #> 4   service_quality  sq_1    0.75
    #> 5   service_quality  sq_2    0.80
    #> 6   service_quality  sq_3    0.77
    #> 7      satisfaction sat_1    0.76
    #> 8      satisfaction sat_2    0.82
    #>
    #> $reliability
    #>                           construct composite_reliability       AVE n_items
    #> digital_marketing digital_marketing             0.8142739 0.5943000       3
    #> satisfaction           satisfaction             0.7689749 0.6250000       2
    #> service_quality     service_quality             0.8171246 0.5984667       3
    #>
    #> $fornell_larcker
    #> NULL
    #>
    #> $htmt
    #> NULL
    #>
    #> $inter_construct_correlations
    #> NULL
    #>
    #> $apa
    #> [1] "Construct validity summaries were computed from supplied loadings."
    #>
    #> $prompt
    #> [1] "Report composite reliability, AVE, Fornell-Larcker, HTMT, and the inter-construct correlation matrix."
    #>
    #> attr(,"class")
    #> [1] "sframe_validity_report"

## Cautious interpretation

Reliability and validity summaries are diagnostics, not automatic decisions. They should be interpreted with the questionnaire wording, sampling context, construct definitions, and planned statistical model.