Who this is for
This vignette is for readers meeting these methods for the first time: graduate students developing a scale, and researchers who need to report content-validity evidence without first reading five primary sources.
It walks through what each part of the output means, what it does not mean, and how to describe the result in a manuscript. The other vignettes show how to run each workflow; this one shows how to read what comes back.
If you only remember one thing: every statistic here is evidence for a judgment you make, not a judgment the package makes for you.
The shared vocabulary
Every flagship workflow returns an object with the same five parts.
| Component | What it holds |
|---|---|
results |
One row per unit of analysis, with a status and a plain-language interpretation |
scale_summary |
The same evidence aggregated to the scale or panel level |
settings |
Every threshold and option that produced this result |
design |
What data you actually had: counts, missingness, effective judge numbers |
details |
Supporting tables and component analyses |
The unit of analysis in results differs by workflow, and
this catches people out. sort_validity(),
rating_validity(), and expert_validity()
return one row per item. judge_validity()
returns one row per judge.
domain_validity() returns one row per blueprint
cell.
Every results table carries a status column
drawn from a shared vocabulary:
statuses <- attr(contentvalid_glossary(), "statuses")
writeLines(strwrap(paste0(statuses$status, ": ", statuses$meaning),
exdent = 2))
#> Supported: The evidence met the criteria set for this analysis.
#> Review: Something here needs a closer look. This is not an instruction
#> to delete anything.
#> Insufficient data: Too little usable data to reach a judgment.
#> Descriptive only: Reported for description only; no decision rule was
#> applied.Review is the one people misread. It does not mean
“delete this.” It means something here rewards a closer look, and you
are the one who looks.
Reading an item-sort result
sorts <- read.csv(
system.file("extdata", "sort_example.csv", package = "contentvalidR"),
stringsAsFactors = FALSE
)
fit_sort <- sort_validity(sorts)
fit_sort
#> <contentvalid_sort> Item-sort analysis
#> Items: 6 | Judges: 20 | Target constructs: 3
#> Test: Howard-Melloy exact target-count test (p0 = .50, alpha = .05)
#> Judges: naive, meaning drawn from the kind of people who will answer the
#> items.
#>
#> 4 of 6 items meet the exact target-assignment criterion.
#> Flagged for review: B2, C2
#>
#> Item-level evidence
#> Item Target Decision Judges Psa 95% CI Csv Competitor p
#> A1 A Retain 18/20 .90 [.70, .97] .85 B; C < .001
#> A2 A Retain 15/20 .75 [.53, .89] .60 B .021
#> B1 B Retain 17/20 .85 [.64, .95] .75 A .001
#> B2 B Review 13/20 .65 [.43, .82] .40 A .132
#> C1 C Retain 18/20 .90 [.70, .97] .85 A; B < .001
#> C2 C Review 14/20 .70 [.48, .85] .50 B .058
#>
#> Judges: assignments to the target construct, out of the judges who sorted
#> the item.
#> 95% intervals for proportions: Wilson score (the default). Newcombe (1998)
#> compared seven methods and recommends score intervals over the Wald
#> interval. An interval reflects how few ratings an item received, not whether
#> the right people rated it.
#>
#> Scale-level Colquitt benchmarks
#> Target Items Mean Psa Psa level Mean Csv Csv level
#> A 2 .83 Strong .73 Strong
#> B 2 .75 Moderate .58 Moderate
#> C 2 .80 Moderate .68 Strong
#> Benchmark set: Overall (not correlation-normed)
#>
#> Colquitt labels are empirical percentile norms derived from scale-level
#> averages, not universal cutoffs or automatic scale-retention rules. They
#> place a scale against published scales. Psa and Csv sit on different scales,
#> so their values cannot be compared with each other; their labels can,
#> because each is a percentile position among published scales.
#>
#> What these columns mean
#> Psa -- Proportion of Substantive Agreement. Share of judges who put the item
#> in the construct it was written for (0 to 1; higher is stronger).
#> 95% CI -- Interval for Psa. Wider when fewer judges sorted the item; the
#> method is named above.
#> Csv -- Coefficient of Substantive Validity. How much more often judges chose
#> the intended construct than its closest rival (-1 to 1; 0 is a tie).
#> Competitor -- Strongest competing construct. The construct other than the
#> intended one that judges chose most often.
#> p -- Howard-Melloy exact test. Probability of at least this many target
#> assignments if each judge picked the target at rate p0; compare with
#> alpha.
#>
#> What the decisions mean
#> Retain -- met the exact target-assignment criterion.
#> Review -- did not meet the exact target-assignment criterion; the competitor
#> column shows where judges put it instead.
#>
#> Full definitions: contentvalid_glossary(). To hide this key:
#> options(contentvalidR.show_key = FALSE).
#>
#> A flag for review is not an automatic deletion decision. Use theory,
#> construct-domain coverage, item wording, and qualitative judge feedback
#> alongside these statistics.
#>
#> See summary(x) for the flagged items and content_report(x) for an APA table.The printout opens with a header line naming the object’s class and
what it holds
(<contentvalid_sort> Item-sort analysis), then the
facts of the design: how many items and judges, and the test applied.
Then comes the verdict (how many items met the criterion, and which were
flagged), and then one row per item. Working across the item table:
- Decision — the workflow’s verdict for the item, stated first so the numbers after it read as the reasons.
-
Judges — how many judges put the item where you
intended, out of how many judged it (
n_targetandninresults). Always read this before the coefficients: an impressive Psa computed from four judges is not impressive. - Psa — the proportion of judges who assigned the item to its intended construct. It answers “did judges recognize what this item is about?” The 95% CI beside it shows how precisely that proportion is known.
- Csv — how much more often the item went to its target than to its competitor. An item can have a decent Psa and a poor Csv if one rival construct keeps attracting it. That pattern means your two construct definitions overlap, which is a definitional problem rather than a wording problem.
- Competitor — the construct judges picked most often instead. This is the single most useful diagnostic column in the table, because it tells you where a weak item drifted, which points at the fix.
-
p — the Howard-Melloy exact test against the
benchmark rate
p0, .50 by default (p_valueinresults). That benchmark is not the rate random sorting would give, which is 1 divided by the number of constructs.
Numbers follow the APA style rules (American Psychological
Association, 2020, Section 6.36). A statistic that cannot exceed 1, such
as a proportion or a p value, is printed without a leading zero
(.90). One that can exceed 1 keeps it (0.57). A p value below
.001 is printed as < .001, and one that would round to 1 as >
.999. Exact ties round half up, so 5 of 8 judges (.625) prints as .63. A
value that could not be computed prints as --, and the
printout says why. The results table keeps every value at
full precision.
In a narrow console, columns that do not fit are left out from the right, never the item, its decision or the statistic the decision rests on, and a line beneath the table names them and the call that shows them. A table with nothing it may leave out prints wider than the console.
The status words read across this package and nomologR as follows
(the same table is in ?contentvalidR):
| contentvalidR | nomologR |
|---|---|
| Supported | no flag |
| Review | review |
| (no counterpart) | concern |
| Insufficient data | not computed |
| Descriptive only | note |
“Review” always means look again, never delete.
The scale-level table adds Colquitt strength labels. These are percentile positions relative to scales published in the literature, not absolute judgments, and they are the most frequently misreported part of this output.
The benchmark trap
Here is the misreading to guard against, using construct ratings:
ratings <- read.csv(
system.file("extdata", "rating_example.csv", package = "contentvalidR"),
stringsAsFactors = FALSE
)
fit_rating <- rating_validity(ratings)
fit_rating$scale_summary[, c("target", "mean_htc", "htc_strength",
"mean_htd", "htd_strength")]
#> target mean_htc htc_strength mean_htd htd_strength
#> 1 A 0.8333333 Weak 0.4375 Very Strong
#> 2 B 0.8333333 Weak 0.4375 Very Strong
#> 3 C 0.8333333 Weak 0.4375 Very StrongAn HTC of about .83 is labeled Weak, while an HTD of
about .44 in the same row is labeled Very Strong. Read as
raw numbers this looks backwards, and it is a common source of
confusion.
The two indices are not on the same scale and their numbers are not comparable:
- HTC is an average rating expressed as a proportion of the scale. Across published scales the Moderate band starts at .84 and Very Strong at .91, so .83 genuinely sits low against that distribution.
- HTD is a difference between the target rating and the other constructs’ ratings, averaged over all of them, so it is much smaller by construction. Its Moderate band starts at .18 and Very Strong at .35, so .44 genuinely sits high.
Compare each index against its own benchmark. Never compare an HTC number with an HTD number, and never treat a benchmark label as a cutoff for retention.
colquitt_benchmarks("htc")
#> <contentvalid_colquitt_norms> Benchmarks for HTC
#> Colquitt et al. (2019). Benchmark set: Overall (not correlation-normed).
#>
#> Band Percentile Minimum
#> Very Strong 80th-99th .91
#> Strong 60th-79th .87
#> Moderate 40th-59th .84
#> Weak 20th-39th .60
#> Lack of 0th-19th none
#>
#> A scale-level mean at or above a band's minimum falls in that band. The bands
#> are percentiles of published scales, not validity cutoffs.
#>
#> See as.data.frame(x) for the unrounded values.
colquitt_benchmarks("htd")
#> <contentvalid_colquitt_norms> Benchmarks for HTD
#> Colquitt et al. (2019). Benchmark set: Overall (not correlation-normed).
#>
#> Band Percentile Minimum
#> Very Strong 80th-99th .35
#> Strong 60th-79th .27
#> Moderate 40th-59th .18
#> Weak 20th-39th .04
#> Lack of 0th-19th none
#>
#> A scale-level mean at or above a band's minimum falls in that band. The bands
#> are percentiles of published scales, not validity cutoffs.
#>
#> See as.data.frame(x) for the unrounded values.Reading expert-panel evidence
relevance <- read.csv(
system.file("extdata", "expert_relevance_example.csv", package = "contentvalidR"),
stringsAsFactors = FALSE
)
panel <- as.matrix(relevance[, setdiff(names(relevance), "expert")])
fit_expert <- expert_validity(panel, mode = "relevance", lo = 1, hi = 4)
fit_expert$results[, c("item", "N", "V", "I_CVI", "I_CVI_low", "I_CVI_high",
"kappa_mod", "recommendation")]
#> item N V I_CVI I_CVI_low I_CVI_high kappa_mod recommendation
#> 1 Item1 8 1.0000000 1.00 0.67559244 1.0000000 1.0000000 Strong support
#> 2 Item2 8 0.9166667 1.00 0.67559244 1.0000000 1.0000000 Strong support
#> 3 Item3 8 0.8333333 1.00 0.67559244 1.0000000 1.0000000 Strong support
#> 4 Item4 8 0.5833333 0.75 0.40927543 0.9285208 0.7192982 Review
#> 5 Item5 8 0.4166667 0.25 0.07147921 0.5907246 0.1578947 ReviewI_CVI is the proportion of experts calling the item
relevant. kappa_mod corrects that for chance agreement, and
the gap between them matters most with small panels, where several
experts can agree by luck alone. Aiken’s V uses the whole
rating scale rather than a relevance cut, so it distinguishes items that
I_CVI rates identically.
Note that the I-CVI criterion depends on panel size. It follows Lynn
(1986): with three to five experts all must agree, from six one may
disagree, and from nine two may. Her table stops at ten experts; beyond
it the package holds her lowest tabled proportion, 7 of 9, which is this
package’s extension of her rule. cvi_criterion shows the
proportion that applies to each item’s panel, so 7 of 9 appears as .778.
An item can therefore change status simply because a judge was added or
dropped.
Reading the intervals
I_CVI_low and I_CVI_high bound the I-CVI.
With eight experts, an item that every expert rated relevant still has a
lower limit well below 1: a panel that size cannot rule out a noticeably
lower relevance rate. Read the interval before treating an I-CVI as
settled, and report it alongside the point estimate.
The interval method is a choice. The Wilson score interval is the default, following Newcombe (1998), and alternatives are available when a study needs to match earlier work:
exact <- expert_validity(panel, mode = "relevance", lo = 1, hi = 4,
proportion_ci = "exact")
exact$results[, c("item", "I_CVI", "I_CVI_low", "I_CVI_high")]
#> item I_CVI I_CVI_low I_CVI_high
#> 1 Item1 1.00 0.63058335 1.0000000
#> 2 Item2 1.00 0.63058335 1.0000000
#> 3 Item3 1.00 0.63058335 1.0000000
#> 4 Item4 0.75 0.34914421 0.9681460
#> 5 Item5 0.25 0.03185403 0.6508558The exact (Clopper-Pearson) interval is conservative, so its limits sit further apart. The printed output always names the method that produced the interval.
Reading panel agreement
Relevance mode also reports one agreement coefficient for the whole
panel, in scale_summary as agreement,
agreement_low, and agreement_high. The full
result, with its explanation, is in details$agreement, and
panel_agreement() produces the same result directly:
panel_agreement(panel, seed = 1)
#> <contentvalid_agreement> Panel agreement
#> Items rated by two or more raters: 5 | Raters: 8
#> Krippendorff's alpha (ordinal) = .69, 95% CI [.07, .86]
#> Identical rating pairs: 62.9%
#>
#> Alpha compares the disagreement observed within items with the disagreement
#> expected if these same ratings were assigned to items at random: 1 means
#> perfect agreement and 0 means agreement no better than chance. Alpha falls
#> when ratings cluster on a few values, because little disagreement is then
#> expected by chance. A high share of identical rating pairs alongside a low
#> alpha reflects that clustering, which is common when nearly every item is
#> rated relevant, and is not by itself evidence of a poor panel.
#>
#> Krippendorff's alpha is the default because it handles ordinal ratings and
#> missing ratings (Zapf et al., 2016). It is a general reliability coefficient;
#> no publication applying it specifically to content-validity panels was found.
#>
#> The interval resamples items with all of their ratings, following Zapf et al.
#> (2016), and varies slightly between runs unless `seed` is set.
#>
#> Panel agreement describes how consistently raters rated these items. It does
#> not show that the items are relevant or that the domain is covered.
#>
#> See as.data.frame(x) for the estimate as one row.Read two numbers together: the coefficient and the share of identical rating pairs. Krippendorff’s alpha, the default, can be low on a panel that agrees closely when nearly every rating is the same value, because chance then predicts very little disagreement. A low alpha next to a high share of identical pairs reflects that clustering. Agreement describes the raters, not the items: a consistent panel can still consistently rate an item as irrelevant.
Reading judge heterogeneity
judge_validity() asks a different question: do your
conclusions depend on these particular judges?
judge_ratings <- rbind(
c(4, 4, 4, 3, 2, 2), c(4, 4, 3, 4, 2, 1), c(4, 3, 4, 4, 1, 2),
c(3, 4, 4, 4, 2, 2), c(4, 4, 4, 4, 2, 1), c(4, 3, 4, 3, 1, 2),
c(4, 4, 3, 4, 2, 2), c(2, 2, 2, 2, 1, 1)
)
dimnames(judge_ratings) <- list(paste0("Judge", 1:8), paste0("Item", 1:6))
fit_judge <- judge_validity(judge_ratings, lo = 1, hi = 4)
fit_judge$results[, c("judge", "mean_rating", "severity_raw",
"differentiation", "recommendation")]
#> judge mean_rating severity_raw differentiation recommendation
#> 1 Judge1 3.166667 -0.2708333 0.9136465 Typical
#> 2 Judge2 3.000000 -0.1041667 1.1754383 Typical
#> 3 Judge3 3.000000 -0.1041667 1.1754383 Typical
#> 4 Judge4 3.166667 -0.2708333 0.9136465 Typical
#> 5 Judge5 3.166667 -0.2708333 1.2351428 Typical
#> 6 Judge6 2.833333 0.0625000 1.0863535 Typical
#> 7 Judge7 3.166667 -0.2708333 0.9136465 Typical
#> 8 Judge8 1.666667 1.2291667 0.4798707 Severeseverity_raw is signed so that positive means
harsher: the judge rates lower than the panel.
differentiation near 1 means the judge used the scale about
as widely as everyone else. The cuts that turn these into a flag are
conventions of this package; print(fit_judge) states them,
and each one is an argument, so you can apply your own.
A judge flagged here is not a judge to delete. A dissenting expert may be the one reading the construct definition correctly. The flag marks ratings worth a second look before you write the study up.
Whether a conclusion rests on a single judge is reported for the items, not the judges:
fit_judge$details$influence_items
#> item n_raters n_relevant status_full_panel fragile changes_without
#> Item1 Item1 8 7 Support FALSE <NA>
#> Item2 Item2 8 7 Support FALSE <NA>
#> Item3 Item3 8 7 Support FALSE <NA>
#> Item4 Item4 8 7 Support FALSE <NA>
#> Item5 Item5 8 0 Review FALSE <NA>
#> Item6 Item6 8 0 Review FALSE <NA>fragile is TRUE for an item whose review
status would change if one judge were removed, which happens when the
item is one judge away from the other side of the CVI criterion for its
panel size: at the criterion, or one short of it. Every judge on one
side of such an item changes it alike, so it is a fact about the item
and no judge is flagged for it.
The panel-level dependability coefficient answers a planning question:
summary(fit_judge)$gtheory$judges_needed
#> target n_judges_relative n_judges_absolute
#> 1 0.7 1 2
#> 2 0.8 2 2
#> 3 0.9 3 5That table reports how many judges a target coefficient would need.
Where it returns NA, no realistic panel reaches that
target, which usually means the judges barely distinguished the items.
The numbers are estimates from one panel’s ratings, with no interval, so
read them as planning figures.
Reading domain coverage
assignments <- data.frame(
item = paste0("I", 1:7),
construct = c("Autonomy", "Autonomy", "Autonomy", "Autonomy",
"Competence", "Competence", "Relatedness"),
stringsAsFactors = FALSE
)
fit_domain <- domain_validity(
assignments,
cell_col = "construct",
domain = c("Autonomy", "Competence", "Relatedness", "Belonging")
)
fit_domain$results[, c("cell", "n_items", "share", "recommendation")]
#> cell n_items share recommendation
#> 1 Autonomy 4 0.5714286 Over-represented
#> 2 Competence 2 0.2857143 Covered
#> 3 Relatedness 1 0.1428571 Thinly covered
#> 4 Belonging 0 0.0000000 Not coveredThe Belonging row is the point of this analysis. No item
addresses that cell, and no item-level index could ever have
told you, because relevance statistics can only describe items
that exist. A perfect I-CVI on every item you wrote says nothing about
the facet you forgot.
This only works because the full cell list was supplied through
domain. Omit it and the analysis can only describe the
cells that already contain items, and the output says so rather than
implying complete coverage.
Common misreadings
“Review means delete.” It does not. It means look closer. Deleting every flagged item optimizes a statistic at the cost of the content domain, which is the opposite of content validity.
“Strong means good.” Benchmark labels are percentile
positions against published scales, and they describe a scale’s mean,
not an item. Strong says that the scale’s mean falls
between the 60th and 79th percentiles of the scales Colquitt et
al. (2019) collected. It does not say that the scale, or any item in it,
is fit for your purpose.
“One index is enough.” No single coefficient establishes content validity. These are components of an argument that also rests on construct definitions, domain coverage, cognitive interviewing, and expert comment.
“The numbers are comparable.” HTC against HTD, Psa against Csv, or one index against the benchmark belonging to a different index: these comparisons are not meaningful.
“A high coefficient means the domain is covered.”
Relevance and coverage are different questions. Only
domain_validity() addresses coverage, and only when you
give it the full blueprint.
“More judges always helps.” More judges narrows
intervals, but it can also change a criterion: the I-CVI guideline
shifts at six experts. Check expert_power() for that step
rather than assuming. (judges_needed, above, answers a
different question: how many judges a dependability target needs.)
Writing it up
A defensible sentence reports the estimate, its precision, the decision rule you applied, and what you concluded, without implying the statistic made the decision:
Six items were sorted by 20 naive judges into three constructs. We applied the exact target-assignment test of Howard and Melloy (2016), with a null target rate of .50 and an alpha of .05, under which an item needs 15 of 20 target assignments. Four items met the criterion: A1 and C1 (Psa = .90, 95% CI [.70, .97], p < .001), B1 (Psa = .85, 95% CI [.64, .95], p = .001), and A2 (Psa = .75, 95% CI [.53, .89], p = .021). Items B2 (Psa = .65, 95% CI [.43, .82], p = .132) and C2 (Psa = .70, 95% CI [.48, .85], p = .058) did not, and judges who missed the target chose a single competing construct most often: A for B2 and B for C2. Rather than removing them, we revised their wording to sharpen the distinction from that construct and re-sorted. Intervals are Wilson score intervals (Newcombe, 1998).
Report what you cannot establish as well:
These analyses address definitional correspondence and distinctiveness. They do not establish that the item set covers the construct domain; coverage was assessed separately against the blueprint.
settings and design hold everything a
reader needs to reproduce the decision rules, so report them rather than
only the coefficients:
fit_sort$settings[c("p0", "alpha", "judge_type")]
#> $p0
#> [1] 0.5
#>
#> $alpha
#> [1] 0.05
#>
#> $judge_type
#> [1] "naive"Turning off the inline key
Printed output includes a key defining each column. Once the terms are familiar:
options(contentvalidR.show_key = FALSE)The definitions remain available at any time:
contentvalid_glossary("expert-panel")References
American Psychological Association. (2020). Publication manual of the American Psychological Association (7th ed.). https://doi.org/10.1037/0000165-000
Colquitt, J. A., Sabey, T. B., Rodell, J. B., & Hill, E. T. (2019). Content validation guidelines: Evaluation criteria for definitional correspondence and definitional distinctiveness. Journal of Applied Psychology, 104(10), 1243–1265. https://doi.org/10.1037/apl0000406
Howard, M. C., & Melloy, R. C. (2016). Evaluating item-sort task methods: The presentation of a new statistical significance formula and methodological best practices. Journal of Business and Psychology, 31(1), 173–186. https://doi.org/10.1007/s10869-015-9404-y
Lynn, M. R. (1986). Determination and quantification of content validity. Nursing Research, 35(6), 382–385. https://doi.org/10.1097/00006199-198611000-00017
Newcombe, R. G. (1998). Two-sided confidence intervals for the single proportion: Comparison of seven methods. Statistics in Medicine, 17(8), 857–872. https://doi.org/10/cpchjg