Filtering - Filter One Question by Another

From Q
Jump to navigation Jump to search

Create a new version of a variable set, in which each variable has been filtered by a category from another variable set, or, with a single filter applied to all variables in the set

This QScript creates a new copy of a question, in which each variable has been filtered by a category from another question, or, with a single filter applied to all variables in the question.

Examples

You can work through the examples of the QScript below yourself in Q using this example data set.

Filtering a question using a filter

The table on the left shows a SUMMARY table. The table in the middle shows a table of a new question created using this QScript, where the an existing filter called Female has been used to create a new variable (in which cases that are not female have missing values). The table to the right shows a table of the original question, filtered in the usual way (i.e., by creating and applying a filter directly to the table); it gives the same result as applying this QScript.

Filtering a question by each of the categories in another

The first table shows a Pick One - Multi question. The table underneath shows this same table, but with each category split by different age categories.

Filtering each variable of a question

The table in the top left shows a SUMMARY of Preferred Cola with the sample sizes of each of the cola brands. The table on the top right shows a SUMMARY of a Pick Any - Grid question, where the Base n for each cell is the total sample of 327 respondents. The table at the bottom shows the result when the grid is filtered by Preferred Cola - the cells of the table are now based only on respondents who prefer the cola brand for that row.

Technical details

You will be prompted to select:

  1. The question that you want to filter.
  2. The question whose categories you want to use as filters.
  3. Whether you want to apply a single filter to each variable in the selected question, or to expand the selected question by making a new copy for each filter category.

When you choose to apply a single filter to each variable in the selected question, you will be given the option to choose the filter to apply to each variable manually, or to let Q try to match up the variables with the filters automatically. The automatic matching searches each variable label for the category labels from the filter question. If some variables cannot be matched correctly to filters then you will be given the option to match filters to variables manually.

When you choose to expand the question by creating a new filtered copy of each variable for each filter, the new variables will be combined into a new question. For example, if the input question is a Number question, then the new question will be a Number - Multi question with one variable for each filter category.

The choice of whether or not to expand the question is not available for Text, Number, or Pick One questions (which are always expanded), or for Pick Any - Grid or Number - Grid questions (which cannot be expanded). Filtering is not available for Experiment, Ranking, or Date questions because it does not make sense in these cases.

If the question that you select to filter with is a Pick One question that is tagged as a Filter () in the Variables and Questions tab then you will be given the option to apply this single filter to all of the variables in the question that you wish to filter (rather than filtering by each of the categories in the question).

A new folder will be created in your report that contains a table showing the new question, and a second item that contains a list of which variables have been filtered by each of the categories in the filter question.

The new variables are generated with JavaScript formulas that take the value of the original variable for respondents in the filter category, and a value of NaN for respondents who are not in that category. The values and labels are copied from the original question so that any recoding or relabeling that has been done in the original question will be carried through to the new copy.

Requires Q 4.8 or later.

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

// Create New Variables - Filter One Question by Another Question
 
// This QScript takes two questions - the primary question and the filter question and does
// one of two things, depending on the input questions and the options selected by the user.
 
// The two questions are
 
// Primary Question: Can be a Pick One - Multi, Number - Multi, Pick Any, or Pick Any - Grid
// Filter Question: Can be a Pick One question or a Pick Any question
 
// There are three different modes of generating new variables:
 
// 1. Creates new JavaScript variables for each variable in the primary question
// which are filtered by the corresponding categories in the filter question.
// - Called "Individual Mode".
 
// 2. Creates a new copy of all of the variables in the question for each filter category and
// combines them into a question of the appropriate type.
// - Called "Expansion Mode".
 
// 3. When the filter question is tagged as a filter then each variable in the primary question
// will be duplicated, and the duplicate variable will take a value of NaN for cases where the
// value in the filter variable is 0 or NaN. The Question Type is unchanged.
// - Called "Single Filter Mode".
 
 
// Input                Expansion               Individual
// --------------------------------------------------------------
// Text                 Text - Multi    
// Text - Multi                                 Text - Multi
// Number               Number - Multi  
// Number - Multi       Number - Grid           Number - Multi
// Number - Grid                                Number - Grid
// Pick One             Pick One - Multi    
// Pick One - Multi     Pick One - Multi        Pick One - Multi
// Pick Any             Pick Any - Grid         Pick Any
// Pick Any - Grid                              Pick Any - Grid
 
includeWeb('JavaScript Utilities');
includeWeb('QScript Selection Functions');
includeWeb("QScript Functions to Generate Outputs");


if (!main())
    log("Qscript cancelled.");
 
function main() {
    try {
        var is_displayr = (!!Q.isOnTheWeb && Q.isOnTheWeb());
        var structure_name = is_displayr ? "variable set" : "question";

        // User selections of questions
        var data_file = requestOneDataFileFromProject();
        var candidate_types = ["Pick One", "Pick One - Multi", "Pick Any", "Pick Any - Grid", "Number", "Number - Multi", "Number - Grid", "Text", "Text - Multi"];
        var candidate_primary_questions = getAllQuestionsByTypes([data_file], candidate_types);
        var candidate_filter_questions = getAllQuestionsByTypes([data_file], ["Pick One", "Pick Any"]);
        if (candidate_primary_questions.length < 1)
            throw new SetupError("Did not find any appropriate " + structure_name + "s to filter. Only " + structure_name + "s of the following types are supported: " + candidate_types.join(", ") + ".");
        if (candidate_filter_questions.length < 1)
            throw new SetupError("Did not find any appropriate " + structure_name + "s to use as filters.");
 
        var primary_question = selectOneQuestion("Select the " + structure_name + " that you want to filter.", candidate_primary_questions);
        var filter_question = selectOneQuestion("Select the " + structure_name + " whose categories you wish to use as filters.", candidate_filter_questions);

        if (primary_question == null || filter_question == null)
            return false;
 
        var expansion_mode;
        var single_filter_mode;
        if (filter_question.isFilter & filter_question.variables.length == 1) {
            single_filter_mode = askYesNo("The selected filter " + structure_name + ", " + filter_question.name + ", is tagged as a filter. "
                                          + "Click Yes to create a new copy of the " + structure_name + " with this filter applied. "
                                          + "Click No to create new variables for each of the categories in the filter " + structure_name + ".");
        }
 
 
        var filter_question_type = filter_question.questionType;
        var primary_variables = primary_question.variables;
        var primary_labels = primary_variables.map(function (v) { return v.label; });
        var primary_question_type = primary_question.questionType;
 
 
        if (!single_filter_mode) {
            // Determine the labels of the filters
            var filter_question_data_reduction = filter_question.dataReduction;
            var filter_labels;
 
            filter_labels = filter_question_data_reduction.rowLabels;
            if (!filter_question_data_reduction.netRows) {
                filter_labels = filter_labels.filter(function (label) { return label != "NET"; });
            } else {
                var net_rows = filter_question_data_reduction.netRows;
                for (var j = filter_labels.length - 1; j >= 0; j--) {
                    if (net_rows.indexOf(j) > -1)
                        filter_labels.splice(j,1);
                }
            }
 
            if (unique(filter_labels).length != filter_labels.length)
                throw new SetupError("Cannot match filters to variables because the filter category labels are not unique.");
 
            // Determine if filtering in expansion mode or individual mode
            var always_expansion_mode = ["Text", "Number", "Pick One"];
            var always_individual_mode = ["Pick Any - Grid", "Number - Grid", "Text - Multi"];
            if (always_expansion_mode.indexOf(primary_question_type) > -1)
                expansion_mode = true;
            else if (always_individual_mode.indexOf(primary_question_type) > -1)
                expansion_mode = false;
            else
                expansion_mode = askYesNo("Click Yes to expand the " + structure_name + " by the filter categories. Click No to apply a single filter to each variable in " + primary_question.name);
 
            if (expansion_mode) {
                var filter_matches = [];
                primary_variables.forEach(function (v) {
                    filter_matches = filter_matches.concat(filter_labels.map(function (label) {
                        return { variable: v, filterLabel: label };
                    }));
                });
 
                filter_matches = generateExpansionModeVariableLabels(filter_matches, primary_question_type);
            } else {
                if (unique(primary_labels).length != primary_labels.length)
                    throw new SetupError("Cannot match filters to variables because the variable labels in the selected " + structure_name + " are not unique.");
                var auto_setup = askYesNo("Would you like " + (is_displayr ? "Displayr" : "Q") + " to try to match the filters to the variables in your " + structure_name + " automatically?");
                if (auto_setup) {
                    try {
                        var filter_matches = automaticFilterMatch(primary_variables, filter_labels);
                    } catch (e) {
                        if (e instanceof AutoDetectError) {
                            var continue_manually = confirm(e.message + " Click OK to match the filters to the variables in your " + structure_name + " manually.");
                            if (continue_manually)
                                auto_setup = false;
                            else
                                return false;
                        } else
                            throw e;
                    }       
                }
                if (!auto_setup)
                    var filter_matches = manualFilterMatch(primary_variables, filter_labels);
            }
        } else
            expansion_mode = false;
 
        // Create new variables and new question
        var new_vars;
        if (single_filter_mode)
            new_vars = applySingleFilter(filter_question, primary_question);
        else
            new_vars = createFilteredVariables(filter_matches, primary_question, filter_question, filter_labels, expansion_mode);
 
        var new_question_type = primary_question.questionType;
        if (expansion_mode) {
            if (primary_question_type == "Pick One")
                new_question_type = "Pick One - Multi";
            else if (primary_question_type == "Number")
                new_question_type = "Number - Multi";
            else if (primary_question_type == "Pick Any")
                new_question_type = "Pick Any - Grid";
            else if (primary_question_type == "Text")
                new_question_type = "Text - Multi";
            else if (primary_question_type == "Number - Multi")
                new_question_type = "Number - Grid";
            else if (primary_question_type == "Pick One - Multi")
                new_question_type = "Pick One - Multi";
            else
                throw primary_question_type;
        }
        var new_q = data_file.setQuestion(preventDuplicateQuestionName(data_file, primary_question.name + " filtered by " + filter_question.name), new_question_type, new_vars);
        insertAtHoverButtonIfShown(new_q);
 
        // If the new question is a grid, check that it is valid, and if it isn't then change
        // the question type.
        if (new_question_type == "Pick Any - Grid" || new_question_type == "Number - Grid") {
            if (!new_q.isValid) {
                new_question_type = new_question_type == "Pick Any - Grid" ? "Pick Any" : "Number - Multi";
                new_q.questionType = new_question_type;
            }
        }
 
        // If the new question is a Pick Any then copy the 'Count this value'
        // settings from the old question.
        if (new_question_type.indexOf("Pick Any") > -1) {
            var value_attributes = primary_question.valueAttributes;
            var unique_values = primary_question.uniqueValues;
            var target_value_attributes = new_q.valueAttributes;
            var target_unique_values = new_q.uniqueValues;
            unique_values.forEach(function (x) {
                target_value_attributes.setCountThisValue(x, value_attributes.getCountThisValue(x));
            });
            new_q.needsCheckValuesToCount = primary_question.needsCheckValuesToCount;
        }
 
        // add spans for expansion mode for Pick One - Multli
        if (expansion_mode && new_question_type == "Pick One - Multi" && primary_question_type == "Pick One - Multi") {
            var data_reduction = new_q.dataReduction;
            var new_labels = new_vars.map(function (v) { return v.label; });
 
            var label_prefixes = primary_variables.map(function (v) { return v.label; });
            label_prefixes.forEach(function (prefix) {
                var codes = filter_matches.filter(function (obj) { return obj.newVariableLabel == prefix + " - " + obj.filterLabel; })
                                          .map(function (obj) { return obj.newVariableLabel; });
                data_reduction.span(codes, prefix);
            });
            filter_matches.forEach(function (obj) {
                data_reduction.rename(obj.newVariableLabel, obj.filterLabel);
            });
        }
 
 
        // Add a new table for the new question and add a text item describing which variables
        // were filtered by each filter.        
        if (!is_displayr)
        {
            var new_group = project.report.appendGroup();
            new_group.name = "Filtered Question";
            var new_table = new_group.appendTable();
            new_table.primary = new_q;
     
            if (!single_filter_mode) {
                var new_text_item = new_group.appendText();
     
                // Text item title
                var title_builder = Q.htmlBuilder();
                title_builder.appendParagraph(primary_question.name + " filtered by " + filter_question.name, { font: 'Tahoma', size: 20 });
                new_text_item.title = title_builder;
                var html_report = Q.htmlBuilder();
                html_report.setStyle({ font: 'Lucida Console', size: 10 });
     
                var filters_used_paragraph = "The following filters have been applied to the variables in the question:";
                html_report.appendParagraph(filters_used_paragraph, { font: 'Lucida Console', size: 10 });
                html_report.appendParagraph(null);
     
                // Report table header
                var table_header = [["Filter Category", "Variables Filtered"]];
                html_report.appendTable(table_header, [20, 50], "", { font: 'Lucida Console', size: 10 });
     
                // Generate table to display the variables that matched each filter
                var filter_report_table = [];
                var filter_match_counts = filter_labels.map(function (filter_label) {
                    var count = 0;
                    filter_matches.forEach(function (match) {
                        if (filter_label == null && match.filterLabel == null)
                            count ++;
                        else if (filter_label != null && match.filterLabel != null)
                            if (match.filterLabel.trim() == filter_label.trim())
                                count ++;
                    });
                    return {label: filter_label, count: count};
                });
                var not_used_filters = filter_match_counts.filter(function (obj) { return obj.count == 0;})
                                                          .map(function (obj) { return obj.label; });
                var used_filters = difference(filter_labels, not_used_filters);
                used_filters.forEach(function (filter) {
                    var first_match = true;
                    filter_matches.forEach(function (obj) {
                        if (obj.filterLabel != null) {
                            if (obj.filterLabel.trim() == filter.trim()) {
                                if (first_match) {
                                    filter_report_table.push(["", ""]);
                                    filter_report_table.push([filter + ":", obj.variable.label]);
                                    first_match = false;
                                } else
                                    filter_report_table.push(["", obj.variable.label]);
                            }
                        }
                    });
                });
     
                // Add rows for variables included but not matched to a filter
                var not_filtered = filter_matches.filter(function (obj) { return obj.filterLabel == null;} );
                if (not_filtered.length > 0) {
                    var first_match = true;
                    not_filtered.forEach(function (obj) {
                        if (first_match) {
                            filter_report_table.push(["", ""]);
                            filter_report_table.push(["Not filtered:", obj.variable.label]);
                            first_match = false;
                        } else
                            filter_report_table.push(["", obj.variable.label]);
                    });
                }
     
                html_report.appendTable(filter_report_table, [20, 50], null, { font: 'Lucida Console', size: 10 });
     
                // Describe any variables not included
                var included_variables = filter_matches.map(function (obj) { return obj.variable.label;} );
                var not_used_variables = difference(primary_labels, included_variables);
                if (not_used_variables.length > 0) {
                    var not_included_paragraph = "The following variables were not included in the new question:"; 
                    html_report.appendParagraph(null);
                    html_report.appendParagraph(not_included_paragraph, { font: 'Lucida Console', size: 10 });
                    html_report.appendParagraph(null);
                    html_report.appendTable(not_used_variables.map(function (x) { return [x]; }), [50], null, { font: 'Lucida Console', size: 10 });
                }
     
                // Describe any filters not used
                if (not_used_filters.length > 0) {
                    var not_used_filters_paragraph = "The following categories were not used to filter any variables:";
                    html_report.appendParagraph(null);
                    html_report.appendParagraph(not_used_filters_paragraph, { font: 'Lucida Console', size: 10 });
                    html_report.appendParagraph(null);
                    html_report.appendTable(not_used_filters.map(function (x) { return [x]; }), [50], null, { font: 'Lucida Console', size: 10 });
                }
     
                new_text_item.content = html_report;
     
                conditionallyEmptyLog("A table showing the new filtered version of " + primary_question.name + " has been added to your report, along with a description of which variables match each filter.");
            } else {
                conditionallyEmptyLog("A table showing " + primary_question.name + " filtered by " + filter_question.name + " has been added to your report.");
            }
            // More recent Q versions can point the user to the new items.
            if (fileFormatVersion() > 8.65)
                project.report.setSelectedRaw([new_group.subItems[0]]);
        }
        return true;
 
    } catch (e) {
        if (e instanceof SetupError)
            log(e.message);
        else
            throw e;
    }
}
 
// Creates filtered variables for expansion mode and individual mode.
function createFilteredVariables(filter_matches, primary_question, filter_question, filter_labels, expansion_mode) {
    if (filter_matches.length == 0)
        throw new SetupError("No variables were selected for filtering.");

    var data_file = primary_question.dataFile; 
    var filter_variables = filter_question.variables;
    var filter_variable_names = filter_variables.map(function (v) { return v.name; } );
    var filter_question_type = filter_question.questionType;
    var primary_question_type = primary_question.questionType;
    var is_text = primary_question_type.indexOf("Text") > -1;
    var filter_question_data_reduction = filter_question.dataReduction;
    var primary_question_variable_names = primary_question.variables.map(function (v) { return v.name;});

    // Get array of values or variable names to filter each primary variable by
    if (filter_question_type == "Pick One") {
        var filter_array = filter_labels.map(function (label) {
            return filter_question_data_reduction.getUnderlyingValues(label);
        });
    } else {
        var filter_array = filter_labels.map(function (label) {
            return filter_question_data_reduction.getUnderlyingVariables(label).map(function (v) { return v.name; });
        });
    }
 
    // Generate new variables
    var new_variables = [];
    var new_variable_data = [];
    var last_var = filter_matches[filter_matches.length - 1].variable;
    filter_matches.forEach(function (match) {
        var source_value_expression = "\tif(isNaN(Q.Source(" + match.variable.name + ")))\r\n\t\t" + match.variable.name + ";\r\n\telse\r\n\t\tQ.Source(" + match.variable.name + ");";
        var no_value_expression = is_text ? "\r\nelse \"\";" : "\r\nelse NaN;";
        var expression;
        if (match.filterLabel == null)
            expression = source_value_expression + ";";
        else {
            var filter_index = filter_labels.indexOf(match.filterLabel);
            expression = "if (";
            if (filter_question_type == "Pick One") {
                filter_array[filter_index].forEach(function (val, index) {
                    var val_exp;
                    if (isNaN(val))
                        val_exp = "isNaN(Q.Source(" + filter_variable_names[0] + "))";
                    else 
                        val_exp = "Q.Source(" + filter_variable_names[0] + ") == " + val;
                    if (index > 0 )
                        expression += " || ";
                    expression += val_exp
                });
 
            } else {
                filter_array[filter_index].forEach(function (name, index) {
                    if (index == 0)
                        expression += name;
                    else
                        expression += " || " + name;
                });
            }
            expression += "){\r\n" + source_value_expression + "\r\n}" + no_value_expression;
        }
        var short_filter_label = match.filterLabel == null ? "NoFilter" : match.filterLabel.replace(/\W/g, "");
        var new_var_label = expansion_mode ? match.newVariableLabel : match.variable.label;
        var new_var = data_file.newJavaScriptVariable(expression, 
                                                      is_text, 
                                                      preventDuplicateVariableName(data_file, match.variable.name + "_f_" + short_filter_label), 
                                                      new_var_label, last_var);
        new_var.variableType = match.variable.variableType;
        new_variables.push(new_var);
        new_variable_data.push({ variable: new_var, index: primary_question_variable_names.indexOf(match.variable.name) });
        last_var = new_var;
        if (!is_text){
            copyValueAttributesToFilteredVariable(new_var, match.variable);
 
        }
    });

    // For grids it is important the the ordering of the variables is
    // the same for the new filtered question as for the old question.
    if (primary_question_type.indexOf("Grid") > -1) {
        new_variable_data.sort(function (a, b) { return a.index - b.index; });
        var last_var = new_variable_data[0].variable;
        new_variable_data.forEach(function (obj, ind) {
            if (ind > 0) {
                data_file.moveAfter([obj.variable], last_var);
                last_var = obj.variable;
            } 
        });
        new_variables = new_variable_data.map(function (obj) { return obj.variable; });
    }

    return new_variables;
}
 
// Creates filtered variables for Single Filter Mode
function applySingleFilter(filter_question, primary_question) {
    var new_variables = [];
    var data_file = primary_question.dataFile;
    var filter_var = filter_question.variables[0];
    var filter_var_name = filter_var.name;
    var is_text = primary_question.variables[0].variableType == "Text";
    var last_var = primary_question.variables[primary_question.variables.length - 1];
    primary_question.variables.forEach(function (v) {
        var source_value_expression = "\tif(isNaN(Q.Source(" + v.name + ")))\r\n\t\t" + v.name + ";\r\n\telse\r\n\t\tQ.Source(" + v.name + ");";
        var expression = "if (" + filter_var_name + " > 0){\r\n" + source_value_expression + "\r\n}";
        if (is_text)
            expression += "else '';";
        else
            expression += "else NaN;";
        var new_var = data_file.newJavaScriptVariable(expression,
                                                      is_text,
                                                      preventDuplicateVariableName(data_file, v.name + "_f_" + filter_var_name),
                                                      v.label, last_var);
        last_var = new_var;
        new_var.variableType = v.variableType;
        new_variables.push(new_var);
        if (!is_text)
            copyValueAttributesToFilteredVariable(new_var, v);
    });
    return new_variables;
}
 
function automaticFilterMatch(primary_variables, filter_labels) {
    // Match the variables to the filters based on the variable labels
    var filter_matches = primary_variables.map(function (variable) {
        return {variable: variable, filterLabel: matchVariableLabelToFilterLabel(variable, filter_labels)}
    })
 
    // Count the number of matches for each filter.
    var filter_match_counts = filter_labels.map(function (filter_label) {
        var count = 0;
        filter_matches.forEach(function (match) {
            if (match.filterLabel.trim() == filter_label.trim())
                count ++;
        });
        return {label: filter_label, count: count};
    });
    var max_filter_count = 0;
    var second_max_filter_count = 0;
    var max_label;
    var second_max_label;
    // Find largest filter count.
    filter_match_counts.forEach(function (match) {
        if (match.count > max_filter_count) {
            max_filter_count = match.count;
            max_label = match.label;
        }
 
    });
    // Find second-largest filter count.
    filter_match_counts.forEach(function (match) {
        if (match.count > second_max_filter_count && match.count < max_filter_count) {
            second_max_filter_count = match.count;
            second_max_label = match.label;
        }
    });
 
    // Throw errors when:
    // 1 - There are no matches.
    // 2 - Filters match different numbers of variables. This ignores any filter categories
    //     that do not match any variables.
    if (max_filter_count == 0)
        throw new AutoDetectError("The filters did not match any of the variables.");
    else if (second_max_filter_count > 0)
        throw new AutoDetectError("The filters match different numbers of variables. For example, the filter " 
                                  + max_label + " matches " + max_filter_count + " variables, but the filter "
                                  + second_max_label + " only matches " + second_max_filter_count + " variable" 
                                  + (second_max_filter_count == 1 ? "." : "s."));
    return filter_matches;
}
 
 
function manualFilterMatch(primary_variables, filter_labels) {
    var primary_labels = primary_variables.map(function (v) { return v.label; });
    var used_labels = [];
    var filter_matches = [];
    filter_labels.forEach(function (filter) {
        var remaining_labels = difference(primary_labels, used_labels);
        if (remaining_labels.length != 0) {
            var selected_indices = selectMany("Select the variables that should be filtered by \'" + filter + "\'", remaining_labels);
            var selected_labels = getElementsOfArrayBySelectedIndices(remaining_labels, selected_indices);
            selected_labels.forEach(function (label) {
                used_labels.push(label);
                filter_matches.push( {variable: primary_variables[primary_labels.indexOf(label)], filterLabel: filter} );
            });
        }
    });
    var remaining_labels = difference(primary_labels, used_labels);
    if (remaining_labels.length > 0) {
        var include_remaining = askYesNo("Some variables have not been matched to a filter:\r\n" + remaining_labels.join("\r\n") + "\r\nWould you like to include these variables without a filter? Click \'No\' to exclude these variables from the new question.");
        if (include_remaining)
            remaining_labels.forEach(function (label) {
                filter_matches.push( {variable: primary_variables[primary_labels.indexOf(label)], filterLabel: null} );
            });
    }
    return filter_matches;
}
 
// Copies the value attributes from one variable to another
// assuming that, apart from NaN, all of the source values in
// to_variable exist in from_variable. This is guaranteed
// when to_variable is a copy of from_variable by source value.
function copyValueAttributesToFilteredVariable(to_variable, from_variable) {
    var target_unique_values = to_variable.uniqueValues;
    var source_unique_values = from_variable.uniqueValues;
    var target_value_attributes = to_variable.valueAttributes;
    var source_value_attributes = from_variable.valueAttributes;
    // Copy labels, values, and missing data
    target_unique_values.forEach(function (x) {
        if (!isNaN(x)) {
            target_value_attributes.setLabel(x, source_value_attributes.getLabel(x));
            target_value_attributes.setValue(x, source_value_attributes.getValue(x));
            target_value_attributes.setIsMissingData(x, source_value_attributes.getIsMissingData(x));
        } 
    });
 
    // Sepcial handling for when NaN has been recoded to ensure that the recoded nan doesn't
    // get mixed in with the new missing values that are being introduced via the filter.
    if (source_unique_values.some(function (x) { return isNaN(x); })) {
        var recoded_nan = source_value_attributes.getValue(NaN);
        if (!isNaN(recoded_nan)) {
            target_value_attributes.setLabel(recoded_nan, source_value_attributes.getLabel(NaN))
            target_value_attributes.setIsMissingData(recoded_nan, source_value_attributes.getIsMissingData(NaN))
        }
    }
} 
 
function matchVariableLabelToFilterLabel(variable, filter_labels) {
    var variable_label = variable.label.trim();
    var label_matches = filter_labels.map(function (label) {
        return {label: label, 
                matchIndex: variable_label.indexOf(label.trim()), 
                length: label.trim().length};
    }).filter(function (obj) {
        return obj.matchIndex > -1;
    });
 
    if (label_matches.length == 0)
        throw new AutoDetectError("Could not find a filter to match variable with label \'" + variable_label + "\'.");
 
    if (label_matches.length == 1)
        return label_matches[0].label;
 
    // Where there are multiple matches, return the match with the longest label
    var largest_matching_label_length = -1;
    var largest_matching_label = null;
    label_matches.forEach(function (obj) {
        if (obj.length == largest_matching_label_length)
            throw new AutoDetectError("Found more than one filter to match variable with label \'" + variable_label + "\'.");
        else if (obj.length > largest_matching_label_length) {
            largest_matching_label_length = obj.length;
            largest_matching_label = obj.label;
        }
    });
 
    return largest_matching_label;
}
 
// Generate appropriate variable labels when filtering in expansion mode
function generateExpansionModeVariableLabels(filter_matches, primary_question_type) {
    var simple_types = ["Number", "Text", "Pick One"];
    if (simple_types.indexOf(primary_question_type) > -1)
        return filter_matches.map(function (obj) {
            return { variable: obj.variable, filterLabel: obj.filterLabel, newVariableLabel: obj.filterLabel };
        });
    else
        return filter_matches.map(function (obj) {
            return { variable: obj.variable, filterLabel: obj.filterLabel, newVariableLabel:  obj.variable.label + " - " + obj.filterLabel};
        });
}
 
// A custom error object so we can abort the setup of the filtered question,
// and catch this error, presenting the message to the user without
// causing the QScript to crash and show an error report.
function SetupError(message) {
    this.message = message;
}
 
// Custom error to tell the user that there is a problem detecting
// the filter matching automatically. Will be used to allow the
// user to proceed with manual filter matching.
function AutoDetectError(message) {
    this.message = message;
}

Prior to the 15th of December, 2015, this page was known as Create New Variables - Filter One Question by Another Question

See also