Skip to contents

This tutorial starts with an existing study directory and an existing dataset. It takes the study through identity setup, data registration, descriptive tables and EDA plots. A later tutorial can then begin with a specific analysis type instead of repeating this setup.

Adoption is additive. It creates the hvtiR study contract without renaming the legacy working directories or deleting old files.

If the study does not exist yet and you are starting from a newly delivered dataset, follow Start a new study instead.

The executable example uses one 40-row synthetic dataset. Its files stay in a temporary directory and disappear after the article renders. For translating existing SAS descriptive jobs, see From SAS descriptive jobs to R.

Open the study as an RStudio Project

In RStudio, choose File > New Project > Existing Directory and select the study directory. Open that .Rproj whenever you work on the study. RStudio starts the session in the project directory, so there is no working-directory step to remember or repeat.

Confirm the location before changing the study:

When the Console is already at the study root, use "." anywhere a function asks for root. The executable example below uses an explicit temporary path only because it cannot open an RStudio Project while pkgdown renders the page.

Select the R version

If the study already has an renv.lock, its R Version entry records the R version to preserve. Select that version in RStudio and restart the session. Do not silently upgrade an already pinned study.

If the study is not already pinned, select R 4.6 instead of the older 4.4.1 default, restart the session and confirm the active version:

R.version.string

Load the study packages

Install or update the hvtiR family, then check whether anything else is missing or out of date:

pak::pak("ehrlinger/hvtiR")
hvtiR::status()

In the study session, attach the two packages used throughout this tutorial:

Adopt the existing study

study_setup() writes _study.yml, environment defaults and a project file when one is missing. With adopt = TRUE, it keeps the existing directory layout and every existing study file. Use the title and ID from Study Tracker:

study_setup(
  root = ".",
  study = "Study title from Study Tracker",
  study_tracker_id = 42L,
  adopt = TRUE
)
study_root()
study_status()

The current API requires root for this first call. Here "." is the project directory that RStudio established. Once _study.yml exists, study_root(), open_job() and render_job() find it from anywhere inside the project.

The executable example uses its temporary directory instead:

# A vignette builds a throwaway study; in a real study, run hvtiR::setup().
study_setup(
  adopted_root,
  study = "Synthetic legacy study",
  study_tracker_id = 42L,
  adopt = TRUE,
  cleanup = FALSE,
  renv = FALSE
)
#> Study: /tmp/RtmpH93zVz/file207f4f195510/legacy-study
#> 
#> [x] _study.yml — study: Synthetic legacy study
#> [ ] renv.lock — no renv.lock; run renv::init() in the study project
#> [ ] manifest.yaml — no manifest.yaml; register_data() creates it
#> [ ] dataset — no default dataset registered; run register_data()
#> [ ] provenance — no .qmd/.Rmd sources found; 0 sidecars
#> 
#> 0 .R  |  0 .qmd/.Rmd  |  0 .sas  |  0 provenance sidecars
root <- adopted_root

Pin the package environment

study_status() reports a missing renv.lock until the study initializes its package environment. For an unpinned study, do this after selecting R 4.6 and restarting RStudio.

First open RStudio’s Terminal tab at the study root and run:

organize_templates.sh

This qhsprograms script creates templates/ under each first-level study folder, moves that folder’s tp* files into it and replaces the root .renvignore with the centrally managed copy used for dependency discovery. Before running it, make sure a destination templates/ directory does not already contain a same-named file. Then return to the Console:

renv::init()

# Install or update the packages used by the study, then record them.
renv::snapshot()

Snapshot again when an accepted result needs a new reproducible package state. The temporary example does not run renv::init() because it changes the active R project while the article renders.

Register the existing dataset

Registration records the input filename, checksum and population. It does not declare a study-wide endpoint or cohort: an endpoint-free descriptive job and an event-time analysis can read the same dataset with different valid job definitions.

register_data(
  built = "built.csv",
  role = "study",
  population = "Patients meeting the study inclusion criteria"
)
verify_manifest()

Do not invent an outcome or follow-up field during setup. Each analysis job declares its own outcome variables, coding, filters and cohort checks. The descriptive jobs below depend only on a registered study dataset.

register_data(
  adopted_root,
  built = "built.csv",
  role = "study",
  population = "Synthetic full cohort"
)
#> Study: /tmp/RtmpH93zVz/file207f4f195510/legacy-study
#> 
#> [x] _study.yml — study: Synthetic legacy study
#> [ ] renv.lock — no renv.lock; run renv::init() in the study project
#> [x] manifest.yaml — 1 dataset entry verified by checksum
#> [x] dataset — built.csv, registered as built_20261022.parquet
#> [ ] provenance — no .qmd/.Rmd sources found; 0 sidecars
#> 
#> 0 .R  |  0 .qmd/.Rmd  |  0 .sas  |  0 provenance sidecars
verify_manifest(file.path(adopted_root, "manifest.yaml"))
cfg <- study_config(adopted_root)
study_data <- read_built(cfg)
nrow(study_data)
#> [1] 40

Compare the registered file and its manifest checksum with the data build before proceeding. A job that filters this data, or analyses an event and follow-up time, must derive and check its own reference counts.

If EDA uses a subset

The jobs can read the registered study dataset directly. If EDA needs a subset, create it with a stated rule, save it as a separate dataset and register that file too. For example:

study_data <- read_built(study_config())
eda <- subset(study_data, age >= 50)  # Replace with the study's actual rule.
utils::write.csv(
  eda, file.path(study_dir("datasets"), "eda.csv"), row.names = FALSE
)
register_data(
  built = "eda.csv",
  dataset = "eda",
  role = "named",
  kind = "subset",
  population = "Patients age 50 or older"
)

In a job that uses this subset, set DATASET <- "eda" and ANALYSIS_SET <- NULL. When the whole registered study dataset is the intended population, use DATASET <- "built" and ANALYSIS_SET <- NULL instead. For a subset of the study dataset that several jobs share, an analysis set is the alternative: write it with hvtiRdatabuild::write_analysis_set() and read it with DATASET <- "built" and the set’s name in ANALYSIS_SET.

Create the descriptive and EDA jobs

open_job() creates a missing job and opens it in RStudio. If the job already exists, it opens the study-authored file without replacing any edits.

Understand the name

Each call supplies four fields. prefix and qualifier select a template; the other two fields keep related outputs together:

  • dc means descriptive: the checks, tables and EDA plots. Legacy studies also hold dp (descriptive plot) jobs, which these templates replace.
  • Qualifiers such as general, tables and eda select one template in that family.
  • During EDA there may be no modeled endpoint or analysis method. cohort names the subject being described and eda names the stage. These are organizational labels, not invented analysis choices.

For example, open_job("dc", "cohort", "eda", qualifier = "tables") creates descriptive/dc.tables.cohort.eda.qmd in an adopted legacy study. Its HTML report stays beside the QMD, while its Word table goes under documents/cohort-eda/. The trends and EDA report jobs sit in descriptive/ too, and their plot PNGs go under graphs/cohort-eda/.

Start with the generally applicable jobs

Create general checks and descriptive tables for every registered study:

general <- open_job("dc", "cohort", "eda", qualifier = "general")
tables <- open_job("dc", "cohort", "eda", qualifier = "tables")

The EDA report template is also part of this stage, but it requires a meaningful X_VAR for the horizontal axis. Create it when the dataset has an appropriate reference date, calendar year or follow-up measure:

eda <- open_job("dc", "cohort", "eda", qualifier = "eda")

Its follow-up section needs the death and follow-up fields; take "followup" out of SECTIONS when the data do not have them; SECTIONS <- c("continuous", "percent", "count") draws the variable sections alone.

Two additional jobs are conditional. Add dc-gfup only when the registered event and follow-up fields answer a real completeness question. Add dc-trends only when the data have a meaningful calendar or operation-time field:

gfup <- open_job("dc", "cohort", "eda", qualifier = "gfup")
trends <- open_job("dc", "cohort", "eda", qualifier = "trends")

The synthetic data have all of those fields, so the executable example scaffolds all five jobs without opening editor windows:

study_jobs <- c(
  general = add_job("dc", "cohort", "eda", adopted_root, "general"),
  tables = add_job("dc", "cohort", "eda", adopted_root, "tables"),
  gfup = add_job("dc", "cohort", "eda", adopted_root, "gfup"),
  trends = add_job("dc", "cohort", "eda", adopted_root, "trends"),
  eda = add_job("dc", "cohort", "eda", adopted_root, "eda")
)
study_jobs
#>                                                                               general 
#> "/tmp/RtmpH93zVz/file207f4f195510/legacy-study/descriptive/dc.general.cohort.eda.qmd" 
#>                                                                                tables 
#>  "/tmp/RtmpH93zVz/file207f4f195510/legacy-study/descriptive/dc.tables.cohort.eda.qmd" 
#>                                                                                  gfup 
#>    "/tmp/RtmpH93zVz/file207f4f195510/legacy-study/descriptive/dc.gfup.cohort.eda.qmd" 
#>                                                                                trends 
#>  "/tmp/RtmpH93zVz/file207f4f195510/legacy-study/descriptive/dc.trends.cohort.eda.qmd" 
#>                                                                                   eda 
#>     "/tmp/RtmpH93zVz/file207f4f195510/legacy-study/descriptive/dc.eda.cohort.eda.qmd"

Work the jobs and generate output

Each new job contains EDIT: markers for decisions that cannot come from the template: variables, labels, groups, units and output choices. open_job() opens the QMD in RStudio’s file editor. Edit the file there, working from the top marker downward, and Render to see what your choices do. A draft render carries a DRAFT banner while any marker remains.

Always render; do not run the chunks by hand with Run All. Rendering starts at the top in a clean session, much like running a SAS job from beginning to end, so the report comes from this job alone. Run All works in your current session, where an object or a setting left by another job can stand in for one this job does not make. Render every edited job before leaving the study so the saved QMD is still in an executable state. If you want the same final check from the Console, run render_job(tables, final = TRUE), substituting the job you edited.

The HTML report stays beside its QMD in descriptive/. The descriptive Word table is filed under documents/cohort-eda/; trend and EDA PNGs are filed under graphs/cohort-eda/. Open those artifacts and check the population, variables, labels, counts, summaries and axes—not merely that the files exist.

If the study has older SAS jobs, continue with From SAS descriptive jobs to R. That article owns the detailed source mapping and migration workflow so adoption itself remains short and non-destructive.

When registered data change

Registration is endpoint-neutral: it records the dataset file, its population description and manifest facts, but does not declare a study-wide endpoint or cohort. After the study dataset is rebuilt, review the rebuilt file before registering it. hvtiRutilities::review_built() compares the working file in the datasets folder with its registered version and describes how the data changed, without writing anything. It does not certify that the new file is analytically or clinically correct; that judgment stays with you. When the review reads as expected, hvtiRutilities::update_manifest(dataset = "study") registers the rebuilt file as the next dated version, keeps every earlier version and records the change in manifest.yaml. Jobs read the new version from then on, so review dependent analysis sets and rerender their jobs.