includeWeb("QScript Table Functions"); // for largeCrosstabsAllowed
// Also includes QScript Utility Functions for inDisplayr()
includeWeb('QScript Selection Functions'); // for getAllUserSelections
includeWeb("QScript Functions to Generate Outputs"); // for createReport
if (!Crosstabs()) {
conditionallyEmptyLog("QScript cancelled.");
} else {
conditionallyEmptyLog("QScript finished.");
}
function Crosstabs() {
let ask_statistics = !inDisplayr();
let user_inputs = userInputsForCrosstabs(ask_statistics, false);
if (!user_inputs) {
return false;
}
let page_names = user_inputs.questions_array.map(qs => crosstabPageName(qs.primary, qs.secondary));
let report = createReport("Crosstabs",
user_inputs.questions_array,
page_names,
user_inputs.statistics,
user_inputs.weight,
user_inputs.filters,
user_inputs.delete_tables,
user_inputs.p_value_cutoff,
user_inputs.sort_tables,
false,
!user_inputs.as_pages);
if (!report) { // All crosstabs not significant at cutoff
return false;
}
conditionallyEmptyLog('Crosstabs have been added to the "' +
report.name + '" group.');
return true;
}
function userInputsForCrosstabs(ask_statistics, ask_filters_and_weight) {
let web_mode = inDisplayr();
let ALLOWED_STATS_Q = ["%", "Column %", "Row %", "Total %", "Average",
"Median", "Index", "n", "Column n", "Row n", "Base n"];
let allowed_stats;
if (web_mode)
allowed_stats = ALLOWED_STATS_Q.map(function(s){ return project.translations.get(s); });
else
allowed_stats = ALLOWED_STATS_Q;
const data_file = requestOneDataFileFromProject(true);
let row_col_questions = askForCrosstabQuestions(data_file);
let row_questions = row_col_questions.row;
let column_questions = row_col_questions.column;
if (row_questions.length === 0) // Abort QScript due to large crosstabs
return null;
const as_pages = onPages("crosstabs");
flattenSelectedQuestions(row_questions, true);
flattenSelectedQuestions(column_questions, true);
let questions_array = row_questions.flatMap(rq =>
column_questions.map(
cq => ({primary: rq, secondary: cq})));
let delete_tables = false;
let sort_tables = false;
let p_value_cutoff = Infinity;
// In Displayr user can sort and delete easily via the toolbar
if (!web_mode) {
var show_opts = ["All tables",
"Delete tables not significant at the 0.1 level",
"Delete tables not significant at the 0.05 level",
"Delete tables not significant at the 0.01 level",
"Delete tables not significant at the 0.001 level",
"Delete tables not significant at the 0.0001 level"];
// Sorting happens within each row by column variables, so if only one
// column variable, nothing to sort
if (column_questions.length > 1) {
show_opts.splice(1, 0, "Sort tables by statistical significance of column data");
show_opts = show_opts.concat(
["Sort and delete tables not significant at 0.1",
"Sort and delete tables not significant at 0.05",
"Sort and delete tables not significant at 0.01",
"Sort and delete tables not significant at 0.001",
"Sort and delete tables not significant at 0.0001"]);
}
var show_opt_idx = selectOne("Show", show_opts, null, 0);
var selected_opt = show_opts[show_opt_idx];
sort_tables = selected_opt.match("^Sort|^All tables sorted") !== null;
delete_tables = selected_opt.match("[Dd]elete") !== null;
if (delete_tables)
p_value_cutoff = selected_opt.match("0[.][0]{0,3}[15]")[0];
}
if (ask_statistics) {
var stat_selection_text = "Which statistic would you like to show on the table? " +
"(Statistics will only be shown where they make sense " +
"for the data selected in the rows and columns of the tables.)";
var selected_stats_idx = selectMany(stat_selection_text, allowed_stats);
allowed_stats = ALLOWED_STATS_Q.filter((s,i) => selected_stats_idx.includes(i));
} else
allowed_stats = null;
let selected_weight = null;
let selected_filters = [];
if (ask_filters_and_weight) {
selected_filters = askForFilters(data_file);
selected_weight = askForWeightVariable(data_file);
}
return {
questions_array: questions_array,
sort_tables: sort_tables,
delete_tables: delete_tables,
p_value_cutoff: p_value_cutoff,
statistics: allowed_stats,
filters: selected_filters,
weight: selected_weight,
as_pages: as_pages,
};
}
function askForCrosstabQuestions(data_file) {
var column_q_choices = [];
var row_q_choices = [];
var row_q_choices = data_file.questions.filter(function (q) { return q.isValid && !q.isHidden && q.questionType.indexOf("Text") == -1});
var column_q_choices = row_q_choices.filter(function (q) { return q.questionType != 'Ranking' && q.questionType != 'Experiment'});
if (row_q_choices.length == 0 || column_q_choices.length == 0) {
log("Not enough questions available to make crosstabs.")
}
var row_questions, column_questions;
while (!row_questions || !oneOrMoreQuestions(row_questions))
row_questions = selectManyQuestions('Please select data to place in the rows:',
row_q_choices).questions;
while (!column_questions || !oneOrMoreQuestions(column_questions))
column_questions = selectManyQuestions('Please select data to place in the columns:',
column_q_choices).questions;
if (!largeCrosstabsAllowed(row_questions, column_questions)) {
return {row: [], column: []};
}
return {row: row_questions, column: column_questions};
}
function askForFilters(data_file) {
let selected_filters = []; // "Total Sample"
const filter_questions = data_file.questions.filter(v => v.isFilter);
if (filter_questions.length > 0) {
let filter_variables = filter_questions[0].variables
for (i = 1; i < filter_questions.length; i++)
filter_variables = filter_variables.concat(filter_questions[i].variables);
let filter_opts = ["Total Sample"];
filter_opts = filter_opts.concat(filter_variables.map(v =>v.label));
selected_filters = selectMany("Please select filter(s) to apply to each crosstab.",
filter_opts, null, [0]);
if (selected_filters == 0)
selected_filters = [];
else {
selected_filters = selected_filters.filter(function gt0(x){ return x > 0; });
selected_filters = selected_filters.map(function idx(x){return filter_variables[x-1];});
}
}
return selected_filters;
}
function askForWeightVariable(data_file) {
let selected_weight = null;
const weight_questions = data_file.questions.filter(q => q.isWeight);
if (weight_questions.length > 0) {
let weight_variables = weight_questions[0].variables;
for (i = 1; i < weight_questions.length; i++)
weight_variables = weight_variables.concat(weight_questions[i].variables);
let weight_opts = ["None"];
weight_opts = weight_opts.concat(weight_variables.map(v => v.label));
selected_weight = selectOne("Please select a weight to apply to each crosstab.",
weight_opts, null, 0);
if (selected_weight == 0)
selected_weight = null;
else
selected_weight = weight_variables[selected_weight-1];
}
return selected_weight;
}
/// primary - A QScript Question, the primary question in the crosstab
/// secondary - A QScript Question representing the secondary question in the
/// crosstab or either of the strings "RAW DATA" or "SUMMARY".
function crosstabPageName(primary, secondary) {
let page_name = primary.name || "";
if (secondary === "RAW DATA") {
page_name += "- Raw Data";
} else if (secondary.hasOwnProperty("name")){
page_name += " by " + secondary.name;
}
return page_name;
}
// replicate R's order() function
// e.g. sortWithIndices(['b','c','a']) = [2,1,0]
function order(arr) {
var arr_with_index = [];
for (var i in arr) {
arr_with_index.push([arr[i], i]);
}
arr_with_index.sort(function(left, right) {
return left[0] - right[0];
});
var idxs = [];
for (var j in arr_with_index) {
// arr.push(arr_with_index[j][0]);
idxs.push(arr_with_index[j][1]);
}
return idxs;
}
function addStat(table, stat) {
cell_stats = table.cellStatistics;
cell_stats.push(stat);
table.cellStatistics = cell_stats;
return table.cellStatistics.indexOf(stat) > -1;
}
function oneOrMoreQuestions(questions) {
if (questions.length == 0) {
alert('Select one or more questions.');
return false;
} else
return true;
}