Marketing - MaxDiff - Analyze with Ranking Question (Legacy) - MaxDiff Ranking Question from Design and Choice Data

From Q
Jump to navigation Jump to search

This QScript sets up a MaxDiff experiment in Q using an experimental design that is stored in a spreadsheet. For this to work, the files need to be set up according to the details below.

Technical details

You will be prompted to:

  1. Select the variables that contain the best or most-preferred option for each task.
  2. Select the variables that contain the worst or least-preferred option for each task.
  3. Select the file that contains your experimental design.
  4. Select the file that contains the labels of the alternatives (only if the labels of the alternatives are not contained in the best/worst variables).
  5. Select the variable that determines which version of the design was shown to each respondent (only if there is more than one version).

Setting up the data

Two or three files are required for the setup. You need:

  1. A Q project with a data file containing the version and which options were chosen as best and worst. See the section on Standard analyses for MaxDiff on the Displayr wiki for more details and DIY MaxDiff for example data files.
  2. A CSV or XLSX file containing the experimental design, set up as described in the section on setting up an experimental design in the MaxDiff page on the Displayr Wiki. You can download examples to compare from DIY MaxDiff.
  3. If the variables in your Q Project file that contain the choices of best and worst have inconsistent Value Attributes (see the Displayr Wiki for more information about this), with the values and labels relating to which option was chosen (e.g., "Option 1", "Option 2"), you will also be asked for a a third CSV or XLSX file that contains the labels of the aternatives, as shown below:

ExampleMaxDiffLabels.PNG


Worked example

To run the worked example:

  • Download the Excel files.
  • Download the QPack and open it.
  • Run the script using Automate > Browse Online Library.

How to apply this QScript

  • Start typing the name of the QScript into the Search features and data box in the top right of the Q window.
  • Click on the QScript when it appears in the QScripts and Rules section of the search results.

OR

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

Customizing the QScript

This QScript is written in JavaScript and can be customized by copying and modifying the JavaScript.

Customizing QScripts in Q4.11 and more recent versions

  • Start typing the name of the QScript into the Search features and data box in the top right of the Q window.
  • Hover your mouse over the QScript when it appears in the QScripts and Rules section of the search results.
  • Press Edit a Copy (bottom-left corner of the preview).
  • Modify the JavaScript (see QScripts for more detail on this).
  • Either:
    • Run the QScript, by pressing the blue triangle button.
    • Save the QScript and run it at a later time, using Automate > Run QScript (Macro) from File.

Customizing QScripts in older versions

  • Copy the JavaScript shown on this page.
  • Create a new text file, giving it a file extension of .QScript. See here for more information about how to do this.
  • Modify the JavaScript (see QScripts for more detail on this).
  • Run the file using Automate > Run QScript (Macro) from File.

JavaScript

// Setup MaxDiff Question
 
includeWeb('QScript Selection Functions');
includeWeb('QScript Utility Functions'); 
includeWeb('QScript Functions to Generate Outputs'); 
 
if (!main())
    log("QScript cancelled.");
else
    conditionallyEmptyLog("QScript finished.")
 
 
function main() {

    Array.prototype.max = function() {
      return Math.max.apply(null, this);
    };

    Array.prototype.min = function() {
      return Math.min.apply(null, this);
    };


 
    if (!requireDataFile())
        return false;
 
    var data_file = project.dataFiles[0];
    if (project.dataFiles.length > 1)
        data_file = selectOneDataFile("Select the data file which contains the MaxDiff data:", project.dataFiles);
    var all_variables = data_file.variables.filter(function (v) { return !v.question.isHidden; });
    var Qversion = fileFormatVersion(); 
 
    // Prompt the user to tell us which variables tell us the 'most' (best) selection
    // and which variables tell us the 'least' (worst) selection
    var most_variables = selectManyVariablesByName("Please select the variables that contain the selections for the best (most preferred) option from each choice task."
        + "\r\n You can make multiple selections by holding the Ctrl key on your keyboard. Note, the order of these must match the order of the tasks in your design.", all_variables, false).names;
    var least_variables = selectManyVariablesByName("Please select the variables that contain the selections for the worst (least preferred) option from each choice task."
        + "\r\n You can make multiple selections by holding the Ctrl key on your keyboard. Note, the order of these must match the order of the tasks in your design.", all_variables, false).names;
 
    // Import the MaxDiff design data file
    if (Qversion < 8.39) {
        var given_valid_file = false;
        while (!given_valid_file) {
            var file_name = prompt("Please paste the path and filename of the CSV file that contains your experimental design. For example: C:\\Chris\\Documents\\design.csv");
            try {
                var design_file = project.addDataFile(file_name, 'MaxDiff Design', {col_names_included: true});
                given_valid_file = true;
            } catch (e) {
                alert("Invalid file name!")
                given_valid_file = false;
            }
        }
    } else {
        var design_file = project.addDataFileDialog("Select Excel/CSV File Containing Experimental Design", {col_names_included: true});
    }
 
 
 
    // Get the design and related information
    var design_info = scrapeMaxDiffDesignFromDataFile(design_file);
    var design_array = design_info.designArray;
    var num_versions = uniqueElementsInArray(design_info.versions).length; // Number of versions of choice task
    var num_tasks = uniqueElementsInArray(design_info.tasks).length; // Number of tasks in each version
    var num_alts_per_task = design_array[0][0].length;
    var alternatives_array = design_info.alternatives;
    var design_string = generateDesignArrayString(design_array); // Contains the epxerimental design
 
    var version;
    if (num_versions > 1) {
        // Determine the variable that specifies the version of the task and check that the number of versions matches the design
        var version_var = selectOneVariableByName("Please choose the variable that contains the version of the design that was shown to each respondent.", all_variables);
        version = version_var.name;
        var versions_in_choice_data = Math.max.apply(null, version_var.rawValues.filter(function(x) { return !isNaN(x); }));
        if (versions_in_choice_data > num_versions) {
            design_file.remove();
            throw new UserError("The number of versions in the design (" + num_versions + ") is less than the number of versions in the data set (" + versions_in_choice_data + ")");
        }
    } else
        version = 1;
 
 
 
    // Obtain labels for each alternative from the value attributes, or if that is not possible,
    // prompt the user to provide a CSV file that contains the labels
    var max_diff_labels = [];
    var first_var = data_file.getVariableByName(most_variables[0]);

    var all_max_diff_variables = most_variables.concat(least_variables);
    all_max_diff_variables = all_max_diff_variables.map(function (name) { return data_file.getVariableByName(name); });
    
    // Work out if the variables contain value labels for all possible alternatives
    var total_num_alts = alternatives_array.length;
    var values_per_variable = all_max_diff_variables.map(function (v) { return v.uniqueValues.filter(function (x) { return !isNaN(x); }).length; });
    var all_values_in_data = [];
    all_max_diff_variables.forEach(function (v) {
        a = v.uniqueValues;
        a.forEach(function (x) { if (!isNaN(x) && all_values_in_data.indexOf(x) == -1) all_values_in_data.push(x); });
    });

    var max_vals = values_per_variable.max();
    var min_vals = values_per_variable.min();
    var values_length_consistent = max_vals == min_vals;

    if (values_length_consistent && max_vals != total_num_alts && max_vals != num_alts_per_task) {
        log("There are a total of " + total_num_alts + " alternatives with " + num_alts_per_task + " shown. The number of response codes in your best/worst variables is " + max_vals + ", which matched neither of these. The MaxDiff variables cannot be created.");
        return false;
    }

    var variables_contain_all_alts = all_values_in_data.length > num_alts_per_task;
    
    // Get the labels from the data if possible, otherwise get a file with the labels in it.    
    if (variables_contain_all_alts && (values_per_variable.indexOf(total_num_alts) > -1) ) {
        // Alternatives are labeled in the variables
        var target_var = all_max_diff_variables[values_per_variable.indexOf(total_num_alts)]
        var value_attributes = target_var.valueAttributes;
        var unique_values = target_var.uniqueValues.filter(function (x) { return !isNaN(x); });
        var num_vals = unique_values.length;
        for (var j = 0; j < num_vals; j++) {
            max_diff_labels.push(value_attributes.getLabel(unique_values[j]));
        }
    } else {
        // Prompt to obtain   
        if (Qversion < 8.39) {
            var given_valid_file = false;
            while (!given_valid_file) {
                var alt_file_name = prompt("Please paste the path and filename of a CSV file containing the labels for the alternatives in order.\r\n"
                    + "This file should contain a single column with the heading 'Labels' and " + total_num_alts + " additional entries. For example: C:\\Chris\\Documents\\labels.csv");
                try {
                    var labels_file = project.addDataFile(alt_file_name, "Alternative Labels", {col_names_included: true});
                    given_valid_file = true;
                } catch (e) {
                    alert("Invalid file name!");
                    given_valid_file = false;
                }
            }
        } else {
            var labels_file = project.addDataFileDialog("Select Excel/CSV File Containing Alternative Labels", {col_names_included: true});
        }
 
 
        max_diff_labels = labels_file.variables[0].rawValues;
        // Check the number of labels supplied
        if (max_diff_labels.length != total_num_alts) {
            log("There are a total of " + total_num_alts + " but " + max_diff_labels.length + " labels have been provided. The MaxDiff variables cannot be created.");
            return false;
        }
    } 

    maxDiffSetup(preventDuplicateQuestionName(data_file, "Max Diff"), "maxDiffVariables" + makeid(), most_variables, least_variables, max_diff_labels, design_string, num_tasks, version, variables_contain_all_alts, data_file);
 
    // Remove extra data files
    design_file.remove();
    if(labels_file)
        labels_file.remove();
 
    return true;
}
 
 
//A generic function for setting up MaxDiff experiments in Q
function maxDiffSetup(question_name, prefix, mosts, leasts, labels, design, n_sets, version, variables_contain_all_alts, data_file){
    var n_alternatives = labels.length;
    //creating the variables
    var last_var = null;
    var new_question_variables = new Array();
    for (var set = 0; set < n_sets; set++) {
        for (var alt = 1; alt <= n_alternatives; alt++) {
            var name = prefix + "_" + (set + 1) + "_" + alt;
 
            // We create a slightly different JavaScript expression depending on whether the most/least variables
            // tell us which option the respondent chose from the total list of options, or if they only tell us
            // which option was shown from among the options shown in the choice task.
            if (variables_contain_all_alts)
                var expression = "var vers = " + version + " - 1;\r\nvar des = " + design +";\r\nif (isNaN(vers)) NaN;\r\nelse {\r\n\tvar set = des[vers][" + set +  "]; \r\n\tif (set.indexOf(" + alt + ") == -1) NaN;\r\n\telse if (" + alt + " == " + mosts[set] + ") 1;\r\n\telse if (" + alt + " == " + leasts[set]+") -1;\r\n\telse 0;\r\n}";        
            else
                var expression = "var vers = " + version + " - 1;\r\nvar des = " + design +";\r\nif (isNaN(vers)) NaN;\r\nelse {\r\n\tvar set = des[vers][" + set +  "]; \r\n\tif (isNaN(vers)) NaN; \r\n\tif (set.indexOf(" + alt + ") == -1) NaN;\r\n\telse if (" + alt + " == set[" + mosts[set] + " - 1]) 1;\r\n\telse if (" + alt + " == set[" + leasts[set]+" - 1]) -1;\r\telse 0;\r\n}";
            var new_var = data_file.newJavaScriptVariable(expression, false, name, labels[alt-1], last_var, {skipValidation:true,accelerate:true});
            last_var = new_var;
            new_question_variables.push(new_var);
        }
    }
    //setting as a ranking question
    var new_question = data_file.setQuestion(question_name, "Ranking", new_question_variables);
    data_file.moveAfter(data_file.getQuestionByName(question_name).variables, null);
    //creating a table with the new question
    var t = project.report.appendTable();
    t.primary = new_question;
    log("An analysis of " + question_name + " has been added to the end of this report.");
}
 
 
 
 
// This function generates an experimental design array from
// the design stored in a data file.
// This function assumes that:
//
// 1. The first column in the data file tells us the version of the design.
//
// 2. The second column enumerates the task number.
//
// 3. All columns after the second contain the alternatives shown to the
// respondent in that task in that version.
 
function scrapeMaxDiffDesignFromDataFile(data_file) {
 
    // Check that the data is not text
    var types = data_file.variables.map(function (v) { return v.variableType; });
    if (types.indexOf("Text") > -1)
        throw new UserError("Some variables in the design file are Text. All columns of the design should contain numbers only.");
 
    var raw_data = extractRawDataFromDataFile(data_file);
    var versions = raw_data[0];
    var tasks = raw_data[1];
    var possible_alternatives = [];
    var alternatives = [];
    var num_columns = raw_data.length;
    var num_rows = raw_data[0].length;
 
    // Extract the alternatives
    for (var j = 2; j < num_columns; j++)
        alternatives.push(raw_data[j]);
 
    var num_alternatives = alternatives.length;
 
    var design = [];
    var sub_design = [];
    var current_task = [];
 
    var last_version = versions[0];
 
    // Construct the design array
    for (var j = 0; j < num_rows; j++) {
        // Get the current set of alternatives
        current_task = [];
        for (var k = 0; k < num_alternatives; k++) {
            current_task.push(alternatives[k][j]);
            if (possible_alternatives.indexOf(alternatives[k][j]) == -1)
                possible_alternatives.push(alternatives[k][j]);
        }
 
        var current_version = versions[j];
        if (current_version == last_version) {
            sub_design.push(current_task);
        } else {
            design.push(sub_design);
            sub_design = [];
            sub_design.push(current_task);
            last_version = current_version;
        }
    }
    // Add the final version
    design.push(sub_design);
    var possible_alternatives = possible_alternatives.sort(function(a,b) {return a - b;} );
 
    return {designArray: design, versions: versions, tasks: tasks, alternatives: possible_alternatives};
}
 
 
 
// Extracts the raw data from a data file and returns it as an array
// Each element of the output array is an array containing the data from
// the variable at the same index in the data file.
function extractRawDataFromDataFile(data_file) {
    var variables = data_file.variables;
    var num_vars = variables.length;
 
    var raw_data = [];
 
    for (var j = 0; j < num_vars; j ++)
        raw_data.push(variables[j].rawValues);
 
    return raw_data;
}
 
 
function generateDesignArrayString(design_array) {
    var string = "[";
    var num_versions = design_array.length;
    for (var j = 0; j < num_versions; j++) {
        string = string + "[";
        var num_tasks = design_array[j].length;
        for (var k = 0; k < num_tasks; k++) {
            string = string + "[" + design_array[j][k].join(', ') + "]";
            if (k < num_tasks - 1)
                string = string + ",";
        }
        string = string + "]";
        if (j < num_versions - 1)
            string = string + ",";
    }
    string = string + "]";
    return string;
}
 
// an array of unique values from http://dreaminginjavascript.wordpress.com/2008/08/22/eliminating-duplicates/
function uniqueElementsInArray(array){
    var i,
        len=array.length,
        out=[],
        obj={};
 
    for (i=0;i<len;i++) {
        obj[array[i]]=0;
    }
    for (i in obj) {
        out.push(i);
    }
    return out;
}

See also

Further reading: MaxDiff software