#The IRT functions
#' @title IRT Data File Creator
#'
#' @description Formats Responses for IRT Calibration
#'
#' @details
#' Formats a CRM-downloaded Test Responses dataframe for use
#' in IRT item calibration.
#' Similarly to original SPSS script, creates concatenated names
#' for Items and People and sorts by this.
#' Reshapes responses from long to wide.
#'
#' @param responses The test responses dataframe (see import.responses)
#' @param write boolean (default = FALSE)- write to .txt file for Winsteps use
#' @param form The form code (only necessary if write = TRUE)
#' @return A response dataframe formatted to be the Winsteps data file
#' @examples irt.createdata(ResponseImport, TRUE, "AB2017A_01")
#' @family IRT Calibration
irt.createdata <- function(responses, write, form){
#create column ItemNameConcat for sorting - scored + domain + item
responses$ItemNameConcat <- NA
responses$ItemNameConcat <- paste(ifelse(responses$ItemStatus=="Scored", "S", "X"), responses$DomainNumber, "_", responses$ItemName, sep="")
#sort by candidate, attempt, and ItemNameConcat
responses <- responses[order(responses$ClientCandidateId, responses$AttemptNumber, responses$ItemNameConcat),]
#subset out only first attempt test-takers
responses<-subset(responses, AttemptNumber==1)
#new column ClientNameConcat = candidate id + attempt number (first only) + form id (space delineated with leading zeros)
responses$ClientNameConcat <- NA
responses$ClientNameConcat <- paste(sprintf("%06d", responses$ClientCandidateId), sprintf("%02d", responses$AttemptNumber), responses$Form, sep=" ")
#temp dataframe w/ only client annd item id concats and item score
responses <- subset(responses, select = c("ClientNameConcat", "ItemNameConcat", "ItemScore"))
#moves temp dataframe from wide to long, rows = client cols = item
responses <-reshape(responses, idvar = "ClientNameConcat", timevar= "ItemNameConcat", direction = "wide")
#re-sort to ensure items in correct order
responses <- responses[, order(names(responses))]
#add trailing whitespace to ClientNameConcat
responses$ClientNameConcat <- sprintf("%-45s", responses$ClientNameConcat)
if (!missing(write)){
if(write){
#write dataframe to working directory, file name = form + _DATA.txt, no quotes, delineations, row/col names, etc.
write.table(responses, paste(form, "_DATA.txt", sep = ""), sep="", quote = FALSE, row.names = FALSE, col.names = FALSE, na=" ")
}
}
return(responses)
}
#' @title IRT Item Weight File Creator
#'
#' @description Formats Item Weights for IRT Calibration
#'
#' @details
#' Creates an Item Weight file for CRM Calibration.
#' If round = 1, take Winsteps Input- data (see irt.createdata),
#' weights based on Item Status.
#' If round = 2, takes Winsteps Output- IRT Item table,
#' and weights based on Item Status, Outfit MSQ, and Displacement.
#'
#' @param data The IRT item output
#' @param write boolean (default = FALSE)- write to .txt file for Winsteps use
#' @param form The form code (only necessary if write = TRUE)
#' @param round Is this the first (1) or second(2) calibration?
#' @param outfitflag a vector of the outfit flagging parameters
#' @param displacementflag a vector of the displacement parameters
#' @return An item Weights table
#' @examples irt.itemweight(CalibrationData, TRuE, "AB2017A_01", 1, outfitflag, displacementflag)
#' @family IRT Calibration
irt.itemweight <- function(data, write, form, round, outfitflag, displacementflag){
if(missing(write)){
write <- FALSE
}
if(round == 1){
Items <- colnames(data[2:ncol(data)])
Items <- substr(Items, 11, 20)
itemWeight1 <- data.frame(ItemId = Items)
itemWeight1$rowNum <- NA
itemWeight1$rowNum <- 1:nrow(itemWeight1)
itemWeight1$Weight <- NA
itemWeight1$Weight <- ifelse(grepl("X", itemWeight1$ItemId), 0, 1)
if(write){
write.table(
subset(itemWeight1, Weight == 0, select = c(rowNum, Weight)),
file = paste(form, "_Item_Weight_", round, ".txt", sep = ""), quote = FALSE, row.names = FALSE, col.names = FALSE)
}
return(itemWeight)
}
if(round == 2){
#weight items by outfit_msq
data$WEIGHT <- with(data, ifelse(is.na(OUT.MSQ) | WEIGHT==0, 0,
ifelse(OUT.MSQ < outfitflag[1] | OUT.MSQ > outfitflag[2], 0, 1)))
#weight items by displacement
data$WEIGHT <- with(data, ifelse(is.na(DISPLACE) | WEIGHT==0, 0,
ifelse(abs(DISPLACE) > abs(displacementflag[1]), 0, 1)))
data$WeightOutfit <- NA
data$WeightOutfit <- with(data, ifelse(OUT.MSQ < outfitflag[1], "; item Outfit too low", ifelse(OUT.MSQ > outfitflag[2], "; item Outfit too high", NA)))
data$WeightDisplace <- NA
data$WeightDisplace <- with(data, ifelse(abs(DISPLACE) > abs(displacementflag[1]), "; item Displacement too high", NA))
data$WeightReason <- paste(data$WeightOutfit, data$WeightDisplace, sep = " ")
itemWeight <- subset(data, select = c(ENTRY, WEIGHT, WeightReason))
if(write){
write.table(
subset(itemWeight, WEIGHT==0),
file = paste(form, "_Item_Weight", round, ".txt", sep=""), quote = FALSE, row.names = FALSE, col.names = FALSE)
}
return(itemWeight)
}
}
#' @title IRT Anchor File Creation
#'
#' @description Creates and Formats Anchors for IRT Calibration.
#'
#' @details
#' Takes a list of items being calibrated (using the concatenated
#' item name from irt.createdata), and a table of anchor items
#' (see imports.anchors).
#' Merges the two and outputs a list of anchor items-
#' they must be scored on the form, and have an IRT_b value present in
#' the anchor download.
#'
#'
#' @param items The list of items to calibrate in IRT calibration names, see irt.createdata)
#' @param anchors A table of Anchor Items (see imports.anchors)
#' @param round Is this the first (1) or second(2) calibration? (default = 1)
#' @param write boolean (default = FALSE)- write to .txt file for Winsteps use
#' @param form The form code (only necessary if write = TRUE)
#' @return An Anchor table
#' @examples irt.itemanchor(ItemList, AnchorImport, 1, TRUE, "AB2017A_01")
#' @family IRT Calibration
irt.itemanchor <- function(items, anchors, round, write, form){
if(missing(round)) {round <- 1}
if(round == 1){
items <- subset(items, grepl("S", items))
AnchorOutput <- data.frame(ItemId = substr(items, 11, 20))
AnchorOutput$rowNum <- NA
AnchorOutput$rowNum <- 1:nrow(AnchorOutput)
AnchorOutput <- merge(AnchorOutput, anchors, by = "ItemId", all.x = true)
AnchorOutput <- subset(AnchorOutput, AnchorOutput$IRT_b !="", select = c('rowNum', 'IRT_b'))
if(!missing(write) & write){
write.table(AnchorOutput, file = paste(form, "_Anchor_Items", round, ".txt", sep = ""), quote = FALSE, row.names = FALSE, col.names = FALSE)
}
return(AnchorOutput)
}
if(round == 2) {
items$ItemId <- substr(items$NAME, 4, 9)
items <- subset(items, grepl("S", items$NAME), select = c("ENTRY", "MEASURE"))
if(!missing(write) & write){
write.table(items, file = paste(form, "_Anchor_Items", round,".txt", sep = ""), quote = FALSE, row.names = FALSE, col.names = FALSE)
}
return(items)
}
}
#' @title IRT Structure File Creation
#'
#' @description Creates and Formats Item Structures for IRT Calibration.
#'
#' @details
#' Takes a list of items being calibrated (using the concatenated
#' item name from irt.createdata), and a table of anchor items
#' (see imports.anchors).
#' Merges the two and outputs a list of anchor items-
#' they must be scored on the form, and have an IRT_b value present in
#' the anchor download.
#'
#'
#' @param items The list of items to calibrate in IRT calibration names, see irt.createdata)
#' @param anchors A table of Anchor Items (see imports.anchors)
#' @param write boolean (default = FALSE)- write to .txt file for Winsteps use
#' @param form The form code (only necessary if write = TRUE)
#' @return An item structure table
#' @examples irt.itemweight(ResponseImport, AnchorImport, 1, TRUE)
#' @family IRT Calibration
irt.itemstructure <- function(items, anchors, write, form){
items <- subset(items, grepl("S", items))
StructOutput <- data.frame(ItemId = substr(items, 11, 20))
StructOutput$rowNum <- NA
StructOutput$rowNum <- 1:nrow(StructOutput)
StructOutput <- merge(StructOutput, anchors, by = "ItemId", all.x = true)
StructOutput <- subset(StructOutput, StructOutput$d1 !="", select = c('rowNum', 'd1', 'd2', 'd3', 'd4'))
if (nrow(StructOutput) != 0){
StructOutput$Zero <- "0,.00"
StructOutput$One <- with(StructOutput, paste("1", d1, sep = ","))
StructOutput$Two <- with(StructOutput, paste("2", d2, sep = ","))
StructOutput$Three <- with(StructOutput, paste("3", d3, sep = ","))
StructOutput$Four <- with(StructOutput, paste("4", d4, sep = ","))
StructOutput <- StructOutput[, c('rowNum', 'Zero', 'One', 'Two', 'Three', 'Four')]
StructOutput <- reshape(StructOutput, direction = "long", idvar = "rowNum", varying = list(2:6))
StructOutput <- StructOutput[, c(1, 3)]
structList <- data.frame(do.call('rbind', strsplit(StructOutput$Zero,',',fixed=TRUE)))
StructOutput <- data.frame("order" = StructOutput$rowNum, "points" = structList[1], "value" = structList[2])
#sort by order, points
StructOutput <- StructOutput[order(StructOutput$order),]
if(!missing(write)){
if(write){
write.table(StructOutput, file = paste(form, "_SAFile.txt", sep = ""), quote = FALSE, row.names = FALSE, col.names = FALSE, sep = "\t")
}
}
}
return(StructOutput)
}
#' @title IRT Control Card Creation
#'
#' @description Creates and Formats Control Card for IRT Calibration.
#'
#' @details
#' Takes a list of items being calibrated (using the concatenated
#' item name from irt.createdata), and a table of anchor items
#' (see imports.anchors).
#' Merges the two and outputs a list of anchor items-
#' they must be scored on the form, and have an IRT_b value present in
#' the anchor download.
#'
#' @param responses The test responses dataframe (see import.responses)
#' @param data The table used as the 'DATA' file for the IRT calibration (see irt.createdata)
#' @param struct The structure table as input for the IRT Calibration
#' @param items The list of items to calibrate in IRT calibration names, see tool.itemlist)
#' @param form The form code (only necessary if write = TRUE)
#' @param write boolean (default = FALSE)- write to .txt file for Winsteps use
#' @param round Is it the first or second calibration?
#' @return The winsteps Control Card
#' @examples irt.controlcard(ResponseImport, ResponseImportClientResults, StructOutput, ItemList, "AB2016A_01", 1, TRUE)
#' @family IRT Calibration
irt.controlcard <- function( responses, data, struct, items, form, round, write){
title <- paste("title=", form, sep = "") #labels output w/ form name
data <- paste("data=", form, "_DATA.txt", sep = "") #name of data file
item1 <- "item1=46" #location (text-file column) of first item
ni <- paste("ni=", ncol(data)-1, sep = "") #number of items
name1 <- "name1=1" #location (text-file column) of first character of client name
namelen<- "namelen=6" #length of client name
csv <- "csv=Y" #outputs item/person stats as a csv
hlines <- "hlines=Y" #header lines in output?
# isgroups <- "isgroups=Item_Groups.txt" #item grouping file
if(length(unique(responses$ItemType)) > 1 ){
isgroups <- "isgroups=0" #item grouping file
} else {isgroups <- "isgroups="}
if(max(responses$ItemScore)>1){
codes <- "codes="
for (i in 0:max(responses$ItemScore)){ ###will need to change this people may not score 4
codes <- paste(codes, i, sep = "")
}
}else {codes <- "codes=01"} #list of acceptable item responses
misscore<-"misscore=-1" #treat missing data as not administered
totalscore<-"totalscore=Yes" #Include extreme responses in reported scores
discrim<-"discrim=Yes" #report empirical item discriminations
asymptote<-"asymptote=Yes" #report the values of the Upper and Lower asymptotes in the Item Tables and IFILE=
ptbiserial<-"ptbiserial=yes" #report point-biserial
pvalue<-"PVALUE = Yes" # report proportion-correct-values
prcomp<-"PRCOMP = S" # Principal components analysis on standardized residuals
udecimals<-"UDECIMALS = 5" # report out to the maximum number (4) of decimal places
tables<-"TABLES = 101100011101010000010010" # Output select tables
CClistStatic<-c(misscore, totalscore, discrim, asymptote,ptbiserial,pvalue,prcomp,udecimals,tables)
# ifile <- paste("ifile=",form,"_Item_File.csv", sep = "")
# pfile <- paste("pfile=",form,"_Person_File.csv", sep = "")
# iafile<- paste("iafile=",form,"_Anchor_Items.txt", sep = "")
ifile <- paste("ifile=", form, "_Item_File", round, ".csv", sep = "")
pfile <- paste("pfile=", form, "_Person_File", round, ".csv", sep = "")
iafile <- paste("iafile=", form,"_Anchor_Items", round, ".txt", sep = "")
iwfile <- paste("iweight=", form,"_Item_Weight_", round, ".txt", sep = "")
if (round > 1){
pwfile <- paste("pweight=", form, "_Person_Weight.txt", sep="")
}
else {pwfile <- ""}
safile <- ifelse(nrow(struct) == 0, "", paste("safile=", form, "_SAFile.txt", sep = ""))
#safile <- paste("safile=", form, "_SAFile.txt", sep = "")
#model<- "models=Item_Models.txt"
model<- "models=R"
controlCardText <- c("&INST", title, data, item1, ni, name1, namelen, csv, hlines,
codes, isgroups, ifile, pfile, iafile, iwfile, pwfile, safile, model, CClistStatic, "&END")
controlCardText <- c(controlCardText, items, "END LABELS")
if(!missing(write)){
if(write){
write(controlCardText, file = paste(form, "_Control_Card", round, ".txt", sep=""), sep="\n")
}
}
return(controlCardText)
}
#' @title IRT Item Table Cleaner
#'
#' @description Formats the IRT item table for use with CTT IA.
#'
#' @details
#' Takes the IRT item table (Winsteps Output), renames the necessary columns,
#' and puts it in the standard order.
#'
#' @param itemTable The IRT item Table (Winsteps Output)
#' @return The IRT item table formatted for use wih CTT IA.
#' @examples irt.cleanupresults(IRTitemTable2)
#' @family IRT Calibration
irt.cleanupresults <- function(itemTable){
itemTable$ItemId <- substr(itemTable$NAME, 4, 9)
#
reorderIRT<- c("ItemId","MEASURE","MODLSE","IN.MSQ","IN.ZSTD","OUT.MSQ","OUT.ZSTD","DISPLACE")
#
itemTable<- itemTable[, c(reorderIRT, setdiff(names(itemTable), reorderIRT))]
setnames(itemTable, old = c("ItemId",'MEASURE','MODLSE','IN.MSQ','IN.ZSTD','OUT.MSQ','OUT.ZSTD','DISPLACE'), new = c('Item','irt_b','irt_b_se','infit_msq','infit_z','outfit_msq','outfit_z','displacement'))
#
itemTable$ExamFirstTime <- "First-Timers"
#
itemTable <- subset(itemTable, select = c('Item','irt_b','irt_b_se','infit_msq','infit_z','outfit_msq','outfit_z','displacement', 'ExamFirstTime'))
###we are missing irt_d1,irt_d2,irt_d3,irt_d4 I added some code that could add the threashold but we would have to modify above
return(itemTable)
}
#' @title IRT Person Weight
#'
#' @description Creates a weight file for people based on
#' outfit and displacement flags.
#'
#' @details
#' Takes the IRT person table (Winsteps Output) and weights based on
#' outfit and displacement flags.
#'
#' @param data The IRT person table
#' @param write boolean (default = FALSE)- write to .txt file for Winsteps use
#' @param form The form code (only necessary if write = TRUE)
#' @param outfitflag a vector of the outfit flagging parameters
#' @param displacementflag a vector of the displacement parameters
#' @return The IRT item table formatted for use wih CTT IA.
#' @examples irt.cleanupresults(IRTitemTable2)
#' @family IRT Calibration
irt.personweight <- function(data, write, form, outfitflag, displacementflag){
#weight people by outfit_msq
data$WEIGHT <- with(data, ifelse(is.na(OUT.MSQ) | WEIGHT==0, 0,
ifelse(OUT.MSQ < outfitflag[1] | OUT.MSQ > outfitflag[2], 0, 1)))
#weight people by displacement
data$WEIGHT <- with(data, ifelse(is.na(DISPLACE) | WEIGHT==0, 0,
ifelse(abs(DISPLACE) > abs(displacementflag[1]), 0, 1)))
data$WeightOutfit <- NA
data$WeightOutfit <- with(data, ifelse(OUT.MSQ < outfitflag[1], "; person Outfit too low", ifelse(OUT.MSQ > outfitflag[2], "; person Outfit too high", NA)))
data$WeightDisplace <- NA
data$WeightDisplace <- with(data, ifelse(abs(DISPLACE) > abs(displacementflag[1]), "; person Displacement too high", NA))
data$WeightReason <- paste(data$WeightOutfit, data$WeightDisplace, sep = " ")
###########################################
personWeight <- subset(data, WEIGHT==0, select = c(ENTRY, WEIGHT, WeightReason))
if(!missing(write)){
if(write){
write.table(personWeight, file = paste(form, "_Person_Weight.txt", sep=""), quote = FALSE, row.names = FALSE, col.names = FALSE)
}
}
return(personWeight)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.