The full HiTOP-SR asks 405 items and returns 76 scale scores, which is more than many studies need and more than many participants will sit through. A module is a chosen set of the instrument’s scales, administered and scored on its own.
This article walks the whole module workflow end to end: choosing which scales to include, describing the module, generating the files you will actually field, and scoring the data that comes back. For scoring a complete administration, see Scoring the HiTOP-SR instead.
Choosing Scales
Start from the menu. available_scales() lists every
scale the instrument offers, along with the camelCase stem that will
name its scored output column, the number of items it contributes, and a
brief clinician-facing definition (Brief, printed here
truncated; hitopsr_definitions carries it in full).
scale_menu <- available_scales("hitopsr")
scale_menu
#> # A tibble: 76 × 4
#> Scale camelCase nItems Brief
#> <chr> <chr> <int> <chr>
#> 1 Agoraphobia agoraphobia 5 Fear and avoidance of situat…
#> 2 Antisocial Behavior antisocialBehavior 8 Behavior that goes against s…
#> 3 Appearance Focus appearanceFocus 5 Excessive focus on appearanc…
#> 4 Appetite Loss appetiteLoss 3 Decreased appetite and food …
#> 5 Binge Eating bingeEating 3 Eating unusually large amoun…
#> 6 Bodily Distress bodilyDistress 6 Physical symptoms like pain,…
#> 7 Body Dissatisfaction bodyDissatisfaction 4 Persistent dissatisfaction w…
#> 8 Callousness callousness 6 Disregard for others' feelin…
#> 9 Checking checking 5 Repetitive urges to check ta…
#> 10 Cleaning cleaning 6 Fear of dirt and germs, lead…
#> # ℹ 66 more rowsBoth name columns are accepted when you build a module, so you can
write scale names the way they are printed on the form
("Antisocial Behavior") or the way they appear in scored
output ("antisocialBehavior").
A module is built from these 76 scales only. The instrument also
defines 17 subscales (see hitopsr_subscales), but a
subscale is not a unit you can select here — hitop_module()
rejects a subscale name as unknown. Ask for the scale that contains it
instead.
Because this is an ordinary tibble, you can also use it to plan the length of your module. For example, to see the shortest scales:
scale_menu[order(scale_menu$nItems), ][1:5, ]
#> # A tibble: 5 × 4
#> Scale camelCase nItems Brief
#> <chr> <chr> <int> <chr>
#> 1 Appetite Loss appetiteLoss 3 "Decreased app…
#> 2 Binge Eating bingeEating 3 "Eating unusua…
#> 3 Difficulties Reaching Orgasm difficultiesReachingOrgasm 3 "Trouble reach…
#> 4 Excoriation excoriation 3 "Repetitive sk…
#> 5 Low Sexual Arousal lowSexualArousal 3 "Problems with…Building the Module
hitop_module() turns a set of scale names into a
validated description of the module. It resolves the names, works out
which items those scales need, and records the reverse-keying flags that
scoring will require.
four_scale <- hitop_module(
instrument = "hitopsr",
scales = c(
"Agoraphobia", "Appetite Loss",
"Antisocial Behavior", "Romantic Disinterest"
)
)
four_scale
#> <hitop_module> hitopsr: 21 items from 4 scales
#> * Agoraphobia
#> * Antisocial Behavior
#> * Appetite Loss
#> * Romantic DisinterestThe printed summary reports how many items the module needs. The description itself is a plain list, so you can look inside it:
four_scale$camelCase
#> [1] "agoraphobia" "antisocialBehavior" "appetiteLoss"
#> [4] "romanticDisinterest"
four_scale$nItems
#> [1] 21
four_scale$items
#> [1] 42 66 68 109 118 144 152 156 167 185 187 202 239 260 268 274 291 310 338
#> [20] 389 390Note that $items holds the original
HiTOP-SR item numbers. That is true of the descriptor whatever a
generator later prints: item 42 of the full instrument is item 42 here,
which is what lets data collected with a module be scored against the
full instrument’s key.
Generating the Instrument
Each of the three HiTOP-SR generators takes a module
argument. Pass the description and you get a form containing only that
module’s items.
The three do not number those items alike, on purpose. The Word form
is what a participant fills in on paper, so
generate_docx_hitopsr() numbers it 1 to
n down the page – a four-scale module reads 1, 2, 3 rather
than 7, 42, 213. Pass renumber = FALSE to keep the original
numbers instead. In the Qualtrics and REDCap exports an item number
names a collected data column, so those keep the original HiTOP-SR
numbers always; renumbering them would rename variables in dictionaries
already in the field.
A Word form also says on its face that it is a module.
generate_docx_hitopsr() heads a module form
HiTOP-SR Module (v1.0) where it heads a full-instrument
form HiTOP-SR (v1.0). Pass title = to print
something else — a study name, say — and that string is used verbatim on
either kind of form.
One combination the Word generator refuses is
include_subscales = TRUE together with module:
a subscale can draw items from scales outside the module, so its scoring
row would list items the form does not contain. The call aborts rather
than print a row that cannot be scored.
generate_docx_hitopsr() also takes
randomize = TRUE, which prints the items in a random order.
A renumbered module form — the default — is still numbered
1 to n and carries a crosswalk from each
printed number back to its original HiTOP-SR number, printed whether or
not include_scoring appends the key, so it can be scored
from the paper alone. That is the only case the crosswalk is printed in.
Under renumber = FALSE there is none, because the printed
numbers already are the original ones; and shuffling the full instrument
prints none either, since 405 pairs would fill a page. In both of those
cases read the order from the item_order attribute of the
returned path, or save it with descriptor = below. Call
set.seed() beforehand to make an order reproducible.
One thing to watch: by default score_hitopsr() expects a
module’s items in ascending original order, not the
order a shuffled form prints them in. Data entered straight off a
shuffled form has its columns in the order the form printed: the first
column holds the answer to the paper’s item 1, the second the answer to
item 2, and so on. Score such columns with
layout = "printed" and a module that carries the printed
order on its item_order attribute, which is what the
descriptor saved with descriptor = below gives you. The
function then puts the columns back into instrument order itself.
Scoring printed-order columns under the default layout returns wrong
scale scores with no error raised, and scoring columns already in
instrument order under layout = "printed" scrambles what
was right.
Here we write all three formats into a temporary folder; in your own work you would give a real path or let the default filename land in your working directory.
outdir <- tempdir()
docx_file <- generate_docx_hitopsr(
file = file.path(outdir, "hitopsr_module.docx"),
module = four_scale
)
qualtrics_file <- generate_qualtrics_hitopsr(
file = file.path(outdir, "hitopsr_module.txt"),
module = four_scale
)
redcap_file <- generate_redcap_hitopsr(
file = file.path(outdir, "hitopsr_module.zip"),
module = four_scale
)
basename(c(docx_file, qualtrics_file, redcap_file))
#> [1] "hitopsr_module.docx" "hitopsr_module.txt" "hitopsr_module.zip"The DOCX file is a paper form ready to print, the .txt
file imports into Qualtrics as an advanced-format question block, and
the .zip file imports into REDCap as an instrument. See Importing Instruments into Qualtrics and
REDCap for the import steps themselves.
Selecting the Collected Columns
Once the data comes back, it has only the module’s item columns — plus whatever else your platform recorded, such as participant identifiers and demographics. Both scoring functions need you to name those item columns.
We will stand in for module-collected data using
ku_hitopsr, the package’s real example dataset. Its first
two columns are participant and biosex, and
its 405 item columns are named hsr_001 through
hsr_405:
data("ku_hitopsr")
names(ku_hitopsr)[1:5]
#> [1] "participant" "biosex" "hsr_001" "hsr_002" "hsr_003"Select the item columns by name, never by position.
The numbers in four_scale$items are item numbers, not
column numbers. They coincide only in a data frame that is exactly the
405 items in order — and this one is not, because two other columns come
first. Build the column names instead:
item_cols <- sprintf("hsr_%03d", four_scale$items)
collected <- ku_hitopsr[item_cols]
ncol(collected)
#> [1] 21That width matches the module’s own item count, which is the check worth running before you score anything:
ncol(collected) == four_scale$nItems
#> [1] TRUEIndexing by four_scale$items directly would have
selected columns 42, 66, 68, and so on by position, which in
this frame are the wrong items — shifted by the two leading columns:
names(ku_hitopsr[four_scale$items])[1:4]
#> [1] "hsr_040" "hsr_064" "hsr_066" "hsr_107"Those are items 40, 64, 66, and 107 masquerading as the module’s
items. Nothing would have failed loudly; the scores would simply have
been wrong. If your item columns carry other names,
rename_hitopsr_items() can rename them to standard item
numbers first; its prefix argument sets the stem those
names are built from.
Scoring and Reliability
Hand the same description back through the module
argument. Without it, score_hitopsr() expects a full
administration and stops on the item count (“Expected 405 items but got
21”); with it, the items argument names the columns you
actually have, in instrument order, and module tells the
function which scales they belong to.
module_scores <- score_hitopsr(
data = collected,
items = names(collected),
module = four_scale,
append = FALSE
)
module_scores
#> # A tibble: 411 × 4
#> hsr_agoraphobia hsr_antisocialBehavior hsr_appetiteLoss
#> <dbl> <dbl> <dbl>
#> 1 1 1.5 1
#> 2 1.4 1 1.67
#> 3 2 1.12 1
#> 4 1.2 1 1.33
#> 5 1.6 1 2.67
#> 6 1 1 1.33
#> 7 1 1 1
#> 8 1 1 1.33
#> 9 2 1 1
#> 10 1 1 1
#> # ℹ 401 more rows
#> # ℹ 1 more variable: hsr_romanticDisinterest <dbl>Only the module’s scales come back, in the order they appear in
hitopsr_scales. The values are exactly what a full
administration would have produced for those scales — a scale score
depends only on its own items, so dropping the other 72 scales’ columns
cannot move it:
full_scores <- score_hitopsr(
data = ku_hitopsr,
items = sprintf("hsr_%03d", 1:405),
append = FALSE
)
all.equal(module_scores, full_scores[names(module_scores)])
#> [1] TRUEreliability_hitopsr() takes the same argument and
returns one row per module scale.
reliability_hitopsr(
data = collected,
items = names(collected),
module = four_scale,
omega = FALSE
)
#> # A tibble: 4 × 4
#> Scale camelCase nItems alpha
#> <chr> <chr> <int> <dbl>
#> 1 Agoraphobia agoraphobia 5 0.820
#> 2 Antisocial Behavior antisocialBehavior 8 0.351
#> 3 Appetite Loss appetiteLoss 3 0.762
#> 4 Romantic Disinterest romanticDisinterest 5 0.821Reliability is worth checking on your own module rather than assumed.
In this sample the four coefficients run from modest down to negative:
Romantic Disinterest returns a negative α, which is not a small number
but a sign that its items covary near zero or oppositely in these data,
so a mean of them is not measuring one thing. Short scales make α less
stable, but shortness alone does not produce a negative value. Set
omega = FALSE to skip McDonald’s ω, which needs the
lavaan package.
The srange, prefix, missing,
calc_se, and append arguments all behave
exactly as they do for a full administration; see Scoring the HiTOP-SR for those.
calc_se is deprecated there and here alike
— it, and the _se columns it adds, will be removed in a
future release, and interval_hitopsr() replaces it.
Saving the Module Beside the Form
Everything above depends on still having four_scale when
the data comes back. Months later, in a fresh session, rebuilding it
means retyping every scale name — and a typo scores the wrong scales
while a forgotten scale scores none.
write_module() saves the description to a small JSON
file you can keep beside the forms you generated:
descriptor <- write_module(four_scale, file.path(outdir, "hitopsr_module.json"))
cat(readLines(descriptor), sep = "\n")
#> {
#> "format": "1.0",
#> "package": "hitop",
#> "packageVersion": "0.2.0",
#> "buildDate": "2026-09-11",
#> "instrument": "hitopsr",
#> "scales": ["Agoraphobia", "Antisocial Behavior", "Appetite Loss", "Romantic Disinterest"],
#> "items": [42, 66, 68, 109, 118, 144, 152, 156, 167, 185, 187, 202, 239, 260, 268, 274, 291, 310, 338, 389, 390],
#> "nItems": 21
#> }You do not have to remember to call it. Each of the three generators
takes a descriptor argument, so one call writes both the
form and the file that scores it:
generate_docx_hitopsr(
file = file.path(outdir, "hitopsr_module2.docx"),
module = four_scale,
descriptor = file.path(outdir, "hitopsr_module2.json")
)A call that passes no module writes a descriptor naming
every scale, so a full administration is described too. And on a
shuffled Word form the descriptor also records the printed order, which
is the one thing a whole-instrument form gives you nowhere else — no
crosswalk is printed for one:
set.seed(42)
shuffled_descriptor <- file.path(outdir, "hitopsr_shuffled.json")
generate_docx_hitopsr(
file = file.path(outdir, "hitopsr_shuffled.docx"),
module = four_scale,
randomize = TRUE,
descriptor = shuffled_descriptor
)
shuffled <- read_module(shuffled_descriptor)
attr(shuffled, "item_order")
#> [1] 291 118 42 185 109 66 310 338 156 152 390 167 389 144 187 274 239 68 202
#> [20] 260 268Those are the original HiTOP-SR item numbers in the order the page
printed them. Responses entered off that form have their columns in the
order the form printed, the first column holding the paper’s item 1.
Score them with layout = "printed" and the module that
carries the order:
score_hitopsr(collected, items = seq_along(collected), module = shuffled,
layout = "printed")The scoring function puts the columns back into instrument order
itself. Columns already in instrument order take the default,
layout = "instrument".
The file is plain text, so you can read it, edit it, and send it to a
collaborator. read_module() turns it back into a
module:
reloaded <- read_module(descriptor)
reloaded
#> <hitop_module> hitopsr: 21 items from 4 scales
#> * Agoraphobia
#> * Antisocial Behavior
#> * Appetite Loss
#> * Romantic DisinterestWhat comes back is not simply what the file said. The file records
scale names; the items and their reverse-keying flags are
rebuilt from this package’s own tables, so a descriptor can never
introduce a scoring key of its own. The items the file
records are checked against that rebuild, and a disagreement stops with
an error rather than scoring quietly — which is what you want if the
file was written by an older version of the package whose tables have
since changed.
Hand the reloaded module to score_hitopsr() exactly as
you would the original:
identical(
score_hitopsr(collected, items = names(collected), module = reloaded, append = FALSE),
module_scores
)
#> [1] TRUE