Examine the calibration work of expert judges for the development of pilot ASAP-AQ using the Thurstone scaling method.
The calibration dataset is in long format, with each item’s score by a judge on its own row.
cal.long <- read.csv("asapaq-calibration-long.csv")
str(cal.long)
## 'data.frame': 833 obs. of 5 variables:
## $ uniqueItemID: chr "FORA01JEFI" "FORA02JEFI" "FORA03JEFI" "FORA04JEFI" ...
## $ constructID : chr "FORA" "FORA" "FORA" "FORA" ...
## $ itemID : chr "FORA01" "FORA02" "FORA03" "FORA04" ...
## $ judgeID : chr "JEFI" "JEFI" "JEFI" "JEFI" ...
## $ Score : int 6 4 9 6 5 9 11 8 1 1 ...
This shows a score by each judge for each item, which is assigned an item ID. The uniqueItemID is unique to a judge also. It will be removed because it’s not useful.
How many judges have contributed and how many scores did each provide?
# Remove the uniqueItemID column
cal.scores <- cal.long %>% select(-uniqueItemID)
table(cal.long$judgeID)
##
## CADO CHGP ENZI JEFI LEDR MILE STHE
## 119 119 119 119 119 119 119
This table shows if any items were missed by a judge.
First, subset the data for each construct.
cal.scores.fora <- cal.scores %>% filter(constructID == "FORA")
cal.scores.epft <- cal.scores %>% filter(constructID == "EPFT")
cal.scores.ehdp <- cal.scores %>% filter(constructID == "EHDP")
Reshape the data from long to wide format for correlation analysis.
# Reshape the data from long to wide format
cal.wide.fora <- spread(cal.scores.fora, key = judgeID, value = Score)
cal.wide.epft <- spread(cal.scores.epft, key = judgeID, value = Score)
cal.wide.ehdp <- spread(cal.scores.ehdp, key = judgeID, value = Score)
Calculate the correlation matrix
First for FORA:
#FORA
cor_matrix.fora <- cor(cal.wide.fora[, 3:ncol(cal.wide.fora)], use = "complete.obs", method ="spearman")
print(cor_matrix.fora)
## CADO CHGP ENZI JEFI LEDR MILE STHE
## CADO 1.0000000 0.6106019 0.12225651 0.426631795 0.253599094 0.7734847 0.5472404
## CHGP 0.6106019 1.0000000 0.39922443 0.362759775 0.246701144 0.5680445 0.7584499
## ENZI 0.1222565 0.3992244 1.00000000 0.013547029 0.100254932 0.3330968 0.3902311
## JEFI 0.4266318 0.3627598 0.01354703 1.000000000 0.003393348 0.4773003 0.3160193
## LEDR 0.2535991 0.2467011 0.10025493 0.003393348 1.000000000 0.2966773 0.2769670
## MILE 0.7734847 0.5680445 0.33309685 0.477300283 0.296677284 1.0000000 0.6070602
## STHE 0.5472404 0.7584499 0.39023110 0.316019254 0.276967014 0.6070602 1.0000000
This shows that there are some more promising and less promising groupings, if needed.
#EPFT
cor_matrix.epft <- cor(cal.wide.epft[, 3:ncol(cal.wide.epft)], use = "complete.obs", method="spearman")
print(cor_matrix.epft)
## CADO CHGP ENZI JEFI LEDR MILE STHE
## CADO 1.0000000 0.5428397 0.3739515 0.4474811 0.6960196 0.6493145 0.6248978
## CHGP 0.5428397 1.0000000 0.3499198 0.6199409 0.4618531 0.4204491 0.3414341
## ENZI 0.3739515 0.3499198 1.0000000 0.1640116 0.3881987 0.3385423 0.1764487
## JEFI 0.4474811 0.6199409 0.1640116 1.0000000 0.4839430 0.3954859 0.3382670
## LEDR 0.6960196 0.4618531 0.3881987 0.4839430 1.0000000 0.5905670 0.3953956
## MILE 0.6493145 0.4204491 0.3385423 0.3954859 0.5905670 1.0000000 0.3806970
## STHE 0.6248978 0.3414341 0.1764487 0.3382670 0.3953956 0.3806970 1.0000000
#EHDP
cor_matrix.ehdp <- cor(cal.wide.ehdp[, 3:ncol(cal.wide.ehdp)], use = "complete.obs", method = "spearman")
print(cor_matrix.ehdp)
## CADO CHGP ENZI JEFI LEDR MILE STHE
## CADO 1.0000000 0.6173547 0.5508085 0.5461203 0.4845838 0.6303347 0.6251446
## CHGP 0.6173547 1.0000000 0.3808666 0.4577192 0.5375495 0.8884571 0.9022045
## ENZI 0.5508085 0.3808666 1.0000000 0.2249980 0.3614261 0.3235858 0.3071095
## JEFI 0.5461203 0.4577192 0.2249980 1.0000000 0.5369401 0.5478325 0.5595225
## LEDR 0.4845838 0.5375495 0.3614261 0.5369401 1.0000000 0.5469815 0.5784848
## MILE 0.6303347 0.8884571 0.3235858 0.5478325 0.5469815 1.0000000 0.8649401
## STHE 0.6251446 0.9022045 0.3071095 0.5595225 0.5784848 0.8649401 1.0000000
Now analyze the central tendency and variability of each unique item based on all judges’ scores.
#function for finding the mode of scores
mode_function <- function(v) {
uniq_vals <- unique(v)
uniq_vals[which.max(tabulate(match(v, uniq_vals)))]
}
cal.analysis_summary <- cal.long %>%
group_by(constructID, itemID) %>%
summarize(
mean_score = mean(Score),
standard_deviation = sd(Score),
standard_error = sd(Score) / sqrt(n()),
variance = var(Score),
mode = mode_function(Score),
median_score = median(Score),
interquartile_range = IQR(Score),
range = max(Score) - min(Score),
min = min(Score),
max = max(Score),
n = n()
) %>%
ungroup()
## `summarise()` has grouped output by 'constructID'. You can override using the
## `.groups` argument.
print(cal.analysis_summary)
## # A tibble: 119 × 13
## constructID itemID mean_score standard_deviation standard_error variance
## <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 EHDP EHDP01 1.14 0.378 0.143 0.143
## 2 EHDP EHDP02 5.29 4.19 1.58 17.6
## 3 EHDP EHDP03 2.86 0.378 0.143 0.143
## 4 EHDP EHDP04 3.43 2.57 0.972 6.62
## 5 EHDP EHDP05 9.57 2.99 1.13 8.95
## 6 EHDP EHDP06 7.14 0.900 0.340 0.810
## 7 EHDP EHDP07 4.57 3.69 1.39 13.6
## 8 EHDP EHDP08 3.29 2.21 0.837 4.90
## 9 EHDP EHDP09 3.86 3.63 1.37 13.1
## 10 EHDP EHDP10 1 0 0 0
## # ℹ 109 more rows
## # ℹ 7 more variables: mode <int>, median_score <int>,
## # interquartile_range <dbl>, range <int>, min <int>, max <int>, n <int>
Merge cal.analysis_summary with imported “Item Evaluation.csv” to get the item descriptions included.
item_evaluation <- read.csv("Item Evaluation.csv")
Join item texts to calibration data and write as “Calibrated Items All Judges Complete.csv” and use this for selecting based on central tendency and vairability of each item’s score across all judges.
df <- left_join(item_evaluation, cal.analysis_summary, by = "itemID")
write.csv(df, "Calibrated Items All Judges Complete.csv", row.names = FALSE)
# Get unique judge IDs
judge_ids <- unique(cal.long$judgeID)
# Create combinations of three judges
comb <- combn(judge_ids, 4, simplify = FALSE)
# Initialize a list to store the results
results <- list()
# Loop through each combination of three judges
for (i in seq_along(comb)) {
# Create a label for the current combination of judges
label <- paste(comb[[i]], collapse = "_")
# Filter the data to include only the current combination of judges
filtered_data <- cal.long %>%
filter(judgeID %in% comb[[i]]) %>%
group_by(constructID, itemID) %>%
summarize(
mean_score = mean(Score),
standard_deviation = sd(Score),
standard_error = sd(Score) / sqrt(n()),
variance = var(Score),
mode = mode_function(Score),
median_score = median(Score),
interquartile_range = IQR(Score),
range = max(Score) - min(Score),
min = min(Score),
max = max(Score),
n = n(),
.groups = 'keep'
) %>%
ungroup()
# Store the results in the list
results[[label]] <- filtered_data
}
Loop through the results to show the number of items IQR <= 2.
for (label in names(results)) {
current_result <- results[[label]]
filtered_rows <- current_result %>%
filter(interquartile_range <= 2)
cat("Number of items with IQR <= 2 for combination:", label)
print(nrow(filtered_rows))
cat("\n")
}
## Number of items with IQR <= 2 for combination: JEFI_ENZI_STHE_CADO[1] 60
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_STHE_LEDR[1] 57
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_STHE_MILE[1] 67
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_STHE_CHGP[1] 67
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_CADO_LEDR[1] 58
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_CADO_MILE[1] 63
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_CADO_CHGP[1] 63
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_LEDR_MILE[1] 59
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_LEDR_CHGP[1] 58
##
## Number of items with IQR <= 2 for combination: JEFI_ENZI_MILE_CHGP[1] 64
##
## Number of items with IQR <= 2 for combination: JEFI_STHE_CADO_LEDR[1] 67
##
## Number of items with IQR <= 2 for combination: JEFI_STHE_CADO_MILE[1] 81
##
## Number of items with IQR <= 2 for combination: JEFI_STHE_CADO_CHGP[1] 77
##
## Number of items with IQR <= 2 for combination: JEFI_STHE_LEDR_MILE[1] 71
##
## Number of items with IQR <= 2 for combination: JEFI_STHE_LEDR_CHGP[1] 75
##
## Number of items with IQR <= 2 for combination: JEFI_STHE_MILE_CHGP[1] 74
##
## Number of items with IQR <= 2 for combination: JEFI_CADO_LEDR_MILE[1] 73
##
## Number of items with IQR <= 2 for combination: JEFI_CADO_LEDR_CHGP[1] 74
##
## Number of items with IQR <= 2 for combination: JEFI_CADO_MILE_CHGP[1] 77
##
## Number of items with IQR <= 2 for combination: JEFI_LEDR_MILE_CHGP[1] 69
##
## Number of items with IQR <= 2 for combination: ENZI_STHE_CADO_LEDR[1] 63
##
## Number of items with IQR <= 2 for combination: ENZI_STHE_CADO_MILE[1] 65
##
## Number of items with IQR <= 2 for combination: ENZI_STHE_CADO_CHGP[1] 70
##
## Number of items with IQR <= 2 for combination: ENZI_STHE_LEDR_MILE[1] 59
##
## Number of items with IQR <= 2 for combination: ENZI_STHE_LEDR_CHGP[1] 59
##
## Number of items with IQR <= 2 for combination: ENZI_STHE_MILE_CHGP[1] 71
##
## Number of items with IQR <= 2 for combination: ENZI_CADO_LEDR_MILE[1] 61
##
## Number of items with IQR <= 2 for combination: ENZI_CADO_LEDR_CHGP[1] 61
##
## Number of items with IQR <= 2 for combination: ENZI_CADO_MILE_CHGP[1] 70
##
## Number of items with IQR <= 2 for combination: ENZI_LEDR_MILE_CHGP[1] 63
##
## Number of items with IQR <= 2 for combination: STHE_CADO_LEDR_MILE[1] 74
##
## Number of items with IQR <= 2 for combination: STHE_CADO_LEDR_CHGP[1] 77
##
## Number of items with IQR <= 2 for combination: STHE_CADO_MILE_CHGP[1] 81
##
## Number of items with IQR <= 2 for combination: STHE_LEDR_MILE_CHGP[1] 74
##
## Number of items with IQR <= 2 for combination: CADO_LEDR_MILE_CHGP[1] 74
Change the value of label to the match the codes of the group of best-agreement judges with underscore between (from manually analyzing the previous output).
label <- "STHE_CADO_MILE_CHGP"
cat("Results for combination:", label)
## Results for combination: STHE_CADO_MILE_CHGP
current_result <- results[[label]]
print(table(current_result$interquartile_range))
##
## 0 0.25 0.5 0.75 1 1.25 1.5 1.75 2 2.25 2.5 2.75 3 3.25 3.5 4
## 9 7 13 4 8 14 13 8 5 6 9 3 5 2 2 4
## 4.25 4.75 5.5 6.5 7.5
## 2 2 1 1 1
print(describe(current_result$interquartile_range))
## vars n mean sd median trimmed mad min max range skew kurtosis se
## X1 1 119 1.74 1.38 1.5 1.57 1.48 0 7.5 7.5 1.34 2.48 0.13
hist(current_result$interquartile_range)
Merge result with imported Items Evaluation.csv to add item text,
df <- left_join(item_evaluation, current_result, by = "itemID")
Write to .csv file for later filtering and selecting items in Excel.
write.csv(df, paste("Calibrated Items Best Group Complete",label,".csv"), row.names = FALSE)