Replaced ambiguous model, transition, scoring, and cross-validation entry
points with level-specific APIs: fit_item_lca(), fit_person_lca(),
count_transitions(), count_item_transitions(), score_item_lca(),
score_individual_lca(), and cv_individual_lca(). Superseded entry points
have been removed.
Raw-response functions now infer the binary or don't-know transition schema
from the data. na_as only controls whether NA is an observed don't-know
response or structural missingness.
Standardized shared argument names to pre_test, post_test, and
guessing_probability. group_adj() now uses
knowledge_given_dont_know and returns adjusted_responses and
mean_learning.
lca_se() and validate_recovery() use an optional seed; NULL uses the
current random-number-generator state, while an explicit seed is preserved.
Cross-sectional learning uses empirical-logit smoothing and returns NA for
items with no observed responses.
Bootstrap and cross-validation computations retain one inferred response schema across resamples and folds.
DK model summaries and validation examples correctly identify learning as
gk + dk.
validate_recovery() now rejects degenerate generating models at the
parameter boundary, where recovery diagnostics are not meaningful.
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
behavior.
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() callsThe "Using guess" vignette now runs its examples. They had all been
eval = FALSE, and eight had broken as the API changed, including both
lca_se() examples. Its simulation sections no longer report coverage that
validate_recovery() stopped returning in 0.8.0 or make claims about
inter-item correlation that apply only to fit_person_lca().
citation("guess") now cites the Cor and Sood (2016) Political Analysis
paper for the method and the package for the software. The package entry
previously printed an empty year. A CITATION.cff, generated from
DESCRIPTION and inst/CITATION by cffr, gives GitHub its "Cite this
repository" entry.