if (!requireNamespace("catR", quietly = TRUE)) install.packages("catR") if (!requireNamespace("readxl", quietly = TRUE)) install.packages("readxl") # ========================================== # Computerized Assessment Tool (catR, 3PL) # Files expected in same folder: # - R_Assessment_Tool.R # - Survey_ItemBank_for_R.xlsx # ========================================== library(catR) library(readxl) # ---- Load item bank ---- excel_path <- "C:/R/Survey_ItemBank_for_R.xlsx" ib <- read_excel(excel_path) # Expected columns in Excel: # Question_ID | Item_Text | Answe3r_Options | Correct_Answer | a | b | c # Build catR item parameter matrix a, b, c, D a <- as.numeric(ib$a) b <- as.numeric(ib$b) c <- as.numeric(ib$c) itemBank <- cbind(a, b, c, rep(1, length(a))) # D = 1 # ---- Config ---- max_items <- 10L # fixed-length assessment (can be changed) theta_current <- 0 # initial theta guess administered <- integer(0) # indices of items asked responses <- integer(0) # 1 correct, 0 incorrect # Keep track of only scored (objective) items for theta estimation scored_admin <- integer(0) # indices of scored items scored_resp <- integer(0) # responses (1/0) for scored items log_rows <- list() cat("\n===== Adaptive Assessment (3PL; ML; length =", max_items, ") =====\n") cat("Answer by typing the OPTION NUMBER (1, 2, 3, ...) and press Enter.\n") # ---- Ask one item ---- ask_item <- function(i_row) { qid <- if ("Question_ID" %in% names(ib)) ib$Question_ID[i_row] else i_row qtext <- ib$Item_Text[i_row] opts <- ib$Answer_Options[i_row] cat("\n------------------------------------------------------------\n") cat(sprintf("Question %s\n", as.character(qid))) cat(paste0(qtext, "\n")) cat("Options:\n") cat(paste0(opts, "\n")) # e.g., "1 = ..., 2 = ..., 3 = ..." repeat { ans <- readline(prompt = "Your answer (option number): ") if (nzchar(ans) && grepl("^[0-9]+$", ans)) return(as.integer(ans)) cat("Please enter a number like 1, 2, 3 ...\n") } } # ---- Adaptive loop ---- while (length(administered) < max_items) { # select next item (MFI) sel <- nextItem(itemBank, theta = theta_current, out = administered, criterion = "MFI") i <- sel$item # ask user choice <- ask_item(i) # correct option (must be the option number in Excel) correct_option <- suppressWarnings(as.integer(ib$Correct_Answer[i])) # If NA (opinion/Likert item), do not score it if (is.na(correct_option)) { is_correct <- NA_integer_ } else { is_correct <- as.integer(identical(choice, correct_option)) # add to scored lists for theta estimation scored_admin <- c(scored_admin, i) scored_resp <- c(scored_resp, is_correct) } # record in main log (keep NA for opinion items) administered <- c(administered, i) responses <- c(responses, ifelse(is.na(is_correct), NA_integer_, is_correct)) # re-estimate theta ONLY from scored items if (length(scored_resp) > 0) { theta_current <- thetaEst( it = itemBank[scored_admin, , drop = FALSE], x = scored_resp, method = "ML", D = 1 ) } # log log_rows[[length(administered)]] <- data.frame( step = length(administered), question_index = i, question_id = if ("Question_ID" %in% names(ib)) as.character(ib$Question_ID[i]) else as.character(i), user_choice = choice, correct_option = correct_option, correct = is_correct, scored = !is.na(is_correct), theta_hat = as.numeric(theta_current) ) cat(sprintf( "→ Saved. Correct option = %s | Your answer = %s | Θ̂ (ML) = %.3f\n", ifelse(is.na(correct_option), "NA", as.character(correct_option)), as.character(choice), as.numeric(theta_current) )) # If opinion-based (no correct answer), show a clear note if (is.na(correct_option)) { cat("→ Opinion-based item: excluded from scoring.\n") } } # ---- Finish ---- results <- do.call(rbind, log_rows) write.csv(results, "assessment_log.csv", row.names = FALSE) cat("\n===== Assessment finished =====\n") cat(sprintf("Items administered: %d\n", nrow(results))) cat(sprintf("Final ability estimate (Θ̂, ML, D=1): %.3f\n", results$theta_hat[nrow(results)])) cat(sprintf("Total correct (scored): %d / %d\n", sum(results$correct, na.rm = TRUE), sum(!is.na(results$correct)))) cat('Log saved as "assessment_log.csv" in this folder.\n')