Session 6 covers two main ways to map perceptual data - (i) using the attribute ratings (AR) method to create p-maps and joint-space maps (JSMs), and (ii) using the overall similarity (OS) approach to create multidimensional scaling (MDS) maps.
We also saw some 101 stuff on positioning, definitional terms, common positioning strategies etc. The point was to get you thinking on how the mapping process could throw insights onto positioning in general, which strategy to adopt based on what criteria etc.
OK, next, what will follow is the code and snapshots of the plots that emerge from the classwork examples I did. Again, you are strongly encouraged to replicate the classwork examples at home. Copy-paste a only a few lines of code at a time after reading the comments next to each line of code.
{P.S.- the statements following a '#' are for documentation purposes only and aren't executed}.So, without further ado, let us start right away:
##########################################
1. Simple Data Visualization using biplots: USArrests example.
We use USArrests data (inbuilt R dataset) to see how it can be visualized in 2 dimensions. Just copy-paste the code below onto the R console [Hit 'enter' after the last line]. Need to install package "MASS". Don't reinstall if you have already installed it previously. A package once installed lasts forever.
rm(list = ls()) # clear workspace install.packages("MASS") # install MASS package mydata = USArrests # USArrests is an inbuilt dataset pc.cr = princomp(mydata, cor=TRUE) # princomp() is core func summary(pc.cr) # summarize the pc.cr object biplot(pc.cr) # plot the pc.cr object abline(h=0); abline(v=0) # draw horiz and vertical axes |
2. Code for making Joint Space maps:
I have coded a user-defined function called JSM in R. You can use it whenever you need to make joint space maps provided just by invoking the function. All it requires to work is a perceptions table and a preference rating table. First copy-paste the entire block of code below onto your R console. Those interested in reading the code, pls copy-paste line-by-line. I have put explanations in comments ('#') for what the code is doing.
## --- Build func to run simple perceptual maps --- ## JSM = function(inp1, prefs){ #JSM() func opens
# inp1 = perception matrix with row and column headers par(pty="s") # set square plotting region fit = prcomp(inp1, scale.=TRUE) # extract prin compts plot(fit$rotation[,1:2], # use only top 2 prinComps type ="n", xlim=c(-1.5,1.5), ylim=c(-1.5,1.5), # plot parms main ="Joint Space map - Home-brew on R") # plot title abline(h=0); abline(v=0) # build horiz and vert axes attribnames = colnames(inp1); brdnames = rownames(inp1) # -- insert attrib vectors as arrows -- for (i1 in 1:nrow(fit$rotation)){ arrows(0,0, x1 = fit$rotation[i1,1]*fit$sdev[1], y1 = fit$rotation[i1,2]*fit$sdev[2], col="blue", lwd=1.5); text(x = fit$rotation[i1,1]*fit$sdev[1], y = fit$rotation[i1,2]*fit$sdev[2], labels = attribnames[i1],col="blue", cex=1.1)} # --- make co-ords within (-1,1) frame --- # fit1=fit; fit1$x[,1]=fit$x[,1]/apply(abs(fit$x),2,sum)[1] fit1$x[,2]=fit$x[,2]/apply(abs(fit$x),2,sum)[2] points(x=fit1$x[,1], y=fit1$x[,2], pch=19, col="red") text(x=fit1$x[,1], y=fit1$x[,2], labels=brdnames, col="black", cex=1.1) # --- add preferences to map ---# k1 = 2; #scale-down factor pref = data.matrix(prefs)# make data compatible pref1 = pref %*% fit1$x[,1:2]; for (i1 in 1:nrow(pref1)){ segments(0, 0, x1 = pref1[i1,1]/k1, y1 = pref1[i1,2]/k1, col="maroon2", lwd=1.25); points(x = pref1[i1,1]/k1, y = pref1[i1,2]/k1, pch=19, col="maroon2"); text(x = pref1[i1,1]/k1, y = pref1[i1,2]/k1, labels = rownames(pref)[i1], adj = c(0.5, 0.5), col ="maroon2", cex = 1.1)} # voila, we're done! # } # JSM() func ends
|
3. OfficeStar MEXL example done on R
Goto LMS folder 'Session 6 files'. The file 'R code officestar.txt' contains the code (which I've broken up into chunks and annotated below) and the files 'officestar data1.txt' and 'officestar pref data2.txt' contain the average perceptions or attribute table and preferences table respectively.
Step 3a: Read in the attribute table into 'mydata'.
# -- Read in Average Perceptions table -- # mydata = read.table(file.choose(), header = TRUE) mydata = t(mydata) #transposing to ease analysis mydata #view the table read # extract brand and attribute names # brdnames = rownames(mydata); attribnames = colnames(mydata) |
Step 3b: Read into R the preferences table into 'prefs'.
# -- Read in preferences table -- # pref = read.table(file.choose()) dim(pref) #check table dimensions pref[1:10,] #view first 10 rows |
Step 3c: Run Analysis
# creating empty pref dataset pref0 = pref*0; rownames(pref0) = NULL JSM(mydata, pref0) # p-map without prefs information |
The above code will generate a p-map (without the preference vectors). Should look like the image below (click for larger image):
However, to make true joint-space maps (JSMs), wherein the preference vectors are overlaid atop the p-map, run the one line code below:
JSM(mydata, pref) |
That is it. That one function call executes the entire JSM sequence. The result can be seen in the image below.
Again, the JSM function is generic and can be applied to *any* dataset in the input format we just saw to make joint space maps from. Am sure you'll leverage the code for animating your project datasets. Let me or Ankit know in case any assistance is needed in this regard.
4. Session 2 survey Data on firm Perceptions:
Lookup LMS folder 'session 6 files'. Save the data and code files to your machine. Data files are 'courses data.txt' for the raw data on perceptions and courses data prefs.txt' for the preference data with student names on it. Now let the games begin.
# read in data mydata = read.table(file.choose()) # 'courses data.txt' head(mydata) # I hard coded attribute and brand names attrib.names = c("Brd.Equity", "career.growth.oppty", "roles.challenges", "remuneration", "overall.preference") brand.names = c("Accenture", "Cognizant", "Citi", "Facebook", "HindLever") |
Should you try using your project data or some other dataset, you'll need to enter the brand and attribute names for that dataset in the same order in which they appear in the dataset, separately as given above.I then wrote a simple function, titled 'pmap.inp()' to denote "p-map input", to transform the raw data into a brands-attributes average peceptions table. Note that the below code is specific to the last set of columns being the preferences data.
# construct p-map input matrices using pmap.inp() func pmap.inp = function(mydata, attrib.names, brand.names){ #> pmap.inp() func opens a1 = NULL for (i1 in 1:length(attrib.names)){ start = (i1-1)*length(brand.names)+1; stop = i1*length(brand.names); a1 = rbind(a1, apply(mydata[,start:stop], 2, mean)) } # i1 loop ends rownames(a1) = attrib.names; colnames(a1) = brand.names a1 } # pmap.inp() func ends a1 = pmap.inp(mydata, attrib.names, brand.names)
|
The above code should yield the average perceptions table that will look something like this:
And now, we're ready to run the analysis. First the p-map without the prefences and then the full JSM.
# now run the JSM func on data percep = t(a1[2:nrow(a1),]); percep # prefs = mydata[, 1:length(brand.names)] prefs = read.table(file.choose(), header = TRUE) # 'courses data prefs.txt' prefs1 = prefs*0; rownames(prefs1) = NULL # null preferences doc created JSM(percep, prefs1) # for p-map sans preferences |
Should produce the p-map below: (click for larger image)
And the one-line JSM run:
JSM(percep, prefs) # for p-map with preference data |
Should produce the JSM below:
Follow the rest of the HW code given to run segment-wise JSMs in the same fashion.
5. Running JSMs for individuals: (Useful for your HW)
One of your session 6 HW components will require you to make individual level JSMs and compare then with the class average JSMs. Use the following code to get that done:
### --- For Session 6 HW --- ### # Use code below to draw individual level JSM plots: student.name = c("Sachin") # say, student's name is Sachin
# retain only that row in the raw data which has name 'Sachin'
# run the pmap.inp() func to build avg perceptions table percep.test = t(a1.test[1:(nrow(a1.test)-1),]);
# introduce a small perturbation lest matrix not be of full rank prefs.test = prefs[(rownames(prefs) == student.name),]; prefs.test
# run analysis on percep.test and prefs.test |
This is what I got as Mr Sachin's personal JSM:
More generally, change the student name to the one you want and run the above code.
5. Running MDS code with Car Survey Data:
In LMS folder 'session 6 files', the data are in 'mds car data raw v1.txt'. Read it in and follow the instructions here.
# --------------------- # ### --- MDS code ---- ### # --------------------- # rm(list = ls()) # clear workspace mydata = read.table(file.choose(), header = TRUE) # 'mds car data raw.txt' dim(mydata) # view dimension of the data matrix brand.names = c("Hyundai", "Honda", "Fiat", "Ford", "Chevrolet", "Toyota", "Nissan", "TataMotors", "MarutiSuzuki")
|
Note that I have hard-coded the brand names into 'brand.names' If you want to use this MDS code for another dataset (for your project, say) then you'll have to likewise hard-code the brand.names in.Next, I defined a function called run.mds() that takes as input the raw data and the brand names vector, runs the analysis and outputs the MDS map. Cool, or what..
### --- copy-paste MDS func below as a block --- ### ### -------------block starts here ---------------- ### run.mds = function(mydata, brand.names){ # build distance matrix # k = length(brand.names); dmat = matrix(0, k, k); for (i1 in 1:(k-1)){ a1 = grepl(brand.names[i1], colnames(mydata));
for (i2 in (i1+1):k){a2 = grepl(brand.names[i2], colnames(mydata)); a3 = a1*a2; a4 = match(1, a3); dmat[i1, i2] = mean(mydata[, a4]); dmat[i2, i1] = dmat[i1, i2] } #i2 ends } # i1 ends colnames(dmat) = brand.names; rownames(dmat) = brand.names ### --- run metric MDS --- ### d = as.dist(dmat) # Classical MDS into k dimensions # fit = cmdscale(d,eig=TRUE, k=2) # cmdscale() is core MDS func fit # view results # plot solution #
x = fit$points[,1]; plot(x, y, xlab="Coordinate 1", ylab="Coordinate 2", main="Metric MDS", xlim = c(floor(min(x)), ceiling(max(x))), ylim = c(floor(min(y)), ceiling(max(y))), type="p",pch=19, col="red"); text(x, y, labels = rownames(fit$points), cex=1.1, pos=1); abline(h=0); abline(v=0)# horiz and vertical lines drawn } # run.mds func ends ### ---------block ends here-------------------- ### |
Time now to finally invoke the run.mds func and get the analysis results:
# run MDS on raw data (before segmenting) run.mds(mydata, brand.names) |
The resulting MDS map looks like this:
Sudhir
This comment has been removed by the author.
ReplyDeleteHey Professor, I was trying the JSM function code. It is throwing this error:
ReplyDelete+ pref1 = pref %*% fit1$x[,1:2]for (i1 in 1:nrow(pref1)){
Error: unexpected 'for' in:
"
pref1 = pref %*% fit1$x[,1:2]for"
>
Please suggest changes.
Thanks,
Vineet
This comment has been removed by the author.
ReplyDeleteHey Professor,
ReplyDeleteAfter playing with the code a bit, i figured out the problem. The JSM function code in 5th line from the last is mentioned in the post as :
"pref1 = pref %*% fit1$x[,1:2]for (i1 in 1:nrow(pref1)){"
It should be changed to:
"pref1 = pref %*% fit1$x[,1:2] <- Need a line break here
for (i1 in 1:nrow(pref1)){"
Another change in the same function:
“ # --- make co-ords within (-1,1) frame --- #
fit1=fitfit1$x[,1]=fit$x[,1]/apply(abs(fit$x),2,sum)[1] “
should be changed to:
“ # --- make co-ords within (-1,1) frame --- #
fit1=fit
fit1$x[,1]=fit$x[,1]/apply(abs(fit$x),2,sum)[1] “
Also, the last line of the function need one more terminating curly brace }.
The last line should be changed from :
“ text(x = pref1[i1,1]/k1, y = pref1[i1,2]/k1, labels = rownames(pref)[i1], adj = c(0.5, 0.5), col ="maroon2", cex = 1.1)} “
to:
“ text(x = pref1[i1,1]/k1, y = pref1[i1,2]/k1, labels = rownames(pref)[i1], adj = c(0.5, 0.5), col ="maroon2", cex = 1.1)}}”
In point 5.
MDS function. The second line of code should be changed from
“ # build distance matrix # k = length(brand.names)”
to:
“ # build distance matrix #
k = length(brand.names) “
Looks to me a blogspot issue wherein the line breaks copied from the R Console are getting lost while publishing.
Thanks,
Vineet
Hi Vineet,
ReplyDeleteThanks. Yes, line breaks don't automate in HTML. will update the code pasted here.
However, you're advised to instead use the R code Classwork.txt file I've put up on LMS rather than the one here. That one works fine, we've tested it before sending it out.
Hope that helps.
Sudhir
Good find Vineet.
ReplyDeleteProfessor,
I am not able to locate the Classwork.txt anywhere on LMS. Kindly share the URL please.
Hi Srikant,
DeleteAnkit handles all LMS related work. Let me call and ask him.
Sudhir
This comment has been removed by the author.
ReplyDeleteOK> You deleted before I could reply. But hopefully it's because you figured a way out of the problem.
DeleteSudhir