Modify Data - Use Question as Template for Modifying Other Questions

From Q
Jump to navigation Jump to search

This QScript replicates changes made to one question (e.g., merging categories, renaming of categories), to other questions of the same structure (i.e., same number of categories with the same labels).

Technical details

Changes are replicated between Pick One and Pick One - Multi questions, or between two Pick Any questions. Only questions that have the same set of categories can be modified. If any of the target questions have been modified previously, for example by making changes on the table or by editing the Value Attributes, then it will not be possible to replicate the changes for these questions, and the QScript will generate a report describing what needs to be done to allow those questions to be used. See below for instructions on how to revert changes.

The types of changes that can be copied are:

  • Renamed categories
  • Hidden categories
  • Removed categories
  • Merged categories and NETs
  • Recoded categories

When you run this script you will be asked to select the question to use as a template. The script will then identify all questions which can be changed in the same way and you can then choose which questions you would like to modify. Once the script is finished a new group will be added to your Report which contains a description of what changes have been made, and a table showing each of the changed questions.

Reverting existing changes

The questions that you choose choose to modify with this QScript must start with all of their original labels and values unchanged. If some changes have already been made, then these will need to be reverted before you can replicate the changes from your main question. If some questions need to be reverted then this QScript will generate a report describing which questions need to be reverted and how to do so.

If changes have been made in the table only (for example renaming labels on the table, merging categories or adding NETs, or hiding/removing categories) then you can right-click on a SUMMARY table for this question and select Revert.

If changes have been made to the Value Attributes (for example recoding) then you instead need to:

  1. Highlight the variable(s) for the question in the Variables and Questions tab.
  2. Right-click and select Revert to Source.
  3. Ensure the question has the same Question Type as it did previously. If there are many variables, right-click them and use Set Question. If there is a single variable then click in the Question Type column to select the Question Type.

How to apply this QScript

  • Start typing the name of the QScript into the Search features and data box in the top right of the Q window.
  • Click on the QScript when it appears in the QScripts and Rules section of the search results.

OR

  • Select Automate > Browse Online Library.
  • Select this QScript from the list.

Customizing the QScript

This QScript is written in JavaScript and can be customized by copying and modifying the JavaScript.

Customizing QScripts in Q4.11 and more recent versions

  • Start typing the name of the QScript into the Search features and data box in the top right of the Q window.
  • Hover your mouse over the QScript when it appears in the QScripts and Rules section of the search results.
  • Press Edit a Copy (bottom-left corner of the preview).
  • Modify the JavaScript (see QScripts for more detail on this).
  • Either:
    • Run the QScript, by pressing the blue triangle button.
    • Save the QScript and run it at a later time, using Automate > Run QScript (Macro) from File.

Customizing QScripts in older versions

  • Copy the JavaScript shown on this page.
  • Create a new text file, giving it a file extension of .QScript. See here for more information about how to do this.
  • Modify the JavaScript (see QScripts for more detail on this).
  • Run the file using Automate > Run QScript (Macro) from File.

JavaScript

if (!main()) 
    log("QScript cancelled.");
else
    conditionallyEmptyLog("QScript finished.");
 
function main() {
    includeWeb('QScript Selection Functions');
    includeWeb('QScript Value Attributes Functions');
    includeWeb('QScript Data Reduction Functions');
    includeWeb('QScript Functions to Generate Outputs');
 
    var dr_log_message_start = "The following questions have had changes made in the tables (for example, merging categories, renaming, etc) and cannot be modified by this QScript:\r\n";
 
    var dr_log_message_end = ["To undo these changes so that the questions can be modified by this QScript, right-click on a SUMMARY table for this question and select 'Revert'.",
                          "If you wish to take a copy of the question before reverting, right-click on the table and select Duplicate Question."];
 
    var va_log_message_start = "The following questions have had changes made to their Value Attributes (they have been recoded or have their labels changed) and cannot be modified by this QScript:\r\n";
 
    var va_log_message_end = ["To undo these changes so that the questions can be modified by this QScript:",
                          "1. Highlight the variable(s) for the question in the Variables and Questions tab.",
                          "2. Right-click them, and select Revert To Source.",
                          "3. Ensure that the Question Type has not changed. If there are multiple variables then right-click and use Set Question. If there is a single variable then click in the Question Type column.\r\n"];
 
 
    var selected_datafiles = dataFileSelection();
    var candidate_template_questions = getAllQuestionsByTypes(selected_datafiles, ["Pick One", "Pick One - Multi", "Pick Any"]);
    candidate_template_questions = candidate_template_questions.filter(function (q) { return q.isValid; });
    if (candidate_template_questions.length == 0) {
        log("No Pick One, Pick One - Multi, or Pick Any questions are available to use as templates.");
        return false;
    }
 
    var template_question = selectOneQuestion("Please select a question to use as the template.", candidate_template_questions);
    var template_question_type = template_question.questionType;

    var template_labels = getTemplateLabels(template_question);
    if (unique(template_labels).length != template_labels.length) {
        var duplicate_counts = template_labels.map(function (label1) {
            var matches = template_labels.filter(function (label2) {
                return label2 == label1;
            });
            return {label: label1, count: matches.length};
        });
        var duplicates = duplicate_counts.filter(function (obj) { return obj.count > 1; }).map(function (obj) { return obj.label; });
        log("The labels in your selected question are not unique, and so it is not possible to use this question as a template. Duplicates can be removed by right-clicking on the label, selecting Rename, and changing the label.");
        log("The labels that are duplicated are:");
        log(duplicates.join("\r\n"));
        return false;
    }


 
    // Determine the questions that can be modified based on the user's first selection
    var target_types = [];
    if (template_question_type.indexOf("Pick One") == 0)
        target_types = ["Pick One", "Pick One - Multi"];
    else
        target_types = ["Pick Any"];
    var target_questions = getAllQuestionsByTypes(selected_datafiles, target_types).filter(function (q) { return q.isValid; });
    if (template_question_type.indexOf("Pick One") == 0)
        target_questions = target_questions.filter(function (question) { return sourceValueAttributesMatch(template_question, question); });
    else if (template_question_type == "Pick Any")
        target_questions = target_questions.filter(function (question) { return pickAnyCodesMatch(template_question, question); });
 
    target_questions = target_questions.filter(function (q) { return q.name != template_question.name; });
    if (target_questions.length == 0) {
        log("There are no questions that have the same categories as " + template_question.name + " (this comparison is performed based on both categories shown and also on any set as Missing Data).");
        return false;
    }
 
    var selected_targets = selectManyQuestions("Select questions to change: ", target_questions, false).questions;
 
    var valid_targets = [];
    var dr_changed_targets = [];
    var va_changed_targets = [];
 
    // Check the selected questions for changes to the value attributes
    // and data reduction.
    selected_targets.forEach(function (question) {
        var data_reduction_changes = getDataReductionChanges(question);
 
        if (!sourceValueAttributesMatchCurrentValueAttributes(question))
            va_changed_targets.push(question);
        else if (data_reduction_changes.isChanged)
            dr_changed_targets.push(question);
        else {
            // If it's a Pick One or Multi, revert any extra missing data selections before processing
            if (question.questionType.indexOf("Pick One" == 0) && data_reduction_changes.removedCodes.length > 0)
                revertRemovedCodesForPickOne(question, data_reduction_changes.removedCodes);
            valid_targets.push(question);
        }
    });
 
 
    // Check that the data reductions for these questions have not already been modified.
    // We're not going to try to change questions that have already been modified in this
    // way.
    var log_paragraphs = [];
    var keep_going = true;
    if (valid_targets.length == 0) {
        log_paragraphs.push("All of the selected questions have already been modified, so cannot be modified by this QScript.\r\n");
        keep_going = false;
    }
 
    if (dr_changed_targets.length > 0) {
        log_paragraphs.push(dr_log_message_start);
        dr_changed_targets.forEach(function (q) {
            log_paragraphs.push(" * " + q.name);
        });
        log_paragraphs.push("\r\n");
        log_paragraphs = log_paragraphs.concat(dr_log_message_end);
        log_paragraphs.push("\r\n");
    }
 
    if (va_changed_targets.length > 0) {
        log_paragraphs.push(va_log_message_start)
        va_changed_targets.forEach(function (q) {
            log_paragraphs.push(" * " + q.name);
        });
        log_paragraphs.push("\r\n");
        log_paragraphs = log_paragraphs.concat(va_log_message_end);
        log_paragraphs.push("\r\n");
    }
 
 
 
    if (keep_going && (dr_changed_targets.length > 0 || va_changed_targets.length > 0)) {
        keep_going = askYesNo("The questions listed below cannot be changed by this QScript because they have already been modified. Would you like to continue without these questions?\r\n" 
                 + dr_changed_targets.map(function (q) { return q.name; }).concat(va_changed_targets.map(function (q) { return q.name; })).join("\r\n") );
    }
 
    if (!keep_going) {
        log_paragraphs.forEach(function (og) { log(og);});
        return false;
    }
 
 
    // Apply the changes to the selected questions
    var changes = applyDataReductionChangesExactly(template_question, valid_targets);
 
    // Generate a report(s) and tables for the user
    // Reporting on questions successfully changed
    var html = generateDataReductionChangesHTMLReport(changes, template_question, valid_targets);
    var new_group = project.report.appendGroup();
    new_group.name = "Changes copied from " + template_question.name;
    var text_item = new_group.appendText();
    text_item.content = html;
    var title_build = Q.htmlBuilder();
    title_build.setStyle(_GLOBAL_ITEM_HEADING_STYLE)
    title_build.appendParagraph("Changed questions");
    text_item.title = title_build;
    valid_targets.forEach(function (q) {
        var new_table = new_group.appendTable();
        new_table.primary = q;
    });
    // More recent Q versions can point the user to the new items.
    if (fileFormatVersion() > 8.65)
        project.report.setSelectedRaw([text_item]);
 
    // Reporting on questions that we can't change
    if (log_paragraphs.length > 0) {
        simpleHTMLReport(log_paragraphs, "Some questions could not be modified", new_group, true, false);
    }
 
    log_paragraphs.forEach(function (og) { log(og);});
    return true;
}
 
// Returns true if the two questions have the same source values and source labels in the same order
// This is only used for Pick Ones.
function sourceValueAttributesMatch(question_1, question_2) {
    var attributes_1 = question_1.valueAttributes;
    var values_1 = question_1.uniqueValues;
    var labels_1 = values_1.map(function (v) { return attributes_1.getSourceLabel(v); });
    var attributes_2 = question_2.valueAttributes;
    var values_2 = question_2.uniqueValues;
    var labels_2 = values_2.map(function (v) { return attributes_2.getSourceLabel(v); });
    if (values_1.length != values_2.length)
        return false;
    for (var j = 0; j < values_1.length; j++) {
        if (values_1[j].toString() != values_2[j].toString())
            return false;
        if (labels_1[j] != labels_2[j])
            return false;
    }
    return true;
}
 
 
// Get data reduction changes for a question
function getDataReductionChanges(target_question) {
 
    // Obtain Data Reduction Changes
    var renamed_codes = getRenamedCodes(target_question);
    var hidden_codes = getHiddenCodes(target_question);
    var merged_codes = getMergedCodes(target_question);
    var removed_codes = getRemovedCodes(target_question);
 
    // Obtain the appropriate object depending on whether
    // the codes in the question represent values or variables
    if (target_question.questionType.indexOf("Pick One") > -1) {
        renamed_codes = renamed_codes.renamedValueCodes;
        hidden_codes = hidden_codes.hiddenValueCodes;
        removed_codes = removed_codes.removedValueCodes;
    } else if (target_question.questionType == "Pick Any") {
        renamed_codes = renamed_codes.renamedVariableCodes;
        hidden_codes = hidden_codes.hiddenVariableCodes;
        removed_codes = removed_codes.removedVariableCodes;        
    }
 
    // Any removed codes should be removed from the list of hidden codes
    var removed_labels = removed_codes.map(function (obj) { return obj.sourceLabel; });
    if (removed_labels.length > 0) {
        for (var j = hidden_codes.length - 1; j >= 0; j--) {
            if (removed_labels.indexOf(hidden_codes[j].sourceLabel) > -1)
                hidden_codes.splice(j,1);
        }
    }
 
    // Separate the NET from the set of merged categories
    var true_merges = [];
    var original_net_found = false;
    var net_label = null;
    merged_codes.forEach(function (obj) {
        if (matchOriginalNET(target_question, obj.array, removed_codes)) {
            if (!original_net_found) {
                original_net_found = true;
                net_label = obj.label;
            }
            else
                true_merges.push(obj);
        }
        else
            true_merges.push(obj);
    });
 
    var recoded_values = [];
    if (target_question.questionType.indexOf("Pick One") == 0)
        recoded_values = getValueChanges(target_question);
 
    var is_changed = (renamed_codes.length > 0) 
                      || (hidden_codes.length > 0) 
                      || (target_question.questionType == "Pick Any" && removed_codes.length > 0) //Removing codes is not a problem for Pick One as we can easily get them back
                      || (true_merges.length > 0) 
                      || (!original_net_found);
 
    return { isChanged: is_changed, 
             renamedCodes: renamed_codes, 
             hiddenCodes: hidden_codes,
             removedCodes: removed_codes, 
             trueMerges: true_merges, 
             NETlabel: net_label, 
             containsOriginalNet: original_net_found,
             recodedValues: recoded_values };
}

function getTemplateLabels(template_question) {

    var template_question_type = template_question.questionType;
    var template_data_reduction = template_question.dataReduction;
    var template_labels;
    var is_trasposed = false;
    if (template_question_type == "Pick One - Multi")
        is_trasposed = template_data_reduction.transposed;

    // Figure out the ordering of the codes in the template question
    if (template_question_type.indexOf("Pick One") == 0) {
 
        if (template_question_type == "Pick One")
            template_labels = template_data_reduction.rowLabels;
        else if (is_trasposed) // else Pick One Multi with 2 axes
            template_labels = template_data_reduction.columnLabels;
        else
            template_labels = template_data_reduction.rowLabels;
    } else if (template_question_type == "Pick Any") 
        template_labels = template_data_reduction.rowLabels;

    return template_labels;  
}
 
 
// This function performs the changes from the template question to the
// target questions, assuming that the target questions have the same 
// value attributes (Pick One/Multi) or variables with the same labels (Pick Any)
function applyDataReductionChangesExactly(template_question, target_questions) {
 
    var template_question_type = template_question.questionType;
    var template_data_reduction = template_question.dataReduction;
    var template_labels;
 
    var template_changes = getDataReductionChanges(template_question);
 
    var renamed_codes = template_changes.renamedCodes;
    var hidden_codes = template_changes.hiddenCodes;
    var true_merges = template_changes.trueMerges;
    var net_label = template_changes.NETlabel;
    var original_net_found = template_changes.containsOriginalNet;
    var removed_codes = template_changes.removedCodes;
    var recoded_values = template_changes.recodedValues;
    var is_trasposed = false;
    if (template_question_type == "Pick One - Multi")
        is_trasposed = template_data_reduction.transposed;
 
    template_labels = getTemplateLabels(template_question);

    // apply the data reduction changes to each target question
    target_questions.forEach(function (question) {
        var target_question_type = question.questionType;
        var is_pick_one_type = target_question_type.indexOf("Pick One") == 0;
 
        var reduction = question.dataReduction;
        var attributes = question.valueAttributes;
 
        var current_net_label = getNETName(question);
 
        // Make sure the same categories are missing/removed
        if (is_pick_one_type)
            reconcileRemovedCodesPickOne(template_question, question);
        else if (target_question_type == "Pick Any") {
            if (removed_codes.length > 0) {
                var initial_net_codes = question.variables.map(function (v) { return v.label; });
                removed_codes.forEach(function (obj) {
                    var cur_code = obj.sourceLabel;
                    var k = initial_net_codes.indexOf(cur_code);
                    initial_net_codes.splice(k,1);
                    reduction.hide(cur_code);
                });
                reduction.hide(current_net_label);
                reduction.createNET(initial_net_codes, current_net_label);
            }
        }
 
        // Tidy up NET
        if (!original_net_found)
            reduction.hide(current_net_label);
        else if (net_label != current_net_label)
            reduction.rename(current_net_label, net_label);
 
        // Perform merges. These will be done as using Create NET.
        // Any duplicate categories created will be cleaned up
        // in the next stage.
        true_merges.forEach(function (obj) {
            var merge_labels;
            if (is_pick_one_type)
                merge_labels = obj.array.map(function (x) { return attributes.getSourceLabel(x); });
            else if (target_question_type == "Pick Any")
                merge_labels = obj.array.map(function (v) { return v.label; });
            reduction.createNET(merge_labels, obj.label);
        });
 
        // Hide codes not shown as standalone codes in the template.
        hidden_codes.forEach(function (obj) {
            reduction.hide(obj.sourceLabel);
        });
 
        // Rename 
        renamed_codes.forEach(function (obj) {
            reduction.rename(obj.sourceLabel, obj.label);
        });
 
        // Match up ordering of codes
        reduction.moveAfter(template_labels[0], null);
        for (var j = 1; j < template_labels.length; j++){
            if (reduction.contains(template_labels[j]) != null && reduction.contains(template_labels[j-1]) != null )
                reduction.moveAfter(template_labels[j], template_labels[j-1]);
        }
 
        // Change any values if they have been recoded
        if (recoded_values) {
            recoded_values.forEach(function (obj) {
                setValueForVariablesInQuestion(question, obj.source, obj.new_val);
            });
        }
 
        if (template_question_type == "Pick One - Multi" && target_question_type == "Pick One - Multi")
            reduction.transposed = is_trasposed;
 
 
    });
    return template_changes;
}
 
 
// Create the content of the Text item which describes to the user the changes that have been applied
function generateDataReductionChangesHTMLReport(changes, template_question, target_questions) {
 
    var renamed_codes = changes.renamedCodes;
    var hidden_codes = changes.hiddenCodes;
    var true_merges = changes.trueMerges;
    var net_label = changes.NETlabel;
    var original_net_found = changes.containsOriginalNet;
    var recoded_values = changes.recodedValues;
    var removed_codes = changes.removedCodes;
 
    var current_net_label = getProjectNETTranslation();
 
    var target_question_type = template_question.questionType;
    var is_pick_one_type = target_question_type.indexOf("Pick One") == 0;
 
    var html_report = Q.htmlBuilder();
    html_report.setStyle(_GLOBAL_REPORT_BODY_TEXT_STYLE);
 
    // Log Questions
    html_report.appendParagraph("The following questions have been modified: ", {bold: true});
    html_report.appendParagraph(null);
    target_questions.forEach(function (q) {
        html_report.appendParagraph(q.name);
    });
    html_report.appendParagraph(null);
    html_report.appendParagraph(null);
 
    // Log changes
    html_report.appendParagraph("The following changes have been applied:", {bold: true});
    html_report.appendParagraph(null);
 
    // Renamed Codes
    if (renamed_codes.length > 0 || net_label != current_net_label && original_net_found) {
        html_report.appendParagraph("Renamed codes:");
        html_report.appendParagraph(null);
        if (renamed_codes.length > 0) {
            renamed_codes.forEach(function (obj) {
                html_report.appendParagraph('"' + obj.sourceLabel + '" to "' + '"' + obj.label + '"');
            });
        }
        // Renamed NET
        if (net_label != current_net_label && original_net_found)
            html_report.appendParagraph("\"" + current_net_label + "\" to \"" + net_label + "\"");
        html_report.appendParagraph(null);
    }
 
    // Merged Codes
    if (true_merges.length > 0) {
        var attributes = template_question.valueAttributes;
        html_report.appendParagraph("Created NETs:");
        html_report.appendParagraph(null);
        true_merges.forEach(function (obj) {
            var merge_labels;
            if (is_pick_one_type)
                merge_labels = obj.array.map(function (x) { return attributes.getSourceLabel(x); });
            else if (target_question_type == "Pick Any")
                merge_labels = obj.array.map(function (v) { return v.label; });
            var merge_string = "[" + merge_labels.join(", ") + "]";
            html_report.appendParagraph(obj.label + ": " + merge_string);
        });
        html_report.appendParagraph(null);
    }
 
    // Hidden Codes
    if (hidden_codes.length > 0) {
        html_report.appendParagraph("Hidden:")
        html_report.appendParagraph(null);
        hidden_codes.forEach(function (obj) {
            html_report.appendParagraph(obj.sourceLabel);
        });
    }
 
    if (!original_net_found) {
        html_report.appendParagraph("Hid the NET");
    }
 
    if (hidden_codes.length > 0 || !original_net_found)
        html_report.appendParagraph(null);
 
    // Removed Codes
    if (removed_codes.length > 0) {
        html_report.appendParagraph("Removed:")
        html_report.appendParagraph(null);
        removed_codes.forEach(function (obj) {
            html_report.appendParagraph(obj.sourceLabel);
        });
        html_report.appendParagraph(null);
    }
 
    // Recoded Values
    if (recoded_values) {
        if (recoded_values.length > 0) {
            html_report.appendParagraph("Recoded:")
            html_report.appendParagraph(null);
            recoded_values.forEach(function (obj) {
                html_report.appendParagraph(obj.source + " to " + obj.new_val);
            });    
        }
    }
 
    return html_report;
}
 
 
// Template and target question must have the same source values and labels
function reconcileRemovedCodesPickOne(template_question, target_question) {
    var template_attributes = template_question.valueAttributes;
    var target_attributes = target_question.valueAttributes;
    var template_values = template_question.uniqueValues;
    var target_values = target_question.uniqueValues;
    template_values.forEach(function (v) {
        if (template_attributes.getIsMissingData(v))
            target_attributes.setIsMissingData(v, true);
    });
}
 
 
// Returns true if the array of codes matches the original net for the question
function matchOriginalNET(question, codes, removed_variable_codes) {
    var original_net = generateOriginalNETArray(question, removed_variable_codes).map(function (x) { return x.toString(); });
    if (original_net.length != codes.length)
        return false;
    var codes_as_strings;
    if (question.questionType.indexOf("Pick One") == 0)
        codes_as_strings = codes.map(function (x) { return x.toString(); });
    else
        codes_as_strings = codes.map(function (v) { return v.name; });
    original_net.forEach(function (s) {
        if (codes_as_strings.indexOf(s))
            return false;
    });
    return true;
}
 
 
// Compare the source label or variable label to the current label in the data reduction
// and return an array of objects containing labels that don't match.
// 
// Each object in the array has properties:
//
// 1. sourceLabel:  This is the original label for this code, derived either from a Value Label or
//                  a variable label depending on the question type.
// 2. label:        This is the current label in the data reduction.
// 3. source:       This is either the source value (when label belongs to a value) or the variable
//                  (when the label belongs to a variable).
function getRenamedCodes(question) {
    var question_type = question.questionType;
    var value_attributes = question.valueAttributes;
    var single_value_codes;
    var single_variable_codes;
    var renamed_value_codes = [];
    var renamed_variable_codes = [];
    if (question_type.indexOf("Pick One") == 0)
        single_value_codes = getAllUnderlyingValues(question).filter(function (x) { return x.array.length == 1; });
    else if (question_type == "Pick Any")
        single_variable_codes = getAllUnderlyingVariables(question).filter(function (x) { return x.array.length == 1; });
 
    if (single_value_codes != null) {
        single_value_codes.forEach(function (x) {
            var source_label = value_attributes.getSourceLabel(x.array[0]);
            if (source_label != x.label)
                renamed_value_codes.push({sourceLabel: source_label, label: x.label, source: x.array[0]});
        });
    }
 
    if (single_variable_codes != null) {
        single_variable_codes.forEach(function (x) {
            // Trim labels before comparing because data reduction labels get trimmed by default.
            if (x.array[0].label.trim() != x.label.trim()) {
                renamed_variable_codes.push({sourceLabel: x.array[0].label, label: x.label, source: x.array[0]});
            }
        });
    }
 
    return { renamedValueCodes: renamed_value_codes, renamedVariableCodes: renamed_variable_codes };      
}
 
// Determines any codes that have been hidden in the data reduction. These are codes that
// appear in the value attributes and are non-missing (or as a variable in the question) 
// but which do not appear in the data reduction as single codes.
function getHiddenCodes(question) {
    var question_type = question.questionType;
    var value_attributes = question.valueAttributes;
    var hidden_value_codes = [];
    var hidden_variable_codes = [];
    if (question_type.indexOf("Pick One") == 0) {
        var all_value_codes = getAllUnderlyingValues(question);
        var used_values = [];
        all_value_codes.forEach(function (obj) {
            if (obj.array.length == 1)
                used_values = used_values.concat(obj.array);
        });
        var non_missing_values = question.uniqueValues.filter(function (x) { return !value_attributes.getIsMissingData(x); });
        non_missing_values.forEach(function (v) {
            if (used_values.indexOf(v) == -1)
                hidden_value_codes.push( { value: v, sourceLabel: value_attributes.getSourceLabel(v) });
        });
    } else if (question_type == "Pick Any") {
        var variables = question.variables;
        var variable_names = variables.map(function (v) { return v.name; });
        var code_variables = getAllUnderlyingVariables(question);
        var used_variables = [];
        code_variables.forEach(function (obj) {
            if (obj.array.length == 1)
                used_variables = used_variables.concat(obj.array.map(function (v) { return v.name; }));
        });
        variables.forEach(function (v) {
            if (used_variables.indexOf(v.name) == -1)
                hidden_variable_codes.push( { name: v.name, sourceLabel: v.label } );
        });
    }
    return { hiddenValueCodes: hidden_value_codes, hiddenVariableCodes: hidden_variable_codes };
}
 
// Return an array of objects corresponding to data reduction codes that are formed
// as a merge of two or more codes. 
function getMergedCodes(question) {
    var question_type = question.questionType;
    var underlying;
    if (question_type.indexOf("Pick One") != -1)
        underlying = getAllUnderlyingValues(question);
    else 
        underlying = getAllUnderlyingVariables(question);
    var merged_categories = underlying.filter(function (x) { return x.array.length > 1; });
 
    if (question_type == "Pick Any") {
        merged_categories = merged_categories.map(function (obj) {
            return { label: obj.label, array: obj.array };
        });
    }
    return merged_categories;
}
 
// Creates an array that contains the original elements of the NET for the question.
// For Pick One and Pick One - Multi questions this is an array of the source values
// that correspond to non-missing categories.
// For Pick Any questions this is an array of the names of the variables in the question.
function generateOriginalNETArray(question, removed_variable_codes) {
    if (question.questionType.indexOf("Pick One") == 0) {
        var value_attributes = question.valueAttributes;
        var non_missing_values = question.uniqueValues.filter(function (v) { return !value_attributes.getIsMissingData(v); });
        return non_missing_values;
    } else if (question.questionType == "Pick Any") {
        var removed_vars = removed_variable_codes.map(function (obj) {return obj.name; })
        return question.variables.filter(function (v) { return removed_vars.indexOf(v.name) == -1; })
                                 .map(function (v) { return v.name; });
    } else
        throw new Error(question.questionType);
}
 
 
// Determine any values that are different to the source values
function getValueChanges(question) {
    var attributes = question.valueAttributes;
    var unique_values = question.uniqueValues;
    var value_changes = [];
    unique_values.forEach(function (x) {
        var val = attributes.getValue(x);
        if (!isNaN(x)) {
            if (val != x)
                value_changes.push({ source: x, new_val: val });
        } else if (!isNaN(val))
            value_changes.push({ source: x, new_val: val });
    });
    return value_changes;
}
 
// Identify codes that have been removed from the question.
// For Pick One or Pick One - Multi questions, a code is removed
// if it has a value in the value attributes that is not NaN,
// but it has been set as missing data.
// For Pick Any questions, a code is removed if it has a variable
// as part of the question but does not appear in any of the
// codes in the data reduction.
function getRemovedCodes(question) {
    var removed_value_codes = [];
    var removed_variable_codes = [];
    if (question.questionType.indexOf("Pick One") == 0) {
        var unique_values = question.uniqueValues;
        var value_attributes = question.valueAttributes;
        unique_values.forEach(function (v) {
            if (!isNaN(v) && value_attributes.getIsMissingData(v))
                removed_value_codes.push( { value: v, sourceLabel: value_attributes.getSourceLabel(v) });
        });
    } else if (question.questionType == "Pick Any") {
        var underlying = getAllUnderlyingVariables(question).map(function (obj) { return obj.array.map(function (v) { return v.name; }); });
        var all_names_in_data_reduction = [];
        underlying.forEach(function (array) {
            all_names_in_data_reduction = all_names_in_data_reduction.concat(array);
        });
        question.variables.forEach(function (v) {
            if (all_names_in_data_reduction.indexOf(v.name) == -1)
                removed_variable_codes.push( { name: v.name, sourceLabel: v.label } );
        });
    }
    return { removedVariableCodes: removed_variable_codes, removedValueCodes: removed_value_codes };
}
 
 
// Function to tell when the codes 
function pickAnyCodesMatch(template_question, target_question) {
    var original_template_labels = template_question.variables.map(function (v) { return v.label; });
    var original_target_labels = target_question.variables.map(function (v) { return v.label; });
    if (original_target_labels.length != original_template_labels.length)
        return false;
    else {
        var matches = true;
        original_template_labels.forEach(function (label) {
            if (original_target_labels.indexOf(label) == -1)
                matches = false;
        });
        return matches;
    }
}
 
function getNETName(question) {
    // In older versions we can't automatically get the name of the NET
    if (!question.dataReduction.netRows)
        return "NET";
    var type = question.questionType;
    if (["Pick Any", "Pick One", "Pick One - Multi"].indexOf(type) == -1)
        return null;
    var data_reduction = question.dataReduction;
    var net_row_labels = data_reduction.netRows.map(function (x) { return data_reduction.rowLabels[x]; } );
    var net_column_labels = data_reduction.netColumns.map(function (x) { return data_reduction.columnLabels[x]; } );
 
    if (net_row_labels.length > 0)
        return net_row_labels[0];
    else if (net_column_labels.length > 0)
        return net_column_labels[0];
    else 
        return null;
}
 
function getProjectNETTranslation() {
    // Older versions of Q can't read the translations
    if (fileFormatVersion() < 8.81)
        return "NET";
    var item = project.report.appendTable();
    var net_translation = item.translations.get("NET");
    item.deleteItem();
    return net_translation;
}
 
function sourceValueAttributesMatchCurrentValueAttributes(question) {
    var value_attributes = question.valueAttributes;
    var unique_values = question.uniqueValues;
    return unique_values.every(function (x) {
        var cur_val = value_attributes.getValue(x);
        return (isNaN(x) ? isNaN(cur_val) : cur_val == x)
                && value_attributes.getSourceLabel(x) == value_attributes.getLabel(x);
    });
}
 
function revertRemovedCodesForPickOne(question, removed_codes) {
    var value_attributes = question.valueAttributes;
    removed_codes.forEach(function(obj) {
        value_attributes.setIsMissingData(obj.value, false);
    })
}

Prior to the 15th of December, 2015, this page was known as Modifying Rows and Columns - Use a Question as a Template for Modifying Other Questions

See also