@@ -15,6 +15,7 @@ suppressPackageStartupMessages({
15
15
library(jsonlite )
16
16
library(stringr )
17
17
library(dplyr )
18
+ library(purrr )
18
19
library(readr )
19
20
source(" qsf-utils.R" )
20
21
})
@@ -57,34 +58,74 @@ get_qsf_file <- function(path,
57
58
) {
58
59
# Read file as json.
59
60
qsf <- read_json(path )
60
-
61
61
# # Block
62
62
shown_items <- get_shown_items(qsf )
63
63
64
64
# # Questions
65
65
questions <- Filter(function (elem ) { elem [[" Element" ]] == " SQ" }, qsf $ SurveyElements )
66
66
67
+ qids <- questions %> %
68
+ map_chr(~ .x $ Payload $ QuestionID )
69
+
67
70
qid_item_map <- list ()
68
71
questions_out <- list ()
69
- for (question in questions ) {
70
- question <- question $ Payload
72
+ for (question_raw in questions ) {
73
+ question_raw <- question_raw $ Payload
71
74
72
75
# Skip items not shown to respondents.
73
- if ( ! (question $ QuestionID %in% shown_items ) ) {
76
+ if ( ! (question_raw $ QuestionID %in% shown_items ) ) {
74
77
next
75
78
}
76
79
77
- if (! identical(keep_items , c(" all" ))) {
78
- question <- question [names(question ) %in% c(" QuestionID" , keep_items )]
80
+ question <- question_raw [names(question_raw ) %in% c(" QuestionID" , keep_items )]
81
+
82
+ recode_values <- question_raw $ RecodeValues # If doesn't exist, will be NULL
83
+ carryforward_choices <- question_raw $ DynamicChoices $ Locator # If doesn't exist, will be NULL
84
+
85
+ if (! is.null(carryforward_choices )) {
86
+ # Get choices that are programmed specifically for this question
87
+ old_choices <- question $ Choices
88
+
89
+ # Get carried-forward choices
90
+ carryforward_choices_qid <- carryforward_choices %> %
91
+ str_split(. , " /" ) %> %
92
+ map(~ .x [3 ]) %> % unlist()
93
+ carryforward_question <- questions [qids == carryforward_choices_qid ][[1 ]]$ Payload
94
+ carryforward_choices <- carryforward_question $ Choices
95
+
96
+ # By default, carried forward choices are coded during the carry as
97
+ # "x<original code>". They are then recoded as pure numeric codes using
98
+ # the `RecodeValues` field. Some carried forward choices do not have
99
+ # `RecodeValues` defined and so in that case we don't want to prepend the
100
+ # codes with "x".
101
+ if (! is.null(recode_values )) {
102
+ names(carryforward_choices ) <- paste0(" x" , names(carryforward_choices ))
103
+ }
104
+ # Combine new choices and carried-forward choices
105
+ question $ Choices <- c(old_choices , carryforward_choices )
79
106
}
80
107
81
- # Rearrange Answers/Choices elements to be consistent between matrix and
82
- # other items.
83
108
if (" QuestionType" %in% names(question )) {
84
109
if (question $ QuestionType == " Matrix" ) {
110
+ # Rearrange Answers/Choices elements to be consistent between matrix and
111
+ # other items.
85
112
question $ Subquestions <- question $ Choices
86
113
question $ Choices <- question $ Answers
87
114
question $ Answers <- NULL
115
+
116
+ # Recode subquestion names to match exported data.
117
+ # FALSE if not set, otherwise a list
118
+ matrix_subquestion_field_names <- question_raw $ ChoiceDataExportTags
119
+ if (! inherits(matrix_subquestion_field_names , " list" )) {
120
+ # When subquestion field names are not set, generate incrementing names
121
+ names(question $ Subquestions ) <- paste(
122
+ question $ DataExportTag ,
123
+ 1 : length(question $ Subquestions ),
124
+ sep = " _"
125
+ )
126
+ } else {
127
+ names(question $ Subquestions ) <- matrix_subquestion_field_names [names(question $ Subquestions )] %> % unlist()
128
+ }
88
129
}
89
130
}
90
131
@@ -136,8 +177,13 @@ diff_surveys <- function(old_qsf, new_qsf) {
136
177
old_questions <- old_qsf $ questions
137
178
new_questions <- new_qsf $ questions
138
179
139
- added <- setdiff(new_shown_items , old_shown_items )
140
- removed <- setdiff(old_shown_items , new_shown_items )
180
+ added_qs <- setdiff(new_shown_items , old_shown_items )
181
+ added <- rep(NA , length(added_qs ))
182
+ names(added ) <- added_qs
183
+
184
+ removed_qs <- setdiff(old_shown_items , new_shown_items )
185
+ removed <- rep(NA , length(removed_qs ))
186
+ names(removed ) <- removed_qs
141
187
142
188
added_df <- create_diff_df(added , " Added" , NULL , new_questions )
143
189
removed_df <- create_diff_df(removed , " Removed" , old_questions , NULL )
@@ -171,13 +217,33 @@ diff_question <- function(names, change_type=c("Choices", "QuestionText",
171
217
old_qsf , new_qsf ) {
172
218
change_type <- match.arg(change_type )
173
219
174
- changed <- c ()
220
+ changed <- list ()
175
221
for (question in names ) {
176
222
if ( ! identical(old_qsf [[question ]][[change_type ]], new_qsf [[question ]][[change_type ]]) ) {
177
- changed <- append(changed , question )
223
+ changed_subquestions <- c()
224
+ if (change_type == " Subquestions" ) {
225
+ subquestion_codes <- unique(
226
+ c(
227
+ names(old_qsf [[question ]][[change_type ]]),
228
+ names(new_qsf [[question ]][[change_type ]])
229
+ )
230
+ )
231
+
232
+ for (code in subquestion_codes ) {
233
+ if ( ! identical(old_qsf [[question ]][[change_type ]][[code ]], new_qsf [[question ]][[change_type ]][[code ]]) ) {
234
+ changed_subquestions <- append(changed_subquestions , code )
235
+ }
236
+ }
237
+ changed_subquestions <- paste(changed_subquestions , collapse = " , " )
238
+ }
239
+
240
+ if (length(changed_subquestions ) == 0 ) {
241
+ changed_subquestions <- NA
242
+ }
243
+ changed [[question ]] <- changed_subquestions
178
244
}
179
245
}
180
- out <- create_diff_df(changed , change_type , old_qsf , new_qsf )
246
+ out <- create_diff_df(unlist( changed ) , change_type , old_qsf , new_qsf )
181
247
182
248
return (out )
183
249
}
@@ -208,25 +274,26 @@ create_diff_df <- function(questions, change_type=c("Added", "Removed",
208
274
Choices = " Answer choices changed" ,
209
275
Subquestions = " Matrix subquestions changed"
210
276
)
211
- questions <- sort(questions )
212
277
213
- if (! is.null(old_qsf , new_qsf {
214
- old_qids <- sapply(questions , function (question ) { old_qsf , new_qsfquestion ]]$ QuestionID })
278
+ if (! is.null(old_qsf )) {
279
+ old_qids <- sapply(names( questions ) , function (question ) { old_qsf [[ question ]]$ QuestionID })
215
280
} else {
216
281
old_qids <- NA
217
282
}
218
283
if (! is.null(new_qsf )) {
219
- new_qids <- sapply(questions , function (question ) { new_qsf [[question ]]$ QuestionID })
284
+ new_qids <- sapply(names( questions ) , function (question ) { new_qsf [[question ]]$ QuestionID })
220
285
} else {
221
286
new_qids <- NA
222
287
}
223
288
224
289
out <- data.frame (
225
290
change_type = change_descriptions [[change_type ]],
226
- item = questions ,
291
+ item = names( questions ) ,
227
292
old_qid = old_qids ,
228
- new_qid = new_qids
229
- )
293
+ new_qid = new_qids ,
294
+ impacted_subquestions = questions
295
+ ) %> %
296
+ arrange(item )
230
297
}
231
298
232
299
return (out )
0 commit comments