This release incorporates 0.6.0, which was tagged but never submitted to CRAN, together with the further breaking changes below. The version on CRAN is 0.3.0, so 0.4.0, 0.5.x and 0.6.0 are all superseded here.
Model entry points now state their level and
estimand. lca_fit() is replaced by
item_lca_fit() for independent item-wise fits.
person_item_lca_fit() jointly estimates one shared latent
trajectory per person, common class proportions, and item-specific
guessing rates. posterior_class_probs() and
posterior_learned() now extract results from that explicit
person/item fit instead of silently fitting a different model from an
item-wise result.
Functions that were not IRT models no longer claim to
be. lca_irt() is replaced by
lca_difficulty() because it only reparameterizes the LCA
guessing rate. estimate_ability() is replaced by
estimate_logit_score(), its unsupported hand-built
"rasch" branch is removed, and
cross_sectional_irt() is replaced by
cross_sectional_learning_score(). The latter is a
descriptive bounded score, not a calibrated probability of
learning.
Missing-response semantics are now explicit and
consistent. Every function accepting raw responses uses
na_as = c("dk", "missing"). The default treats
NA as an observed don’t-know response and selects the
nine-cell model; explicit "d"/"DK" always does
the same. When NA is structural missingness,
missing_action = "omit" excludes incomplete pairs and
"error" rejects them. Structural missingness is not added
to the latent-class model.
validate_recovery() no longer reports
invalid confidence-interval coverage. The function had used one
Monte Carlo standard deviation as every replication’s standard error,
which is not replicate-specific interval coverage. It now reports bias,
RMSE, and Monte Carlo SD, and averages estimates across every simulated
item instead of silently keeping only the first.
The don’t-know model now matches the model in the
paper. The likelihood implemented a different model from the
one in Cor and Sood, equation (2). It kept the two latent classes the
paper’s identifying assumption sets to zero, dropped the two the paper
keeps, and reused the survivors in the d0 and
d1 cells:
| cell | paper | what the code computed |
|---|---|---|
x10 |
gamma(1-gamma)*gg |
(1-gamma)*gamma*gg + kg |
x11 |
gamma^2*gg + gamma*gk + kk |
... + gamma*kg + kk |
x1d |
gamma*gd |
gamma*gd + kd |
xd0 |
(1-gamma)*dg |
kg |
xd1 |
gamma*dg + dk |
gamma*gk + kd |
In short, the know-to-guess class was conflated with don’t-know-to-guess, and know-to-don’t-know with don’t-know-to-know.
DK parameters are renamed. kg and
kd are gone; dg (don’t know -> guess) and
dk (don’t know -> know) take their place. The order is
now gg, gk, gd, kk, dg, dk, dd, gamma. Code that indexes
DK parameters by name or position must be updated. Learning is
gk + dk – those who learned the item from guessing, plus
those who learned it from confessed ignorance. It was
gk + kd, which the vignette rationalised as “true learning
plus those who learned but lost confidence”.
simulate_lca_dk() gains dg and
dk arguments and loses kg and
kd. The simulator drew the same classes the
likelihood named, so it generated knowledge loss and could never
generate anyone who moved from a don’t-know response to knowing – which
is half of what the model defines as learning. No test could catch the
likelihood bug while the simulator agreed with it.
fit_model() returns NA for data
without don’t-know responses. That model has 3 free parameters
against 3 free cell probabilities. It is saturated, so there are no
degrees of freedom and no test to report. It previously reported a
p-value computed on df = 3, counting none of the parameters
estimated from the same counts. For the DK model the degrees of freedom
are now 1 rather than 8, which is what makes the test able to reject at
all.
The two consequences of the old cell equations were both invisible from outside:
They were not a distribution. They summed to
between 1.02 and 2.29 rather than 1, so the objective carried a spurious
-N log S(theta) term worth up to ~182,000 log-likelihood
units, with no statistical content.
The parameters were not identified. Fed exact model-implied counts with no sampling noise, the old estimator returned parameters off by up to 0.041 while the negative log-likelihood differed by 0.0009 in 1,741,431 – observationally equivalent, so no dataset of any size could separate them. The same test now recovers every parameter to within 6e-05.
The paper’s model is identified in closed form –
gamma/(1-gamma) is x10/x00, and every lambda
follows – and over-identified by exactly one degree of freedom, the
restriction x1d/x0d = x10/x00.
stnd_cor() now estimates paired learning
from the same respondents in both corrected totals. With
wave-specific missingness, the previous implementation subtracted
marginal pre- and post-test totals calculated from different respondent
sets, then divided that unmatched difference by the number of complete
pairs. Marginal pre- and post-test scores still use everyone observed at
each wave; learning now uses complete pairs throughout and returns
NA when no pair is observed.
Zero-probability likelihood cells now contribute zero when their observed count is zero and infinite loss only when they are observed. Perplexity and cross-validation no longer discard impossible held-out observations or divide by unobserved pairs.
Items with and without observed don’t-know responses can now be combined safely: four-cell item transitions are promoted to the shared nine-cell schema with zero DK counts instead of being recycled into a malformed matrix.
Tibble inputs now use vector-safe column extraction and produce the same transitions, fits, corrections, and missing-response behavior as data frames.
dk_cell_probs() and
nodk_cell_probs(), which every likelihood, expected-count
and goodness-of-fit routine calls.End-to-end model-criticism tests verify that raw individual and aggregated item log-likelihoods and perplexities agree for binary, NA-coded DK, and structurally missing data. Additional tests cover cross-validation denominators, tibble pipelines, response-code validation, and simulation recovery.
New tests/testthat/test-dk-model-spec.R: the cells
sum to 1, the structural zeros hold, the closed-form inversion recovers
every parameter, the over-identifying restriction holds, the estimator
recovers the truth from exact counts, the simulator can produce learning
from confessed ignorance, and the goodness-of-fit test spends 1 degree
of freedom.
test-econometric-likelihood.R used to restate the
cell equations inline and assert only that they were non-negative. They
were, and they also summed to 2.29. It now calls the function the
likelihood uses and checks that they sum to 1.
stnd_cor() understated learning when
responses were missing. The numerators counted observed
responses but the denominator was nrow(), so every item
with missing data was shrunk toward zero in proportion to its
missingness. On one item with true learning 0.384, a 10% missing rate
returned 0.341 and a 40% rate returned 0.227 – a 41% understatement.
Scores are now divided by the number of responses each item actually
has, and learn by the respondents who answered at both
waves. This is the bias the package exists to remove, so it mattered
more than its size suggests.
lca_se() failed for most item
counts. A resamp_agg matrix was allocated
2 * n_items wide while a transition row is 4 wide (or 9
with don’t-know responses), and it indexed row n_items –
the last item – rather than the aggregate row at
n_items + 1. It errored outright on 3 and 5 items, and on
every item count tried when the data contained DK responses, while
silently recycling the row at 4 and 8 items. Nothing ever read the
variable, so it has been removed. Bootstrapped standard errors now work
for any item count, with or without DK.
Removed R/fit_nodk.R and
R/fit_dk.R. Both were shadowed at load time by the
wrappers in R/fit_unified.R, which defines the same names
later in alphabetical order, so the code never ran. The dead copies
passed model-expected counts to chisq.test() as the
observed vector and observed proportions as p – had load
order ever changed, they would have returned chi-square statistics
between 0.83x and 2.08x the correct ones. fit_nodk() and
fit_dk() remain exported and unchanged in
behaviour.
The optimiser no longer prints its iteration
trace. solnp() is called with
control = list(trace = 0), so lca_fit() and
lca_se() are silent. A 100-resample bootstrap previously
emitted hundreds of lines, which is how a genuine warning gets
lost.
lca_difficulty() and simulate_lca()
documentation with ASCII. They produced LaTeX errors when building the
PDF manual, so R CMD check --as-cran reported an ERROR and
two WARNINGs on any machine with a working TeX installation.tests/testthat/test-audit-regressions.R: 22 tests
covering the above, each of which fails against the previous
release.R CMD check --as-cran is clean: 0 errors, 0 warnings,
and the one NOTE is the absence of the optional V8 package
for math rendering.
l prefix. The new
naming pattern is {pre_state}{post_state}:
lgg → gg, lgk →
gk, lkk → kk (gamma
unchanged)lgg → gg, lgk →
gk, lgd → gd, lkg →
kg, lkk → kk, lkd →
kd, ldd → ddsimulate_lca() and simulate_lca_dk() to
generate data from known parameters for validation studiesvalidate_recovery() for Monte Carlo validation of parameter
estimates# Old code
result$params["lgk", ]
result$params[c("lgg", "lgk", "lkk"), ]
# New code
result$params["gk", ]
result$params[c("gg", "gk", "kk"), ]lgg, lgk, lgc, lkk, lcg, lck, lcc, gammalgg, lgk, lgd, lkg, lkk, lkd, ldd, gammalca_cor() output when using DK dataparams["gamma", ]) instead of
numeric indices, making the code more readable and robust to parameter
reorderingfit_model(),
fit_dk(), and fit_nodk() now use formulas
consistent with the likelihood functioncell_probs() and calculate_expected_values()
now use correct DK model formulascell_probs() to match the likelihood function exactlycalculate_expected_values()helper-simulation.R with reusable functions for parameter
recovery testingtest-econometric-formula-derivation.R: Validates
likelihood formula componentstest-econometric-identification.R: Tests parameter
identification conditionstest-econometric-likelihood.R: Verifies likelihood
computation correctnesstest-econometric-parameter-recovery.R: Monte Carlo
parameter recovery validationtest-econometric-se-validation.R: Standard error
computation verificationutils-validation.Rstop() calls and :: namespace patternscheckmate dependency for robust input
validationgoji dependency by implementing internal
zero1 function@importFrom
declarationsmake coverage command for quick coverage
analysisMakefile for common development tasksT/F with
TRUE/FALSE throughout|| for scalar
comparisonsstringsAsFactors argumentsmapply() calls