Repository navigation
contentvalidR 0.7.0
Seventh public release. v0.7.0 is about being safe to depend on. It says in
writing what code can rely on from one version to the next, freezes the
handoff object that another package reads, and settles the one deprecation
that a 1.0 release would otherwise lock in. It also adds a worked example that
carries a single item set from an expert panel into response data, and it
corrects the stated basis of the Delphi kappa interval.
The package continues to declare Imports: stats only.
One breaking change. anova_content() no longer accepts posthoc, and
passing it is an error. The argument has had no effect on any result in any
release, and it has warned in every one since 0.1.0. See below.
No default changes, and no computed value changes. Across all six workflows,
every Delphi stability method, and the bootstrap intervals, every value 0.6.0
returns is identical in 0.7.0. Handoff objects gain four columns (note,
keying, response_min, response_max), all appended within schema
version 1, so a reader written for 0.6.0 still reads them.
New arguments: content_handoff(reverse_keyed = , response_scale = ). New
vignette: vignette("one-item-set-both-stages"). New example data: three
walkthrough_*.csv files in inst/extdata.
Published on GitHub and R-universe. 0.4.0 remains in CRAN's review queue, and
CRAN policy asks that no further version be submitted while one is pending.
The one deprecation in flight is resolved
- Breaking:
anova_content()'sposthocargument is removed
(#60). Passing it is now
an error rather than a warning.- Replacement: none is needed. Tukey/Duncan post-hoc testing was dropped
before the first release because the Hinkin-Tracey question is answered
directly by planned target-versus-orbiting contrasts, which
anova_content()has always run. The argument has had no effect on any
result in any version. - It warned from the beginning. The deprecation warning was in the
initial commit and stood through 0.1.0, 0.2.0, 0.3.0, 0.3.1, 0.4.0, 0.5.0,
and 0.6.0 — seven releases against the policy's minimum of one. - Why now. The stability policy removes a deprecation in a minor release
before 1.0, and only in a major release from 1.0 onward. This is the last
release that can do it without waiting for 2.0. - The argument sat seventh in the signature, ahead of
alpha, so anyone
who passes arguments by position pasttarget_mapshould check their call.
- Replacement: none is needed. Tukey/Duncan post-hoc testing was dropped
posthoc_passis now documented as deprecated. It is a duplicate of
contrast_passand has been returned silently since the first release,
without ever being marked. It is still returned and still correct, since
the policy requires a release in which reading it keeps working; read
contrast_passinstead. It is the last column, so removing it later cannot
move another.- Both events are recorded here, which is the point. The policy says
NEWS.mdrecords a deprecation and its removal with the replacement; the
posthocdeprecation was never recorded at all until now.?contentvalidR
now citesposthocas the completed cycle andposthoc_passas the one in
progress, and the test suite checks that both are in the state the policy
claims. csv_binom_test()moves from Tier 3 to Tier 2. Tier 3 describes helpers
"kept for continuity with older analyses", which are "not recommended
workflows" and "may be deprecated and removed". That is the wrong description
of the Howard and Melloy (2016) exact test thatsort_validity()runs on
every item. The tiers become promises at 1.0, so a load-bearing function had
to stop being filed under the one that promises least. No code changes.
One item set carried through both stages
- New
vignette("one-item-set-both-stages")follows twelve items from an
expert panel through the handoff and into response data
(#55), so the pair of
stages is demonstrated rather than asserted. Every step runs: the second half
usesstatsonly, and thenomologRcall is shown but not run, since
neither package depends on the other. - The point of the walkthrough is the disagreement between the stages, and it
runs in both directions.- Two items fail content review, for opposite reasons: one keeps its target
and misses the criterion, the other loses its target to a competing facet. - Two pass content review and then misbehave.
EF4is sorted to effort
regulation by eighteen of twenty judges and carries almost no common
variance;TF4is sorted to one facet and loads on both. A panel cannot
see either, which is the reason for running the second stage. EF3is the reverse, and the sharper case: an empirical screen flags it
and it is worth keeping. Nearly everyone endorses "I finish the assignments
that count toward my grade" — 96% answer in the top two categories — so its
variance is a third of every other item's and it cannot correlate strongly
with anything. Its correlation looks as bad asEF4's, and only the
distribution tells them apart. It is also the only item covering the
completion of required work, so dropping it would narrow the domain the
panel defined. Added at the nomologR maintainers' suggestion, so that both
packages' articles make the same point about it.- Meanwhile the item that met the content criterion by a single judge behaves
perfectly well, so a borderline content result is not read as a
prediction. EF2andTF2are written the other way round, and the walkthrough
passes that tocontent_handoff(reverse_keyed = )along with the
respondents' one-to-five scale. It then shows why the handoff has to carry
both: before recoding,TF2correlates at -0.51 with its own facet, which
looks like the strongest evidence against an item that item analysis
produces and is in fact a coding error. Recoded with the keying and the
scale limits the handoff carries, it is +0.51. Added together with the
keying field, at the nomologR maintainers' request, so that no reader ever
receives reverse-worded data without the means to know it.
- Two items fail content review, for opposite reasons: one keeps its target
- Three new files in
inst/extdata:walkthrough_items.csv(the items, their
facet, their stems, what each one was built to do, and whether it is
reverse-worded),
walkthrough_sort.csv(twenty judges), andwalkthrough_responses.csv(400
respondents, twelve items, a two-levelcohortvariable, and the rejected
items still present). They are simulated. No participant was involved and
no real instrument is reproduced. data-raw/build-walkthrough-data.Rwrites all three and states the
generating model in full: the two correlated facets, every loading, the
response thresholds, and the single cohort shift. It uses base R only and is
deterministic.nomologRmirrors the response file from the same script, so
the two packages cannot drift.- A test file holds the vignette to its claims: which two items the panel
flags, which met the criterion by one judge, thatEF4is the weak one and
TF4the ambiguous one, thatEF3is flagged for restricted variance rather
than for lacking common variance, and thatTF6is the only item that shifts
by cohort. If the data are regenerated and a claim stops holding, the suite
fails rather than the vignette quietly becoming wrong.
Handoff schema version 1 is frozen
?content_handoffgains two sections. What version 1 freezes names every
field and column a reader can rely on, states that each keeps its name,
position, and type, and lists what is deliberately not frozen: which rows
appear, the labels in thestatisticcolumn, the prose innote,rule,
recommendation, andcitation, the contents ofsettingsanddesign,
and the printed output. If the schema ever changes gives the version-2 path
and the two-line version check a reader should gate on.- The frozen contract is now data rather than prose, so the tests and the
documentation cannot drift apart. Handoffs from all five workflows that can
produce one are checked against it, column by column, along with the
guarantee that makes the freeze useful: every handoff carries every column,
so a statistic with no interval carriesNAin all four interval columns and
a workflow with no panel coefficient returns a zero-rowpanel_statistics
with the full column set. Handoffs from different workflows therefore bind
without reconciling them first. - Before the freeze closed, it was checked against every reader.
nomologRconfirmed the frozen list covers everything its reader consumes,
and named two gaps, each blocking a specific computation rather than
expressing a preference. Both are added.solomonRwas asked too
(solomonR#32): its
fit_solomon_sem_latent()takes item names as character vectors, which is
exactly what a handoff carries.
content_handoff()gainsreverse_keyedandresponse_scale, recorded
per item in three newitem_evidencecolumns:keying(1forward,-1
reverse-worded),response_min, andresponse_max.- Keying, because an even-odd consistency index must recode reverse-worded
items first or a consistent respondent looks careless, and because a
negative corrected item-total correlation means a coding error on an item
that was never recoded and evidence against the item on one that was. - The response scale, because screening for out-of-range answers needs the
scale's limits rather than the observed ones: a category nobody used is
still a legal answer. - Both come from the analyst, never from the fit. A panel's
loandhi
are the scale the experts rated relevance on, usually 1 to 4, not the scale
respondents answer. Copying them across would hand a reader the wrong
limits, and it would then reject every legitimate top-category answer. So
both default toNA, meaning unknown, and a test guards against the fit's
scale ever leaking into these columns. reverse_keyed = character(0)records that someone checked and no item is
reversed, which is different from not having said.- Naming a reverse-worded item without
response_scalewarns, since the
item cannot be recoded without the scale's limits. A reader that recodes
would otherwise refuse one step later, where the omission is harder to
trace. Suggested by the nomologR maintainers.
- Keying, because an even-odd consistency index must recode reverse-worded
- Item text was considered and left out. It was wanted only if every workflow
that can produce a handoff has the wording, and none of them collects it. - Apart from those three columns, the freeze changes no object: a 0.7.0 handoff
is a 0.6.0 handoff plus them and thenotecolumn described below. All are
appended after the original columns, as the additive rule requires.
The kappa interval in a Delphi is a published procedure
- The bootstrap interval beside a Delphi stability kappa was described in the
output and the help page as this package's own extension. That was wrong.
Resampling the units that the two raters cross-classify, recomputing kappa,
and taking the percentile interval is the procedure Klar, Lipsitz, Parzen and
Leong (2002) set out; in a Delphi those units are the experts, each carrying
their rating in both rounds. - In its place,
?delphi_validitygains Reading the kappa interval, which
says what the interval is and gives the two things a reader needs in order to
report it honestly: Klar et al.'s measured coverage for a nominal 95%
interval was about 83% with 20 units, 89% with 25, 91% with 30, and reached
94% only from 40 up, so a panel-sized interval is narrower than its label;
and their simulations used an unweighted kappa on two categories, so the
ordinal weighted case has not been simulated. The printed output carries the
short form of both. panel_agreement()keeps its extension warning for Gwet's AC1. Zapf et al.
(2016) evaluated the bootstrap for Fleiss' kappa and Krippendorff's alpha
only, and do not discuss AC1 at all, so applying their interval to it is an
extension. That caveat now travels with the number: an AC1 interval in
content_handoff()carries it inpanel_statistics$note, where before it
reached only the console. (content_report()tabulates item-level evidence
and never shows a panel coefficient, so there is nothing to carry there.)- The reference list in the README catches up. It had no Delphi sources at
all, although the workflow shipped in 0.5.0 and the prose above it cites Holey
et al., Chaffin and Talley, Dajani et al. and Scheibe et al. by name. Nine
references are added in APA form with DOIs: those four, plus Cohen (1968),
Fleiss and Cohen (1973), Landis and Koch (1977), Diamond et al. (2014), and
Klar et al. (2002). - Two printed claims fixed. With
B = 0,delphi_validity()still described
intervals it had not computed and still printed emptylowandhigh
columns; it now omits both. The printed-claims test that should have caught
this used a statistic that has no interval at all, so it passed vacuously.
A written stability policy
?contentvalidRnow states what code can rely on across versions
(the 1.0 criteria on
nomologR#53):- the public API in three tiers, from the recommended workflows through the
component indices to the auxiliary helpers, with every exported function
placed in one; - the deprecation cycle: a warning that names the replacement, standing for
at least one minor release before anything is removed; - a changed default treated as a breaking change, since it can silently
change published numbers; - what the handoff schema guarantees a downstream package;
- which versions of R are supported.
- the public API in three tiers, from the recommended workflows through the
- Tests keep the policy honest: a newly exported function fails the suite
until it is classified, and the deprecation the policy cites as its worked
example is checked to still warn and still work.
Saying what NA means
-
item_statisticsandpanel_statisticsgain anotecolumn, carrying the
producing function's own sentence for why a value or interval is absent or
degenerate, such as "Kappa is undefined: every rating fell in the same
category in both rounds."- Added so a downstream report can print the reason instead of
reconstructing method-specific semantics by switching on statistic names,
which would go silently wrong whenever a method is added. The design was
argued out with the nomologR maintainers on
nomologR#46. - Display text only. Never match on it, branch on it, or parse it; its
wording may change in any minor release. - Always character, never
NA:""means there is nothing to say. - It never replaces the values. Which of the two
NAcases applies stays
readable fromvalueandproportion unchanged, and that remains the
supported way to decide anything. - Additive within schema version 1: it is appended after every existing
column, and objects from 0.6.0 and earlier simply have no such column.
- Added so a downstream report can print the reason instead of
-
Documented the two reasons a Delphi stability statistic can be
NA
(#48), which a reader
can tell apart from the object alone:proportion unchangedis alsoNA: the item has no pair of consecutive
rounds, because it was rated in one round only;proportion unchangedhas a value: a pair exists, but the statistic is
undefined for that data.
-
The second case is easy to misreport. An undefined kappa beside
proportion unchangedof 1 is perfect stability that kappa cannot
express: kappa is chance-corrected, so when every paired rating in both
rounds falls in one category, the disagreement expected by chance is zero
and kappa is 0/0. Saying only "not estimable" would describe a defect that
does not exist. -
The row is always present, so an
NAis a statement about the data rather
than a missing record. Raised by the nomologR maintainers from the 0.6.0
fixtures.