Filtering - Apply Filters to Columns or Rows

From Q
Jump to: navigation, search

The rule applies separate filters to specified columns or rows of a table. These filters are applied in addition to existing filters applied to the whole table. Optionally, the filtered rows or columns can be added at the bottom or right of the table.

Example

In the table below, the row shown in green has an additional filter applied, so that only respondents who prefer coke are included. In this case the numbers in that row have been replaced by the filtered statistics.

FilteringRowRule.PNG

Adding new rows or columns

This table shows a SUMMARY of a Pick One - Multi question. In this table we can compare the proportion of respondents who Love Coca Cola and Pepsi with the complete scale showing. If we would also like to split these results by Gender then we can use the rule to provide results filtered by Males and Females on the same table.

FilteringNewColumnsBefore.PNG

The options for doing so are shown below. Note that we have two filters, Male and Female already set up in the project, and we have specified labels and spans for the new columns.

FilteringNewColumnsOptions.PNG

The resulting table now has four additional columns added, one for each of the filters that we have specified. Note that the statistics below the table (Average and Column n) have also been filtered.

FilteringNewColumnsAfter.PNG

Technical details

  1. To remove a column or row from filtering, select "(none)" in the combo box. The filtered columns or rows are shaded light green by default, although shading can be turned off in the Rule's settings.
  2. Combo boxes remember the last selected column, row and filter labels. If the table questions or filters are changed but happen to have column, row or filter labels matching the previous selections, then the current selections will correspond to the new columns, rows or filters with the matching labels. If there is no matching label, then the selection will be empty. Be aware that the table or chart does not provide an alert when its values have changed due to changes to questions or filters.
  3. Significance testing results are not available on the table due to the nature of the modifications made by this Rule. A warning will be placed in the footer of the table, and there is an option to turn off the warning in the Rule's settings. Some statistical testing can be added manually using the rule Significance Testing - Independent Samples Column Means and Proportions Tests.
  4. Tables showing Date questions cannot be filtered by this rule.
  5. When not adding columns or rows only a single filter may be selected for each column or row.
  6. Filtering changes the numbers only within the filtered column or row, and any rows or columns which are not selected for filtering remain unchanged.

Marginal statistics

When filtering the columns of the table the Statistics - Below are also filtered but the Statistics - Right are not available.

When filtering the rows of the table the Statistics - Right are filtered but the Statistics - Below are not available.

The exceptions to this are Ranking and Experiment questions for which both Statistics - Below and Statistics - Right are unavailable when this rule is applied.

How to apply this rule

For the first time in a project

  • Select the table(s)/chart(s) that you wish to apply the rule to.
  • Start typing the name of the Rule into the Search features and data box in the top right of the Q window.
  • Click on the Rule when it appears in the QScripts and Rules section of the search results.

OR

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

Additional applications of the rule

  • Select a table or chart that has the rule and any table(s)/chart(s) that you wish to apply the rule to.
  • Click on the Rules tab (bottom-left of the table/chart).
  • Select the rule that you wish to apply.
  • Click on the Apply drop-down and choose your desired option.
  • Check New items to have it automatically applied to new items that you create. Use Edit > Project Options > Save as Template to create a new project template that automatically uses this rule.

Removing the rule

  • Select the table(s)/chart(s) that you wish to remove the rule from.
  • Press the Rules tab (bottom-right corner).
  • Press Apply next to the rule you wish to remove and choose the appropriate option.

How to modify the rule

  • Click on the Rules tab (bottom-left of the table/chart).
  • Select the rule that you wish to modify.
  • Click Edit Rule and make the desired changes. Alternatively, you can use the JavaScript below to make your own rule (see Customizing Rules).

JavaScript

includeWeb('Table JavaScript Utility Functions');
includeWeb('JavaScript Array Functions');
includeWeb('QScript Utility Functions');
 
if (fileFormatVersion() > 8.41 && !(fileFormatVersion() > 8.81))
    form.ruleNotApplicable("this rule currently only works in Q 4.9.7 and Q 4.11.2 and higher");
 
var no_selection_string = '(none)';
var rule_name = 'Apply Filters to Columns or Rows';
 
form.setHeading(rule_name);
form.setSummary(rule_name);
table.requireNumericTable();
table.requireOriginalRowsColumns();
 
var no_net_available_types = ["Date", "Ranking", "Experiment"];
var blue_question = table.blueQuestion;
var brown_question = table.brownQuestion;
var blue_question_type = blue_question.questionType;
var brown_question_type = "SUMMARY";
if (brown_question == "RAW DATA")
    form.ruleNotApplicable("this rule does not work with RAW DATA");
if (brown_question != "SUMMARY")
    brown_question_type = brown_question.questionType;
 
// Check for availability of marginal statistics
var below_table_exists = belowTableExists();
var right_table_exists = rightTableExists();
 
// Specify which statistics will be copied across from the filtered tables
// regardless of whether or not they are selected on the table by the user.
// This ensures that the resulting table has all the necessary statistics
// for manual statistical testing (which is done in a separate rule).
var mandatory_cell_statistics = ['Standard Deviation',
                                 'Column Standard Error',
                                 'Base Population',
                                 'Effective Base n',
                                 'Base n',
                                 'Column Population',
                                 'Column n',
                                 'Average',
                                 'Column %',
                                 'Row %',
                                 '%',
                                 'Standard Error'];
 
var mandatory_below_statistics = ['Standard Deviation',
                                  'Standard Error',
                                  'Column Population',
                                  'Base Population',
                                  'Average',
                                  'Column n',
                                  'Base n',
                                  'Base Population'];
var table_stats_to_filter = table.statistics
                                 .concat(mandatory_cell_statistics.filter(function (stat) { 
                                                                             return table.availableStatistics.indexOf(stat) > -1 
                                                                                    && table.statistics.indexOf(stat) == -1;
                                                                         }));
var below_stats_to_filter;
if (below_table_exists)
    below_stats_to_filter = below_table.statistics
                                       .concat(mandatory_below_statistics.filter(function (stat) { 
                                                                                     return below_table.availableStatistics.indexOf(stat) > -1
                                                                                            && below_table.statistics.indexOf(stat) == -1;
                                                                                 }));
 
// Check that there are filters in the project, and get the list
var filters = [];
project.dataFiles.forEach(function (data_file) {
    filters = filters.concat(data_file.variables.filter(function(v) { return v.question.isFilter; }));
});
if (filters.length == 0) {
    table.suppressOutput('The Rule "' + rule_name + '" requires filters in the project. Add filters to the project or remove the Rule from the table.');
    form.ruleNotApplicable('there are no filters in the project.');
}
var filter_labels = project.dataFiles.length == 1 ?
                    filters.map(function(v) { return v.label.trim(); }) :
                    filters.map(function(v) { return v.label.trim() + ' [' + v.question.dataFile.name + ']'; });
filter_labels = enumerateDuplicatesInStringArray(filter_labels, '(', ')');
 
// Add a none option to the items in the filter menu and append a corresponding blank element to the list of filters
filter_labels = [no_selection_string].concat(filter_labels);
filters = [''].concat(filters);
 
// Column or row combobox
var controls = [];
var filter_cols_filter_rows = ['Filter columns', 'Filter rows'];
var filter_col_row_combobox = form.newComboBox('columnOrRow', filter_cols_filter_rows);
filter_col_row_combobox.setDefault(table.columnLabels == null ? filter_cols_filter_rows[1] : filter_cols_filter_rows[0]);
var filter_columns = filter_col_row_combobox.getValue() == filter_cols_filter_rows[0];
filter_col_row_combobox.lineBreakAfter = true;
controls.push(filter_col_row_combobox);
 
// Check that there are valid columns
if (filter_columns && table.columnLabels == null) {
    table.suppressOutput('The Rule "' + rule_name + '" requires columns that are not statistics. Change the questions in the table or remove the Rule from the table.');
    form.ruleNotApplicable('the columns in this table are statistics.');
} 
 
// Check box for adding new columns instead of modifying existing ones
var add_new_box = form.newCheckBox('addnew', 'Add new ' + (filter_columns ? 'columns' : 'rows'));
add_new_box.setDefault(false);
add_new_box.lineBreakAfter = true;
controls.push(add_new_box);
var adding_new = add_new_box.getValue();
 
// Trim column/row labels and change column/row labels that collide with no_selection_string
var col_row_combobox_alternatives = (filter_columns ? table.columnLabels : table.rowLabels).map(function (label) {
    return label.trim();
}).map(function (label) {
    if (label == no_selection_string)
        return label + (filter_columns ? ' (column)' : ' (row)');
    else
        return label;
});
col_row_combobox_alternatives = [no_selection_string].concat(enumerateDuplicatesInStringArray(col_row_combobox_alternatives, '(', ')'));
 
var selected_cols_rows = [];
var selected_filters = [];
var new_labels = [];
var entered_span_labels = [];
var last_index = 0;
var current_index = 0;
 
// Loop to allow user to specify information about which rows/columns should be filtered
// with which variables.
var col_row_label = filter_columns ? 'Column:' : 'Row:';
while (true) {
    var col_row_combobox = form.newComboBox((filter_columns ? 'column' : 'row') + current_index, col_row_combobox_alternatives);
    var selected_col_row = col_row_combobox.getValue();
    if (selected_col_row == null) // stop adding when the combobox selection is null
        break;
    else if (selected_col_row != no_selection_string) { // only add comboboxes with a column/row selected
        controls.push(form.newLabel(col_row_label));
        controls.push(col_row_combobox);
        controls.push(form.newLabel('Filter:'));
        var filter_combobox = form.newComboBox((filter_columns ? 'columnFilter' : 'rowFilter') + current_index, filter_labels);
        filter_combobox.setDefault(no_selection_string);
        if (!adding_new)
            filter_combobox.lineBreakAfter = true;
        controls.push(filter_combobox);
 
        // If adding new columns/rows also provide inputs for new labels and span labels 
        if (adding_new) {    
            controls.push(form.newLabel((filter_columns ? 'Column' : 'Row') + ' label:'));
            var new_label_box = form.newTextBox('chb' + current_index);
            controls.push(new_label_box);
            controls.push(form.newLabel((filter_columns ? 'Column' : 'Row') + ' span:'));
            var span_label_box = form.newTextBox('sl' + current_index);
            span_label_box.lineBreakAfter = true;
            controls.push(span_label_box);
        }
 
        var filter_value = filter_combobox.getValue();
        // If the user has selected "(none)" then don't do anything
        if (filter_value != no_selection_string) {
            selected_filters.push(filter_value);
            selected_cols_rows.push(selected_col_row);
            if (adding_new) {
                new_labels.push(new_label_box.getValue());
                entered_span_labels.push(span_label_box.getValue());
            }
        }
        last_index = current_index + 1;
    }
    current_index++;
}
 
// Add last combobox at last_index
controls.push(form.newLabel(col_row_label));
var last_col_row_combobox = form.newComboBox((filter_columns ? 'column' : 'row') + last_index, col_row_combobox_alternatives);
last_col_row_combobox.setDefault(no_selection_string);
last_col_row_combobox.lineBreakAfter = true;
controls.push(last_col_row_combobox);
 
// Add checkbox for displaying footer message
var checkbox_footer = form.newCheckBox('footer', 'Show significance testing message in footer');
checkbox_footer.setDefault(true);
controls.push(checkbox_footer);
 
var cell_higlight_color;
var shade_cells = false;
if (!table.forPlot) {
    checkbox_footer.lineBreakAfter = true;
    var checkbox_shading = form.newCheckBox('shading', 'Shade ' + (filter_columns ? 'columns' : 'rows') + ' that have filters applied');
    checkbox_shading.setDefault(!adding_new);
    controls.push(checkbox_shading);
    shade_cells = checkbox_shading.getValue();
    if (shade_cells) {
        var picker = form.newColorPicker('colorPicker');
        picker.setDefault('LightGreen');
        controls.push(picker);
        cell_higlight_color = picker.getValue();
        form.setOutputColor(cell_higlight_color);
    } else
        form.setOutputColor('');
}
 
form.setInputControls(controls);
 
// Prevent the user from applying multiple filters to the same row/column when not adding
// rows/columns to the table.
var unique_cols_rows = uniqueElementsInArray(selected_cols_rows);
if (!adding_new && unique_cols_rows.length < selected_cols_rows.length)
    form.ruleNotApplicable('multiple filters are selected for the same ' + (filter_columns ? 'column' : 'row'));
 
// Can't filter tables with Date questions because such tables can change the number of rows/columns that are available
// when filtered
if (blue_question_type == "Date" || brown_question_type == "Date")
    form.ruleNotApplicable('table has a Date question selected');
 
// Can't apply to SUMMARY tables for Ranking or Experiment questions
if (brown_question == "SUMMARY" && no_net_available_types.indexOf(blue_question_type) > -1)
    form.ruleNotApplicable('this rule is not compatible with SUMMARY tables of ' + blue_question_type + ' questions');
 
// Check for Statistics - Right and Statistics - Below
// These can't be filtered for Date, Ranking, and Experiment questions.
// When filtering columns, Statistics - Right should not be shown as there is no way to recalculate them.
// The same is true for Statistics - Below when the rows are being filtered.
if (below_table_exists && below_table.statistics.length > 0 && (no_net_available_types.indexOf(blue_question_type) > -1 || !filter_columns) ) {
    table.suppressOutput('"Statistics - Below" are not compatible with this Rule. Unselect them from the table or remove the Rule from the table.');
    form.ruleNotApplicable('Statistics - Below are not compatible with this rule for this table');
}
if (right_table_exists && right_table.statistics.length > 0 && (no_net_available_types.indexOf(brown_question_type) > -1 || filter_columns) ) {
    table.suppressOutput('"Statistics - Right" are not compatible with this Rule. Unselect them from the table or remove the Rule from the table.');
    form.ruleNotApplicable('Statistics - Right are not compatible with this rule for this table');
}
 
// Turn off significance testing
preventAllSignificanceTesting(table, rule_name, checkbox_footer.getValue()); 
if (below_table_exists)
    preventAllSignificanceTesting(below_table, rule_name, false);
if (right_table_exists)
    preventAllSignificanceTesting(right_table, rule_name, false);
 
// Combine information about each filter into an object for easy reference
var filter_objects = [];
var new_spans = uniqueElementsInArray(entered_span_labels).map(function (span) { return {label: span, indices: [] }; });
for (var j = 0; j < selected_filters.length; j++) {
    var col_row_index = col_row_combobox_alternatives.indexOf(selected_cols_rows[j]) - 1;
    filter_objects.push({ filter: filterNameAndDataFileName(filters[filter_labels.indexOf(selected_filters[j])]),
                          source_index: col_row_index,
                          destination_index: NaN, // To be filled in once new columns/rows have been added in next step
                          span: entered_span_labels[j],
                          new_label: new_labels[j] });
}
 
// Add new columns and spans when requested by the user.
// These are added so that rows/columns belonging to the same span are adjacent.
// Works out the source and destination column for each filter.
var tidied_filter_objects;
if (adding_new) {
    var span_labels = new_spans.map(function (span) { return span.label; });
    tidied_filter_objects = [];
    span_labels.forEach(function (span) {
        tidied_filter_objects = tidied_filter_objects.concat(filter_objects.filter(function (obj) { return obj.span == span; }));
    });
 
    // Add a new column for each form entry.
    tidied_filter_objects.forEach(function (obj) {
        var last_index = (filter_columns ? table.numberColumns : table.numberRows) - 1;
        if (filter_columns)
            insertColumnAfterComplete(last_index, obj.new_label);
        else
            insertRowAfterComplete(last_index, obj.new_label);
        // Add the index of the new column or row to the set of indices for its span
        var spandex = span_labels.indexOf(obj.span);
        new_spans[spandex].indices.push(last_index + 1);
        obj.destination_index = last_index + 1;
    });
} else {
    tidied_filter_objects = filter_objects.map(function (obj) { 
                                                   obj.destination_index = obj.source_index;
                                                   return obj;
                                               });
}
 
// Add spans
new_spans.forEach(function (obj) {
    if (obj.label != "") {
        if (filter_columns)
            table.spanColumns(obj.indices, obj.label);
        else
            table.spanRows(obj.indices, obj.label);
    }
});
 
// Color the appropriate cells when specified
if (shade_cells) {  
    var colors = table.cellColors;
 
    // Obtain array for colors of marginal statistics
    var marginal_colors = null;
    if (filter_columns && below_table_exists) {
        marginal_colors = below_table.cellColors; 
    } else if (!filter_columns && right_table_exists) {
        marginal_colors = right_table.cellColors;
    }
 
    // Change the colors
    tidied_filter_objects.forEach(function (obj) {
        var target_index = obj.destination_index;
        if (filter_columns) {
            for (var row_index = 0; row_index < table.numberRows; row_index++)
                colors[row_index][target_index] = cell_higlight_color;
        } else {
            for (var col_index = 0; col_index < table.numberColumns; col_index++)
                colors[target_index][col_index] = cell_higlight_color;
        }
        if (marginal_colors != null) {
            if (marginal_colors.length == 1)
                marginal_colors[0][target_index] = cell_higlight_color;
            else
                marginal_colors[target_index][0] = cell_higlight_color;
        }
    });
    table.cellColors = colors;
    if (marginal_colors != null) {
        if (filter_columns)
            below_table.cellColors = marginal_colors;
        else
            right_table.cellColors = marginal_colors;
    }
}
 
// Do we need to filter any marginal stats?
var filter_below = filter_columns && below_table_exists && no_net_available_types.indexOf(blue_question_type) == -1;
var filter_right = !filter_columns && right_table_exists && right_table.statistics.length > 0;
 
// Copy filtered statistics in the main table and marginal table
tidied_filter_objects.forEach(function (obj) {
    var filter_names = ['!UseQFilters'];
    filter_names.push(obj.filter);
    var filtered_table_stats = getFilteredTableStatistics(filter_names);
    table_stats_to_filter.forEach(function (stat) {
        var current_stats = table.get(stat);
        if (filter_columns) {
            for (var row = 0; row < table.numberRows; row++)
                current_stats[row][obj.destination_index] = filtered_table_stats[stat][row][obj.source_index];
        } else {
            for (var col = 0; col < table.numberColumns; col++)
                current_stats[obj.destination_index][col] = filtered_table_stats[stat][obj.source_index][col];
        }
        table.set(stat, current_stats);
    });
 
    // Handle marginal stats
    if (filter_below || filter_right) {
        var filtered_marginals = getFilteredMarginalStatistics(filter_columns, filter_names);
        var target_table = filter_columns ? below_table : right_table;
        var marginal_stats_to_filter = filter_columns ? below_stats_to_filter : right_table.statistics;
        marginal_stats_to_filter.forEach(function (stat) {
            // Copy values
            var current_values = target_table.get(stat);
            var marginal_is_row = current_values.length == 1;
            var new_value;
 
            // Determine the new value to insert.
            // Statistics related to significance testing are left as blank or NaN.
            // It doesn't make sense to compute the % Share for the filtered row/column.
            if (["% Share", "t-Statistic", "z-Statistic", "p", "Corrected p", "Multiple Comparison Adjustement"].indexOf(stat) > -1 || stat.indexOf("Confidence") != -1)
                new_value = NaN;
            else if (["Column Names", "Column Comparisons", "Columns Compared"].indexOf(stat) > -1 ) {
                new_value = "";
            } else {
                new_value = filtered_marginals[stat][obj.source_index];
            }
 
            // Insert the new value.
            if (marginal_is_row) {
                current_values[0][obj.destination_index] = new_value;
            } else {
                current_values[obj.destination_index][0] = new_value;
            }
            target_table.set(stat, current_values);
        });
    }
});
 
// Adjust column names when extra columns have been added.
if (adding_new) {
    var LETTERS = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    if (table.numberColumns > 26) {
        var new_lower = new Array(260);
        var new_upper = new Array(260);
        for (var l = 0; l < 26; l++)
            for (var i = 0; i < 10; i++) {
                var counter = i * 26 + l;
                new_upper[counter] = LETTERS[l] + i;
            }
        LETTERS = new_upper;
    }
    if (table.availableStatistics.indexOf("Column Names") > -1) {
        var column_names = table.get("Column Names");
        for (var col = 0; col < table.numberColumns; col++) {
            for (var row = 0; row < table.numberRows; row++) {
                column_names[row][col] = LETTERS[col];
            }
        }
        table.set("Column Names", column_names);
    }
    if (below_table_exists && below_table.availableStatistics.indexOf("Column Names") > -1) {
        var column_names = below_table.get("Column Names");
        var by_row = column_names.length == 1;
        for (var col = 0; col < table.numberColumns; col++) {
            if (by_row)
                column_names[0][col] = LETTERS[col];
            else
                column_names[col][0] = LETTERS[col];
        }
        below_table.set("Column Names", column_names);
    }
}
 
// Modify footer
var footers = table.extraFooters;
footers.push('Some ' + (filter_columns ? 'columns' : 'rows') + ' in this table have filters applied');
table.extraFooters = footers;
 
// Append the data file name to the name of a filter variable 
function filterNameAndDataFileName(filter) {
    return filter.name + ' [' + filter.question.dataFile.name + ']';
}
 
// Generate an array of all of the statistics from the filtered version of table
function getFilteredTableStatistics(filter_array) {
    if (!Q.matrix){
        // Returns a new array with the value repeated.
        // If you want a 1D array, only supply one length.
        // If you want a 2D array, supply both lengths.
        Q.matrix = function (value, num_rows, num_cols) {
            var rows_array = [];
            if (arguments.length > 2) {
                for (var row = 0; row < num_rows; row++) {
                    var col_array = [];
                    for (var col = 0; col < num_cols; col++)
                        col_array[col] = value;
                    rows_array[row] = col_array;
                }
            } else {
                for (var row = 0; row < num_rows; row++)
                    rows_array[row] = value;
            }
            return rows_array;
        };
    }
    var nan_array = Q.matrix(NaN, table.numberRows, table.numberColumns);
    var blank_string_array = Q.matrix(' ', table.numberRows, table.numberColumns);
    var filtered_table = Q.calculateTable(table.blueQuestion, table.brownQuestion, filter_array, '!UseQWeight');
    var filtered_stats = {};
    table.availableStatistics.forEach(function (stat) {
        if (["t-Statistic", "z-Statistic", "p", "Corrected p", "Multiple Comparison Adjustement"].indexOf(stat) > -1)
            filtered_stats[stat] = nan_array;
        else if (["Column Names", "Column Comparisons", "Columns Compared"].indexOf(stat) > -1)
            filtered_stats[stat] = blank_string_array;
        else
            filtered_stats[stat] = filtered_table.get(stat);
    });
    return filtered_stats;
}
 
// Obtain the filtered version of the marginal statistics. 
// If use_below is true we use below_table, otherwise right_table.
// Returns an object which is indexed by the statistic name.
// Each element in the object is a one-dimensional array regardless
// of whether we are filtering below or right
function getFilteredMarginalStatistics(use_below, filter_array) {
    var filtered_stats = {};
    var blue_q = table.blueQuestion;
    var brown_q = table.brownQuestion;
    // In newer versions of Q we can calcultate the filtered marginal stats directly
    if (fileFormatVersion() > 8.41) {
        var margin = use_below ? 'Below' : 'Right';
        var filtered_marginal_table = Q.calculateTable(blue_q, brown_q, filter_array, '!UseQWeight', margin);
        filtered_marginal_table.availableStatistics.forEach(function (stat) {
            var temp_stats = filtered_marginal_table.get(stat);
            filtered_stats[stat] = temp_stats.length == 1 ? temp_stats[0] : temp_stats.map(function (arr) { return arr[0]; });
        });
    } else {
        // In older versions we need to look at the NET or compute a numeric version of
        // the relevant question to derive the marginal stats
        var target_table = use_below ? below_table : right_table;
        
        var blue_q_type = blue_q.questionType;
        var brown_q_type = "SUMMARY";
        if (brown_q != "SUMMARY")
            brown_q_type = brown_q.questionType;
     
        // We need to know the index of the original NET/SUM row or column
        // because we derive some of the marginal stats from the NET. The
        // only time we are allow a table which is missing the important
        // NET is a crosstab with a Number question, in which case there
        // is only a single column/row
        var net_index = use_below ? table.netRows[0] : table.netColumns[0];
        if (net_index == undefined) {
            if (use_below && blue_q_type == "Number")
                net_index = 0;
            else if (!use_below && brown_q_type == "Number")
                net_index = 0;
            else {
                table.suppressOutput('The Rule "' + rule_name + '" cannot be used when there is no NET or SUM ' + (use_below ? "row" : "column"));
                form.ruleNotApplicable('this rule cannot run when there is no NET or SUM ' + (use_below ? "row" : "column"));
            }
        }
     
        // Figure out if we need to make a temporary numeric question & table, and which dropdown
        // will have the numeric version
        var temp_needed = false;
        var temp_in = null;
        if (use_below) {
            if (blue_q_type.indexOf("Pick One") == 0) {
                if (blue_q_type == "Pick One" && ["SUMMARY", "Pick One", "Pick Any", "Number", "Number - Multi", "Date"].indexOf(brown_q_type) > -1) {
                    temp_needed = true;
                    temp_in = "blue";
                } else if (blue_q_type == "Pick One - Multi" && !blue_q.dataReduction.transposed) {
                    temp_needed = true;
                    temp_in = "blue";
                }
            }
        } else {
            if (brown_q_type == "Pick One" && ["Pick One", "Pick Any", "Number", "Number - Multi", "Date"].indexOf(blue_q_type) > -1) {
                temp_needed = true;
                temp_in = "brown";
            } else if (blue_q_type == "Pick One - Multi" && blue_q.dataReduction.transposed) {
                temp_needed = true;
                temp_in = "blue";
            }
        }
     
        // If necessary, create a temporary numeric version of the question, generate its table output and then
        // remove the temporary question.
        var temp_table = null;
        var temp_stat_list = null;
        var std_error_in_temp = false;
        if (temp_needed) {
            if (table.forPlot) {
                table.suppressOutput('The Rule "' + rule_name + '" cannot be used on a plot when there is a Pick One or Pick One - Multi question selected, and Statistics - Below are selected.');
                form.ruleNotApplicable('this rule cannot run on a plot with Statistics - Below selected with a Pick One or Pick One - Multi question.');
            }
     
            var question_to_convert = temp_in == "blue" ? blue_q : brown_q;
            var temp_question = createNumericDuplicate(question_to_convert);
     
            // Make the temporary table
            if (temp_in == "blue")
                temp_table = Q.calculateTable(temp_question, brown_q, filter_array, '!UseQWeight');
            else
                temp_table = Q.calculateTable(blue_q, temp_question, filter_array, '!UseQWeight');
            temp_stat_list = temp_table.availableStatistics;
            std_error_in_temp = temp_stat_list.indexOf("Standard Error") > -1;
        }
     
        // Generate the filtered table for the NET/SUM
        var filtered_table = Q.calculateTable(blue_q, brown_q, filter_array, '!UseQWeight');
        var filtered_stat_list = filtered_table.availableStatistics;
     
        // Loop over the stats, figure out where the stat should be drawn from
        // and store them in an object
        target_table.availableStatistics.forEach(function (stat) {
            if (stat == "Standard Error") {
                // If the Standard Error statistic is found in the temp table then use that standard error
                // over the one in the NET/SUM
                if (std_error_in_temp) {
                    var temp_stats = temp_table.get(stat);
                    filtered_stats[stat] = temp_stats.length == 1 ? temp_stats[0] : temp_stats.map(function (arr) { return arr[0]; });
                } else {
                    var table_stats = filtered_table.get(stat);
                    filtered_stats[stat] = use_below ? table_stats[net_index] : table_stats.map(function (arr) { return arr[net_index]; }); 
                }
            } else if (filtered_stat_list.indexOf(stat) != -1) {
                // Get the statistic from the net row or column
                var table_stats = filtered_table.get(stat);
                filtered_stats[stat] = use_below ? table_stats[net_index] : table_stats.map(function (arr) { return arr[net_index]; }); 
            } else if (temp_needed && temp_stat_list.indexOf(stat) > -1){
                // Get stat from temporary table
                var temp_stats = temp_table.get(stat);
                filtered_stats[stat] = temp_stats.length == 1 ? temp_stats[0] : temp_stats.map(function (arr) { return arr[0]; });
            } else {
                var marginal_equivalent = null;
                // Some stats change their name between the NET and main table, even though
                // the figures are the same.
                if (stat == "Column n" || stat == "Row n") {
                    if (["Pick One - Multi", "Pick Any - Grid"].indexOf(blue_question_type) != -1) {
                        marginal_equivalent = "n";
                    } else if (blue_question_type == "Number - Grid"
                             || (blue_question_type == "Pick Any" && brown_question_type.indexOf("Number") == 0)
                             || (brown_question_type == "Pick Any" && blue_question_type.indexOf("Number") == 0)) {
                        marginal_equivalent = "Base n";
                    }
                } else if (stat == "Column Population" || stat == "Row Population") {
                    if (["Pick One - Multi", "Pick Any - Grid"].indexOf(blue_question_type) != -1) {
                        marginal_equivalent = "Population";
                    } else if (blue_question_type == "Number - Grid"
                             || (blue_question_type == "Pick Any" && brown_question_type.indexOf("Number") == 0)
                             || (brown_question_type == "Pick Any" && blue_question_type.indexOf("Number") == 0)) {
                        marginal_equivalent = "Base Population";
                    }
                }
                if (marginal_equivalent != null) {
                    var table_stats = filtered_table.get(marginal_equivalent);
                    filtered_stats[stat] = use_below ? table_stats[net_index] : table_stats.map(function (arr) { return arr[net_index]; });
                } 
             }
        });
     
        // Tidy up temporary variables
        if (temp_needed)
            deleteQuestion(temp_question);
    }
    return filtered_stats;
}
 
// Returns true if Statistics - Below are available for this table.
function belowTableExists() {
    var exists = true;
    try { 
        below_table.statistics;
    } catch (e) {
        exists = false;
    }
    return exists;
}
 
// Returns true if Statistics - Right are available for this table.
function rightTableExists() {
    var exists = true;
    try { 
        right_table.statistics;
    } catch (e) {
        exists = false;
    }
    return exists;
}
 
// Duplicate the question as a numeric version and make sure that the codes are in the same order as the original
// so that the necessary statistics can be drawn out correctly from the numeric version.
// Returns the duplicate question. This should always be deleted prior to the end of the Rule so that
// we don't keep adding questions to the user's project.
function createNumericDuplicate(question_to_convert) {
    var temp_question = question_to_convert.duplicate(preventDuplicateQuestionName(question_to_convert.dataFile, question_to_convert.name + "TEMP"));
 
    // Convert to numeric
    if (question_to_convert.questionType == "Pick One - Multi") {
        // Sort data reduction of converted temp copy according to original question.
        // Changing the question type mucks up the ordering.
        var original_underlying_variables = getAllUnderlyingVariables(temp_question);
        original_underlying_variables = original_underlying_variables.map(function (obj) { return {label: obj.label, array: obj.array.map(function (v) { return v.name; }) }; });
        temp_question.questionType = "Number - Multi";
        var temp_data_reduction = temp_question.dataReduction;
        var temp_underlying_variables = temp_data_reduction.rowLabels.map(function (label) { return {label: label, array: temp_data_reduction.getUnderlyingVariables(label).map(function (v) { return v.name; })}; });
        // Check for duplicates - can't handle duplicate labels
        var temp_codes = temp_underlying_variables.map(function (obj) { return obj.label; });
        if (uniqueElementsInArray(temp_codes).length != temp_codes.length) {
            deleteQuestion(temp_question);
            table.suppressOutput('The Rule "' + rule_name + '" cannot run because the question contains duplicate labels.');
            form.ruleNotApplicable('the question contains duplicate labels');
        }
 
        // Sort temp codes according to original order
        var last_code = null;
        original_underlying_variables.forEach(function (obj) {
            var target_vname = obj.array[0];
            var target_code = temp_underlying_variables.filter(function (temp_obj) {
                                                                   return temp_obj.array[0] == target_vname;
                                                               })[0].label;
            temp_data_reduction.moveAfter(target_code, last_code);
            last_code = target_code;
        });
    } else if (question_to_convert.questionType == "Pick One")
        temp_question.questionType = "Number";
    else {
        deleteQuestion(temp_question);
        throw("Invalid Question Type: " + question_to_convert.questionType);
    }
    return temp_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.
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;
}
 
function deleteQuestion(question) {
    question.variables.forEach(function (v) { v.deleteVariable(); } );
}

See also