QScript Functions for Driver Analysis

From Q
Jump to navigation Jump to search

This page contains functions created specifically for use with the driver analysis QScripts:

Source Code

includeWeb('QScript Selection Functions');
 
// Main driver analysis function that takes the analysis name (e.g. Kruskal Importance)
// and prompts the user for input variables, then creates a table with the driver analysis
// rule attached.
// The list of possible analysis names are:
// -'Linear Regression Coefficients'
// -'Contribution'
// -'Beta'
// -'Kruskal Importance'
// -'Shapley Importance'
// -'Relative Importance'
 
function driverAnalysis(analysis_name) {
    var positive_inputs_only = analysis_name == 'Elasticity';
    var is_kruskal = analysis_name == 'Kruskal Importance';
    var is_kruskal_shapley_ria = analysis_name == 'Kruskal Importance' || analysis_name == 'Shapley Importance' || analysis_name == 'Relative Importance';
    var is_shapley = analysis_name == 'Shapley Importance';
    
    var version = fileFormatVersion();
    if (version < 8.21) {
        log('This script is not able to run on this version of Q. Please upgrade to the latest version of Q.');
        return false;
    }
 
    var data_files = project.dataFiles;
 
    var dependent_question, independent_question, show_confidence_intervals;
 
    var candidate_questions = [];
    data_files.forEach(function (data_file) {
        candidate_questions = candidate_questions.concat(data_file.questions.filter(isQuestionNumCat).filter(isQuestionNotHidden).filter(isQuestionValid).filter(function (q) { return !q.isBanner; }));
    });
 
    if (candidate_questions.length == 0) {
        alert('There are no suitable questions in this project.');
        return false;
    }
 
    if (positive_inputs_only) {
        candidate_questions = candidate_questions.filter(hasOnlyPositiveVariables);
        if (candidate_questions.length == 0) {
            alert('There are no questions with only positive values.');
            return false;
        }
    }
 
    var candidate_variables = getVariablesFromQuestions(candidate_questions).filter(isNotDriverAnalysisVariable);
 
    // Select dependent question
    var v_dependent = data_files.length == 1 ?
                  selectOneVariableByQuestionNameAndLabel('Select the dependent Variable:', candidate_variables, false) :
                  selectOneVariableByQuestionNameAndLabelAndDataFile('Select the dependent Variable:', candidate_variables, false);
    var name;
    if (v_dependent.question.variables.length > 1)
        name = v_dependent.question.name + ': ' + v_dependent.label + ' (linked)';
    else
        name = v_dependent.question.name + ' (linked)';
    dependent_question = createQuestionWithLinkedVariables(name, [v_dependent], v_dependent.question.dataFile , 'Number', true);
    addDriverAnalysisStamp(dependent_question);
 
    // Select independent question
    while (independent_question == null) {
        var v_independent = data_files.length == 1 ?
                        selectManyVariablesByQuestionNameAndLabel('Select the independent Variables:', candidate_variables, false).variables :
                        selectManyVariablesByQuestionNameAndLabelAndDataFile('Select the independent Variables:', candidate_variables, false).variables;
        if (checkTwoOrMoreVariables(v_independent) && checkVariablesAreFromSameDataFile(v_independent)) {
            independent_question = createIndependentQuestion(v_independent, dependent_question.variables[0]);
            addDriverAnalysisStamp(independent_question);
        }
    }
    
    var num_independent_vars = independent_question.variables.length;
    if (is_kruskal || is_shapley) {
        if (num_independent_vars > 27) {
            alert('Warning: ' + num_independent_vars + ' indepedent Variables were selected in this analysis. However, it is not possible to compute a ' + analysis_name + ' Analysis ' +
                  'with more than 27 independent Variables due to limitations in the algorithm. Johnson\'s Relative Weights algorithm (Relative Importance Analysis) has been used instead; it gives very similar results');
            analysis_name = 'Relative Importance';
        } else {
            if (num_independent_vars > 15) {
                var prompt_message = 'Warning: this model contains ' + num_independent_vars + ' independent Variables. Depending on the number of observations in the data, ' + analysis_name + ' Analysis ' + 
                                     'results can be slow to compute when there are more than 15 independent Variables in the model. ' + 
                                     'Johnson\'s Relative Weights algorithm (Relative Importance Analysis) is recommended instead since it is much faster to compute and it gives very similar results. ' + 
                                     'Would you like to use Relative Importance Analysis instead? ' + 
                                     'Alternatively click Cancel if you wish to cancel this QScript.';
                if (askYesNo(prompt_message, 'https://wiki.q-researchsoftware.com/wiki/Driver_(Importance)_Analysis'))
                    analysis_name = 'Relative Importance';
            }
        }
    }
 
    show_confidence_intervals = askYesNo('Would you like to display confidence intervals for importance estimates?');
 
    var table = project.report.appendTable();
    table.primary = independent_question;
 
    if (analysis_name == 'Elasticity')
        table.translations.set('Average', analysis_name);
    else
        table.translations.set('Average', analysis_name + ' (%)');
    table.translations.set('Missing n', 'Raw ' + analysis_name);
    table.translations.set('SUM', 'R-Squared (%)');
    table.translations.set('Base n', 'Lower Confidence Bound 0.025');
    table.translations.set('Effective Base n', 'Upper Confidence Bound 0.975');
    table.translations.set('Expected Average', 'Expected Coefficient');
 
    if (analysis_name == 'Elasticity')
        table.cellStatistics = show_confidence_intervals ? ['Average', 'Base n', 'Effective Base n'] : ['Average'];
    else
        table.cellStatistics = show_confidence_intervals ? ['Average', 'Base n', 'Missing n', 'Effective Base n'] : ['Average'];
 
    var format;
    if (analysis_name == 'Elasticity')
        table.setDecimalPlaceFor('Average', 3);    
    table.setDecimalPlaceFor('Expected Average', 3);
    table.setDecimalPlaceFor('Base n', 3);
    table.setDecimalPlaceFor('Missing n', 3);
    table.setDecimalPlaceFor('Effective Base n', 3);
    table.setDecimalPlaceFor('Standard Error', 3);
    table.setDecimalPlaceFor('t-Statistic', 1);
    if (dependent_question.dataFile.id) { // Remove when Q 4.10 is stable
        format = project.rules.newCustomRule('driverAnalysisRule();\n' + driverAnalysisRule.toString(),
                                                 { dependentQuestionName: dependent_question.name,
                                                 dependentDataFileName: dependent_question.dataFile.name,
                                                 dependentDataFileId: dependent_question.dataFile.id,
                                                 analysisName: analysis_name,
                                                 independentQuestionName: independent_question.name });
    } else {
        format = project.rules.newCustomRule('driverAnalysisRule();\n' + driverAnalysisRule.toString(),
                                                 { dependentQuestionName: dependent_question.name,
                                                 dependentDataFileName: dependent_question.dataFile.name,
                                                 dependentDataFileId: '',
                                                 analysisName: analysis_name,
                                                 independentQuestionName: independent_question.name });
    }
    table.rules.add(format);
    if (project.report.setSelectedRaw) // Remove when Q 4.10 is stable
        project.report.setSelectedRaw([table]);
 
    table.name = analysis_name + ' for ' + dependent_question.variables[0].label;
 
    log('A driver analysis table has been added to the project.');
    return true;
}
 
function hasOnlyPositiveVariables(question) {
    if (question.questionType == 'Pick Any' || question.questionType == 'Pick Any - Grid')
        return false;
    var variables = question.variables;
    for (var i = 0; i < variables.length; i++) {
        var raw_values = variables[i].rawValues;
        var value_attributes = variables[i].valueAttributes;
        for (var j = 0; j < raw_values.length; j++)
            if (value_attributes.getValue(raw_values[j]) <= 0)
                return false;
    }
    return true;
 
}
function checkTwoOrMoreVariables(variables) {
    if (variables.length >= 2)
        return true;
    alert('There needs to be two or more independent Variables.\n\nClick OK to reselect Variables or Cancel to stop the script.');
    return false;
}
 
function isDriverAnalysisVariable(variable) {
    return variable.name.match(/_DriverAnalysis\d*$/);
}
 
function isNotDriverAnalysisVariable(variable) {
    return !isDriverAnalysisVariable(variable);
}
 
function addDriverAnalysisStamp(question) {
    question.variables.forEach(function (variable) {
        variable.name = preventDuplicateVariableName(variable.question.dataFile, variable.name + '_DriverAnalysis');
    });
}
 
function checkVariablesAreFromSameDataFile(variables) {
    if (variables.length < 2)
        return true;
    for (var i = 1; i < variables.length; i++) {
        if (variables[i].question.dataFile.name != variables[0].question.dataFile.name) {
            alert('Variables must be from the same data file.\n\nClick OK to reselect Variables or Cancel to stop the script.');
            return false;
        }
    }
    return true;
}
 
function createIndependentQuestion(independent_vars, dependent_var) {
    var variable_exp = createExp(independent_vars, dependent_var);
    var data_file = independent_vars[0].question.dataFile;
    var variables_linked = [];
    var v_linked = null;
    var single_question = fromSameQuestion(independent_vars);
    for (var i = 0; i < independent_vars.length; i++) {
        var v = independent_vars[i];
        var linked_name = preventDuplicateVariableName(data_file, v.name + '_L');
        if (single_question)
            var linked_label = v.label;
        else
            var linked_label = getQuestionNameAndVariableLabel(v);
        v_linked = data_file.newJavaScriptVariable(variable_exp + v.name, false, linked_name,
                                                   linked_label, v_linked);
        variables_linked.push(v_linked);
    }
    return data_file.setQuestion(createCompositeQuestionName(independent_vars), 'Number - Multi', variables_linked);
}
 
function createExp(independent_vars, dependent_var) {
    var names = independent_vars.map(function (x) { return x.name; });
    var dependent_data_file_name = dependent_var.question.dataFile.name;
    if (independent_vars[0].question.dataFile.name == dependent_data_file_name)
        names.push(dependent_var.name);
    else {
        var escaped_name = dependent_data_file_name.replace(/\'/g, "\\\'");
        names.push('Q.GetValue(\'' + dependent_var.name + '\',\'' + escaped_name + '\')');
    }
    return 'if (isNaN(' + names.join(') || isNaN(') + '))\n    NaN;\nelse\n    ';
}
 
function createCompositeQuestionName(variables) {
    var composite_name = '';
    var last_question_name = '';
    variables.forEach(function (variable) {
        var this_question_name = variable.question.name;
        if (last_question_name != this_question_name) {
            if (last_question_name != '')
                composite_name += '; ';
            if (variable.question.variables.length == 1)
                composite_name += this_question_name;
            else
                composite_name += this_question_name + ': ' + variable.label;
            last_question_name = this_question_name;
        } else
            composite_name += ', ' + variable.label;
    });
    if (composite_name.length > 128)
        composite_name = composite_name.substring(0, 128) + '...';
    return preventDuplicateQuestionName(variables[0].question.dataFile, composite_name);
}
 
// The rule that gets attached to the driver analysis table.
// This rule is written for Q 4.8.7 and later.
function driverAnalysisRule() { 
    // Controls, not actually displayed, only used to pass parameters
    var text_box_dep = form.newTextBox('dependentQuestionName');
    var text_box_data_file = form.newTextBox('dependentDataFileName');
    var text_box_data_file_id = form.newTextBox('dependentDataFileId');
    var text_box_name = form.newTextBox('analysisName');
    var text_box_indep = form.newTextBox('independentQuestionName');
 
    // Extract parameters
    var dependent_question_name = text_box_dep.getValue();
    var dependent_question;
    var data_file_id = text_box_data_file_id.getValue();
    if (data_file_id != '' && project.getDataFileById) {
        dependent_question = project.getDataFileById(data_file_id).getQuestionByName(dependent_question_name);
        if (!dependent_question)
            table.suppressOutput('The Question "' + dependent_question_name + '" could not be found. This may be because the Question has been renamed. This driver analysis has to be rerun from the Online Library.');
    } else {
        var data_file_name = text_box_data_file.getValue();
        var data_files = project.dataFiles;
        var is_data_file_found = false;
        for (var i = 0; i < data_files.length; i++)
            if (data_files[i].name == data_file_name) {
                is_data_file_found = true;
                dependent_question = project.getDataFileByName(data_file_name).getQuestionByName(dependent_question_name);
                break;
            }
        if (!is_data_file_found)
            table.suppressOutput('The data file "' + data_file_name + '" could not be found. This may be because the project has been updated. This driver analysis has to be rerun from the Online Library.');
        if (!dependent_question)
            table.suppressOutput('The Question "' + dependent_question_name + '" could not be found. This may be because the Question has been renamed. This driver analysis has to be rerun from the Online Library.');
    }
    var analysis_name = text_box_name.getValue();
    var independent_question = table.blueQuestion;
    
    // Check if suffixes should be added on the combo box to warn them of possible slow computations.
    var kruskal_combo_label = 'Kruskal Importance';
    var shapley_combo_label = 'Shapley Importance';
    var slow_options = independent_question.variables.length > 15;
    var slow_string = slow_options ? ' (slow, consider using Relative Importance instead)' : '';
    if (slow_options) { // Add the suffixes to the combo box options if above limits, 15 for Kruskal or Shapley.        
        shapley_combo_label = shapley_combo_label + slow_string;
        kruskal_combo_label = kruskal_combo_label + slow_string;
    }
    var default_combo = analysis_name;
    if (['Kruskal Importance', 'Shapley Importance'].indexOf(analysis_name) > -1 && slow_options)
        default_combo = analysis_name + slow_string;
    
    // Control to select analysis type
    if (form.setTranslation) { // Remove when Q 4.10 is stable
        var combo_box_name = form.newComboBox('analysisNameComboBox', ['Linear Regression Coefficients', 'Contribution', 'Beta', kruskal_combo_label, shapley_combo_label, 'Relative Importance', 'Elasticity']);
        combo_box_name.setDefault(default_combo);
        form.setInputControls([combo_box_name]);
        analysis_name = combo_box_name.getValue();
    }
    // Remove suffixes if they were used
    if (analysis_name == kruskal_combo_label)
        analysis_name = 'Kruskal Importance';
    if (analysis_name == shapley_combo_label)
        analysis_name = 'Shapley Importance';
    
    if (form.setTranslation) { // Remove when Q 4.10 is stable
        if (analysis_name == 'Elasticity')
            form.setTranslation('Average', analysis_name);
        else
            form.setTranslation('Average', analysis_name + ' (%)');
        form.setTranslation('Missing n', 'Raw ' + analysis_name);
    }
 
    var item_name = analysis_name + ' for ' + dependent_question.variables[0].label;
    if (form.setItemName) // Remove when Q 4.10 is stable
        form.setItemName(item_name);
 
    form.setHeading('Driver Analysis');
    form.setSummary(item_name);

    // Check that dependent Question is valid
    if (dependent_question.questionType != "Number")
        table.suppressOutput('The Question type of the dependent Question for driver analysis has been changed. Please change it back to Number and double-click on the blank area to the left to refresh the output.');

    // Check that independent Question in blue dropdown is valid
    var independent_question_name = text_box_indep.getValue();
    if (independent_question.name != independent_question_name)
        table.suppressOutput('The independent Question (blue Question) for driver analysis cannot be changed. It needs to be "' + independent_question_name + '".');
    if (independent_question.questionType != 'Number - Multi')
        table.suppressOutput('The independent Question (blue Question) for driver analysis must be of type "Number - Multi".');
    else if (independent_question.variables.length < 2)
        table.suppressOutput('The independent Question (blue Question) for driver analysis must have two or more Variables.');
    else if (analysis_name == 'Elasticity' && hasNonPositiveValues(independent_question.variables))
        table.suppressOutput('The independent Question (blue Question) for elasticity must have non-negative data.');
    else if (['Kruskal Importance', 'Shapley Importance'].indexOf(analysis_name) > -1 && independent_question.variables.length > 27)
        table.suppressOutput('The independent Question (blue Question) for ' + analysis_name + ' has more than 27 Variables. ' +
                             'The analysis you have requested is not possible because it would take too long to compute. ' + 
                             'Consider using Relative Importance instead of ' + analysis_name + ', as it will generally give similar results for large numbers of Variables.');
    else {
        // Settings for different driver analyses
        var normalized_multiplier = 100;
        var r_squared_name = 'rSquared';
        var normalized_name, raw_name, min_value, max_value;
        switch (analysis_name) {
            case 'Linear Regression Coefficients':
                normalized_name = 'importances';
                raw_name = 'independentVarCoefs';
                min_value = Number.NEGATIVE_INFINITY;
                max_value = Number.POSITIVE_INFINITY;
                break;
            case 'Contribution':
                normalized_name = 'contributions';
                raw_name = 'contributionsRaw';
                min_value = 0;
                max_value = Number.POSITIVE_INFINITY;
                break;
            case 'Beta':
                normalized_name = 'betasNormalized';
                raw_name = 'betas';
                min_value = Number.NEGATIVE_INFINITY;
                max_value = Number.POSITIVE_INFINITY;
                break;
            case 'Kruskal Importance':
                normalized_name = 'kruskalImportanceNormalized';
                raw_name = 'kruskalImportance';
                min_value = 0;
                max_value = 1;
                break;
            case 'Shapley Importance':
                normalized_name = 'shapleyImportanceNormalized';
                raw_name = 'shapleyImportance';
                min_value = 0;
                max_value = 1;
                break;
            case 'Relative Importance':
                normalized_name = 'relativeImportanceNormalized';
                raw_name = 'relativeImportance';
                min_value = 0;
                max_value = 1;
                break;
            case 'Elasticity':
                normalized_name = 'elasticity';
                raw_name = 'elasticity';
                min_value = Number.NEGATIVE_INFINITY;
                max_value = Number.POSITIVE_INFINITY;
                normalized_multiplier = 1;
                r_squared_name = 'elasticityRSquared';
                break;
            default:
                throw new Error('Unknown analysis name: ' + analysis_name);
        }
 
        // If table.brownQuestion is a string, questionType will return undefined and fall back to the string.
        var brown_question_type = table.brownQuestion.questionType || table.brownQuestion;
        if (brown_question_type != 'SUMMARY' && brown_question_type != 'Pick One' && brown_question_type != 'Pick Any' && brown_question_type != 'Date')
            table.suppressOutput('Select SUMMARY or a Pick One, Pick Any or Date Question in the brown dropdown.');
        else {
            var invalid_stat_name = getInvalidSelectedStatistics();
            if (invalid_stat_name != null && !table.forPlot)
                table.suppressOutput('The statistic "' + invalid_stat_name + '" is not valid for this driver analysis. Unselect it from the table.');
            else if (typeof right_table !== 'undefined' && right_table.statistics.length > 0)
                table.suppressOutput('"Right" statistics are not valid for this driver analysis. Unselect them from the table.');
            else if (typeof below_table !== 'undefined' && below_table.statistics.length > 0)
                table.suppressOutput('"Below" statistics are not valid for this driver analysis. Unselect them from the table.');
            else {
                table.showMissingAs('');
                table.hypothesisTestingEnabled = false;
                var stat_ass = table.statisticalAssumptions;
                var selected_stats = table.statistics;
                var score_index = table.statistics.indexOf('Average');
                var r_squared_index = table.rowIndex('R-Squared (%)');
                var not_duplicate_rows = getNotDuplicateRows();
                var z_score;
                if (form.setTranslation && jStat) {
                    form.setTranslation('Base n', 'Lower Confidence Bound ' + (0.5 * stat_ass.overallSignificanceLevel));
                    form.setTranslation('Effective Base n', 'Upper Confidence Bound ' + (1 - 0.5 * stat_ass.overallSignificanceLevel));
                    z_score = jStat.normal(0, 1).inv(1 - 0.5 * stat_ass.overallSignificanceLevel);
                } else
                    z_score = 1.96;
 
                if (brown_question_type == 'SUMMARY')
                    fillSummaryTable();
                else
                    fillCrosstab();
            }
        }
    }
 
    table.extraFooters = ['Dependent Variable: ' + dependent_question.variables[0].label];
 
    function fillSummaryTable() {
        var correction = table.statisticalAssumptions.cellMultipleComparisonCorrection;
        var sig_level = table.statisticalAssumptions.overallSignificanceLevel;
        var has_correction = correction != null;
        var lin_reg = has_correction ? Q.linearRegression(dependent_question, [independent_question], correction, sig_level)
                                     : Q.linearRegression(dependent_question, [independent_question]);
        if (lin_reg.failed)
            table.suppressOutput(lin_reg.failureMessage);
        else {
            setStatistic('Average', getNormalizedScores);
            setStatistic('Missing n', getRawScores);
            setStatistic('Standard Error', getStandardErrors);
            setStatistic('Base n', getLowerConfidenceIntervals);
            setStatistic('Effective Base n', getUpperConfidenceIntervals);
            setStatistic('t-Statistic', getTStatistics);
            setStatistic('z-Statistic', getZStatistics);
            setStatistic('p', getPValues);
            setStatistic('Corrected p', getCorrectedPValues);
            setStatistic('Multiple Comparison Adjustment', getMultipleComparisonAdjustments);
            setStatistic('Expected Average', getExpectedCoefficients);
            if (table.forPlot)
                addPercentSigns();
            setProperty('cellSignificance', getSignificance);
            setProperty('cellArrows', getArrows);
            setProperty('cellFontColors', getFontColors);
        }
 
        // getValues is a function that takes a linear regression object
        function setStatistic(stat_name, getValues) {
            if (selected_stats.indexOf(stat_name) != -1) {
                var new_values = getValues(lin_reg);
                var table_values = table.get(stat_name);
                var c = 0;
                for (var i = 0; i < table.numberRows; i++)
                    if (not_duplicate_rows[i] == 0)
                        table_values[i][0] = NaN;
                    else { // only not duplicate rows are in new_values
                        table_values[i][0] = new_values[c];
                        c++;
                    }
                if (stat_name == 'Average' && r_squared_index != -1)
                    table_values[r_squared_index] = [100 * lin_reg[r_squared_name]];
                table.set(stat_name, table_values);
            }
        }
 
        function setProperty(property_name, getValues) {
            var table_values = table[property_name];
            var new_values = getValues(lin_reg);
            var c = 0;
            for (var i = 0; i < table.numberRows; i++) {
                if (not_duplicate_rows[i] == 0) {
                    if (property_name == 'cellSignificance')
                        table_values[i][0] = false;
                } else {
                    table_values[i][0] = new_values[0][c];
                    c++;
                }
            }
            table[property_name] = table_values;
        }
 
        function addPercentSigns() {
            if (score_index != -1) {
                var cell_texts = table.cellText;
                if (analysis_name != 'Elasticity')
                    for (var i = 0; i < table.numberRows; i++)
                        if (not_duplicate_rows[i] == 1)
                            cell_texts[i][0] = ['%'];
                if (r_squared_index != -1)
                    cell_texts[r_squared_index][0] = ['%'];
                table.cellText = cell_texts;
            }
        }
 
        function getNormalizedScores(lin_reg) { return lin_reg[normalized_name].map(function (x) { return normalized_multiplier * x; }); }
        function getRawScores(lin_reg) { return lin_reg[raw_name]; }
        function getStandardErrors(lin_reg) { return lin_reg[raw_name + 'StandardErrors']; }
        function getLowerConfidenceIntervals(lin_reg) {
            var raw_scores = getRawScores(lin_reg);
            var standard_errors = getStandardErrors(lin_reg);
            return raw_scores.map(function (x, index) { return Math.max(x - z_score * standard_errors[index], min_value)});
        }
        function getUpperConfidenceIntervals(lin_reg) {
            var raw_scores = getRawScores(lin_reg);
            var standard_errors = getStandardErrors(lin_reg);
            return raw_scores.map(function (x, index) { return Math.min(x + z_score * standard_errors[index], max_value)});
        }
        function getTStatistics(lin_reg) { return lin_reg[raw_name + 'TStatistics']; }
        function getZStatistics(lin_reg) { return lin_reg[raw_name + 'ZStatistics']; }
        function getPValues(lin_reg) { return lin_reg[raw_name + 'PValues']; }
        function getCorrectedPValues(lin_reg) { return lin_reg[raw_name + 'CorrectedPValues'].map(function (x) { return Math.min(x, 1); }); }
        function getMultipleComparisonAdjustments(lin_reg) {
            var p_values = getPValues(lin_reg);
            return lin_reg[raw_name + 'CorrectedPValues'].map(function (x, index) { return x / p_values[index]; });
        }
        function getExpectedCoefficients(lin_reg) { return getNormalizedScores(lin_reg).map(function () { return 0; }); }
        function getSignificance(lin_reg) { return stat_ass.significance([getCorrectedPValues(lin_reg)]); }
        function getArrows(lin_reg) { return stat_ass.arrows([getCorrectedPValues(lin_reg)], [getTStatistics(lin_reg)]); }
        function getFontColors(lin_reg) { return stat_ass.fontColors([getCorrectedPValues(lin_reg)], [getTStatistics(lin_reg)]); }
    }
 
    function limitWarnings(warnings, trimmed_suffix) {
        var final_warnings = [];
        var message_line_limit = 25;
        var message_lines = 0;
        var trimmed_warnings = 0;
        warnings.forEach(function (warning) {
            message_lines += (warning.match(/\n/g) || ['']).length; // count lines
            if (final_warnings.length == 0 || message_lines < message_line_limit)
                final_warnings.push(warning);
            else
                trimmed_warnings++;
        });
        if (trimmed_warnings > 0)
            final_warnings.push('.. and ' + trimmed_warnings + trimmed_suffix);
        return final_warnings.join('');
    }
 
    function fillCrosstab() {
        var correction = table.statisticalAssumptions.cellMultipleComparisonCorrection;
        var sig_level = table.statisticalAssumptions.overallSignificanceLevel;
        var has_correction = correction != null;
        var lin_reg_crosstab = has_correction ? Q.linearRegressionCrosstab(dependent_question, [independent_question], table.brownQuestion, correction, sig_level)
                                              : Q.linearRegressionCrosstab(dependent_question, [independent_question], table.brownQuestion);
        var failed_columns = [];
        lin_reg_crosstab.failed.forEach(function (failed, index) {
            if (lin_reg_crosstab.failed[index])
                failed_columns.push('Column "' + lin_reg_crosstab.labels[index] + '" has no results:\n' +
                // Indent each line of the failure message, because it is quite long.
                lin_reg_crosstab.failureMessages[index].replace(/^/gm, '    ') + '\n');
        });
        if (failed_columns.length > 0)
            alert(limitWarnings(failed_columns, ' more columns had no results.'));
        setStatisticCrosstab('Average', getNormalizedScores);
        setStatisticCrosstab('Missing n', getRawScores);
        setStatisticCrosstab('Standard Error', getStandardErrors);
        setStatisticCrosstab('Base n', getLowerConfidenceIntervals);
        setStatisticCrosstab('Effective Base n', getUpperConfidenceIntervals);
        setStatisticCrosstab('t-Statistic', getTStatistics);
        setStatisticCrosstab('z-Statistic', getZStatistics);
        setStatisticCrosstab('p', getPValues);
        setStatisticCrosstab('Corrected p', getCorrectedPValues);
        setStatisticCrosstab('Multiple Comparison Adjustment', getMultipleComparisonAdjustments);
        if (table.forPlot)
            addPercentSignsCrosstab();
        setPropertyCrosstab('cellSignificance', getSignificance);
        setPropertyCrosstab('cellArrows', getArrows);
        setPropertyCrosstab('cellFontColors', getFontColors);
 
        function setStatisticCrosstab(stat_name, getValues) {
            if (selected_stats.indexOf(stat_name) != -1) {
                var table_values = table.get(stat_name);
                var new_values = getValues(lin_reg_crosstab);
                for (var i = 0; i < table.numberColumns; i++) {
                    if (lin_reg_crosstab.failed[i]) {
                        for (var j = 0; j < table.numberRows; j++)
                            table_values[j][i] = NaN;
                    } else {
                        var c_row = 0;
                        for (var j = 0; j < table.numberRows; j++) {
                            if (not_duplicate_rows[j] == 0)
                                table_values[j][i] = NaN;
                            else { // only not duplicate rows are in new_values
                                table_values[j][i] = new_values[i][c_row];
                                c_row++;
                            }
                        }
                        if (stat_name == 'Average' && r_squared_index != -1)
                            table_values[r_squared_index][i] = 100 * lin_reg_crosstab[r_squared_name][i];
                    }
                }
                table.set(stat_name, table_values);
            }
        }
 
        function setPropertyCrosstab(property_name, getValues) {
            var table_values = table[property_name];
            var new_values = getValues(lin_reg_crosstab);
            for (var i = 0; i < table.numberColumns; i++) {
                if (lin_reg_crosstab.failed[i]) {
                    if (property_name == 'cellSignificance')
                        for (var j = 0; j < table.numberRows; j++)
                            table_values[j][i] = false;
                } else {
                    var c_row = 0;
                    for (var j = 0; j < table.numberRows; j++) {
                        if (not_duplicate_rows[j] == 0) {
                            if (property_name == 'cellSignificance')
                                table_values[j][i] = false;
                        } else { // only not duplicate rows are in new_values
                            table_values[j][i] = new_values[i][c_row];
                            c_row++;
                        }
                    }
                }
            }
            table[property_name] = table_values;
        }
 
        function addPercentSignsCrosstab() {
            if (score_index != -1) {
                var cell_texts = table.cellText;
                for (var i = 0; i < table.numberColumns; i++) {
                    if (!lin_reg_crosstab.failed[i]) {
                        if (analysis_name != 'Elasticity') {
                            for (var j = 0; j < table.numberRows; j++) {
                                if (not_duplicate_rows[j] == 1)
                                    cell_texts[j][i] = ['%'];
                            }
                        }
                        if (r_squared_index != -1)
                            cell_texts[r_squared_index][i] = ['%'];
                    }
                }
                table.cellText = cell_texts;
            }
        }
 
        function mapJagged(jagged_array, func) {
            return jagged_array.map(function (x) {
                if (x != null) {
                    return x.map(function (y) {
                        return func(y);
                    });
                } else
                    return null;
            });
        }
 
        function mapTwoJagged(jagged_array_1, jagged_array_2, func) {
            return jagged_array_1.map(function (x, index_1) {
                if (x != null) {
                    return x.map(function (y, index_2) {
                        return func(y, jagged_array_2[index_1][index_2]);
                    });
                } else
                    return null;
            });
        }
 
        function getNormalizedScores(lin_reg) { return mapJagged(lin_reg[normalized_name], function (x) { return normalized_multiplier * x; }); }
        function getRawScores(lin_reg) { return lin_reg[raw_name]; }
        function getStandardErrors(lin_reg) { return lin_reg[raw_name + 'StandardErrors']; }
        function getLowerConfidenceIntervals(lin_reg) { return mapTwoJagged(getRawScores(lin_reg), getStandardErrors(lin_reg),
                                                        function (x, y) { return Math.max(x - z_score * y, min_value); }); }
        function getUpperConfidenceIntervals(lin_reg) { return mapTwoJagged(getRawScores(lin_reg), getStandardErrors(lin_reg),
                                                        function (x, y) { return Math.min(x + z_score * y, max_value); }); }
        function getTStatistics(lin_reg) { return lin_reg[raw_name + 'TStatistics']; }
        function getZStatistics(lin_reg) { return lin_reg[raw_name + 'ZStatistics']; }
        function getPValues(lin_reg) { return lin_reg[raw_name + 'PValues']; }
        function getCorrectedPValues(lin_reg) { return mapJagged(lin_reg[raw_name + 'CorrectedPValues'], function (x) { return Math.min(x, 1); }); }
        function getMultipleComparisonAdjustments(lin_reg) { return mapTwoJagged(getPValues(lin_reg), lin_reg[raw_name + 'CorrectedPValues'],
                                                             function (x, y) { return y / x; }); }
        function getSignificance(lin_reg) { return stat_ass.significance(getCorrectedPValues(lin_reg)); }
        function getArrows(lin_reg) { return stat_ass.arrows(getCorrectedPValues(lin_reg), getTStatistics(lin_reg)); }
        function getFontColors(lin_reg) { return stat_ass.fontColors(getCorrectedPValues(lin_reg), getTStatistics(lin_reg)); }
    }
 
    function getNotDuplicateRows() {
        var not_duplicate = table.get('Not Duplicate');
        var results = [];
        for (var i = 0; i < table.numberRows; i++)
            for (var j = 0; j < table.numberColumns; j++) {
                if (not_duplicate[i][j] == 1) {
                    results.push(1);
                    break;
                }
                if (j == table.numberColumns - 1)
                    results.push(0); // only run if all cells in the row are duplicate
            }
        return results;
    }
 
    function getInvalidSelectedStatistics() {
        var valid_stats = ['Average', 'Missing n', 'Standard Error', 'Base n', 'Effective Base n', 't-Statistic', 'z-Statistic',
                           'p', 'Corrected p', 'Multiple Comparison Adjustment', 'Not Duplicate', 'Expected Average', 'Column n', 'Column Population'];
        var selected_stats = table.statistics;
        for (var i = 0; i < selected_stats.length; i++)
            if (valid_stats.indexOf(selected_stats[i]) == -1)
                return selected_stats[i];
        return null;
    }
 
    function hasNonPositiveValues(variables) {
        for (var i = 0; i < variables.length; i++) {
            var variable = variables[i];
            if (variable.question.questionType == 'Pick Any' || variable.question.questionType == 'Pick Any - Grid')
                return true; // assume has 0 and 1
            else {
                var raw_values = variable.rawValues;
                var value_attributes = variable.valueAttributes;
                for (var j = 0; j < raw_values.length; j++)
                    if (value_attributes.getValue(raw_values[j]) <= 0)
                        return true;
            }
        }
        return false;
    }
}

See also

Further reading: Key Driver Analysis Software