This file contains the data analysis workflow of the our empirical investigation of generated oracles. The goal of this documentation is to provide a detailed description of how we analyze the data obtained from the experiment.
This scripts has the results folder as its input. The script traverses the results from both experiment dates and searches for CSV and JSON files with the given names.
The results are stored in two subfolders in the results folder: dec1 and dec8. In dec1, the participant identifiers are found from 1 to 40, while in dec8 from 41 to 80. Note that these are only participant identifiers, not all folders contain results, as there were only 54 participants in total.
Each participant answer list is being put into a data frame. Then, all of the answers appended to a huge data frame containing all participant data.
answer_files <- list.files(params$results_folder, pattern="*.json", recursive=TRUE, full.names=TRUE)
result_answers <- data.frame(row.names = c("PID","Id","IsOK"), stringsAsFactors = FALSE)
for (json in answer_files) {
# Getting the answers of the participants from the JSON file
answers <- data.frame(fromJSON(readLines(json),simplifyDataFrame=TRUE),stringsAsFactors = FALSE)
# Getting the PID from the base directory
pid <- as.numeric(rep(basename(dirname(json)),nrow(answers)))
# Creating the column for the participant identifier
answers_with_pid <- cbind(pid,answers)
# Appending the new rows the previouses
result_answers <- rbind(result_answers,answers_with_pid)
}
rm(json,pid,answers,answers_with_pid,answer_files)
result_answers$Project <- ifelse(result_answers$pid>40,"MathNet","NBitcoin")
nbitcoin_methods <- c(rep("CompareTo",3),rep("Constructor",3),rep("Equals",3),rep("Min",3),rep("Plus",3))
mathnet_methods <- c(rep("Combinations",3),rep("CombinationsWithRepetition",3),rep("Permutations",3),rep("Variations",3),rep("VariationsWithRepetition",3))
# Creating the mapping between test identifiers, projects and methods
test_method_mapping <- data.frame(Results.Id=rep(0:14,2),Method=c(nbitcoin_methods,mathnet_methods),Project=c(rep("NBitcoin",15),rep("MathNet",15)))
# Adding the method name to each row
result_answers <- plyr::join(result_answers,test_method_mapping,by=c("Results.Id","Project"))
# Adding test identifiers to each row
result_answers$TestId <- car::recode(as.factor(result_answers$Results.Id),"0='T0.1'; 1='T1.1'; 2='T2.1'; 3='T3.2'; 4='T4.2'; 5='T5.2'; 6='T6.3'; 7='T7.3'; 8='T8.3'; 9='T9.4'; 10='T10.4'; 11='T11.4'; 12='T12.5'; 13='T13.5'; 14='T14.5'")
rm(nbitcoin_methods,mathnet_methods)
The golden answers are stored in two files for the projects NBitcoin and MathNet: nbitcoin-gold.csv and mathnet-gold.csv. These are being parsed here.
nbitcoin_golden_answers <- read.csv(file.path(params$golden_folder,"nbitcoin-gold.csv"), header=TRUE, sep = ";",na.strings=c("",""),stringsAsFactors = FALSE)
mathnet_golden_answers <- read.csv(file.path(params$golden_folder,"mathnet-gold.csv"), header=TRUE, sep = ";",na.strings=c("",""),stringsAsFactors = FALSE)
Each video annotation of the participants is put into a data frame. Then, all of the annotations form a huge data frame with participant identifiers. Note that some rows at the start of the CSV from Boris contains only irrelevant metadata, thus they are being skipped dynamically.
video_files <- list.files(params$results_folder, pattern="video.csv", recursive=TRUE, full.names=TRUE)
video_lengths <- data.frame()
result_videos <- data.frame()
for (video in video_files) {
# Getting the number of lines to skip at head (max. 20)
lines_to_skip <- grep("^Time,", readLines(video, n = 20))-1
# Reading annotations
annotations <- read.csv(video,header=TRUE,skip=lines_to_skip,sep=",",as.is = TRUE)
# Creating PID column based on the number of rows
pid <- rep(strsplit(readLines(video,n=1),split = ',')[[1]][2],nrow(annotations))
# Adding the PID column to the annotations
annotations_with_pid <- cbind(pid, annotations)
# Appending the new annotations to the previouses
result_videos <- rbind(result_videos,annotations_with_pid)
# Appending video length
video_lengths <- filter(result_videos %>% select(pid,Media.total.length) %>% distinct_(),pid != 55 & pid != 59)
}
print("Video lengths data NBitcoin")
## [1] "Video lengths data NBitcoin"
video_lengths_nbitcoin <- filter(video_lengths, as.numeric(pid) < 41)
print(paste("Min", min(video_lengths_nbitcoin$Media.total.length/60) , sep=" "))
## [1] "Min 37.4133333333333"
print(paste("Median",median(video_lengths_nbitcoin$Media.total.length/60), sep=" "))
## [1] "Median 46.9433333333333"
print(paste("Mean", mean(video_lengths_nbitcoin$Media.total.length/60), sep=" "))
## [1] "Mean 46.2582905982906"
print(paste("Max", max(video_lengths_nbitcoin$Media.total.length/60), sep=" "))
## [1] "Max 54.9"
print(paste("sd", sd(video_lengths_nbitcoin$Media.total.length/60), sep=" "))
## [1] "sd 3.95760995213189"
print("Video lengths data MathNet")
## [1] "Video lengths data MathNet"
video_lengths_mathnet <- filter(video_lengths, as.numeric(pid) > 40)
print(paste("Min", min(video_lengths_mathnet$Media.total.length/60) , sep=" "))
## [1] "Min 34.8766666666667"
print(paste("Median",median(video_lengths_mathnet$Media.total.length/60), sep=" "))
## [1] "Median 44.5666666666667"
print(paste("Mean", mean(video_lengths_mathnet$Media.total.length/60), sep=" "))
## [1] "Mean 44.5192307692308"
print(paste("Max", max(video_lengths_mathnet$Media.total.length/60), sep=" "))
## [1] "Max 52.61"
print(paste("sd", sd(video_lengths_mathnet$Media.total.length/60), sep=" "))
## [1] "sd 5.24144155210754"
#rm(video,lines_to_skip,video_files,annotations,annotations_with_pid,pid)
The answers of the background questionnaires for each participant are parsed here into a large data frame containing every participant.
bg_answers <- list.files(params$results_folder, pattern="background.csv", recursive=TRUE, full.names=TRUE)
result_bg_answers <- data.frame()
for (answer_file in bg_answers) {
answers <- read.csv(answer_file,header=TRUE,sep=";",as.is = TRUE)
colnames(answers)[1] <- "PID"
result_bg_answers <- rbind(result_bg_answers,answers)
}
rm(bg_answers,answers,answer_file)
The answers of the exit questionnaires for each participant are parsed here into a large data frame containing every participant.
exit_answers <- list.files(params$results_folder, pattern="exit.csv", recursive=TRUE, full.names=TRUE)
result_exit_answers <- data.frame()
for (answer_file in exit_answers) {
answers <- read.csv(answer_file,header=TRUE,sep=";",as.is = TRUE)
colnames(answers)[1] <- "PID"
result_exit_answers <- rbind(result_exit_answers,answers)
}
rm(exit_answers,answer_file,answers)
This section computes some basic results of the participants using the golden data. This includes classification of the participants’ answers as true positive (TP), false positive (FP), true negative (TN) and false negative (FN).
participant_results <- data.frame()
# Iterating through participants
for(pid in unique(result_answers$pid)) {
result_column <- c()
# Iterating through answers for checking correctness
for(test in result_answers[result_answers$pid == pid,]$Results.Id) {
if(as.numeric(pid) < 41) {
# Less than 41 is NBitcoin
golden_answers <- nbitcoin_golden_answers
} else {
# Otherwise is MathNet
golden_answers <- mathnet_golden_answers
}
participant_answer<- result_answers[result_answers$pid == pid & result_answers$Results.Id == test,]$Results.IsOK
if(golden_answers[golden_answers$id == test,]$isok == TRUE) {
# If the golden answer is OK
if(participant_answer == TRUE) {
result_column <- rbind(result_column,"TN")
} else {
result_column <- rbind(result_column,"FP")
}
} else {
# If the golden answer is Wrong
if(participant_answer == FALSE) {
result_column <- rbind(result_column,"TP")
} else {
result_column <- rbind(result_column,"FN")
}
}
}
participant_result_table <- cbind(result_answers[result_answers$pid == pid,],result_column)
colnames(participant_result_table)[7] <- "Check"
participant_results <- rbind(participant_results,participant_result_table)
}
rm(participant_result_table,result_column,golden_answers,pid,test,participant_answer)
The next code summarizes the results of the participants in each row by enumerating their TPs, FPs, TNs and FNs.
participant_result_summary <- data.frame(row.names = c("PID","Project","TP","FP","TN","FN"),stringsAsFactors=FALSE)
# Iterating through participants
for(pid in unique(result_answers$pid)) {
table_with_checks <- participant_results[participant_results$pid == pid,]
tp_count <- length(which(table_with_checks$Check == "TP"))
fp_count <- length(which(table_with_checks$Check == "FP"))
tn_count <- length(which(table_with_checks$Check == "TN"))
fn_count <- length(which(table_with_checks$Check == "FN"))
if(as.numeric(pid) > 40) {
project = "MathNet"
} else {
project = "NBitcoin"
}
participant_summary_row <- data.frame(PID=pid, Project=project, TP=tp_count, FP=fp_count, TN=tn_count, FN=fn_count)
participant_result_summary <- rbind(participant_summary_row, participant_result_summary)
}
rm(participant_summary_row,project,tp_count,tn_count,fp_count,fn_count,table_with_checks,pid)
In this section, the video logs for each participant are analyzed and transformed. This is important for evaluating the time required for each activity. The code collects and summarizes the behaviors for each participant. This includes 1) the time spent with each case both on portal and Visual Studio (VS), 2) time spent with debugging at all and for each case as well, 3) the number of test runs per case, 4) the number of consistent markings per case, 5) the number of changing markings (Wrong to OK, OK to Wrong) per case.
extended_video_annotations <- data.frame(row.names = c("PID","Timestamp","Behavior","Modifier","Active","Page","Window","RunCase","DebugLength","DebugCase"),stringsAsFactors = FALSE)
vs_time_summary <- data.frame(row.names = c("PID","T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5","S","C","P","Total"), stringsAsFactors = FALSE)
portal_time_summary <- data.frame(row.names = c("PID","T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), stringsAsFactors = FALSE)
marked_wrong_summary <- data.frame(row.names = c("PID","T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), stringsAsFactors = FALSE)
marked_ok_summary <- data.frame(row.names = c("PID","T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), stringsAsFactors = FALSE)
cut_time_for_test <- data.frame(row.names = c("PID","T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), stringsAsFactors = FALSE)
sut_time_for_test <- data.frame(row.names = c("PID","T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), stringsAsFactors = FALSE)
put_time_for_test <- data.frame(row.names = c("PID","T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), stringsAsFactors = FALSE)
# Iterating through participants
for(pid in unique(result_answers$pid)) {
annotations_for_participant <- result_videos[result_videos$pid == pid,]
vs_time_summary_row <- data.frame(PID = pid, T0.1 = 0, T1.1 = 0, T2.1 = 0, T3.2 = 0, T4.2 = 0, T5.2 = 0, T6.3 = 0, T7.3 = 0, T8.3 = 0, T9.4 = 0, T10.4 = 0, T11.4 = 0, T12.5 = 0, T13.5 = 0, T14.5 = 0, S = 0, C = 0, P = 0, Total = 0)
portal_time_summary_row <- data.frame(PID = pid, T0.1 = 0, T1.1 = 0, T2.1 = 0, T3.2 = 0, T4.2 = 0, T5.2 = 0, T6.3 = 0, T7.3 = 0, T8.3 = 0, T9.4 = 0, T10.4 = 0, T11.4 = 0, T12.5 = 0, T13.5 = 0, T14.5 = 0)
marked_wrong_summary_row <- data.frame(PID = pid, T0.1 = 0, T1.1 = 0, T2.1 = 0, T3.2 = 0, T4.2 = 0, T5.2 = 0, T6.3 = 0, T7.3 = 0, T8.3 = 0, T9.4 = 0, T10.4 = 0, T11.4 = 0, T12.5 = 0, T13.5 = 0, T14.5 = 0)
marked_ok_summary_row <- data.frame(PID = pid, T0.1 = 0, T1.1 = 0, T2.1 = 0, T3.2 = 0, T4.2 = 0, T5.2 = 0, T6.3 = 0, T7.3 = 0, T8.3 = 0, T9.4 = 0, T10.4 = 0, T11.4 = 0, T12.5 = 0, T13.5 = 0, T14.5 = 0)
cut_time_for_test_row <- data.frame(PID = pid, T0.1 = 0, T1.1 = 0, T2.1 = 0, T3.2 = 0, T4.2 = 0, T5.2 = 0, T6.3 = 0, T7.3 = 0, T8.3 = 0, T9.4 = 0, T10.4 = 0, T11.4 = 0, T12.5 = 0, T13.5 = 0, T14.5 = 0)
sut_time_for_test_row <- data.frame(PID = pid, T0.1 = 0, T1.1 = 0, T2.1 = 0, T3.2 = 0, T4.2 = 0, T5.2 = 0, T6.3 = 0, T7.3 = 0, T8.3 = 0, T9.4 = 0, T10.4 = 0, T11.4 = 0, T12.5 = 0, T13.5 = 0, T14.5 = 0)
put_time_for_test_row <- data.frame(PID = pid, T0.1 = 0, T1.1 = 0, T2.1 = 0, T3.2 = 0, T4.2 = 0, T5.2 = 0, T6.3 = 0, T7.3 = 0, T8.3 = 0, T9.4 = 0, T10.4 = 0, T11.4 = 0, T12.5 = 0, T13.5 = 0, T14.5 = 0)
# Iterating through the annotations of the participant
currently_active <- NA
vs_window <- NA
portal_page <- NA
debug_start_index <- NA
vs_start_index <- -1 # -1: VS has not been activated yet, otherwise the row index of activation point
window_start_index <- -1 # -1: A window in VS has not been activated yet, otherwise the row index of activation point
page_start_index <- -1 # -1: A page in the portal has not been activated yet, otherwise the row index of activation point
# Iterating through each row of a participant
for(i in 1:nrow(annotations_for_participant)) {
# Getting the corresponding row
row <- annotations_for_participant[i,]
# If the row is na somehow (due to some R bug)
if(is.na(row$pid)) {
break;
}
marked_case <- NA
run_case <- NA
debug_time <- NA
debug_case <- NA
# If the portal has been activated
if(row$Behavior == "Portal activated") {
currently_active <- "Portal" # Setting the currently active variable to Portal
if(is.na(portal_page)) {
# If the portal is activated and there were no previous pages, then the home page (H) will open
portal_page = "H"
}
page_start_index <- i
##### VS full timer summarization #####
# If there was a VS activation before, it must be stopped and added to the summary
if(vs_start_index != -1) {
# The elapsed time is the current row timestamp minus the previous VS activation timestamp
elapsed_vs_time <- row$Time - annotations_for_participant[annotations_for_participant$pid == pid,][vs_start_index,]$Time
# The elapsed time is added to the row
vs_time_summary_row$Total <- vs_time_summary_row$Total + elapsed_vs_time
}
vs_start_index <- NA # There is no start index for VS, waiting for the next
##### VS window timer summarization #####
# If there was a VS window activation before, it must stopped and added to the summary
if(window_start_index != -1) {
# Getting the row of previous window activation in VS
window_start_row <- extended_video_annotations[extended_video_annotations$PID == pid,][window_start_index,]
# Calculating the elapsed time in that particular window
elapsed_window_time <- row$Time - window_start_row$Time
# If the window was not C, S, N, P or was not NA (it is a numbered test case)
if(window_start_row$Window != "C" && window_start_row$Window != "S" && window_start_row$Window != "N" && window_start_row$Window != "P" && !is.na(window_start_row$Window)) {
# e.g., TC0 -> 2. column index
vs_time_summary_row[,as.numeric(window_start_row$Window)+2] <- vs_time_summary_row[,as.numeric(window_start_row$Window)+2] + elapsed_window_time
} else if(window_start_row$Window != "N" && !is.na(window_start_row$Window)) {
vs_time_summary_row[,window_start_row$Window] <- vs_time_summary_row[,window_start_row$Window] + elapsed_window_time
# Summarizing class time for given test
if(!is.na(window_start_row$Page) && window_start_row$Page != "H") {
if(window_start_row$Window == "C") {
cut_time_for_test_row[,as.numeric(window_start_row$Page)+2] <- cut_time_for_test_row[,as.numeric(window_start_row$Page)+2] + elapsed_window_time
}
if(window_start_row$Window == "S") {
sut_time_for_test_row[,as.numeric(window_start_row$Page)+2] <- sut_time_for_test_row[,as.numeric(window_start_row$Page)+2] + elapsed_window_time
}
if(window_start_row$Window == "P") {
put_time_for_test_row[,as.numeric(window_start_row$Page)+2] <- put_time_for_test_row[,as.numeric(window_start_row$Page)+2] + elapsed_window_time
}
}
}
}
window_start_index <- NA
} else if(row$Behavior == "VS activated") {
currently_active <- "VS"
# Start VS timer
vs_start_index <- i
window_start_index <- i
# stopping Portal timers
if(page_start_index != -1) {
page_start_row <- extended_video_annotations[extended_video_annotations$PID == pid,][page_start_index,]
elapsed_page_time <- row$Time - page_start_row$Time
if(page_start_row$Page != "H" && !is.na(page_start_row$Page)) {
# e.g., TC0 -> 2. column index
portal_time_summary_row[,as.numeric(page_start_row$Page)+2] <- portal_time_summary_row[,as.numeric(page_start_row$Page)+2] + elapsed_page_time
}
}
page_start_index <- NA
} else if(row$Behavior == "Changed page in portal") {
portal_page <- row$Modifier.1
if(page_start_index != -1) {
page_start_row <- extended_video_annotations[extended_video_annotations$PID == pid,][page_start_index,]
elapsed_page_time <- row$Time - page_start_row$Time
if(page_start_row$Page != "H" && !is.na(page_start_row$Page)) {
# e.g., TC0 -> 2. column index
portal_time_summary_row[,as.numeric(page_start_row$Page)+2] <- portal_time_summary_row[,as.numeric(page_start_row$Page)+2] + elapsed_page_time
}
}
page_start_index <- i
} else if(row$Behavior == "Changed window in VS") {
vs_window <- row$Modifier.1
if(window_start_index != -1) {
window_start_row <- extended_video_annotations[extended_video_annotations$PID == pid,][window_start_index,]
elapsed_window_time <- row$Time - window_start_row$Time
if(window_start_row$Window != "C" && window_start_row$Window != "S" && window_start_row$Window != "N" && window_start_row$Window != "P" && !is.na(window_start_row$Window)) {
# e.g., TC0 -> 2. column index
vs_time_summary_row[,as.numeric(window_start_row$Window)+2] <- vs_time_summary_row[,as.numeric(window_start_row$Window)+2] + elapsed_window_time
} else if(window_start_row$Window != "N" && !is.na(window_start_row$Window)) {
vs_time_summary_row[,window_start_row$Window] <- vs_time_summary_row[,window_start_row$Window] + elapsed_window_time
}
}
window_start_index <- i
} else if(row$Behavior == "Marked as OK") {
marked_case <- portal_page
marked_ok_summary_row[,as.numeric(marked_case)+2] <- marked_ok_summary_row[,as.numeric(marked_case)+2] +1
} else if(row$Behavior == "Marked as WRONG") {
marked_case <- portal_page
marked_wrong_summary_row[,as.numeric(marked_case)+2] <- marked_wrong_summary_row[,as.numeric(marked_case)+2] + 1
} else if(row$Behavior == "Running test") {
run_case <- vs_window
} else if(row$Behavior == "Submit") {
} else if(row$Behavior == "Other event") {
} else if(row$Behavior == "Debug test") {
if(row$Modifier.1 == "S") {
debug_start_index <- i
debug_case <- vs_window
} else if(row$Modifier.1 == "E") {
debug_case <- extended_video_annotations[extended_video_annotations$PID == pid,][debug_start_index,]$DebugCase
debug_time <- row$Time - annotations_for_participant[annotations_for_participant$pid == pid,][debug_start_index,]$Time
debug_start_index <- NA
}
} else if(row$Behavior == "Remove answer") {
marked_case <- row$Modifier.1
} else if(row$Behavior == "Missing test problem") {
if(row$Modifier.1 == "S") {
window_start_row <- extended_video_annotations[extended_video_annotations$PID == pid,][window_start_index,]
elapsed_window_time <- row$Time - window_start_row$Time
if(window_start_row$Window != "C" && window_start_row$Window != "S" && window_start_row$Window != "N" && window_start_row$Window != "P" && !is.na(window_start_row$Window)) {
# e.g., TC0 -> 2. column index
vs_time_summary_row[,as.numeric(window_start_row$Window)+2] <- vs_time_summary_row[,as.numeric(window_start_row$Window)+2] + elapsed_window_time
} else if(window_start_row$Window != "N" && !is.na(window_start_row$Window)) {
vs_time_summary_row[,window_start_row$Window] <- vs_time_summary_row[,window_start_row$Window] + elapsed_window_time
}
} else if(row$Modifier.1 == "E") {
window_start_index <- i
}
}
extended_row <- data.frame(PID=pid,Timestamp=row$Time,Behavior=row$Behavior,Modifier=row$Modifier.1,Active=currently_active,Page=portal_page,Window=vs_window,RunCase=run_case,DebugLength=debug_time,DebugCase=debug_case,stringsAsFactors = FALSE)
extended_video_annotations <- rbind(extended_video_annotations,extended_row)
}
portal_time_summary <- rbind(portal_time_summary, portal_time_summary_row)
vs_time_summary <- rbind(vs_time_summary, vs_time_summary_row)
marked_ok_summary <- rbind(marked_ok_summary, marked_ok_summary_row)
marked_wrong_summary <- rbind(marked_wrong_summary, marked_wrong_summary_row)
cut_time_for_test <- rbind(cut_time_for_test, cut_time_for_test_row)
sut_time_for_test <- rbind(sut_time_for_test, sut_time_for_test_row)
put_time_for_test <- rbind(put_time_for_test, put_time_for_test_row)
}
portal_time_summary$check_sums <- rowSums(portal_time_summary[,c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5")])
vs_time_summary$check_sums <- rowSums(vs_time_summary[,c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5","S","C","P")])
rm(cut_time_for_test_row,extended_row,marked_ok_summary_row, marked_wrong_summary_row, page_start_row,portal_time_summary_row,put_time_for_test_row,row,sut_time_for_test_row,vs_time_summary_row,window_start_row,currently_active,debug_case,debug_start_index,debug_time,elapsed_page_time,elapsed_vs_time,elapsed_window_time,i,marked_case,page_start_index,pid,portal_page,run_case,vs_start_index,vs_window,window_start_index)
After this execution we have the following summaries:
portal_time_summary): time spent on the portal for each test casevs_time_summary): time spent in each window of Visual Studiomarked_ok_summary): the number of times participants marked each case as OKmarked_wrong_summary): the number of times participants marked each case as Wrongcut_time_for_test): the time participants spent on analyzing the class for a given testput_time_for_test}: the time participants spent on analyzing the parameterized unit test for a given testsut_time_for_Test}: the time participants spent on analyzing the system under test (excluding the CUT and PUT)The following sections contain different aspects of data analysis.
Source: http://www.damienfrancois.be/blog/files/modelperfcheatsheet.pdf, https://en.wikipedia.org/wiki/Evaluation_of_binary_classifiers, https://en.wikipedia.org/wiki/Sensitivity_and_specificity
Calculating: accuracy, misclassification rate, true positive rate (sensitivity), true negative rate (specificity), false positive rate (1-specificity)
participant_result_summary$Accuracy <- (participant_result_summary$TP + participant_result_summary$TN) / 15 # (TP+TN)/(TP+TN+FP+FN)
participant_result_summary$Misclassification <- (participant_result_summary$FP + participant_result_summary$FN) / 15
participant_result_summary$Sensitivity <- (participant_result_summary$TP)/(participant_result_summary$TP + participant_result_summary$FN) # (hit rate)
participant_result_summary$Specificity <- (participant_result_summary$TN)/(participant_result_summary$TN + participant_result_summary$FP)
participant_result_summary$FalsePositiveRate <- 1- participant_result_summary$Specificity # False positive rate
prs <- participant_result_summary
participant_result_summary$MCC <- ((prs$TP*prs$TN)-(prs$FP*prs$FN))/sqrt((prs$TP+prs$FP)*(prs$TP+prs$FN)*(prs$TN+prs$FP)*(prs$TN+prs$FN))
rm(prs)
print(median(1- participant_result_summary$Specificity)) # FP
## [1] 0.25
print(median(1- participant_result_summary$Sensitivity)) # FN
## [1] 0.3333333
The following box plots are showing different measures of binary classification separated by the projects.
ggplot(participant_result_summary, aes(x=factor(Project), y=Accuracy)) + geom_boxplot() + ylim(0,1) # Accuracy
ggplot(participant_result_summary, aes(x=factor(Project), y=Misclassification)) + geom_boxplot() + ylim(0,1) # Misclassification
ggplot(participant_result_summary, aes(x=factor(Project), y=FalsePositiveRate)) + geom_boxplot() + ylim(0,1) # False positive rate
# IN PAPER
#pdf(file="boxplot-sensitivity.pdf",width=3.5,height=2.5)
ggplot(participant_result_summary, aes(x=factor(Project), y=Sensitivity)) + geom_boxplot() + xlab("Project") + ylab("TPR") + guides(fill=FALSE) + theme_hc() + scale_y_continuous(limits=c(0,1), breaks=seq(0,1,0.1))# Sensitivity (TPR)
#dev.off()
# IN PAPER
#pdf(file="boxplot-tnr.pdf",width=3.5,height=2.5)
ggplot(participant_result_summary, aes(x=factor(Project), y=Specificity)) + geom_boxplot() + xlab("Project") + ylab("TNR")+ guides(fill=FALSE) + theme_hc() + scale_y_continuous(limits=c(0,1), breaks=seq(0,1,0.1)) # Specificity (true negative rate)
#dev.off()
# IN PAPER
#pdf(file="boxplot-matthews.pdf",width=3.5,height=2.5)
ggplot(participant_result_summary, aes(x=factor(Project), y=MCC)) + geom_boxplot() + xlab("Project") + ylab("MCC") + guides(fill=FALSE) + theme_hc() + scale_y_continuous(limits=c(-1,1), breaks=seq(-1,1,0.2)) # Matthews correlation coefficient
#dev.off()
The following plots summarizes the binary classification for the test cases that are faulty.
palette <- c("#cb181d", "#238b45")
participant_results_nbitcoin_faulty <- participant_results[(participant_results$Results.Id == 5 | participant_results$Results.Id == 7 | participant_results$Results.Id == 10) & (as.numeric(participant_results$pid) < 41),]
participant_results_mathnet_faulty <- participant_results[(participant_results$Results.Id == 2 | participant_results$Results.Id == 4 | participant_results$Results.Id == 11) & as.numeric(participant_results$pid) > 40,]
participant_results_nbitcoin_faulty$Project <- rep("NBitcoin",nrow(participant_results_nbitcoin_faulty))
participant_results_mathnet_faulty$Project <- rep("MathNet",nrow(participant_results_mathnet_faulty))
participant_results_on_faulty_tests <- rbind(participant_results_nbitcoin_faulty, participant_results_mathnet_faulty)
participant_results_on_faulty_tests <- participant_results_on_faulty_tests %>%
group_by(Project, Check) %>%
summarise(count=n()) %>%
mutate(perc=count/sum(count))
ggplot(participant_results_on_faulty_tests, aes(x=Project,y=perc*100,fill=factor(Check))) + scale_fill_manual(values = palette) + geom_bar(stat="identity") + ylab("Percentage") + labs(fill="Result") + theme_hc()
The following plot summarizes the results for the complex test cases (both the faulties and not faulties).
palette <- c("#cb181d", "#238b45")
participant_results_nbitcoin_complex <- participant_results[(participant_results$Results.Id == 7 | participant_results$Results.Id == 10 | participant_results$Results.Id == 13 | participant_results$Results.Id == 14) & (as.numeric(participant_results$pid) < 41),]
participant_results_mathnet_complex <- participant_results[(participant_results$Results.Id == 4 | participant_results$Results.Id == 10 | participant_results$Results.Id == 11 | participant_results$Results.Id == 14) & as.numeric(participant_results$pid) > 40,]
participant_results_nbitcoin_complex$Project <- rep("NBitcoin",nrow(participant_results_nbitcoin_complex))
participant_results_mathnet_complex$Project <- rep("MathNet",nrow(participant_results_mathnet_complex))
participant_results_on_complex_tests <- rbind(participant_results_nbitcoin_complex, participant_results_mathnet_complex)
# Checking not faulty complex tests
complex_not_faulty <- participant_results_on_complex_tests[(participant_results_on_complex_tests$Project == "NBitcoin" & (participant_results_on_complex_tests$Results.Id == 13 | participant_results_on_complex_tests$Results.Id == 14)) |(participant_results_on_complex_tests$Project == "MathNet" & (participant_results_on_complex_tests$Results.Id == 10 | participant_results_on_complex_tests$Results.Id == 14)),]
complex_not_faulty_summarized <- complex_not_faulty %>%
group_by(Project, Check) %>%
summarise(count=n()) %>%
mutate(perc=count/sum(count))
ggplot(complex_not_faulty_summarized, aes(x=Project,y=perc*100,fill=factor(Check))) + geom_bar(stat="identity") + theme_hc() + scale_fill_manual(values = palette) + labs(fill="Result") + ylab("Percentage")
# Checking faulty complex tests
complex_faulty <- participant_results_on_complex_tests[(participant_results_on_complex_tests$Project == "NBitcoin" & (participant_results_on_complex_tests$Results.Id == 7 | participant_results_on_complex_tests$Results.Id == 10)) |(participant_results_on_complex_tests$Project == "MathNet" & (participant_results_on_complex_tests$Results.Id == 4 | participant_results_on_complex_tests$Results.Id == 11)),]
complex_faulty_summarized <- complex_faulty %>%
group_by(Project, Check) %>%
summarise(count=n()) %>%
mutate(perc=count/sum(count))
ggplot(complex_faulty_summarized, aes(x=Project,y=perc*100,fill=factor(Check))) + geom_bar(stat="identity") + theme_hc() + scale_fill_manual(values = palette) + labs(fill="Result") + ylab("Percentage")
The following coloured table contains the evaluation of all participant answers for each test case.
cb_palette <- c("#680008", "#e22828", "#2cba5a", "#01703e")
# IN PAPER
#pdf(file="nbitcoin-tile-map.pdf",width=6.5,height = 3.5)
ggplot(participant_results[participant_results$pid < 41,], aes(x=factor(pid), y=reorder(factor(TestId),Results.Id), fill=as.factor(Check))) +
geom_tile(alpha=0.8,width=.9, height=.9) +theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank()) + scale_fill_manual(values=cb_palette) + xlab("Participant ID") + ylab("Test ID") + labs(fill="Result")
#dev.off()
# IN PAPER
#pdf(file="mathnet-tile-map.pdf",width=5.5,height = 3.5)
ggplot(participant_results[participant_results$pid > 40,], aes(x=factor(pid), y=reorder(factor(TestId),Results.Id), fill=as.factor(Check))) +
geom_tile(alpha=0.8,width=.9, height=.9) +theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank()) + scale_fill_manual(values=cb_palette) + xlab("Participant ID") + ylab("Test ID") + labs(fill="Result")
#dev.off()
In the followings, the time spent in Visual Studio and Portal is presented for each possible window/page (T0-14, S, C and P).
library(ggthemes)
project_column <- data.frame()
for(i in 1:nrow(vs_time_summary)) {
if(as.numeric(vs_time_summary[i,]$PID) > 40) {
project_column[i,"Project"] <- "MathNet"
} else {
project_column[i,"Project"] <- "NBitcoin"
}
}
vs_time_summary_with_projects <- cbind(vs_time_summary[order(vs_time_summary$PID),],project_column)
project_column <- data.frame()
for(i in 1:nrow(portal_time_summary)) {
if(as.numeric(portal_time_summary[i,]$PID) > 40) {
project_column[i,"Project"] <- "MathNet"
} else {
project_column[i,"Project"] <- "NBitcoin"
}
}
portal_time_summary_with_projects <- cbind(portal_time_summary[order(portal_time_summary$PID),],project_column)
# Tests individually
time_spent_tests_vs <- melt(vs_time_summary_with_projects,id.vars=c("PID","Project"), measure.vars=c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"),variable.name="Window",value.name="Time")
time_spent_tests_vs <- time_spent_tests_vs[order(time_spent_tests_vs$PID,time_spent_tests_vs$Window),]
time_spent_tests_portal <- melt(portal_time_summary_with_projects,id.vars=c("PID","Project"), measure.vars=c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"),variable.name="Page",value.name="Time")
time_spent_tests_portal <- time_spent_tests_portal[order(time_spent_tests_portal$PID,time_spent_tests_portal$Page),]
time_spent_tests <- time_spent_tests_vs
time_spent_tests$Time = time_spent_tests$Time + time_spent_tests_portal$Time
time_spent_others <- melt(vs_time_summary_with_projects,id.vars=c("PID","Project"), measure.vars=c("S","C","P"),variable.name="Window",value.name="Time")
time_spent_tests_nbitcoin <- time_spent_tests[time_spent_tests$Project == "NBitcoin",]
time_spent_tests_nbitcoin$TestCategory <- ifelse(time_spent_tests_nbitcoin$Window == "T5.2","Faulty", ifelse(time_spent_tests_nbitcoin$Window == "T7.3" | time_spent_tests_nbitcoin$Window == "T10.4","Faulty", ifelse(time_spent_tests_nbitcoin$Window == "T13.5" | time_spent_tests_nbitcoin$Window == "T14.5", "Not faulty", "Not faulty")))
time_spent_tests_mathnet <- time_spent_tests[time_spent_tests$Project == "MathNet",]
time_spent_tests_mathnet$TestCategory <- ifelse(time_spent_tests_mathnet$Window == "T2.1","Faulty", ifelse(time_spent_tests_mathnet$Window == "T4.2" | time_spent_tests_mathnet$Window == "T11.4","Faulty", ifelse(time_spent_tests_mathnet$Window == "T10.4" | time_spent_tests_mathnet$Window == "T14.5", "Not faulty", "Not faulty")))
# Tests grouped by methods
vs_time_summary_with_projects <- vs_time_summary_with_projects[order(vs_time_summary_with_projects$PID),]
portal_time_summary_with_projects <- portal_time_summary_with_projects[order(portal_time_summary_with_projects$PID),]
pts <- portal_time_summary_with_projects
vts <- vs_time_summary_with_projects
time_spent_tests_grouped <- data.frame(M1=c(1:54),M2=c(1:54),M3=c(1:54),M4=c(1:54),M5=c(1:54))
time_spent_tests_grouped$M1 <- (pts$T0.1+pts$T1.1+pts$T2.1)+(vts$T0.1+vts$T1.1+vts$T2.1)
time_spent_tests_grouped$M2 <- (pts$T3.2+pts$T4.2+pts$T5.2)+(vts$T3.2+vts$T4.2+vts$T5.2)
time_spent_tests_grouped$M3 <- (pts$T6.3+pts$T7.3+pts$T8.3)+(vts$T6.3+vts$T7.3+vts$T8.3)
time_spent_tests_grouped$M4 <- (pts$T9.4+pts$T10.4+pts$T11.4)+(vts$T9.4+vts$T10.4+vts$T11.4)
time_spent_tests_grouped$M5 <- (pts$T12.5+pts$T13.5+pts$T14.5)+(vts$T12.5+vts$T13.5+vts$T14.5)
# Other windows - SUT, CUT and PUT
time_spent_others_nbitcoin <- time_spent_others[time_spent_others$Project == "NBitcoin",]
time_spent_others_mathnet <- time_spent_others[time_spent_others$Project == "MathNet",]
#pdf(file="nbitcoin-time-spent-tests.pdf",width=8,height = 3.5)
ggplot(time_spent_tests_nbitcoin, aes(x=factor(Window), y=Time, fill=factor(TestCategory))) + geom_boxplot() + scale_y_continuous(limits=c(0,500),oob = rescale_none) + theme_hc() + scale_fill_manual(values=c("#BCBABE","#F1F1F2")) + ylab("Time spent [s]") + xlab("Test") + labs(fill="Category") # NBitcoin tests
#dev.off()
#pdf(file="mathnet-time-spent-tests.pdf",width=8,height = 3.5)
ggplot(time_spent_tests_mathnet, aes(x=factor(Window), y=Time, fill=factor(TestCategory))) + geom_boxplot() + scale_y_continuous(limits=c(0,500),oob = rescale_none) + theme_hc() + scale_fill_manual(values=c("#BCBABE","#F1F1F2")) + ylab("Time spent [s]") + xlab("Test") + labs(fill="Category") # MathNet tests
#dev.off()
#pdf(file="nbitcoin-time-spent-others.pdf",width=5,height = 3.5)
ggplot(time_spent_others_nbitcoin, aes(x=factor(Window), y=Time, fill=factor(Window))) + geom_boxplot(fatten=4) + scale_y_continuous(limits=c(0,1200),oob = rescale_none) + theme_hc() + scale_fill_manual(values=c("#A4CABC","#EAB364","#D09683")) + guides(fill=FALSE) + ylab("Time spent [s]") + xlab("Window") # NBitcoin others
#dev.off()
#pdf(file="mathnet-time-spent-others.pdf",width=5,height = 3.5)
ggplot(time_spent_others_mathnet, aes(x=factor(Window), y=Time, fill=factor(Window))) + geom_boxplot(fatten=4)+ theme_hc() + scale_fill_manual(values=c("#A4CABC","#EAB364","#D09683")) + scale_y_continuous(limits=c(0,1200),oob = rescale_none) + guides(fill=FALSE) + ylab("Time spent [s]") + xlab("Window") # MathNet others
#dev.off()
# Joining the dataset
summarized_test_times_portal <- time_spent_tests_portal %>% group_by(PID, Project) %>% summarize(Time=sum(Time)) %>% bind_cols(data.frame(Location=rep("Portal",54)))
summarized_test_time_vs <- time_spent_tests_vs %>% group_by(PID, Project) %>% summarize(Time=sum(Time)) %>% bind_cols(data.frame(Location=rep("VS",54)))
names(time_spent_others)[3] <- "Location"
full_summarized_times <- summarized_test_times_portal %>% bind_rows(summarized_test_time_vs) %>% bind_rows(time_spent_others)
## Warning in rbind_all(x, .id): Unequal factor levels: coercing to character
full_summarized_times$Location <- factor(full_summarized_times$Location, levels=c("Portal", "VS", "C", "S", "P"))
levels(full_summarized_times$Location) <- list("Portal"="Portal", "Test code"="VS", "CUT" = "C", "SUT"="S","PUT"="P")
# IN PAPER
#pdf(file="full-time-spent-nbitcoin.pdf",width=4,height = 2.5)
ggplot(data=filter(full_summarized_times, Project=="NBitcoin"), aes(x=Location,y=Time)) +geom_boxplot() + theme_hc() + scale_y_continuous(limits=c(0,1600), breaks=seq(0,1500,250)) + ylab("Time [s]")
## Warning: Removed 7 rows containing non-finite values (stat_boxplot).
#dev.off()
# IN PAPER
#pdf(file="full-time-spent-mathnet.pdf",width=4,height = 2.5)
ggplot(data=filter(full_summarized_times, Project=="MathNet"), aes(x=Location,y=Time)) +geom_boxplot() + theme_hc()+ scale_y_continuous(limits=c(0,1600), breaks=seq(0,1500,250)) + ylab("Time [s]")
#dev.off()
In the followings, the average time spent everywhere for each case opened in the portal.
cut_times <- melt(cut_time_for_test,id.vars = c("PID"), measure.vars=c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), variable.name = "Test",value.name="Time")
all_times_for_tests <- cut_times
put_times <- melt(put_time_for_test, id.vars = c("PID"), measure.vars=c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), variable.name = "Test",value.name="Time")
all_times_for_tests <- rbind(all_times_for_tests, put_times)
sut_times <- melt(sut_time_for_test, id.vars = c("PID"), measure.vars=c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"), variable.name = "Test",value.name="Time")
all_times_for_tests <- rbind(all_times_for_tests, sut_times)
vs_time_for_tests <- subset(time_spent_tests_vs, select=c(PID,Window,Time))
names(vs_time_for_tests) <- c("PID","Test","Time")
portal_time_for_tests <- subset(time_spent_tests_portal, select=c(PID,Page,Time))
names(portal_time_for_tests) <- c("PID","Test","Time")
portal_plus_vs_time_for_tests <- dplyr::left_join(vs_time_for_tests,portal_time_for_tests,by=c('PID','Test'))
portal_plus_vs_time_for_tests$Time <- portal_plus_vs_time_for_tests$Time.x + portal_plus_vs_time_for_tests$Time.y
portal_plus_vs_time_for_tests <- subset(portal_plus_vs_time_for_tests, select=c(PID,Test,Time))
all_times_for_tests_with_sum <- rbind(all_times_for_tests, portal_plus_vs_time_for_tests)
all_times_for_tests <- rbind(all_times_for_tests, vs_time_for_tests)
all_times_for_tests <- rbind(all_times_for_tests, portal_time_for_tests)
all_times_for_tests <- cbind(all_times_for_tests, c(rep("CUT",nrow(cut_times)),rep("PUT",nrow(put_times)),rep("SUT",nrow(sut_times)),rep("VS",nrow(vs_time_for_tests)),rep("Portal",nrow(portal_time_for_tests))))
names(all_times_for_tests)[4] <- "Location"
all_times_for_tests_with_sum <- cbind(all_times_for_tests_with_sum, c(rep("CUT",nrow(cut_times)),rep("PUT",nrow(put_times)),rep("SUT",nrow(sut_times)),rep("Test",nrow(portal_plus_vs_time_for_tests))))
names(all_times_for_tests_with_sum)[4] <- "Location"
all_times_for_tests <- cbind(all_times_for_tests, c(rep(NA,nrow(all_times_for_tests))))
names(all_times_for_tests)[5] <- "Project"
all_times_for_tests$Project[all_times_for_tests$PID > 40] <- "MathNet"
all_times_for_tests$Project[all_times_for_tests$PID < 41] <- "NBitcoin"
all_times_for_tests_with_sum <- cbind(all_times_for_tests_with_sum, c(rep(NA,nrow(all_times_for_tests_with_sum))))
names(all_times_for_tests_with_sum)[5] <- "Project"
all_times_for_tests_with_sum$Project[all_times_for_tests_with_sum$PID > 40] <- "MathNet"
all_times_for_tests_with_sum$Project[all_times_for_tests_with_sum$PID < 41] <- "NBitcoin"
# Participants 55 and 59 had missing videos in the beginning caused by unexpected shutdowns, thus removing their video timing data
all_times_for_tests <- dplyr::filter(all_times_for_tests, !((PID == 59 & (Test == "T0.1" | Test == "T1.1")) | (PID == 55 & (Test == "T0.1" | Test == "T1.1")) ))
jj <- inner_join(x=all_times_for_tests, y=result_answers, by=c("PID" = "pid", "Test" = "TestId", "Project" = "Project"))
## Warning in inner_join_impl(x, y, by$x, by$y): joining factors with
## different levels, coercing to character vector
time_summary <- jj %>%
group_by(PID,Test,Project) %>%
summarise(sum=sum(Time)) %>%
group_by(Project) %>%
summarize(mean=mean(sum), median=median(sum), sd=sd(sum), min=min(sum), max=max(sum))
time_summary_plot <- jj %>%
group_by(PID,Test,Project,Method) %>%
summarise(Time=sum(Time)) %>%
mutate(Faulty=ifelse( (Project=="NBitcoin" && (Test=="T5.2" || Test=="T7.3" || Test=="T10.4") ) || (Project=="MathNet" && (Test=="T2.1" || Test=="T4.2" || Test=="T11.4") ) ,"YES","NO"))
levels(time_summary_plot$Method)[levels(time_summary_plot$Method)=="CombinationsWithRepetition"] <- "CWithRepetition"
levels(time_summary_plot$Method)[levels(time_summary_plot$Method)=="VariationsWithRepetition"] <- "VWithRepetition"
time_summary_plot$Test <- factor(time_summary_plot$Test, levels=c("T0.1","T1.1","T2.1","T3.2","T4.2","T5.2","T6.3","T7.3","T8.3","T9.4","T10.4","T11.4","T12.5","T13.5","T14.5"))
palette <- c("#FFFFFF","#ff821c")
# IN PAPER
#pdf(file="mathnet-time-spent-tests.pdf",width=7,height = 3.5)
ggplot(data=filter(time_summary_plot, Project == "MathNet"), aes(x=Test,y=Time,fill=factor(Faulty)) ) + geom_boxplot() + facet_grid(~Method, scales="free_x") + theme_hc() + ylab("Time [s]") + xlab("Test ID") + scale_fill_manual(values=palette, guide=FALSE) + scale_y_continuous(limits=c(0,900), breaks=seq(0,900,100))
#dev.off()
# IN PAPER
#pdf(file="nbitcoin-time-spent-tests.pdf",width=7,height = 3.5)
ggplot(data=filter(time_summary_plot, Project == "NBitcoin"), aes(x=Test,y=Time,fill=factor(Faulty))) + geom_boxplot() + facet_grid(~Method, scales="free_x") + theme_hc() + ylab("Time [s]") + xlab("Test ID") +scale_fill_manual(values=palette, guide=FALSE) + scale_y_continuous(limits=c(0,900), breaks=seq(0,900,100))
#dev.off()
qplot(data=filter(all_times_for_tests_with_sum, Project == "MathNet"), Time, geom="histogram") + facet_grid(Location~Test)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
qplot(data=filter(all_times_for_tests_with_sum, Project == "NBitcoin"), Time, geom="histogram") + facet_grid(Location~Test)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
ggplot(data=filter(all_times_for_tests,Project == "MathNet")) +
geom_bar(aes(x = factor(PID), y = Time, fill = Location), stat = "identity") +
facet_wrap(~Test) + theme_bw()
ggplot(data=filter(all_times_for_tests,Project == "NBitcoin")) +
geom_bar(aes(x = factor(PID), y = Time, fill = Location), stat = "identity") +
facet_wrap(~Test) + theme_bw()
ggplot(data=filter(all_times_for_tests_with_sum,Project == "MathNet")) +
geom_bar(aes(x = factor(PID), y = Time, fill = Location), stat = "identity") +
facet_grid(Test~.)
print(time_summary)
## Source: local data frame [2 x 6]
##
## Project mean median sd min max
## (chr) (dbl) (dbl) (dbl) (dbl) (dbl)
## 1 MathNet 113.9035 86.2370 87.49944 6.068 555.028
## 2 NBitcoin 146.8705 117.8215 121.89108 1.823 818.120
The following section contains the statistical analysis we performed in order to capture the key factors found in the data.
# Difference between projects - Sensitivity
sensitivity_nbitcoin <- participant_result_summary$Sensitivity[participant_result_summary$Project == "NBitcoin"]
sensitivity_mathnet <- participant_result_summary$Sensitivity[participant_result_summary$Project == "MathNet"]
sensitivity_sd <- mean(c(sd(sensitivity_nbitcoin),sd(sensitivity_mathnet))) # 0.309
sensitivity_A <- measureA(sensitivity_nbitcoin, sensitivity_mathnet) # 0.651 -> medium-large difference
sensitivity_w <- wilcox.test(sensitivity_nbitcoin, sensitivity_mathnet, exact=FALSE) # p=0.04514 < 0.05 -> reject (samples are from different distribution)
print(mean(participant_result_summary$Sensitivity[participant_result_summary$Project == "NBitcoin"]))
## [1] 0.7666667
print(mean(participant_result_summary$Sensitivity[participant_result_summary$Project == "MathNet"]))
## [1] 0.5972222
print(sensitivity_sd)
## [1] 0.3089999
print(sensitivity_A)
## [1] 0.6513889
print(sensitivity_w)
##
## Wilcoxon rank sum test with continuity correction
##
## data: sensitivity_nbitcoin and sensitivity_mathnet
## W = 469, p-value = 0.04514
## alternative hypothesis: true location shift is not equal to 0
# Difference between projects - Specificity
specificity_nbitcoin <- participant_result_summary$Specificity[participant_result_summary$Project == "NBitcoin"]
specificity_mathnet <- participant_result_summary$Specificity[participant_result_summary$Project == "MathNet"]
specificity_sd <- mean(c(sd(specificity_nbitcoin),sd(specificity_mathnet))) # 0.130
specificity_A <- measureA(specificity_nbitcoin, specificity_mathnet) # 0.5569 -> small difference
specificity_w <- wilcox.test(specificity_nbitcoin, specificity_mathnet, exact=FALSE) # p=0.473 > 0.05 -> not rejecting (samples are likely from same distribution)
print(mean(participant_result_summary$Specificity[participant_result_summary$Project == "NBitcoin"]))
## [1] 0.8
print(mean(participant_result_summary$Specificity[participant_result_summary$Project == "MathNet"]))
## [1] 0.7708333
print(specificity_sd)
## [1] 0.1301958
print(specificity_A)
## [1] 0.5569444
print(specificity_w)
##
## Wilcoxon rank sum test with continuity correction
##
## data: specificity_nbitcoin and specificity_mathnet
## W = 401, p-value = 0.4733
## alternative hypothesis: true location shift is not equal to 0
Note: we excluded participants who selected the “None” or the “One year or less options” (3 participants) to have balanced groups.
# SENSITIVITY
# Ranking programming experience
result_bg_answers$Prog[result_bg_answers$Prog == '5-10'] <- 5
result_bg_answers$Prog[result_bg_answers$Prog == 'none'] <- 0
participant_result_summary_with_bg_answers <- plyr::join(participant_result_summary, result_bg_answers, by=c("PID"))
# Calculating Kruskal-Wallis statistics for the groups
g1_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Prog == 2]
g2_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Prog == 3]
g3_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Prog == 4]
g4_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Prog == 5]
kruskal_test_data_sensitivity <- list(
g1=g1_,
g2=g2_,
g3=g3_,
g4=g4_
)
print(mean(g1_))
## [1] 0.5925926
print(mean(g2_))
## [1] 0.5416667
print(mean(g3_))
## [1] 0.8070175
print(mean(g4_))
## [1] 0.6666667
print(mean(c(sd(g1_),sd(g2_),sd(g3_),sd(g4_))))
## [1] 0.3162253
kruskal_result_sensitivity <- kruskal.test(kruskal_test_data_sensitivity) # 0.1223 > 0.05 -> not rejecting (groups are likely equal, experience less likely influence)
print(kruskal_result_sensitivity)
##
## Kruskal-Wallis rank sum test
##
## data: kruskal_test_data_sensitivity
## Kruskal-Wallis chi-squared = 5.7894, df = 3, p-value = 0.1223
# SPECIFICITY
# Calculating Kruskal-Wallis statistics for the groups
g1_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Prog == 2]
g2_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Prog == 3]
g3_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Prog == 4]
g4_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Prog == 5]
kruskal_test_data_specificity <- list(
g1=g1_,
g2=g2_,
g3=g3_,
g4=g4_
)
print(mean(g1_))
## [1] 0.7037037
print(mean(g2_))
## [1] 0.8020833
print(mean(g3_))
## [1] 0.8157895
print(mean(g4_))
## [1] 0.8111111
print(mean(c(sd(g1_),sd(g2_),sd(g3_),sd(g4_))))
## [1] 0.1228748
kruskal_result_specificity <- kruskal.test(kruskal_test_data_specificity) # 0.182 > 0.05 -> not reject (groups are likely equal, experience less likely influence)
print(kruskal_result_specificity)
##
## Kruskal-Wallis rank sum test
##
## data: kruskal_test_data_specificity
## Kruskal-Wallis chi-squared = 4.8695, df = 3, p-value = 0.1816
# SENSITIVITY
# Ranking industrial experience
participant_result_summary_with_bg_answers$Work[participant_result_summary_with_bg_answers$Work == 'none'] <- 0
participant_result_summary_with_bg_answers$Work[participant_result_summary_with_bg_answers$Work == '6'] <- 1
participant_result_summary_with_bg_answers$Work[participant_result_summary_with_bg_answers$Work == '7-12'] <- 2
participant_result_summary_with_bg_answers$Work[participant_result_summary_with_bg_answers$Work == '1-2'] <- 3
participant_result_summary_with_bg_answers$Work[participant_result_summary_with_bg_answers$Work == '3-5'] <- 4
participant_result_summary_with_bg_answers$Work[participant_result_summary_with_bg_answers$Work == '6-10'] <- 5
participant_result_summary_with_bg_answers$Work[participant_result_summary_with_bg_answers$Work == '10'] <- 6
# Calculating Kruskal-Wallis statistics for the groups
g1_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Work == 0]
g2_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Work == 1]
g3_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Work == 2]
g4_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Work == 3]
g5_ <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$Work == 4]
kruskal_test_data_sensitivity <- list(
g1=g1_,
g2=g2_,
g3=g3_,
g4=g4_,
g5=g5_
)
print(mean(g1_))
## [1] 0.5333333
print(mean(g2_))
## [1] 0.7407407
print(mean(g3_))
## [1] 0.8148148
print(mean(g4_))
## [1] 0.5740741
print(mean(g5_))
## [1] 0.9166667
print(mean(c(sd(g1_),sd(g2_),sd(g3_),sd(g4_),sd(g5_))))
## [1] 0.2715446
kruskal_result_sensitivity <- kruskal.test(kruskal_test_data_sensitivity) # 0.109 > 0.05 -> not rejecting (groups are likely equal, industrial experience less likely influence)
print(kruskal_result_sensitivity)
##
## Kruskal-Wallis rank sum test
##
## data: kruskal_test_data_sensitivity
## Kruskal-Wallis chi-squared = 7.5415, df = 4, p-value = 0.1099
# SPECIFICITY
g1_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 0]
g2_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 1]
g3_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 2]
g4_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 3]
g5_ <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 4]
# Calculating Kruskal-Wallis statistics for the groups
kruskal_test_data_specificity <- list(
g1=g1_,
g2=g2_,
g3=g3_,
g4=g4_,
g5=g5_
)
print(mean(g1_))
## [1] 0.85
print(mean(g2_))
## [1] 0.8425926
print(mean(g3_))
## [1] 0.7222222
print(mean(g4_))
## [1] 0.7314815
print(mean(g5_))
## [1] 0.8541667
print(mean(c(sd(g1_),sd(g2_),sd(g3_),sd(g4_),sd(g5_))))
## [1] 0.1226816
kruskal_result_specificity <- kruskal.test(kruskal_test_data_specificity) # 0.02638 < 0.05 -> reject (groups not equal, industrial experience may influence)
print(kruskal_result_specificity)
##
## Kruskal-Wallis rank sum test
##
## data: kruskal_test_data_specificity
## Kruskal-Wallis chi-squared = 11.016, df = 4, p-value = 0.02638
# Comparing less than 6 months experience and 1-2 years
industrial_exp_1_3_A <- measureA(participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 1],participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 3])
industrial_exp_1_3_w <- wilcox.test(participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 1],participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 3], exact = FALSE)
industrial_exp_1_3_sd <- mean(c(sd(participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 1]),sd(participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 3])))
print(mean(participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 1]))
## [1] 0.8425926
print(mean(participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 3]))
## [1] 0.7314815
print(mean(participant_result_summary$Specificity[participant_result_summary$Project == "MathNet"]))
## [1] 0.7708333
print(industrial_exp_1_3_sd) # 0.116
## [1] 0.1168131
print(industrial_exp_1_3_A) # 0.75 -> large diff
## [1] 0.7515432
print(industrial_exp_1_3_w) # p=0.0091 < 0.05 -> reject (samples are from different distribution)
##
## Wilcoxon rank sum test with continuity correction
##
## data: participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == and participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$Work == 1] and 3]
## W = 243.5, p-value = 0.009109
## alternative hypothesis: true location shift is not equal to 0
Note: Excluding 6 participants who have chosen ‘Never’ or ‘Often’ due to their small numbers. Thus, two groups can be compared with Mann-Whitney U-Test.
# Ranking programming experience
participant_result_summary_with_bg_answers$UT[participant_result_summary_with_bg_answers$UT == 'never'] <- 0
participant_result_summary_with_bg_answers$UT[participant_result_summary_with_bg_answers$UT == 'rarely'] <- 1
participant_result_summary_with_bg_answers$UT[participant_result_summary_with_bg_answers$UT == 'occasionally'] <- 2
participant_result_summary_with_bg_answers$UT[participant_result_summary_with_bg_answers$UT == 'often'] <- 3
# No participant gave 'Always' as an answer
# SENSITIVITY
sensitivity_ut_rarely <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$UT == 1]
sensitivity_ut_occasionally <- participant_result_summary_with_bg_answers$Sensitivity[participant_result_summary_with_bg_answers$UT == 2]
sensitivity_sd <- mean(c(sd(sensitivity_ut_rarely),sd(sensitivity_ut_occasionally))) # 0.327
sensitivity_A <- measureA(sensitivity_ut_rarely, sensitivity_ut_occasionally) # 0.422 -> small diff
sensitivity_w <- wilcox.test(sensitivity_ut_rarely, sensitivity_ut_occasionally, exact=FALSE) # p=0.371 > 0.05 -> not rejecting (samples are likely from same distribution)
print(sensitivity_sd)
## [1] 0.3276099
print(sensitivity_A)
## [1] 0.4222222
print(sensitivity_w)
##
## Wilcoxon rank sum test with continuity correction
##
## data: sensitivity_ut_rarely and sensitivity_ut_occasionally
## W = 209, p-value = 0.3714
## alternative hypothesis: true location shift is not equal to 0
# SPECIFICITY
# Difference between projects - Specificity
specificity_ut_rarely <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$UT == 1]
specificity_ut_occasionally <- participant_result_summary_with_bg_answers$Specificity[participant_result_summary_with_bg_answers$UT == 2]
specificity_sd <- mean(c(sd(specificity_ut_rarely),sd(specificity_ut_occasionally))) # 0.309
specificity_A <- measureA(specificity_ut_rarely, specificity_ut_occasionally) # 0.498 -> no difference
specificity_w <- wilcox.test(specificity_ut_rarely, specificity_ut_occasionally, exact=FALSE) # p=1 > 0.05 -> not rejecting (samples are likely from same distribution)
print(specificity_sd)
## [1] 0.1298307
print(specificity_A)
## [1] 0.4989899
print(specificity_w)
##
## Wilcoxon rank sum test with continuity correction
##
## data: specificity_ut_rarely and specificity_ut_occasionally
## W = 247, p-value = 1
## alternative hypothesis: true location shift is not equal to 0
participant_result_summary_with_bg_answers$TestScore <- c(rep(0,54))
for(pid in unique(participant_result_summary_with_bg_answers$PID)) {
row_for_pid <- filter(participant_result_summary_with_bg_answers, PID == pid)
a1 <- ifelse(row_for_pid$A1 == "true",1,0)
a2 <- ifelse(row_for_pid$A2 == "true",1,0)
a3 <- ifelse(row_for_pid$A3 == "true",1,0)
a4 <- ifelse(row_for_pid$A4 == "true",1,0)
a5 <- ifelse(row_for_pid$A5 == "false",1,0)
participant_result_summary_with_bg_answers$TestScore[participant_result_summary_with_bg_answers$PID == pid] <- a1 + a2 + a3 + a4 + a5
}
Note: does not work with R markdown, use it separately.
# result_exit_answers_corrected <- result_exit_answers[result_exit_answers$agreement2 != "",]
# options = c("Fully agree","Partially agree","Neither agree nor disagree","Partially disagree","Fully disagree")
# questions = c ("A: I had enough time to understand the class under test.",
# "B: I had enough time to review the generated tests.",
# "C: It was easy to understand the class under test.",
# "D: It was easy to understand the generated tests.",
# "E: I am certain I chose the right answers",
# "F: Generated tests are difficult to read.",
# "G: Generated tests are too long to understand.",
# "H: Generated tests are too short to exercise useful behavior.",
# "I: Generated tests had too many assertions.",
# "J: Generated tests had meaningful assertions.",
# "K: It was easy to select test cases with wrong assertions."
# )
#
# data2 <- subset(result_exit_answers_corrected, select = c(agreement1,agreement2,agreement3,agreement4,agreement5,agreement6,agreement7, agreement8, agreement9, agreement10, agreement11))
# names(data2) <- questions
#
#
# for(i in 1:length(questions)){
# data[data[,i] == 'fagree',i] <- "Fully agree"
# data[data[,i] == 'pagree',i] <- "Partially agree"
# data[data[,i] == 'neither',i] <- "Neither agree nor disagree"
# data[data[,i] == 'pdisagree',i] <- "Partially disagree"
# data[data[,i] == 'fdisagree',i] <- "Fully disagree"
#
# data[,i] <- factor(data[,i], levels = options)
# }
# Likert-figure can only be printed to a PDF file, comment out the lines below to generate.
#ldt <- likert(subset(data, select=questions), nlevels = length(options))
#pdf(file="likert.pdf", width=15)
#plot(ldt, ordered=FALSE) + theme(text = element_text(size=15))
#dev.off()