Marketing - Max-Diff - Max-Diff Setup from an Experimental Design

From Q
Jump to: navigation, search

This QScript sets up a Max-Diff experiment in Q using an experimental design that is stored in a spreadsheet.

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).

Assumed existing data format

This script requires that you have the following:

  1. Separate variables in your Q project that store which of the shown alternatives were chosen as most preferred and least preferred, or which of the total set of alternatives was chosen in each task. Important: the ordering of the variables for the tasks in the Variables and Questions tab must be in the same order as the tasks in your design.
  2. A variable in your Q project that stores the version of the Max-Diff design that was shown to each respondent. Where all respondents have seen the same design then this variable is not necessary.
  3. A CSV or XLSX file containing the experimental design. The first column must contain the version of the task (even if the same version was shown to all respondents). The second column must contain the number of the task for that row. Each column after the second must contain the alternative that was shown to the respondent during that task. The first row of the file should contain labels for the columns. An example is shown below.
  4. A CSV or XLSX file containing the labels for each of the possible options, in order. This file must have a single column with the column heading Labels. This is not necessary if the labels of the options are contained in all of your Max-Diff variables. An example is shown below.

Experimental design file format

The experimental design should be laid out in the follow fashion:

ExcelMaxDiffDesign.PNG

Label file format

If the labels are not contained in the Max-Diff variables in your Q project, then you should have an excel file containing the labels in the following format:

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 Max-Diff 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 Max-Diff 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 max-diff 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, 'Max-Diff 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("The number of versions in the design (" + num_versions + ") does not match 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 Max-Diff 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 = first_var.valueAttributes;
        var unique_values = first_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 Max-Diff 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("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

Personal tools
Namespaces

Variants
Actions
Navigation
Categories
Toolbox