Filter - Filter One Question by Another
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:
- The question that you want to filter.
- The question whose categories you want to use as filters.
- 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
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");
filterOneQuestionByAnother()
function filterOneQuestionByAnother() {
try {
const is_displayr = inDisplayr();
const structure_name = is_displayr ? "variable set" : "question";
// User selections of questions
var data_file = requestOneDataFileFromProject(false);
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.");
// This is one of the rare cases we need to keep the prompts in Displayr.
// It is too hard to guide the user otherwise.
let primary_question = selectOneQuestion("Select the " + structure_name + " that you want to filter.", candidate_primary_questions);
let 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;
let expansion_mode;
let single_filter_mode;
if (filter_question.isFilter && filter_question.variables.length == 1) {
if (is_displayr)
single_filter_mode = true;
else
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 " + primary_question.name + " with this filter applied. "
+ "Click No to create new variables for each of the categories in the filter " + structure_name + ".");
}
let filter_question_type = filter_question.questionType;
let primary_variables = primary_question.variables;
let primary_labels = primary_variables.map(function (v) { return v.label; });
let primary_question_type = primary_question.questionType;
let filter_matches;
let filter_labels;
if (!single_filter_mode) {
// Determine the labels of the filters
let filter_question_data_reduction = filter_question.dataReduction;
filter_labels = filter_question_data_reduction.rowLabels;
if (!filter_question_data_reduction.netRows) {
filter_labels = filter_labels.filter(function (label) { return label != "NET"; });
} else {
let net_rows = filter_question_data_reduction.netRows;
for (let 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
const always_expansion_mode = ["Text", "Number", "Pick One"];
const always_individual_mode = ["Pick Any - Grid", "Number - Grid"];
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("Select 'Yes' to split out " + primary_question.name + " by each category in " + filter_question.name +
".\r\nSelect 'No' to match labels between " + primary_question.name + " and " + filter_question.name + ", and create one filtered variable for each match.");
if (expansion_mode) {
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.");
let auto_setup;
if (is_displayr)
auto_setup = true;
else
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 {
filter_matches = automaticFilterMatch(primary_variables, filter_labels);
} catch (e) {
if (e instanceof AutoDetectError) {
let 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)
filter_matches = manualFilterMatch(primary_variables, filter_labels);
}
} else
expansion_mode = false;
// Create new variables and new question
let 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);
let 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.startsWith("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;
}
let 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) {
let value_attributes = primary_question.valueAttributes;
let unique_values = primary_question.uniqueValues;
let target_value_attributes = new_q.valueAttributes;
let target_unique_values = new_q.uniqueValues;
unique_values.forEach(function (x) {
target_value_attributes.setCountThisValue(x, value_attributes.getCountThisValue(x));
});
new_q.needsCheckValuesToCount = false;
}
// Try to add spans for expansion mode for Pick One - Multi
// A check is added because previous scripts applied may cause problems
if (expansion_mode && new_question_type == "Pick One - Multi" && primary_question_type == "Pick One - Multi") {
let data_reduction = new_q.dataReduction;
let new_labels = new_vars.map(function (v) { return v.label; });
let label_prefixes = primary_variables.map(function (v) { return v.label; });
let span_error = [];
label_prefixes.forEach(function (prefix) {
let codes = filter_matches.filter(function (obj) {
return obj.newVariableLabel == prefix + " - " + obj.filterLabel; })
.map(function (obj) { return obj.newVariableLabel; });
try {
data_reduction.span(codes, prefix);
} catch (e) {
span_error.push(e.message);
use_spans = false;
}
});
if (span_error.length == 0)
filter_matches.forEach(function (obj) {
data_reduction.rename(obj.newVariableLabel, obj.filterLabel);
});
else
log("There were problems creating a span from the input question: '" +
unique(span_error) + "'");
}
// Add a new table for the new question and add a text item describing which variables
// were filtered by each filter.
if (!is_displayr)
{
let new_group = project.report.appendGroup();
new_group.name = "Filtered Question";
let new_table = new_group.appendTable();
new_table.primary = new_q;
if (!single_filter_mode) {
let new_text_item = new_group.appendText();
// Text item title
let 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;
let html_report = Q.htmlBuilder();
html_report.setStyle({ font: 'Lucida Console', size: 10 });
let 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
let 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
let filter_report_table = [];
let filter_match_counts = filter_labels.map(function (filter_label) {
let 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};
});
let not_used_filters = filter_match_counts.filter(function (obj) { return obj.count == 0;})
.map(function (obj) { return obj.label; });
let used_filters = difference(filter_labels, not_used_filters);
used_filters.forEach(function (filter) {
let 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
let not_filtered = filter_matches.filter(function (obj) { return obj.filterLabel == null;} );
if (not_filtered.length > 0) {
let 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
let included_variables = filter_matches.map(function (obj) { return obj.variable.label;} );
let not_used_variables = difference(primary_labels, included_variables);
if (not_used_variables.length > 0) {
let 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) {
let 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.");
let data_file = primary_question.dataFile;
let filter_variables = filter_question.variables;
let filter_variable_names = filter_variables.map(function (v) { return v.name; } );
let filter_question_type = filter_question.questionType;
if (!filter_question.isValid || !primary_question.isValid)
throw new SetupError("There are errors in the variables selected. Please fix before re-running.");
let primary_question_type = primary_question.questionType;
let is_text = primary_question_type.indexOf("Text") > -1;
let filter_question_data_reduction = filter_question.dataReduction;
let primary_question_variable_names = primary_question.variables.map(function (v) { return v.name;});
let filter_array;
// Get array of values or variable names to filter each primary variable by
if (filter_question_type == "Pick One") {
filter_array = filter_labels.map(function (label) {
return filter_question_data_reduction.getUnderlyingValues(label);
});
} else {
filter_array = filter_labels.map(function (label) {
return filter_question_data_reduction.getUnderlyingVariables(label).map(function (v) { return v.name; });
});
}
// Generate new variables
let new_variables = [];
let new_variable_data = [];
let last_var = filter_matches[filter_matches.length - 1].variable;
filter_matches.forEach(function (match) {
let 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 + ");";
let no_value_expression = is_text ? "\r\nelse \"\";" : "\r\nelse NaN;";
let expression;
if (match.filterLabel == null)
expression = source_value_expression + ";";
else {
let filter_index = filter_labels.indexOf(match.filterLabel);
expression = "if (";
if (filter_question_type == "Pick One") {
filter_array[filter_index].forEach(function (val, index) {
let 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;
}
let short_filter_label = match.filterLabel == null ? "NoFilter" : match.filterLabel.replace(/\W/g, "");
let new_var_label = expansion_mode ? match.newVariableLabel : match.variable.label;
let 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; });
let 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) {
let new_variables = [];
let data_file = primary_question.dataFile;
let filter_var = filter_question.variables[0];
let filter_var_name = filter_var.name;
let is_text = primary_question.variables[0].variableType == "Text";
let last_var = primary_question.variables[primary_question.variables.length - 1];
primary_question.variables.forEach(function (v) {
let 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 + ");";
let expression = "if (" + filter_var_name + " > 0){\r\n" + source_value_expression + "\r\n}";
if (is_text)
expression += "else '';";
else
expression += "else NaN;";
let 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
let filter_matches = primary_variables.map(function (variable) {
return {variable: variable, filterLabel: matchVariableLabelToFilterLabel(variable, filter_labels)}
})
// Count the number of matches for each filter.
let filter_match_counts = filter_labels.map(function (filter_label) {
let count = 0;
filter_matches.forEach(function (match) {
if (match.filterLabel.trim() == filter_label.trim())
count ++;
});
return {label: filter_label, count: count};
});
let max_filter_count = 0;
let second_max_filter_count = 0;
let max_label;
let 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) {
let primary_labels = primary_variables.map(function (v) { return v.label; });
let used_labels = [];
let filter_matches = [];
filter_labels.forEach(function (filter) {
let remaining_labels = difference(primary_labels, used_labels);
if (remaining_labels.length != 0) {
let selected_indices = selectMany("Select the variables that should be filtered by \'" + filter + "\'", remaining_labels);
let 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} );
});
}
});
let remaining_labels = difference(primary_labels, used_labels);
if (remaining_labels.length > 0) {
let 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) {
let target_unique_values = to_variable.uniqueValues;
let source_unique_values = from_variable.uniqueValues;
let target_value_attributes = to_variable.valueAttributes;
let source_value_attributes = from_variable.valueAttributes;
// Copy labels, values, and missing data
source_unique_values.forEach(function (x) {
if (!isNaN(x)) {
// Ensure that all value labels carry over in JavaScript.
// For constructed variables (like these JavaScript variables)
// values which are empty in the data will not be shown in the
// data reduction. You can get around this by assigning a label
// to the empty value. If the label is equal to the value, an
// extra space needs to be added to get the label to show up.
// In general users would prefer to have an invisible space
// in the label rather than the label not showing at all.
let label_for_value = source_value_attributes.getLabel(x);
if (label_for_value == x) {
label_for_value += " ";
}
target_value_attributes.setLabel(x, label_for_value);
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); })) {
let 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) {
let variable_label = variable.label.trim();
let 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
let largest_matching_label_length = -1;
let 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) {
let 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
- QScript for more general information about QScripts.
- QScript Examples Library for other examples.
- Online JavaScript Libraries for the libraries of functions that can be used when writing QScripts.
- QScript Reference for information about how QScript can manipulate the different elements of a project.
- JavaScript for information about the JavaScript programming language.
- Table JavaScript and Plot JavaScript for tools for using JavaScript to modify the appearance of tables and charts.
Displayr - Filtering
Q Technical Reference
Q Technical Reference
Q Technical Reference
Q Technical Reference
Q Technical Reference > Creating And Modifying Tables
Q Technical Reference > Setting Up Data > Creating New Variables
Q Technical Reference > Setting Up Data > Creating New Variables
Q Technical Reference > Setting Up Data > Filters
Q Technical Reference > Updating and Automation > Automation Online Library
Q Technical Reference > Updating and Automation > JavaScript > QScript > QScript Examples Library
Q Technical Reference > Updating and Automation > JavaScript > QScript > QScript Examples Library > QScript Online Library