Nothing
# --------------------------------------------------------------------------
# test-identifiability-constraints.R
# Tests identifiability constraints and centering for the mfrmr package.
# --------------------------------------------------------------------------
# ---- 3.1 Sum-to-zero constraint on centered facets ---------------------
test_that("centered facet estimates sum to zero", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
method = "JML", model = "RSM", maxit = 40, quad_points = 7
))
for (facet in c("Rater", "Task", "Criterion")) {
est <- fit$facets$others |>
dplyr::filter(Facet == facet) |>
dplyr::pull(Estimate)
expect_equal(sum(est), 0, tolerance = 1e-6,
label = paste("sum-to-zero for", facet))
}
})
# ---- 3.2 Anchor constraint -- anchored level matches its value ---------
test_that("anchored facet level matches its anchor value exactly", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
anchors <- data.frame(
Facet = "Rater", Level = "R2", Anchor = 0,
stringsAsFactors = FALSE
)
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
anchors = anchors, method = "JML", model = "RSM", maxit = 40,
quad_points = 7
))
r2_est <- fit$facets$others |>
dplyr::filter(Facet == "Rater", Level == "R2") |>
dplyr::pull(Estimate)
expect_equal(unname(r2_est), 0, tolerance = 1e-8)
})
# ---- 3.3 Constraint methods preserve ordering and positive correlation --
test_that("constraint method changes preserve ordering and positive correlation", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
fit_centered <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
method = "JML", model = "RSM", maxit = 50, quad_points = 7
))
anchors <- data.frame(
Facet = "Rater", Level = "R1", Anchor = 0,
stringsAsFactors = FALSE
)
fit_anchored <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
anchors = anchors, method = "JML", model = "RSM", maxit = 50,
quad_points = 7
))
# Both LogLik should be finite
expect_true(is.finite(fit_centered$summary$LogLik))
expect_true(is.finite(fit_anchored$summary$LogLik))
# Task estimates (not directly affected by Rater anchor): positive correlation
task_c <- fit_centered$facets$others |>
dplyr::filter(Facet == "Task") |>
dplyr::arrange(Level) |>
dplyr::pull(Estimate)
task_a <- fit_anchored$facets$others |>
dplyr::filter(Facet == "Task") |>
dplyr::arrange(Level) |>
dplyr::pull(Estimate)
expect_gt(cor(task_c, task_a), 0.5)
# Person ability estimates: high correlation across methods
pers_c <- fit_centered$facets$person |>
dplyr::arrange(Person) |>
dplyr::pull(Estimate)
pers_a <- fit_anchored$facets$person |>
dplyr::arrange(Person) |>
dplyr::pull(Estimate)
expect_gt(cor(pers_c, pers_a), 0.9)
})
# ---- 3.4a count_facet_params: 3 levels, centered -> 2 free params ------
test_that("count_facet_params: 3 levels centered gives 2 free params", {
spec <- mfrmr:::build_facet_constraint(
levels = c("A", "B", "C"),
centered = TRUE
)
expect_equal(mfrmr:::count_facet_params(spec), 2)
})
# ---- 3.4b count_facet_params: 3 levels, not centered -> 3 free params --
test_that("count_facet_params: 3 levels not centered gives 3 free params", {
spec <- mfrmr:::build_facet_constraint(
levels = c("A", "B", "C"),
centered = FALSE
)
expect_equal(mfrmr:::count_facet_params(spec), 3)
})
# ---- 3.4c count_facet_params: 3 levels, 1 anchored, centered -> 1 free -
test_that("count_facet_params: 3 levels, 1 anchored, centered gives 1 free param", {
anch <- c(B = 0.5)
spec <- mfrmr:::build_facet_constraint(
levels = c("A", "B", "C"),
anchors = anch,
centered = TRUE
)
expect_equal(mfrmr:::count_facet_params(spec), 1)
})
# ---- 3.4d count_facet_params: all anchored -> 0 free params ------------
test_that("count_facet_params: all anchored gives 0 free params", {
anch <- c(A = 0.1, B = 0.2, C = 0.3)
spec <- mfrmr:::build_facet_constraint(
levels = c("A", "B", "C"),
anchors = anch,
centered = TRUE
)
expect_equal(mfrmr:::count_facet_params(spec), 0)
})
# ---- 3.4e count_facet_params: 1 level, centered -> 0 free params -------
test_that("count_facet_params: 1 level centered gives 0 free params", {
spec <- mfrmr:::build_facet_constraint(
levels = c("A"),
centered = TRUE
)
expect_equal(mfrmr:::count_facet_params(spec), 0)
})
# ---- 3.5 expand_facet_with_constraints round-trip -----------------------
test_that("expand_facet_with_constraints respects anchored value and length", {
anch <- c(B = 0.3)
spec <- mfrmr:::build_facet_constraint(
levels = c("A", "B", "C", "D"),
anchors = anch,
centered = TRUE
)
n_free <- mfrmr:::count_facet_params(spec)
# Feed arbitrary free parameters
free <- c(0.5, -0.2)
full <- mfrmr:::expand_facet_with_constraints(free, spec)
expect_equal(length(full), 4)
# Anchored level must equal its value
expect_equal(unname(full[2]), 0.3, tolerance = 1e-10)
# All values should be finite
expect_true(all(is.finite(full)))
})
# ---- 3.6 Group anchor constraint ---------------------------------------
test_that("group anchor constrains group mean", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
group_anchors <- data.frame(
Facet = c("Rater", "Rater"),
Level = c("R1", "R2"),
Group = c("G1", "G1"),
GroupValue = c(0.1, 0.1),
stringsAsFactors = FALSE
)
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
group_anchors = group_anchors,
method = "JML", model = "RSM", maxit = 50, quad_points = 7
))
r1_est <- fit$facets$others |>
dplyr::filter(Facet == "Rater", Level == "R1") |>
dplyr::pull(Estimate)
r2_est <- fit$facets$others |>
dplyr::filter(Facet == "Rater", Level == "R2") |>
dplyr::pull(Estimate)
group_mean <- mean(c(r1_est, r2_est))
expect_equal(group_mean, 0.1, tolerance = 0.1)
})
# ---- 3.7 Dummy facet: estimates all zero --------------------------------
test_that("dummy facet estimates are all zero", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
dummy_facets = "Criterion",
method = "JML", model = "RSM", maxit = 40, quad_points = 7
))
crit_est <- fit$facets$others |>
dplyr::filter(Facet == "Criterion") |>
dplyr::pull(Estimate)
expect_true(all(crit_est == 0),
info = "Dummy facet estimates should all be exactly 0")
})
# ---- 3.8 noncenter_facet: Person not sum-to-zero, others are -----------
test_that("noncenter_facet Person does not sum to zero; other facets do", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
method = "JML", model = "RSM", maxit = 40, quad_points = 7
))
# Rater, Task, Criterion should each sum to zero
for (facet in c("Rater", "Task", "Criterion")) {
est <- fit$facets$others |>
dplyr::filter(Facet == facet) |>
dplyr::pull(Estimate)
expect_equal(sum(est), 0, tolerance = 1e-6,
label = paste("sum-to-zero check for", facet))
}
# Person (noncenter) need NOT sum to zero -- just check it exists
person_est <- fit$facets$person$Estimate
expect_true(length(person_est) > 0)
})
# ---- 3.9a Single-level facet: build_facet_constraint -> 0 free params --
test_that("single-level facet constraint gives 0 free params", {
spec <- mfrmr:::build_facet_constraint(
levels = c("Only"),
centered = TRUE
)
expect_equal(mfrmr:::count_facet_params(spec), 0)
full <- mfrmr:::expand_facet_with_constraints(numeric(0), spec)
expect_equal(unname(full), 0)
})
# ---- 3.9b Single-level facet in practice: estimate = 0 -----------------
test_that("single-level facet estimate equals 0 in practice", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
# Collapse all tasks into a single level
d$Task <- "SingleTask"
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
method = "JML", model = "RSM", maxit = 40, quad_points = 7
))
task_est <- fit$facets$others |>
dplyr::filter(Facet == "Task") |>
dplyr::pull(Estimate)
expect_equal(unname(task_est), 0, tolerance = 1e-10)
})
# ---- 3.10 Multiple anchors across facets --------------------------------
test_that("multiple anchors across facets are all respected", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
anchors <- data.frame(
Facet = c("Rater", "Task"),
Level = c("R1", "T1"),
Anchor = c(0, -0.3),
stringsAsFactors = FALSE
)
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
anchors = anchors, method = "JML", model = "RSM", maxit = 50,
quad_points = 7
))
r1_est <- fit$facets$others |>
dplyr::filter(Facet == "Rater", Level == "R1") |>
dplyr::pull(Estimate)
t1_est <- fit$facets$others |>
dplyr::filter(Facet == "Task", Level == "T1") |>
dplyr::pull(Estimate)
expect_equal(unname(r1_est), 0, tolerance = 1e-8,
label = "Rater R1 anchor = 0")
expect_equal(unname(t1_est), -0.3, tolerance = 1e-8,
label = "Task T1 anchor = -0.3")
})
# ---- Additional: anchor + centering interaction -------------------------
test_that("non-anchored facets remain centered even when another facet is anchored", {
d <- mfrmr:::sample_mfrm_data(seed = 42)
anchors <- data.frame(
Facet = "Rater", Level = "R1", Anchor = 0.5,
stringsAsFactors = FALSE
)
fit <- suppressWarnings(fit_mfrm(
d, "Person", c("Rater", "Task", "Criterion"), "Score",
anchors = anchors, method = "JML", model = "RSM", maxit = 50,
quad_points = 7
))
# Rater facet has an anchor so sum-to-zero is no longer enforced for Rater
# But Task and Criterion should still sum to zero
for (facet in c("Task", "Criterion")) {
est <- fit$facets$others |>
dplyr::filter(Facet == facet) |>
dplyr::pull(Estimate)
expect_equal(sum(est), 0, tolerance = 1e-6,
label = paste("sum-to-zero for", facet, "with Rater anchored"))
}
# Verify the anchor is respected
r1_est <- fit$facets$others |>
dplyr::filter(Facet == "Rater", Level == "R1") |>
dplyr::pull(Estimate)
expect_equal(unname(r1_est), 0.5, tolerance = 1e-8)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.