QScript Data Reduction Functions

From Q
Jump to navigation Jump to search

The functions on this page can be helpful when writing scripts to work with the Data Reduction of a question.

For built-in functions for working with data reduction objects, see QScript DataReduction.

To make these functions available when writing a QScript or Rule see JavaScript Reference.

dataReductionContainsLabels(question, label_array)

Returns true if all of the labels in label_array are contained in the data reduction for the input question.

getDataReductionLabelsForValues(target_value_array, data_reduction_labels, data_reduction_value_array)

This function returns an array containing the labels in the input data_reduction_labels that match the values in the input target_value_array. The input data_reduction_value_array should have elements that match the order of data_reduction_labels. The labels and value array should be obtained using the function etAllLabelsAndValuesInDataReduction().

getAllUnderlyingValues(question)

Returns an array of objects corresponding to the codes associated with values in the data reduction. Each object has the properties:

  1. label: The data reduction label for this code.
  2. array: The array of values associated with this code.

getAllUnderlyingVariables(question)

Returns an array of objects corresponding to the codes associated with variables in the data reduction. Each object has the properties:

  1. label: The data reduction label for this code.
  2. array: The array of variable names associated with this code.

getMergedCategoriesFromPickOne(question)

Returns an array describing which categories in the Pick One (or Pick One - Multi) question have been merged (excluding the NET). Each entry in the array has:

  1. name: The name of the merged category.
  2. values: An array of the underlying values for the categories that have been merged.

largestDimension(question)

For categorical question types, return the largest number of categories in the table (i.e. between the rows and columns).

mergeCategoriesLessThanOrEqualTo(percentage)

This function returns the percentage value and labels of table rows in a specific question which are less than or equal to a specified percentage value and merges them together in a table.

Source Code

// MANIPULATION OF DATA REDUCTION

// Returns true if all of the labels in label_array are contained in the data reduction for the input question
function dataReductionContainsLabels(question, label_array) {
    var data_reduction = question.dataReduction;
    return label_array.filter(function (x) { return data_reduction.contains(x);}).length == label_array.length;
}


function getDataReductionLabelsForValues(target_value_array, data_reduction_labels, data_reduction_value_array) {
    var num_targets = target_value_array.length;
    var num_labels = data_reduction_labels.length;
    var value_matched;
    var results = [];
    for (var j = 0; j < num_targets; j++) {
        value_matched = false;
        for (var k = 0; k < num_labels; k++) {
            if (equalArraysOfIntegers([target_value_array[j]], data_reduction_value_array[k])){
                results.push(data_reduction_labels[k]);
                value_matched = true;
                break;
            }
        }
        if (!value_matched)
            throw "No label found for value " + target_value_array[j];
    }
    return results;
}

 
// Returns an array of objects corresponding to the codes associated with values in the
// data reduction.
//
// Each object has the properties:
//
// 1. label:    The data reduction label for this code.
// 2. array:    The array of values associated with this code.
function getAllUnderlyingValues(question) {
    var question_type = question.questionType;
    if (question_type.indexOf("Pick One") == -1 && question_type != "Pick Any - Compact")
        return null;
    var data_reduction = question.dataReduction;
    var labels;
    if (question_type == "Pick One")
        labels = data_reduction.rowLabels;
    else // Pick One Multi
        labels = data_reduction.transposed ? data_reduction.columnLabels : data_reduction.rowLabels;
    if (labels == null)
        return null;
    var num_value_codes = labels.length;
    var all_underlying_values = [];
    for (var j = 0; j < num_value_codes; j++)
        all_underlying_values.push({label: labels[j], array: data_reduction.getUnderlyingValues(j)});
    return all_underlying_values;
}
 
// Returns an array of objects corresponding to the codes associated with variables in the
// data reduction.
//
// Each object has the properties:
//
// 1. label:    The data reduction label for this code.
// 2. array:    The array of variable names associated with this code.
function getAllUnderlyingVariables(question) {
    var question_type = question.questionType;
    if (question_type != "Pick Any" && question_type != "Pick One - Multi")
        return null;
    var data_reduction = question.dataReduction;
    var labels;
    if (question_type == "Pick Any")
        labels = data_reduction.rowLabels;
    else
        labels = data_reduction.transposed ? data_reduction.rowLabels : data_reduction.columnLabels;
    var num_variable_codes = labels.length;
    var all_underlying_variables = [];
    for (var j = 0; j < num_variable_codes; j++) {
        if (question_type == "Pick One - Multi")
            all_underlying_variables.push({label: labels[j], array: data_reduction.getUnderlyingVariables(~j)});
        else
            all_underlying_variables.push({label: labels[j], array: data_reduction.getUnderlyingVariables(j)});
    }
    return all_underlying_variables;
}

// Returns an array describing which categories in the pick one (- multi)
// question have been merged (excluding the NET). Each entry in the array has:
// - name: The name of the merged category
// - values: An array of the underlying values for the categories that have
//           been merged
function getMergedCategoriesFromPickOne(question) {
    var value_attributes = question.valueAttributes;
    var non_missing_values = question.uniqueValues.filter(function (x) {
        return !value_attributes.getIsMissingData(x);
    }).sort();
 
    // Get the set of values for each code in the data reduction
    var merging_objects = getAllUnderlyingValues(question);
    if (merging_objects == null)
        return null;
    // Filter out the set of values corresoponding to the NET as
    // we don't want to keep them.
    merging_objects = merging_objects.filter(function (obj) {
        return obj.array.sort().toString() != non_missing_values.toString();
    });
    merging_objects = merging_objects.map(function (obj) {
        return { name: obj.label, values: obj.array };
    });
 
    return merging_objects;
} 

function largestDimension(question) {
    var num_categories;
    var q_type = question.questionType;
    if (["Pick One", "Pick Any", "Pick Any - Compact"].indexOf(q_type) > -1)
        num_categories = question.dataReduction.rowLabels.length;
    else if (["Pick One - Multi", "Pick Any - Grid"].indexOf(q_type) > -1)
        num_categories = Math.max(question.dataReduction.rowLabels.length, question.dataReduction.columnLabels.length);
    else
        num_categories = 0;
    return num_categories;
}

function mergeCategoriesLessThanOrEqualTo(percentage) {
    includeWeb("QScript Functions to Generate Outputs");
    const displayr_fail_message = "Select one or more Nominal, Ordinal, "
        +"Binary - Multi, or Binary - Multi (Compact) variable sets under "
        + "Data Sets, or a table with a corresponding variable set in the "
        + "rows, and run Merge Categories again."

    function mergeSmallCategoriesInQuestion(question, table_output, percentage) {
        if (question.isBanner) {
           log(correctTerminology("Merge categories cannot be a applied to a banner. "
            + "You should merge categories in the questions which make up the banner."));
           return false;
        }

        let data_reduction = question.dataReduction;
        let values = table_output.get("%");
        let labels = table_output.rowLabels;
        let codes_to_merge = [];
        let last_non_merged_code = null;
        let net_rows = getNetRowsOrColumns(data_reduction, false);
        let last_net_label = labels[net_rows[net_rows.length - 1]];
        values.forEach(function (val, ind) {
            if (val <= percentage)
                codes_to_merge.push(labels[ind]);
            else if (labels[ind] != last_net_label)
                last_non_merged_code = labels[ind];
        });

        if (codes_to_merge.length > 1) {
            let merged_name = codes_to_merge.join(" + ");
            data_reduction.merge(codes_to_merge, merged_name);
            data_reduction.moveAfter(merged_name, last_non_merged_code);
            return true;
        } else
            return false;
    }

    // If we're on the web then the user can select in the report or in data.
    // If we're not then the user can only select from the report.
    const web_mode = inDisplayr();
    let selected_questions;
    if (web_mode) {
        let user_selections = getAllUserSelections();
        selected_questions = user_selections.selected_questions.concat(user_selections.questions_in_rows_of_selected_tables);
    } else 
        selected_questions = uniqueQObjects(getQuestionsSelectedInTables().map(function (obj) { return obj.question; }));

    if (selected_questions.length == 0) {
        if (web_mode)
            log(displayr_fail_message);
        else
            log("Select one or more questions or tables and run Merge Categories again.");
        return false;
    }

    let validity = splitArrayIntoApplicableAndNotApplicable(selected_questions, 
        function (q) { 
            return ["Pick One", "Pick Any", "Pick Any - Compact"].indexOf(q.questionType) > -1; 
        });

    let applicable_questions = validity.applicable;
    let not_applicable_questions = validity.notApplicable;

    if (applicable_questions.length == 0) {
        if (web_mode)
            log(displayr_fail_message);
        else
            log("No Pick One, Pick Any, or Pick Any - Compact questions selected.");
        return false;
    }

   
    let modified = [];
    let not_modified = [];
    applicable_questions.forEach(function (q) {
        let output = getSummaryTableOutput(q);
          
        if (output != null) {
            let merge_result = mergeSmallCategoriesInQuestion(q, output, percentage);
            if (merge_result)
                modified.push(q);
            else
                not_modified.push(q);
        }    
                   
    });

    if (web_mode) {

        if (not_modified.length > 0) {
            logQuestionList("No categories to merge for:", not_modified);
        }

        if (not_applicable_questions.length > 0) {
            if (applicable_questions.length > 0 || not_modified.length > 0)
                log("\r\n");
            logQuestionList("Data of the wrong type:", not_applicable_questions);
        }   
    } else {
        if (modified.length > 0 || not_modified.length > 0) {
            let new_group_name = "Questions with small categories merged: " + percentage + "%";
            let new_group = generateGroupOfSummaryTables(new_group_name, modified);
            if (not_modified.length > 0)
                generateSubgroupOfSummaryTables("No categories to merge", new_group, not_modified);
            if (not_applicable_questions.length > 0)
                generateSubgroupOfSummaryTables("Wrong question type", new_group, not_applicable_questions);
            if (fileFormatVersion() > 8.65)
                project.report.setSelectedRaw([new_group.subItems[0]]);     
        }     
    }

    return true;
}

See also