Skip to content

Commit

Permalink
Add workflows for tests and R CMD check (#262)
Browse files Browse the repository at this point in the history
* inital-attempt for running workflows

* add Encoding: UTF-8 in DESCRIPTION

* replace expect_is() since it is deprecated

* dont run tests in check

* fix partial matches

* vuongtest -> nonnest2::vuongtest

* expect_equal(tol=...) -> expect_equal(tolerance=...)

* fix groupName partial match

* unamed arg -> tolerance=...

* tol -> tolerance in test-10-extras.R

* dont trigger tests automatically on push to main
  • Loading branch information
Kss2k authored Nov 11, 2024
1 parent dbf9ad3 commit 3c50fb7
Show file tree
Hide file tree
Showing 44 changed files with 242 additions and 193 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: R-CMD-CHECK

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:

jobs:
R-CMD-CHECK:
runs-on: ubuntu-latest

container:
image: ghcr.io/kss2k/container-mirt:latest

steps:
- name: Check out the repository
uses: actions/checkout@v4

- name: R CMD CHECK
run: |
Rscript -e 'devtools::check(error_on = "warning")'
23 changes: 23 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Tests (Ubuntu-latest)

on:
# push:
# branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:

jobs:
Run-Tests:
runs-on: ubuntu-latest

container:
image: ghcr.io/kss2k/container-mirt:latest

steps:
- name: Check out the repository
uses: actions/checkout@v4

- name: Run tests
run: |
Rscript -e 'options(warnPartialMatchDollar = TRUE); Sys.setenv("_R_CHECK_LENGTH_1_CONDITION_" = "TRUE", "_R_CHECK_LENGTH_1_LOGIC2_" = "TRUE"); devtools::test(stop_on_failure = TRUE)'
5 changes: 4 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,17 @@ Suggests:
nloptr,
sirt,
plink,
mirtCAT
mirtCAT,
testthat (>= 3.0.0)
ByteCompile: yes
LazyLoad: yes
LazyData: yes
LinkingTo: Rcpp, RcppArmadillo
License: GPL (>= 3)
Encoding: UTF-8
Repository: CRAN
Maintainer: Phil Chalmers <rphilip.chalmers@gmail.com>
URL: https://philchalmers.github.io/mirt/, https://github.com/philchalmers/mirt/wiki, https://groups.google.com/forum/#!forum/mirt-package
BugReports: https://github.com/philchalmers/mirt/issues?state=open
RoxygenNote: 7.3.2
Config/testthat/edition: 3
8 changes: 4 additions & 4 deletions R/03-estimation.R
Original file line number Diff line number Diff line change
Expand Up @@ -867,8 +867,8 @@ ESTIMATION <- function(data, model, group, itemtype = NULL, guess = 0, upper = 1
Consider changing the starting values', call.=FALSE)
dontrun <- TRUE
}
lengthsplit <- do.call(c, lapply(strsplit(names(ESTIMATE$correct), 'COV_'), length))
lengthsplit <- lengthsplit + do.call(c, lapply(strsplit(names(ESTIMATE$correct), 'MEAN_'), length))
lengthsplit <- do.call(c, lapply(strsplit(names(ESTIMATE$correction), 'COV_'), length))
lengthsplit <- lengthsplit + do.call(c, lapply(strsplit(names(ESTIMATE$correction), 'MEAN_'), length))
is.latent <- lengthsplit > 2L
if(!dontrun){
if(ESTIMATE$cycles <= 10L)
Expand Down Expand Up @@ -1016,7 +1016,7 @@ ESTIMATION <- function(data, model, group, itemtype = NULL, guess = 0, upper = 1
.mirtClusterEnv$ncores <- 1L
}
for(g in seq_len(Data$ngroups)){
cmods[[g]]@Data <- list(data=Data$data[Data$group == Data$groupName[g], ],
cmods[[g]]@Data <- list(data=Data$data[Data$group == Data$groupNames[g], ],
fulldata=Data$fulldata[[g]], tabdata=Data$tabdata,
Freq=list(Data$Freq[[g]]), K=Data$K)
cmods[[g]] <- calcLogLik(cmods[[g]], opts$draws, G2 = 'return',
Expand Down Expand Up @@ -1166,7 +1166,7 @@ ESTIMATION <- function(data, model, group, itemtype = NULL, guess = 0, upper = 1
} else {
if(Options$exploratory){
FF <- F %*% t(F)
V <- eigen(FF)$vector[ ,1L:nfact]
V <- eigen(FF)$vectors[ ,1L:nfact]
L <- eigen(FF)$values[1L:nfact]
if (nfact == 1L) F <- as.matrix(V * sqrt(L))
else F <- V %*% sqrt(diag(L))
Expand Down
2 changes: 1 addition & 1 deletion R/05-model.elements.R
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ model.elements <- function(model, factorNames, itemtype, nfactNames, nfact, J, K

if(exploratory){
Rpoly <- cormod(data, K, guess)
loads <- eigen(Rpoly)$vector[,seq_len(nfact), drop = FALSE]
loads <- eigen(Rpoly)$vectors[,seq_len(nfact), drop = FALSE]
u <- 1 - rowSums(loads^2)
u[u < .001 ] <- .2
cs <- sqrt(u)
Expand Down
2 changes: 1 addition & 1 deletion R/EMstep.group.R
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ EM.group <- function(pars, constrain, Ls, Data, PrepList, list, Theta, DERIV, so
tmp <- tmp[lower.tri(tmp, TRUE)]
tmpmat <- matrix(0, ns, 2L)
for(i in seq_len(ns))
tmpmat[i, ] <- c(gp$gmean[np + i], gp$gcov[np+i, np+i])
tmpmat[i, ] <- c(gp$gmeans[np + i], gp$gcov[np+i, np+i])
for(g in seq_len(ngroups)){
pars[[g]][[J+1L]]@bindex <- as.integer(c(gp$gmeans[seq_len(np)], tmp))
pars[[g]][[J+1L]]@sindex = tmpmat
Expand Down
2 changes: 1 addition & 1 deletion R/M2.R
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ M2 <- function(obj, type="M2*", calcNull = TRUE, quadpts = NULL, theta_lim = c(-
pars[[g]]@Internals$Prior <- list(obj@Internals$Prior[[g]])
pars[[g]]@Model$Theta <- obj@Model$Theta
}
pars[[g]]@Data <- list(data=obj@Data$data[obj@Data$group == obj@Data$groupName[g], ],
pars[[g]]@Data <- list(data=obj@Data$data[obj@Data$group == obj@Data$groupNames[g], ],
mins=obj@Data$mins, K=obj@Data$K,
fulldata=list(obj@Data$fulldata[[g]]))
if(is(obj, 'MixtureClass')) pars[[g]]@Data$data <- extract.mirt(obj, 'data')
Expand Down
2 changes: 1 addition & 1 deletion R/MultipleGroup-methods.R
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ setMethod(
for(g in 1L:length(ret)){
cmod <- object@ParObjects$pars[[g]]
cmod@Data <- object@Data
cmod@Data$data <- object@Data$data[object@Data$group == object@Data$groupName[g], ]
cmod@Data$data <- object@Data$data[object@Data$group == object@Data$groupNames[g], ]
cmod@Data$Freq[[1L]] <- cmod@Data$Freq[[g]]
cmod@Options$quadpts <- object@Options$quadpts
cmod@Internals$bfactor <- object@Internals$bfactor
Expand Down
4 changes: 2 additions & 2 deletions R/fscores.internal.R
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ EAPsum <- function(x, full.scores = FALSE, full.scores.SE = FALSE,
factorNames <- extract.mirt(x, 'factorNames')
colnames(thetas) <- factorNames[!grepl('\\(',factorNames)]
colnames(SEthetas) <- paste0('SE_', colnames(thetas))
ret <- data.frame(Sum.Scores=Sum.Scores + sum(x@Data$min), thetas, SEthetas)
ret <- data.frame(Sum.Scores=Sum.Scores + sum(x@Data$mins), thetas, SEthetas)
rownames(ret) <- ret$Sum.Scores
if(full.scores){
dat <- x@Data$data
Expand Down Expand Up @@ -1082,4 +1082,4 @@ EAP_classify <- function(ID, log_itemtrace, tabdata, W, nclass){
}
probs <- matrix(expLW / nc, ncol=nclass)
colSums(probs)
}
}
4 changes: 2 additions & 2 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ thetaStack <- function(theta, nclass){
#' }
secondOrderTest <- function(mat, ..., method = 'eigen'){
if(method == 'eigen'){
evs <- eigen(mat, ...)$value
evs <- eigen(mat, ...)$values
ret <- all(!sapply(evs, function(x) isTRUE(all.equal(x, 0))) & evs > 0)
} else if(method == 'chol'){
chl <- try(chol(mat, ...), silent = TRUE)
Expand Down Expand Up @@ -2428,7 +2428,7 @@ MGC2SC <- function(x, which){
}
tmp@Data <- x@Data
tmp@Data$completely_missing <- integer(0L)
tmp@Data$data <- tmp@Data$data[tmp@Data$group == tmp@Data$groupName[which], , drop=FALSE]
tmp@Data$data <- tmp@Data$data[tmp@Data$group == tmp@Data$groupNames[which], , drop=FALSE]
tmp@Data$rowID <- 1L:nrow(tmp@Data$data)
tmp@Data$Freq[[1L]] <- tmp@Data$Freq[[which]]
tmp@Data$fulldata[[1L]] <- x@Data$fulldata[[which]]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
context('basics')
expect_class <- function(x, class) expect_true(inherits(x, class))

test_that('basics', {
group <- rep(c('G1', 'G2'), each=nrow(Science)/2)
Expand All @@ -19,7 +19,7 @@ test_that('basics', {
tscore <- expected.test(mod, Theta)
expect_equal(tscore, c(7.935196,9.270222,10.53705,11.69166,12.7803,13.83405,14.73532), tolerance=1e-6)
IG <- itemGAM(Science[group == 'G1',1], theta_se[,1, drop=FALSE])
expect_is(IG, 'itemGAM')
expect_class(IG, 'itemGAM')

info <- iteminfo(item1, Theta)
expect_equal(as.vector(info), c(0.2031261,0.2118485,0.2069816,0.187747,0.1793368,0.1902587,0.1761818),
Expand Down
60 changes: 30 additions & 30 deletions tests/tests/test-01-mirtOne.R → tests/testthat/test-01-mirtOne.R
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
context('mirtOne')
expect_class <- function(x, class) expect_true(inherits(x, class))

test_that('dich', {
data <- expand.table(LSAT7)
mod1 <- mirt(data, 1, verbose=FALSE)
expect_is(mod1, 'SingleGroupClass')
expect_class(mod1, 'SingleGroupClass')
expect_equal(extract.mirt(mod1, 'df'), 21)
cfs <- as.numeric(do.call(c, coef(mod1)))
expect_equal(cfs, c(0.988, 1.8561, 0, 1, 1.081, 0.808, 0, 1, 1.706, 1.8043, 0, 1, 0.7651, 0.486, 0, 1, 0.7358, 1.8545, 0, 1, 0, 1),
tolerance = 1e-2)
sv <- mod2values(mod1)
sv$est <- FALSE
moddummy <- mirt(data, 1, pars= sv, verbose=FALSE)
expect_is(moddummy, 'SingleGroupClass')
expect_class(moddummy, 'SingleGroupClass')
sv2 <- mod2values(moddummy)
expect_equal(sv$value, sv2$value)
modm1 <- mirt(data, 1, SE = TRUE, SE.type = 'SEM', verbose=FALSE)
Expand All @@ -20,17 +20,17 @@ test_that('dich', {
expect_equal(extract.mirt(modm1, 'condnum'), 30.12751, tolerance = 1e-4)
expect_equal(cfs, c(0.9876, 0.6367, 1.3384, 1.8559, 1.5978, 2.1139, 0, NA, NA, 1, NA, NA, 1.0808, 0.7604, 1.4013, 0.808, 0.6335, 0.9825, 0, NA, NA, 1, NA, NA, 1.7075, 1.0868, 2.3281, 1.8052, 1.4028, 2.2076, 0, NA, NA, 1, NA, NA, 0.765, 0.5065, 1.0235, 0.486, 0.3114, 0.6606, 0, NA, NA, 1, NA, NA, 0.7357, 0.4246, 1.0467, 1.8545, 1.6332, 2.0757, 0, NA, NA, 1, NA, NA, 0, NA, NA, 1, NA, NA),
tolerance = 1e-2)
expect_is(modm1, 'SingleGroupClass')
expect_class(modm1, 'SingleGroupClass')
modm2 <- mirt(data, 1, SE = TRUE, SE.type = 'Richardson', verbose=FALSE)
cfs <- as.numeric(do.call(c, coef(modm2)))
expect_equal(extract.mirt(modm2, 'condnum'), 30.24068, tolerance = 1e-3)
expect_equal(cfs, c(0.988, 0.6406, 1.3354, 1.8561, 1.5984, 2.1138, 0, NA, NA, 1, NA, NA, 1.081, 0.7501, 1.4119, 0.808, 0.6291, 0.9869, 0, NA, NA, 1, NA, NA, 1.706, 1.0779, 2.334, 1.8043, 1.4036, 2.205, 0, NA, NA, 1, NA, NA, 0.7651, 0.5022, 1.028, 0.486, 0.3392, 0.6328, 0, NA, NA, 1, NA, NA, 0.7358, 0.4395, 1.032, 1.8545, 1.6302, 2.0787, 0, NA, NA, 1, NA, NA, 0, NA, NA, 1, NA, NA),
tolerance = 1e-2)
expect_is(modm2, 'SingleGroupClass')
expect_class(modm2, 'SingleGroupClass')
modm2b <- mirt(data, 1, SE = TRUE, SE.type = 'Fisher', verbose=FALSE)
expect_equal(extract.mirt(modm2b, 'condnum'), 29.0323, tolerance = 1e-3)
modm3 <- mirt(data, 1, itemtype = 'Rasch', verbose=FALSE, SE=TRUE)
expect_is(modm3, 'SingleGroupClass')
expect_class(modm3, 'SingleGroupClass')
expect_equal(extract.mirt(modm3, 'df'), 25)
expect_equal(extract.mirt(modm3, 'condnum'), 4.488772, tolerance = 1e-4)
LG <- lagrange(modm3, parnum = list(1, 5))
Expand All @@ -39,7 +39,7 @@ test_that('dich', {
expect_equal(LG2$X2, 0.4816444, tolerance = 1e-4)
dat <- expand.table(LSAT6)
modm3 <- mirt(dat, 1, itemtype = 'Rasch', SE = TRUE, SE.type = 'SEM', verbose=FALSE)
expect_is(modm3, 'SingleGroupClass')
expect_class(modm3, 'SingleGroupClass')
cfs <- as.numeric(do.call(c, coef(modm3)))
expect_equal(cfs, c(1,NA,NA,2.73,2.478,2.983,0,NA,NA,1,NA,NA,1,NA,NA,0.999,0.845,1.152,0,NA,NA,1,NA,NA,1,NA,NA,0.24,0.1,0.38,0,NA,NA,1,NA,NA,1,NA,NA,1.306,1.143,1.47,0,NA,NA,1,NA,NA,1,NA,NA,2.099,1.896,2.303,0,NA,NA,1,NA,NA,0,NA,NA,0.57,0.371,0.77),
tolerance = 1e-2)
Expand All @@ -58,18 +58,18 @@ test_that('dich', {
svalues <- mirt(data, 1, pars = 'values', verbose=FALSE)
svalues[22, 'value'] <- 2
modm5 <- mirt(data, 1, pars = svalues, verbose=FALSE)
expect_is(modm5, 'SingleGroupClass')
expect_class(modm5, 'SingleGroupClass')
expect_warning(modm7 <- mirt(data, 1, '4PL', verbose=FALSE, parprior = list(c(3,7,11,15,19,'norm', -1.7, .1),
c(4,8,12,16,20,'norm', 1.7, .1))),
"EM cycles terminated after 500 iterations.")
expect_equal(extract.mirt(modm7, 'df'), 11)
expect_is(modm7, 'SingleGroupClass')
expect_class(modm7, 'SingleGroupClass')
cfs <- as.numeric(do.call(c, coef(modm7)))
expect_equal(cfs, c(5.12,8.557,0.154,0.859,5.741,3.595,0.16,0.843,10.215,10.937,0.155,0.861,1.284,0.854,0.153,0.845,4.629,8.905,0.154,0.859,0,1), tolerance = 1e-2)
data[1,1] <- data[2,2] <- NA
modm6 <- mirt(data, 1, verbose=FALSE)
expect_equal(modm6@Fit$df, 21)
expect_is(modm6, 'SingleGroupClass')
expect_class(modm6, 'SingleGroupClass')
cfs <- as.numeric(do.call(c, coef(modm6)))
expect_equal(cfs, c(0.969, 1.851, 0, 1, 1.074, 0.808, 0, 1, 1.717, 1.811, 0, 1, 0.763, 0.486, 0, 1, 0.731, 1.852, 0, 1, 0, 1), tolerance = 1e-2)
modideal <- mirt(data, 1, verbose=FALSE, itemtype='ideal')
Expand All @@ -93,45 +93,45 @@ test_that('dich', {
expect_equal(as.numeric(rfit[,1]), c(NA, 0.0468866, 0.3906001, 0.2476980, 0.5195561), tolerance = 1e-2)

fm1 <- fscores(modm1, verbose = FALSE, full.scores=FALSE)
expect_is(fm1, 'matrix')
expect_class(fm1, 'matrix')
expect_true(mirt:::closeEnough(fm1[1:6,'F1'] - c(-1.8665957, -1.5266920, -1.5134024,
-1.1852276, -1.0946830, -0.7666992), -1e-2, 1e-2))
fm2 <- fscores(modm2, method = 'MAP', verbose = FALSE, full.scores=FALSE)
expect_is(fm2, 'matrix')
expect_class(fm2, 'matrix')
expect_true(mirt:::closeEnough(fm2[1:6,'F1'] - c(-1.8165552, -1.4946906, -1.4822982,
-1.1789899, -1.0958928, -0.7951026), -1e-2, 1e-2))
fm3 <- fscores(modm4, method = 'ML', full.scores = TRUE, verbose = FALSE)
expect_is(fm3, 'matrix')
expect_class(fm3, 'matrix')
expect_true(fm3[1, 'F'] == -Inf && fm3[1000, 'F'] == Inf)
expect_true(mirt:::closeEnough(as.numeric(fm3[c(13,34,40),'F'])
- c(-2.783489, -1.750890, -2.783489), -1e-2, 1e-2))
fm3 <- fscores(modm3, method = 'ML', full.scores = TRUE, verbose = FALSE, scores.only=TRUE)
expect_is(fm3, 'matrix')
expect_class(fm3, 'matrix')
fm4 <- fscores(modm6, method = 'ML', full.scores = TRUE, verbose = FALSE)
expect_is(fm4, 'matrix')
expect_class(fm4, 'matrix')
fm5 <- fscores(modm6, method = 'ML', full.scores = FALSE, verbose = FALSE)
expect_is(fm5, 'matrix')
expect_class(fm5, 'matrix')
fm6 <- fscores(modm1, method = 'EAPsum', full.scores = FALSE, verbose = FALSE)
expect_is(fm6, 'data.frame')
expect_class(fm6, 'data.frame')
expect_true(mirt:::closeEnough(as.numeric(as.matrix(fm6)) - c(0,1,2,3,4,5,-1.86979,-1.431861,-0.9488463,-0.4131963,0.1517289,0.7271877,0.6927032,0.6838697,0.6942298,0.7210951,0.758772,0.8009335,12,40,114,205,321,308,10.08994,44.65882,109.773,207.7391,319.1854,308.5536,0.6013157,0.6971433,0.4034422,0.1900446,0.101567,0.0315184), -1e-2, 1e-2))
expect_equal(as.numeric(attr(fm6, 'fit')['rxx_F1']), 0.4319948, tolerance = 1e-4)

res1 <- residuals(modm1, verbose = FALSE)
expect_equal(as.numeric(res1), c(NA,0.451213,0.8562096,2.577395,2.392183,-0.02124177,NA,1.053826,0.2662122,1.383089,-0.02926106,0.03246269,NA,0.1542321,0.002940504,0.05076805,-0.01631601,-0.01241902,NA,9.962506e-06,0.04890994,-0.0371899,-0.00171479,9.981236e-05,NA),
tolerance = 1e-2)
res2 <- residuals(modm2, verbose = FALSE)
expect_is(res1, 'matrix')
expect_is(res2, 'matrix')
expect_class(res1, 'matrix')
expect_class(res2, 'matrix')
IP1 <- itemplot(modm1, 1)
IP2 <- itemplot(modm2, 1)
expect_is(IP1, 'trellis')
expect_is(IP2, 'trellis')
expect_class(IP1, 'trellis')
expect_class(IP2, 'trellis')
TP1 <- plot(modm1)
TP2 <- plot(modm2)
expect_is(TP1, 'trellis')
expect_is(TP2, 'trellis')
expect_class(TP1, 'trellis')
expect_class(TP2, 'trellis')
ifit <- itemfit(modm1, c('S_X2', 'X2', 'Zh'))
expect_is(ifit, 'data.frame')
expect_class(ifit, 'data.frame')
expect_true(mirt:::closeEnough(as.numeric(ifit$Zh) - c(1.431838, 6.354917, 5.310844, 5.804449,
0.696139), -1e-2, 1e-2))
expect_true(mirt:::closeEnough(as.numeric(ifit$X2) - c(91.71819, 390.07985, 145.39978, 329.48529, 129.49679), -1e-2, 1e-2))
Expand All @@ -141,11 +141,11 @@ test_that('dich', {
expect_true(mirt:::closeEnough(as.numeric(ifit$df.S_X2) - c(2,2,2,2,2), -1e-4, 1e-4))

fitm1 <- M2(modm1)
expect_is(fitm1, 'data.frame')
expect_class(fitm1, 'data.frame')
expect_true(mirt:::closeEnough(fitm1$M2 - 11.93841, -1e-2, 1e-2))
expect_true(mirt:::closeEnough(fitm1$df.M2 - 5, -1e-4, 1e-4))
fitm2 <- M2(modm3)
expect_is(fitm2, 'data.frame')
expect_class(fitm2, 'data.frame')
expect_true(mirt:::closeEnough(fitm2$M2 - 5.291576, -1e-2, 1e-2))
expect_true(mirt:::closeEnough(fitm2$df.M2 - 9, -1e-4, 1e-4))

Expand All @@ -160,12 +160,12 @@ test_that('dich', {
tolerance = 1e-2)
expect_equal(extract.mirt(modm2, 'df'), 19)
modm3 <- mirt(data, model, SE = TRUE, verbose=FALSE)
expect_is(modm3, 'SingleGroupClass')
expect_class(modm3, 'SingleGroupClass')

fm1 <- fscores(modm1, verbose = FALSE, full.scores=FALSE)
expect_is(fm1, 'matrix')
expect_class(fm1, 'matrix')
fm2 <- fscores(modm3, method = 'MAP', verbose = FALSE)
expect_is(fm2, 'matrix')
expect_class(fm2, 'matrix')

data[1,1] <- NA
modm1 <- mirt(data, 1, verbose=FALSE)
Expand All @@ -192,6 +192,6 @@ test_that('dich', {
CONSTRAIN = (1-5, a1)'
mod3 <- mirt(dat, syntax, verbose=FALSE)
expect_equal(2*logLik(mod) - logLik(mod2) - logLik(mod3),
0, 1e-2)
0, tolerance=1e-2)
})

Loading

0 comments on commit 3c50fb7

Please sign in to comment.