Two or three files are required for the setup. You need:
// 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;
}