Choice Modeling - Convert Alchemer (Survey Gizmo) Conjoint Data for Analysis

From Q
Jump to navigation Jump to search

This QScript makes it possible to analyze Alchemer (formerly Survey Gizmo) Conjoint data in Q Displayr. Before running this script, Alchemer (formerly Survey Gizmo) Conjoint and respondent data files first need to be added to the project. This script produces questions containing the choices and the respondent version in the respondent data set. These inputs can then be used to run a Conjoint analysis using Latent Class Analysis, Multinomial Logit, or Hierarchical Bayes.

The user is prompted to select a Conjont data set imported from Alchemer (formerly Survey Gizmo), and a respondent data set containing a question Response ID.

To run this script, the Conjoint variable set must first be selected in the Data Tree. If there is more than one other data set containing a variable set Response ID then the respondent data set must also be selected.

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

includeWeb("QScript R Output Functions")

function dataFileHasQuestionWithName(data_file, name, exact = false)
{
    var questions = data_file.questions;
    var n_questions = questions.length;
    for (var i = 0; i < n_questions; i++)
    {
        var patt = exact ? new RegExp("^" + name + "$") : new RegExp("^" + name);
        if (patt.test(questions[i].name))
            return true;
    }
    return false;
}


function getQuestionFromDataFile(data_file, name, exact = false)
{
    var questions = data_file.questions;
    var n_questions = questions.length;
    for (var i = 0; i < n_questions; i++)
    {
        var patt = exact ? new RegExp("^" + name + "$") : new RegExp("^" + name);
        if (patt.test(questions[i].name))
            return questions[i];
    }
    return null;
}

function isRespondentDataFile(data_file)
{
    return dataFileHasQuestionWithName(data_file, "Response ID", true) &&
//           dataFileHasQuestionWithName(data_file, "Date Submitted", true) &&
           !dataFileHasQuestionWithName(data_file, "Set Number", false);
}

function isConjointDataFile(data_file)
{
    return dataFileHasQuestionWithName(data_file, "Response ID", true) &&
           dataFileHasQuestionWithName(data_file, "Set Number", false) &&
           dataFileHasQuestionWithName(data_file, "Card Number", false) &&
           dataFileHasQuestionWithName(data_file, "Score", false);
}

function main()
{
    includeWeb('QScript Utility Functions');
    includeWeb('QScript Selection Functions');

    var data_files = project.dataFiles;

    if (data_files.length < 2)
    {
        log("There needs to be both a respondent data set and a choice data set loaded into your project");
        return;
    }

    var is_displayr = inDisplayr();
    
    if (is_displayr)
    {
        var all_user_selections = getAllUserSelections();
        var selected_data_files = all_user_selections.selected_data_sets;

        //var variables = project.report.selectedVariables();
        if (selected_data_files.length == 0)
        {
            log("A Conjoint data set needs to be selected from 'Data Sets'.");
            return;
        }
        
        if (selected_data_files.length >= 3)
        {
            log("More than 2 data sets are selected. Select a Conjoint data set and a respondent data set.");
            return;
        }
        
        var conjoint_data_file = null;
        var respondent_data_file = null;
        
        if (selected_data_files.length == 1)
        {
            conjoint_data_file = selected_data_files[0];
            if (!isConjointDataFile(conjoint_data_file))
            {
                log("A Conjoint data set needs to be selected from 'Data Sets'.");
                return;
            }
            // Iterate over all data sets in project to identify respondent data set
            var respondent_data_file_index = -1;
            for (var i = 0; i < data_files.length; i++)
            {                
                if (isRespondentDataFile(data_files[i]))
                {
                    if (respondent_data_file_index != -1)
                    {
                        log("Multiple respondent data sets have been identified. Please select both the particular respondent data set you wish to use as well as the conjoint data set from the Data Tree, and try again.");
                        return;
                    }   
                    respondent_data_file_index = i;
                }
            }
            if (respondent_data_file_index > -1)
                respondent_data_file = data_files[respondent_data_file_index];
            else
            {
                log("No suitable respondent data set was found.");
                return;
            }
        }
        else // 2 data files selected
        {
            var file1 = selected_data_files[0];
            var file2 = selected_data_files[1];
            if (isConjointDataFile(file1))
            {
                conjoint_data_file = file1;
                respondent_data_file = file2;
            }
            else
            {
                if (!isConjointDataFile(file2))
                {
                    log("Please select a Conjoint data set from 'Data Sets'.");
                    return;
                }
                conjoint_data_file = file2;
                respondent_data_file = file1;
            }
            if (!isRespondentDataFile(respondent_data_file))
            {
                log("You have selected two data sets and a Conjoint data set has been identified. The other data set is required to contain a 'Response ID' but does not.");
                return;
            }
        }
    }
    else // In Q, ask user to select data sets
    {
        includeWeb('QScript Selection Functions');
        var conjoint
        conjoint_data_file = selectOneDataFile('Select the Alchemer Conjoint data set:', project.dataFiles);
        if (!isConjointDataFile(conjoint_data_file))
        {
            log("The selected data set is not a Conjoint data set.");
            return;
        }

        var respondent_data_file = selectOneDataFile('Select the respondent data set:', project.dataFiles);
        if (!isRespondentDataFile(respondent_data_file))
        {
            log("The selected data set is not a respondent data set.");
            return;
        }
    }

    // Get the number of alternatives per question
    // One example Survey Gizmo file had invalid Card Number values for what seem to be
    // respondents that had missed certain questions, so instead determine alt_per_question
    // from Set Number
    //     var raw_values = getQuestionFromDataFile(conjoint_data_file, "Card Number").variables[0].rawValues;
    //     var alt_per_question = 0;
    //     for (var i = 0; i < raw_values.length; i++)
    //         if (alt_per_question < raw_values[i])
    //             alt_per_question = raw_values[i];
    var raw_values = getQuestionFromDataFile(conjoint_data_file, "Set Number").variables[0].rawValues;
    var setn1 = raw_values[0];
    var alt_per_question = 1;
    for (var i = 1; i < raw_values.length; i++) {
        if (raw_values[i] == setn1)
            alt_per_question++;
        else
            break;
    }    

    // Get the number of questions
    var raw_values = getQuestionFromDataFile(conjoint_data_file, "Set Number").variables[0].rawValues;
    var n_questions = 0;
    for (var i = 0; i < raw_values.length; i++)
        if (n_questions < raw_values[i])
            n_questions = raw_values[i];

    // Check Conjoint data file: question/Set Number variable has right format
    // e.g. 1,1,2,2,3,3,1,1,2,2,... for alt_per_question = 2, n_questions = 3
    // Commented out because it fails for cases where the respondent didn't answer a question,
    //   which can be safely ignored
    // for (var i = 0; i < raw_values.length; i++)
    //     if (Math.floor(i % (alt_per_question*n_questions)/alt_per_question) +1 != raw_values[i])  // (i % n_questions) + 1
    //     {
    //         log("There was an issue reading the Survey Gizmo Conjoint file. Please check that each respondent has the same number of questions.");
    //         return;
    //     }

    // Respondent and Conjoint data set Response ID question
    var respondent_response_id = "`" + respondent_data_file.name + "`$Questions$`Response ID`";
    var conjoint_response_id = "`" + conjoint_data_file.name + "`$Questions$`Response ID`";

    // Move choices to the respondent data set
    var previous_variable = null;

    let r_expr = 
`
id.respondent <- ${respondent_response_id}
id.conjoint <- suppressWarnings(flipTransformations::AsNumeric(${conjoint_response_id}, binary = FALSE))
design.set.number <- ${stringToRName(conjoint_data_file.fileName) + "$Questions$" + 
              stringToRName(getQuestionFromDataFile(conjoint_data_file, "Set Number", false).name)}
alternative <- ${stringToRName(conjoint_data_file.fileName) + "$Questions$" + 
              stringToRName(getQuestionFromDataFile(conjoint_data_file, "Card Number", false).name)}
score <- ${stringToRName(conjoint_data_file.fileName) + "$Questions$" + 
              stringToRName(getQuestionFromDataFile(conjoint_data_file, "Score", false).name)}

if (any(! unique(score) %in% c(0, 100))) stop("All of the values for the Score variable should be either 0 or 100, where 100 indicates the alternative selected by the respondent. This indicates that this data does not come from a Choice-Based Conjoint study.")

# Convert scores of 100 to the alternative selected
# and drop rows that do not correspond to a selected option
score <- as.numeric(as.character(score))
dat = data.frame(id.conjoint, design.set.number, alternative, score)
dat$score[dat$score == 100] <- as.numeric(as.character(dat$alternative[dat$score == 100]))
dat <- dat[dat$score != 0, ]

# Unstack the selected choices, creating a Version column
# and one Choice column for each of the choice tasks
library(reshape2)
choice <- reshape(dat, idvar = "id.conjoint", timevar = "design.set.number", drop = "alternative", direction = "wide")
colnames(choice) = c("Version", paste0("Choice ", 1:(ncol(choice) - 1) ))
unique.levels = sort(unique(unlist(choice[, 2:ncol(choice)])))

# Convert choices to factors with consistent levels
for (col in 2:ncol(choice)) {
    choice[[col]] = factor(choice[[col]], levels = unique.levels)
}

# Match the IDs (Version) from the experimental design to those
# in the survey data set. At this stage, any respondents
# in the survey data who are not present in the design will
# be assigned missing values
choice <- choice[match(id.respondent, choice$Version), ]

# Assign a value for "version" to respondents not in the
# design. These will be ignored during estimation because they do not
# have choice selections. This prevents an error when checking the design
choice$Version[is.na(choice$Version)] <- min(choice$Version, na.rm = TRUE)

choice

`

    // Create the variables in the survey data set

    let new_question;
    try {
        new_question = respondent_data_file.newRQuestion(r_expr, 
            preventDuplicateQuestionName(respondent_data_file, "Choice Data"),
            "choice" + makeid(),
            null);
    } catch (e) {
        log("Could not create Choice variables. Please contact support for assistance. The error was: " + e);
        return false;
    }

    // Split out the version and choice variables
    new_question.variables.forEach(function (v) {
        let label = v.label;
        let type = v.label.indexOf("Version") != -1 ? "Number" : "Pick One" 
        respondent_data_file.setQuestion(preventDuplicateQuestionName(respondent_data_file, label + " from " + conjoint_data_file.name),
                                             type, [v])
    });


    var structure = is_displayr ? "Variable Sets" : "Questions";
    if (!is_displayr)
        log(structure + " containing the choices and the respondent versions have been added to the respondent data set.");
}

main();

See also