--- title: "Guided workflow with nomo_run()" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Guided workflow with nomo_run()} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} library(nomologR) knitr::opts_chunk$set( collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE ) ``` `nomo_run()` is an orchestrator, not an automatic scale-decider. Its job is to make the staged workflow easier to reproduce while preserving the package's central rule: > Evidence may flow automatically. Consequential decisions do not. This walkthrough uses `nomo_demo_network`, a simulated validation study with three constructs (Agency, Persistence, and Social desirability), an observed Performance outcome, and two administration groups. See `?nomo_demo_network`. ## A fresh run ```{r fresh} scales <- list( Agency = c("ag1", "ag2", "ag3", "ag4"), Persistence = c("pe1", "pe2", "pe3", "pe4"), SocialDesirability = c("sd1", "sd2", "sd3") ) run <- nomo_run( data = nomo_demo_network, scales = scales, settings = list(factors = list(seed = 2026)) ) run ``` The initial run audits each scale's items and computes factor-retention evidence. It then pauses before EFA. The factor count is not silently copied from parallel analysis. ```{r requests-1} nomo_table(run, "requests") ``` Each decision request separates: - **Observation** — what the evidence shows; - **Reason** — why a researcher decision is required; - **Options** — what can defensibly happen next; - **Consequence** — what the choice changes. ## Resume with an explicit factor count ```{r factor-count} run <- nomo_run( resume = run, decisions = list( factor_count = list( value = c(Agency = 1, Persistence = 1, SocialDesirability = 1), rationale = paste( "Each scale was written to measure one construct, and the", "retention evidence was reviewed before choosing one factor." ) ) ) ) run ``` The completed screening and factor-retention objects are reused. EFA is fitted using the explicit factor-count decision, with the retention stage's correlation and modeling context retained. The workflow then pauses before CFA model specification. ## Confirmatory model handoff ```{r cfa-model} run <- nomo_run( resume = run, decisions = list( cfa_model = list( value = nomo_model(scales), rationale = "Prespecified three-construct measurement model." ) ) ) run ``` `nomo_run()` does not translate the EFA into CFA syntax. The researcher owns the confirmatory model; `nomo_model()` simply writes the simple-structure syntax for the scales that were already declared. Once supplied, the pipeline computes: ```text nomo_cfa() | +-- nomo_reliability() | +-- nomo_validity() ``` and pauses again. ## Why pause after measurement evidence? A model can converge and still contain evidence that deserves review. Reliability or AVE can look strong while construct separation or local CFA evidence remains strained. Therefore the pipeline does not automatically carry the model into invariance or structural theory tests. ```{r requests-2} nomo_table(run, "requests") ``` The researcher chooses either `measurement_model = "proceed"` or `measurement_model = "revise"`. Choosing `revise` does not trigger an automated modification-index search, parameter freeing, or item deletion; the current workflow remains an auditable record. To carry a revision forward with its reasoning attached, use `nomo_revise()`, shown in [Revising with a recorded lineage](#revising-with-a-recorded-lineage) below. ## Optional invariance and network branches Future-stage settings can be added at a pause without recomputing completed stages. ```{r branches} h <- nomo_hypotheses( "Agency -> Persistence" = positive(min = .20), "Agency <-> SocialDesirability" = negligible(within = c(-.15, .15)), "Agency -> Performance" = positive() ) run <- nomo_run( resume = run, settings = list( invariance = list( group = "group", levels = c("configural", "metric", "scalar"), localize = TRUE ), network = list( hypotheses = h ) ) ) ``` Then continue explicitly: ```{r proceed} run <- nomo_run( resume = run, decisions = list( measurement_model = list( value = "proceed", rationale = "Measurement evidence reviewed before planned downstream tests." ) ) ) run nomo_table(run, "stages") ``` The completed component objects remain available for direct inspection: ```{r components} nomo_table(run$results$invariance, "fit")[, c("level", "cfi", "rmsea", "delta_cfi")] nomo_table(run$results$network, "hypotheses")[, c( "relation", "estimate", "ci_lower", "ci_upper", "concordance" )] ``` The invariance branch retains its diagnostics without automatically releasing constraints (see [Measurement invariance](measurement-invariance.html) for the `ag3` intercept). The network branch uses explicit hypotheses and does not create a single nomological-validity score. ## Prespecified one-call workflow Researchers who have already made the consequential decisions — for example in a preregistration — can supply them together. `mode = "research"` gives a more compact presentation of the same statistical behavior: ```{r one-call} run_prespecified <- nomo_run( data = nomo_demo_network, scales = scales, mode = "research", decisions = list( factor_count = c(Agency = 1, Persistence = 1, SocialDesirability = 1), cfa_model = nomo_model(scales), measurement_model = "proceed" ), settings = list( factors = list(seed = 2026), network = list(hypotheses = h) ) ) run_prespecified ``` This is still not hidden automation: every consequential decision was explicit before the run began. ## Revising with a recorded lineage Suppose the measurement evidence prompts a change. `nomo_revise()` creates a child workflow that keeps the parent as its documented ancestor, instead of starting an unrelated run: ```{r revise} revised <- nomo_revise( run, cfa_model = paste( "Agency =~ ag1 + ag2 + ag3 + ag4", "Persistence =~ pe1 + pe2 + pe3 + pe4", "SocialDesirability =~ sd1 + sd2 + sd3", "ag1 ~~ ag2", sep = "\n" ), rationale = paste( "Items ag1 and ag2 use nearly identical wording, so their residual", "association plausibly exceeds what the common factor explains." ), origin = "post_hoc" ) revised ``` The child reruns the staged evidence with the revised model and pauses at the measurement review, so the revised evidence is inspected before any downstream branch runs. The parent object is unchanged and remains a complete record. ```{r revise-lineage} nomo_table(revised, "lineage")[, c( "revision", "change_type", "origin", "rationale", "comparison" )] ``` Because a revision is a claim that one model is preferable to another, `nomo_revise()` compares them with `nomo_compare()`: ```{r revise-comparison} nomo_table(revised$revision_comparison, "comparisons")[, c( "model", "relation", "chisq_diff", "df_diff", "p_value", "delta_cfi" )] ``` ```{r revise-values, include=FALSE} rev_cmp <- revised$revision_comparison$comparisons rev_log <- revised$decision_log[revised$decision_log$id == "revision", ] fmt_rev_p <- if (rev_cmp$p_value < .001) { "< .001" } else { paste("=", formatC(rev_cmp$p_value, format = "f", digits = 3)) } ``` Here the revision changes almost nothing (chi-square difference = `r sprintf("%.2f", rev_cmp$chisq_diff)`, df = `r rev_cmp$df_diff`, p `r fmt_rev_p`; CFI changes by `r sprintf("%+.4f", rev_cmp$delta_cfi)`). Once the common factor is accounted for, the residual association these two items were expected to share is close to zero, so the data give no reason to prefer the revised model over its parent. A revision is not automatically an improvement, and `nomo_revise()` does not choose: it records the attempt, its rationale, and the comparison, so keeping the parent model is as visible as adopting the revision. The origin still matters. The change was prompted by inspecting these data, so the decision log records it as post hoc and says what follows from that: ```{r revise-log} cat(rev_log$consequence) ``` Revisions chain. A revision of `revised` would carry two lineage rows, so the full path from the original model to the reported one stays visible in `nomo_table(x, "lineage")` and in the report's revision-lineage section. ## Starting from content review Scale development begins before response data exist. The companion package [`contentvalidR`](https://github.com/JUhalt/contentvalidR) handles content review: expert relevance panels, item sorts, and Delphi rounds. It ends in a *handoff*, which records the items carried forward, each held-back item with its reasons, and the rule that made each decision. `nomologR` takes the handoff itself, not a copied list of item names, so the reasons travel with the items. The joint walkthrough for the two packages, [One item set, both stages](https://juhalt.github.io/contentvalidR/articles/one-item-set-both-stages.html), follows one item set from content review in `contentvalidR` through this workflow. In `contentvalidR`, a handoff is made from a fitted review. The chunk below is shown, not run: ```r fit <- contentvalidR::sort_validity(sorts) handoff <- contentvalidR::content_handoff( fit, reverse_keyed = c("EF2", "TF2"), response_scale = c(1, 5) ) ``` `nomologR` does not depend on `contentvalidR`. This package ships the handoff that `contentvalidR` 0.7.0 produces for its walkthrough item sort, so the rest of this section runs without it: ```{r handoff} handoff <- readRDS( system.file("extdata", "content-handoff-walkthrough.rds", package = "nomologR") ) handoff$items ``` Responses to those items, simulated here, with EF2 and TF2 worded in reverse: ```{r handoff-data} set.seed(46) n <- 400 ef <- rnorm(n) tf <- 0.4 * ef + sqrt(1 - 0.4^2) * rnorm(n) likert <- function(f) pmin(5, pmax(1, round(3 + 0.8 * f + rnorm(n, sd = 0.8)))) responses <- data.frame( sapply(paste0("EF", 1:6), function(i) likert(ef)), sapply(paste0("TF", 1:6), function(i) likert(tf)) ) responses$EF2 <- 6 - responses$EF2 responses$TF2 <- 6 - responses$TF2 ``` Passing the handoff as `items` screens only the carried items: ```{r handoff-screen} screened <- nomo_screen(responses, items = handoff, effort = TRUE) screened$items review_log <- nomo_table(screened, "decision_log") cat(review_log$observation[review_log$object %in% c("content_review", "EF5", "TF5")], sep = "\n") ``` EF5 and TF5 were held back by the item sort. They are not analyzed, and nothing here reinstates them. The log quotes their status and recommendation in `contentvalidR`'s own words. The keying declared at content review reached the careless-responding indices without being retyped. An item is never treated as forward keyed because keying was undeclared, and a response scale is never inferred from the data. Passed as `scales`, the handoff defines a guided run. Its design log records that the item membership came from content review, not from these data: ```{r handoff-run} run_reviewed <- nomo_run( responses, scales = handoff, settings = list(factors = list(seed = 46)) ) run_reviewed$scales design <- run_reviewed$decision_log design[design$source == "content_review", c("id", "scope")] ``` A report from this run opens with a content-review section, so the archive starts where the validity argument starts. Some reviews have no construct mapping, such as an expert relevance panel rating a single item set. A handoff from one of those can be screened, but a guided run refuses it. The run needs scales, and `nomologR` does not invent construct membership. A handoff from a later `contentvalidR` release, with a schema version this release does not read, is refused with both package versions named. ## Reproducibility and provenance ```{r provenance} nomo_table(run, "decisions") nomo_table(run, "settings") nomo_table(run, "recipe") head(nomo_table(run, "component_log"), 10) ``` The guided object retains: - source sample roles; - supplied scale membership; - component settings; - explicit decisions and rationales; - complete component result objects; - outstanding decision requests; - component decision and evidence logs; - the sequence of resume calls. This structure feeds the reproducible report described in [Archiving a nomologR workflow](reproducible-report.html). ## Research basis Staged scale development with explicit decisions follows guidance such as Clark and Watson (1995, 2019), Hinkin (1998), and Boateng et al. (2018). Recording decisions and rationales responds to evidence that undisclosed analytic flexibility undermines measurement claims (Simmons, Nelson, & Simonsohn, 2011; Flake, Pek, & Hehman, 2017; Flake & Fried, 2020). Full references are in `?nomo_run` and the [research basis](research-basis.html) article.