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

includeWeb("JavaScript Array Functions");
includeWeb("QScript Functions to Generate Outputs");
includeWeb("QScript Selection Functions");
includeWeb("QScript Table Functions");
includeWeb("QScript Utility Functions");

// 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) {
    const 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) {
    const num_targets = target_value_array.length;
    const num_labels = data_reduction_labels.length;
    let value_matched;
    const results = [];
    for (let j = 0; j < num_targets; j++) {
        value_matched = false;
        for (let 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) {
    const question_type = question.questionType;
    if (question_type.indexOf("Pick One") == -1 && question_type != "Pick Any - Compact") {
        return null;
    }
    const data_reduction = question.dataReduction;
    let 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;
    }
    const num_value_codes = labels.length;
    const all_underlying_values = [];
    for (let 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 _a;
    const question_type = question.questionType;
    if (question_type != "Pick Any" && question_type != "Pick One - Multi") {
        return null;
    }
    const data_reduction = question.dataReduction;
    let labels;
    if (question_type == "Pick Any") {
        labels = data_reduction.rowLabels;
    }
    else {
        labels = data_reduction.transposed ? data_reduction.rowLabels : (_a = data_reduction.columnLabels) !== null && _a !== void 0 ? _a : [];
    }
    const num_variable_codes = labels.length;
    const all_underlying_variables = [];
    for (let 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) {
    const value_attributes = question.valueAttributes;
    const non_missing_values = question.uniqueValues.filter(x => {
        return !value_attributes.getIsMissingData(x);
    }).sort();

    // Get the set of values for each code in the data reduction
    let 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();
    });

    const merged_objects = merging_objects.map(function (obj) {
        return { name: obj.label, values: obj.array };
    });

    return merged_objects;
}

function largestDimension(question) {
    var _a, _b;
    let num_categories;
    const 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, (_b = (_a = question.dataReduction.columnLabels) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
    }
    else {
        num_categories = 0;
    }
    return num_categories;
}

function mergeCategoriesLessThanOrEqualTo(percentage) {
    const displayr_fail_message = "Select one or more Nominal, Ordinal, "
        + "Binary - Multi, or Binary - Multi (Compact) variable sets under "
        + "Data Sources, 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;
        }

        const data_reduction = question.dataReduction;
        const values = table_output.get("%").map(x => x[0]);
        const labels = table_output.rowLabels;
        const codes_to_merge = [];
        let last_non_merged_code = null;
        const net_rows = getNetRowsOrColumns(data_reduction, false);
        const 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) {
            const 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) {
        const user_selections = getAllUserSelections();
        selected_questions = user_selections.selected_questions.concat(user_selections.questions_in_rows_of_selected_tables);
    }
    else {
        selected_questions = uniqueQObjects(getQuestionsSelectedInTables().map(obj => 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;
    }

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

    const applicable_questions = validity.applicable;
    const 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;
    }


    const modified = [];
    const not_modified = [];
    applicable_questions.forEach(function (q) {
        const output = getSummaryTableOutput(q);

        if (output != null) {
            const 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) {
            const new_group_name = "Questions with small categories merged: " + percentage + "%";
            const new_group = generateGroupOfSummaryTables(new_group_name, modified);
            if (new_group) {
                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