R/Run_Conjoint_Analysis.R

Defines functions Run_Conjoint_Analysis

Documented in Run_Conjoint_Analysis

###########################################################
#                     Conjoint Analysis                   #
###########################################################
#    R Codes for Marketing Analytics                      #
#    Author: Moein Khanlari, PhD                          #
#    Version 1.0                                          #
#    Copyright© 2019, Moein Khanlari All rights reserved. #
#    This software is provided to students at the         #
#    University of New Hampshire on an "AS IS" BASIS,     #
#    WITH ABSOLUTELY NO WARRANTIES either expressed or    #
#    implied. The software may not be redistributed       #
#    in whole or part without the express written         #
#    permission of the author.                            #
###########################################################


# # Remove all variables from memory to start fresh
# rm(list=ls())
# # If there are plots, delete them to start fresh
# if (length(dev.list())!= 0) dev.off()



####################################################
#######      Setting the Working Directory   #######
# The working directory is the folder in which
# we will place our R code and data files.

# # Here, I automatically set the working directory
# # to the folder that contains THIS R Script
# if (!require(rstudioapi)) install.packages("rstudioapi")
# setwd(dirname(rstudioapi::getActiveDocumentContext()$path))

# If the above code fails, uncomment the setwd() line below
# and type the path of the data file in the quotation marks:
# setwd("Folder_path_goes_here")


# # Load a set of functions that the instructor has written
# # for this course if they do not already exist in memory
# if(!exists("MarketingAnalytics", mode="function"))
#   source("../z_Extras/MarketingAnalytics_functions.R")

####################################################


################# Analyze ratings data and calculate partial utility utilities ##############
# This script assumes that you have
# designed a conjoint study based on a number of product attributes
# and levels, and that you have collected ratings data
# from a represenative sample of customers in your target market
# on the hypothetical product profiles from that design.
#
# For data collection, you will need to ask
# respondents to rate the product
# profiles from your conjoint study design on a scale
# from 0 to 100, with 100 representing a
# product that they like a lot.
# you will need to put all those ratings in
# an Excel .CSV file with the following structure:
#
# product1 Product2 ....  ProductN
# rating1  rating2  ....  ratingN
# rating1  rating2  ....  ratingN
# rating1  rating2  ....  ratingN
# rating1  rating2  ....  ratingN
# .
# .
# .
# Each row above would contain all the ratings of one respondent
# and the template shown above assumes N products are being rated by each respondent
# where N is the number of product profiles in your conjoint study design.
#
# As an example, for 16 products and 50 respondents, this scheme
# would lead to a .CSV file with 51 rows and 16 columns
# Note, there would be a header row of product names, hence 50+1 rows.


################# Estimate partial utilities ##############

# A technical note:
# This code requires all attribute levels to be unique, such that no two
# attribute levels from different attributes are identical.
# If some attributes have similar levels, you will need to add extra characters
# to some attribute levels
# to make sure all attribute levels are unique: For example, if you have
# two Attributes both of which have levels "Yes" and "No", add the attribute name
# to the "Yes" and "No" values, so that no two attribute levels are  identical
# For example a Bluetooth with Yes and No as its levels would be renamed to
# "bluetoothYes", "bluetoothNo".

#' Conjoint Analysis
#' 
#' This function conducts a conjoint analysis. It requires five csv data files that are listed below.
#' 
#' @param design the design table csv file
#' @param products the list of tested products as a csv file
#' @param ratings ratings of the products in the conjoint survey in a csv file
#' @param alternatives list of proposed product alternatives as a csv file
#' @param competitors list of competitors' products in the market as a csv file
#' @param resizepaper how much larger should the report paper be to fit everything?
#'
#' @export
#' 
#' @examples
#' \dontrun{
#' # This is the sample code to be copied and used in a new R Script:
#' library(listentodata)
#' clear_console()
#' select_folder()  #Select the folder where your data files are
#' design = read.csv("Tablet_levels_attributes.csv", na.strings=c("","NA")) #design table
#' products = read.csv("Tablet_survey_products.csv")    # READ PRODUCT PROFILES DATA
#' ratings =  read.csv("Tablet_survey_ratings.csv")    # READ RATINGS DATA
#' alternatives = read.csv("Tablet_New_Product_Alternatives.csv") # Load New product alternative
#' competitors = read.csv("Tablet_competitors.csv")  # Load competitors products
#' resizepaper = 1
#' Run_Conjoint_Analysis(design, products, ratings, alternatives, competitors, resizepaper)
#' }
Run_Conjoint_Analysis = function(design, products, ratings, alternatives, competitors, resizepaper=1) {
  
cat(".\n")
cat("..\n")
cat("...\n")
  
cat("1. Pre-processing data... ")
  
  
  DesignTable = design
  df_products = products
  df_ratings = ratings
  alternatives = alternatives
  competitors = competitors
  larger = resizepaper
  

# # Let's take a look at the design matrix
# (DesignTable = read.csv(file="Tablet_levels_attributes.csv",na.strings=c("","NA")))

# # Read the data from a .CSV Excel file and place it in a new variable:
# (df_products = read.csv(file="Tablet_survey_products.csv"))             # READ PRODUCT PROFILES DATA

# df_ratings =  read.csv(file="Tablet_survey_ratings.csv")             # READ RATINGS DATA

myratings = data.frame(t(df_ratings))
utils::head(myratings)

# # Load New product alternative
# (alternatives = read.csv(file="Tablet_New_Product_Alternatives.csv"))

# Create a copy of alternative with attribute names
alts_with_names = alternatives
row.names(alts_with_names) = names(DesignTable)[2:ncol(DesignTable)]
alts_with_names

# # Load competitors products
# (competitors = read.csv(file="Tablet_competitors.csv"))

# Create a copy of competitors with attribute names
comps_with_names = competitors
row.names(comps_with_names) = names(DesignTable)[2:ncol(DesignTable)]
comps_with_names

#A minor renaming to make product profile names consistent across different projects or data sets.
names(df_products) = paste0(rep("prod",ncol(df_products)),1:ncol(df_products))
myproducts = data.frame(t(df_products))

cat("Done!\n")

cat("2. Calculating Partial utilities...")

# Estimate Partial Utilities
pws = Estimate_Partworth_Utilities(myratings,myproducts)
# View the top rows of pws
utils::head(pws)
cat("Done!\n")

################# Conduct Market share Analysis using partial utilities ##############

# In this section, you can analyze market shares for
# existing and/or new products based on the knowledge
# obtained about customer preferences from their ratings data
# This knowledge is now summarized in respondents' partial utilities.
#
# To convert preferences into choices, we need to make an assumption
# about how people convert their preferences to choices.
# We can use one of three choice rules:

# Choice Rule 1: First preference or utility maximization rule
# Choice Rule 2: Share of Preference rule
# Choice Rule 3: Logit Choice rule

cat("3. Estimating market shares(MS) for alternatives (no comp.)...")

# Now we can estimate market shares for alternative products competing against one another
ourMarketShares0 = Estimate_Market_Shares(alternatives,pws)
row.names(ourMarketShares0) = names(alternatives)

# print and plot the results
ourMarketShares0

#Plot_MS_rule1() calculates market shares using First Choice Rule
#Plot_MS_rule2() calculates market shares using Preference Share Rule
#Plot_MS_rule3() calculates market shares using Logit Choice Rule

# # Plotting commented out for the function
# Plot_MS_rule1(ourMarketShares0, "Only for our alternatives in the absence of competition")
# Plot_MS_rule2(ourMarketShares0, "Only for our alternatives in the absence of competition")
# Plot_MS_rule3(ourMarketShares0, "Only for our alternatives in the absence of competition")

cat("Done!\n")

cat("4. Estimating MS for competitors (no alt.)...")

# Let's also estimate market shares for each alternative product in the face of existing competition

# Load competitor product profiles


# First estimate current market shares of competitors:

competitionset = competitors
# Now we can estimate market shares
competitorShares = Estimate_Market_Shares(competitionset,pws)
row.names(competitorShares) = names(competitionset)
# print and plot the results
competitorShares

# # Plotting commented out for the function
# Plot_MS_rule3(competitorShares, "Only for our competitors before we enter the market")
cat("Done!\n")
cat("5. Estimating MS for alternatives among competitors...")
# Now Add the new product alternatives to the mix of competitors one by one and estimate their market share
# in the presence of competitors.
#
# Specify which alternative product from alternatives you'd like to test against the competitors
# using the for loop. To simplify the Tutorial, I have commented out the parts that show
# the market shares using the first two choice rules and only display the results from the
# Logit choice rule.

# # Plotting commented out for the function
# for (j in 1:ncol(alternatives)){
#   competitionset = cbind(competitors,alternatives[,j])
#   names(competitionset)[dim(competitionset)[2]] = names(alternatives)[j]
#   ourMarketShares = Estimate_Market_Shares(competitionset,pws)
#   row.names(ourMarketShares) = names(competitionset)
#   ourMarketShares
#   # Plot_MS_rule1(ourMarketShares)
#   # Plot_MS_rule2(ourMarketShares)
#   Plot_MS_rule3(ourMarketShares, paste("when we launch Alternative", j ," \n against the current competitors"))
#   # readline(prompt="Press [enter] to see the next Market Share Chart")
# }
cat("Done!\n")

cat("6. Estimating MS for all potential products...")


################# Find the optimal product design from all possible designs and partial utility estimates ####
# First create a list of all attributes.

Attributes = list()
for (i in 1:(ncol(DesignTable)-1)){
  xxx = DesignTable[,i+1]
  xxx = xxx[xxx !=""]
  xxx = xxx[!is.na(xxx)]
  Attributes[[i]] = xxx
}

All_combinations = expand.grid(Attributes)
All_products = data.frame(t(All_combinations))
names(All_products) = paste0(rep("product",ncol(All_products)),1:ncol(All_products))

All_products[,1:5]

# Now let's plot the top 5 products with highest market shares
# the second argument in the function below is the rule type

MS = Estimate_Market_Shares(All_products,pws)
cat("Done!\n")

cat("7. Finding optimal product(OP) in the market of all products...")

utils::head(MS)
dim(MS)

# Sort market shares for each choice rule from highest to lowest
MS1 = MS[order(MS$MS_FirstChoice, decreasing = TRUE),]
MS2 = MS[order(MS$MS_PreferenceShare, decreasing = TRUE),]
MS3 = MS[order(MS$MS_Logit, decreasing = TRUE),]

# select the 100 products with the highest market shares for the logit rule
MS3_50 = MS3[1:50,]

# select the products with the top 5 market shares for each of the three choice rules
MS1 = MS1[1:5,]
MS2 = MS2[1:5,]
MS3 = MS3[1:5,]

# Plot market shares for best products and identify them

# # Plotting commented out for the function
# Plot_MS_rule3(MS3_50, paste("For the top 50 products in the design space \n when all",dim(All_products)[2],"products are launched."))

# # Plotting commented out for the function
# Plot_MS_rule1(MS1, paste("For the top 5 products in the design space \n when all",dim(All_products)[2],"products are launched."))
All_products[,which(colnames(All_products) %in% row.names(MS1))]

# # Plotting commented out for the function
# Plot_MS_rule2(MS2, paste("For the top 5 products in the design space \n when all",dim(All_products)[2],"products are launched."))
All_products[,which(colnames(All_products) %in% row.names(MS2))]

# # Plotting commented out for the function
# Plot_MS_rule3(MS3, paste("For the top 5 products in the design space \n when all",dim(All_products)[2],"products are launched."))
All_products[,which(colnames(All_products) %in% row.names(MS3))]


# Which product maximizes market share when using the First Choice rule?
which(MS$MS_FirstChoice==max(MS$MS_FirstChoice))
# What are its attribute levels or features?
data.frame(All_products[,which(MS$MS_FirstChoice==max(MS$MS_FirstChoice))])
# Which product maximizes market share when using the Preference Share rule?
which(MS$MS_PreferenceShare==max(MS$MS_PreferenceShare))
# What are its attribute levels or features?
All_products[,which(MS$MS_PreferenceShare==max(MS$MS_PreferenceShare))]
# Which product maximizes market share when using the Logit Choice rule?
which(MS$MS_Logit==max(MS$MS_Logit))
# What are its attribute levels or features?
All_products[,which(MS$MS_Logit==max(MS$MS_Logit))]

winners = c(which(MS$MS_FirstChoice==max(MS$MS_FirstChoice)), which(MS$MS_PreferenceShare==max(MS$MS_PreferenceShare)), which(MS$MS_Logit==max(MS$MS_Logit)))
winners = unique(winners)
finalists=All_products[, winners]
row.names(finalists) = colnames(DesignTable)[-1]

cat("Done!\n")

cat("8. Finding OP in the market of competitors...Be patient :) ...")

# Find the optimal product design based on existing competitors.

# It would also be a good idea to test each potential product
# against the existing competitors and see which
# one gains the highest market share.

# The result should be similar to previous findings but this is
# a direct check: We are comparing the market share of each of all our 2592
# products against the existing competitors to see which single
# potential would be the strongest against the current competitors.

# We will only use the Logit choice rule here:
old_Logit_Marketshare = 0
best_Logit_Marketshare = 0
keepthis = 0

# identify the best product and save its index number in keepthis
for (j in 1:dim(All_products)[2]){
  competitionset = cbind(competitors,All_products[,j])
  names(competitionset)[dim(competitionset)[2]] = names(All_products)[j]
  ourMarketShares = Estimate_Market_Shares(competitionset,pws)
  row.names(ourMarketShares) = names(competitionset)
  best_Logit_Marketshare = ourMarketShares[dim(competitionset)[2],2]
  if (best_Logit_Marketshare>old_Logit_Marketshare){
    keepthis = j
    old_Logit_Marketshare = best_Logit_Marketshare
  }
}
keepthis

# Locate the best product and compare its market share agains existing competitors
j = keepthis
All_products[,j]
competitionset = cbind(competitors,All_products[,j])
names(competitionset)[dim(competitionset)[2]] = names(All_products)[j]
bestproductMS = Estimate_Market_Shares(competitionset,pws)
row.names(bestproductMS) = names(competitionset)

cat("Done!\n")

cat("9. Saving results in a pdf file...")

# Plot the results: Which product is the single strongest potential product
# against the current competitors?

# # Plotting commented out for the function
# Plot_MS_rule1(bestproductMS, paste("For the STRONGEST potential product out of all",dim(All_products)[2],"products \n against the current competitors"))
# Plot_MS_rule2(bestproductMS, paste("For the STRONGEST potential product out of all",dim(All_products)[2],"products \n against the current competitors"))
# Plot_MS_rule3(bestproductMS, paste("For the STRONGEST potential product out of all",dim(All_products)[2],"products \n against the current competitors"))

mytablecolors = c("#e5f5e0","#fff7dc")
mytheme = gridExtra::ttheme_minimal(
  core=list(bg_params = list(fill =  mytablecolors, col=NA),
            fg_params=list(fontface=3)),
  colhead=list(fg_params=list(col="navyblue", fontface=4L)),
  rowhead=list(fg_params=list(col="black", fontface=3L)))
# (Optional) In the above theme, we can use fill = hsv(0.61,seq(0.1,1,length.out = 25), 0.99,0.6) to paint the table with shades of blue

# Show the segment sizes table with the new formatting
# # Plot Outputs commented out for the function
# gridExtra::grid.arrange(gridExtra::tableGrob(finalists, theme=mytheme))


################# save results in a pdf file ############

# Decide what to call your file name; make sure to put .pdf at the end of the name
filename =  "! Results_Conjoint_Analysis.pdf"

# If the tables are nod displayed properly in the pdf pages, change the
# value for "larger" below to 1.5, 1.7, 1.8, 2, or larger values for larger
# page sizes
# larger =  1

# pdf(filename, height=larger*8.5, width=larger*11)
suppressWarnings(res7 <- try(grDevices::pdf(filename, height=larger*8.5, width=larger*11), silent = TRUE))


# # Create a theme for formatting our tables
# if (!require(gridExtra)) install.packages("gridExtra")
# library(gridExtra)
mytablecolors = c("#e5f5e0","#fff7dc")
mytheme = gridExtra::ttheme_minimal(
  core=list(bg_params = list(fill =  mytablecolors, col=NA),
            fg_params=list(fontface=3)),
  colhead=list(fg_params=list(col="navyblue", fontface=4L)),
  rowhead=list(fg_params=list(col="black", fontface=3L)))
# (Optional) In the above theme, we can use fill = hsv(0.61,seq(0.1,1,length.out = 25), 0.99,0.6) to paint the table with shades of blue

# Tables
gridExtra::grid.arrange(gridExtra::tableGrob(alts_with_names, theme=mytheme))
gridExtra::grid.arrange(gridExtra::tableGrob(comps_with_names, theme=mytheme))

# Plots
Plot_MS_rule1(ourMarketShares0, "Only for our alternatives in the absence of competition")
Plot_MS_rule2(ourMarketShares0, "Only for our alternatives in the absence of competition")
Plot_MS_rule3(ourMarketShares0, "Only for our alternatives in the absence of competition")
Plot_MS_rule3(competitorShares, "Only for our competitors before we enter the market")


for (j in 1:ncol(alternatives)){
  competitionset = cbind(competitors,alternatives[,j])
  names(competitionset)[dim(competitionset)[2]] = names(alternatives)[j]
  ourMarketShares = Estimate_Market_Shares(competitionset,pws)
  row.names(ourMarketShares) = names(competitionset)
  ourMarketShares
  # Plot_MS_rule1(ourMarketShares)
  # Plot_MS_rule2(ourMarketShares)
  Plot_MS_rule3(ourMarketShares, paste("when we launch Alternative", j ," \n against the current competitors"))
  # readline(prompt="Press [enter] to see the next Market Share Chart")
}

# Plot top products
Plot_MS_rule3(MS3_50, paste("For the top 50 products in the design space \n when all",dim(All_products)[2],"products are launched."))

Plot_MS_rule1(MS1, paste("For the top 5 products in the design space \n when all",dim(All_products)[2],"products are launched."))

Plot_MS_rule2(MS2, paste("For the top 5 products in the design space \n when all",dim(All_products)[2],"products are launched."))

Plot_MS_rule3(MS3, paste("For the top 5 products in the design space \n when all",dim(All_products)[2],"products are launched."))





# Plot the results: Which product is the single strongest potential product
# against the current competitors?

Plot_MS_rule1(bestproductMS, paste("For the STRONGEST potential product out of all",dim(All_products)[2],"products \n against the current competitors"))

Plot_MS_rule2(bestproductMS, paste("For the STRONGEST potential product out of all",dim(All_products)[2],"products \n against the current competitors"))

Plot_MS_rule3(bestproductMS, paste("For the STRONGEST potential product out of all",dim(All_products)[2],"products \n against the current competitors"))

gridExtra::grid.arrange(gridExtra::tableGrob(finalists, theme=mytheme))

grDevices::dev.off()
if (!is.null(res7)) cat(" ***Failed!*** :\"-( \n")
if (is.null(res7)) cat("Done! :D \n")

cat(" ...............................................................\n\n")




if (!is.null(res7)){
  cat("\n ERROR:\n The analysis was performed, but we were not able to\n save the results in \"! Results_Conjoint_Analysis.pdf\"")
  cat(" \n This is probably due to a PDF file with the same name being open.\n")
  cat(" Make sure you close that file, and then run the previous line of code again.")
  
} else {
  cat("\n Conjoint Analysis was performed on these data!\n")
  cat("\n Results have been saved in a file named: \"! Results_Conjoint_Analysis.pdf\"\n")
  cat(" You can find the result file in this folder:\n ")
  cat(as.character(getwd()))
  cat(" \n\n ")
  cat(" If you see any warnings below, simply disregard them.\n\n ")
}





}
1moein/listentodata documentation built on Nov. 14, 2024, 5:35 p.m.