20
$\begingroup$

I have 82 respondents in 2 groups (43 in Group A and 39 in Group B) that completed a survey of 65 Likert questions each ranging from 1 – 5 (strongly agree - strongly disagree). I therefore have a dataframe with 66 columns (1 for each question + 1 indicating group allocation) and 82 rows (1 for each respondent).

Using R or SPSS does anyone know a nice way to visualize this data.

I need something like this: enter image description here
(from Jason Bryer)

But I cannot get the initial section of code to work. Alternatively, I found really good examples of how to visualize Likert data from a previous Cross Validated post: Visualizing Likert Item Response Data but there are no guides or instructions on how to create these centered count graphs or stacked barcharts using R or SPSS.

$\endgroup$
5
  • 1
    $\begingroup$ Hi Adam, to clarify further, were you wanting to use the visualizations to show differences between the groups? If so, that is not a recommended method. $\endgroup$
    – Michelle
    Commented Mar 23, 2012 at 0:30
  • $\begingroup$ Jason Bryer's package didn't use to work for me, but I think he updated it, and it's working beautifully right now. I also added a pull-request with an extra feature to store column names as attributes, and groups. Using this, I am easily able to visualize a 45 question Likert questionnaire split into groups, even split against another variable if I choose to. (I output using knitr, so it ends up with as lot's of sub plots on a website, not one gigantic plot). I did a detailed writeup here: reganmian.net/blog/2013/10/02/… $\endgroup$ Commented Oct 2, 2013 at 21:55
  • $\begingroup$ Just FYI, for those of you reading these answers in the future, it looks like some of the features and functionality of irutils regarding likert data has been moved into the Likert R package (see CRAN here). $\endgroup$ Commented Oct 15, 2013 at 14:49
  • $\begingroup$ The link bryer.org/2011/visualizing-likert-items appears to be broken. A correction or replacement would be welcome. $\endgroup$
    – Nick Cox
    Commented Feb 23, 2018 at 14:40
  • 1
    $\begingroup$ This kind of question -- with its strong focus on specific code -- is less welcome in 2018 than it was in 2012. Regardless of that, some cross-references for anyone interested by this are stats.stackexchange.com/questions/56322/… and stats.stackexchange.com/questions/148554/… $\endgroup$
    – Nick Cox
    Commented Feb 23, 2018 at 14:47

4 Answers 4

31
$\begingroup$

If you really want to use stacked barcharts with such a large number of items, here are two possible solutions.

Using irutils

I came across this package some months ago.

As of commit 0573195c07 on Github, the code won't work with a grouping= argument. Let's go for Friday's debugging session.

Start by downloading a zipped version from Github. You'll need to hack the R/likert.R file, specifically the likert and plot.likert functions. First, in likert, cast() is used but the reshape package is never loaded (although there's an import(reshape) instruction in the NAMESPACE file). You can load this yourself beforehand. Second, there's an incorrect instruction to fetch items labels, where a i is dangling around line 175. This has to be fixed as well, e.g. by replacing all occurrences of likert$items[,i] with likert$items[,1]. Then you can install the package the way you are used to do on your machine. On my Mac, I did

% tar -czf irutils.tar.gz jbryer-irutils-0573195
% R CMD INSTALL irutils.tar.gz

Then, with R, try the following:

library(irutils)
library(reshape)

# Simulate some data (82 respondents x 66 items)
resp <- data.frame(replicate(66, sample(1:5, 82, replace=TRUE)))
resp <- data.frame(lapply(resp, factor, ordered=TRUE, 
                          levels=1:5, 
                          labels=c("Strongly disagree","Disagree",
                                   "Neutral","Agree","Strongly Agree")))
grp <- gl(2, 82/2, labels=LETTERS[1:2]) # say equal group size for simplicity

# Summarize responses by group
resp.likert <- likert(resp, grouping=grp)

That should just work, but the visual rendering will be awful because of the high number of items. It works without grouping (e.g., plot(likert(resp))), though.

enter image description here

I would thus suggest to reduce your dataset to smaller subsets of items. E.g., using 12 items,

plot(likert(resp[,1:12], grouping=grp))

I get a 'readable' stacked barchart. You can probably process them afterwards. (Those are ggplot2 objects, but you won't be able to arrange them on a single page with gridExtra::grid.arrange() because of readability issue!)

enter image description here

Alternative solution

I would like to draw your attention on another package, HH, that allows to plot Likert scales as diverging stacked barcharts. We could reuse the above code as shown below:

resp.likert <- likert(resp)
detach(package:irutils)
library(HH)
plot.likert(resp.likert$results[,-6]*82/100, main="")

but that will complicate things a bit because we need to convert frequencies to counts, subset the likert object produced by irutils, detach package, etc. So let's start again with fresh (counts) statistics:

plot.likert(t(apply(resp, 2, table)), main="", as.percent=TRUE,
            rightAxisLabels=NULL, rightAxis=NULL, ylab.right="", 
            positive.order=TRUE)

enter image description here

To use a grouping variable, you'll need to work with an array of numerical values.

# compute responses frequencies separately by grp
resp.array <- array(NA, dim=c(66, 5, 2))
resp.array[,,1] <- t(apply(subset(resp, grp=="A"), 2, table))
resp.array[,,2] <- t(apply(subset(resp, grp=="B"), 2, table))
dimnames(resp.array) <- list(NULL, NULL, group=levels(grp))
plot.likert(resp.array, layout=c(2,1), main="")

This will produce two separate panels, but it fits on a single page.

enter image description here

Edit 2016-6-3

  1. As of now likert is available as separate package.
  2. You do not need reshape library or detach both irutils and reshape
$\endgroup$
5
  • $\begingroup$ The last plot reminds me of population pyramids. We should get some real data to see how they work "in the wild", with some data that is not so orderly. I'll admit they are eye catching and pretty though. $\endgroup$
    – Andy W
    Commented Mar 23, 2012 at 17:10
  • $\begingroup$ @Andy That's the case, indeed. See HH::as.pyramidLikert. $\endgroup$
    – chl
    Commented Mar 23, 2012 at 17:12
  • 1
    $\begingroup$ +1, library(HH) is definitely the way to go. But something has gone wrong with your second last plot in the ordering of agree/disagree etc. $\endgroup$ Commented Mar 1, 2013 at 22:47
  • $\begingroup$ @PeterEllis Yup, it looks like response categories are in the wrong order, indeed. (The order of labels was lost when tabulating the data, and table names are arranged following lexicographic order.) For a quick hack, we can just replace t(apply(resp, 2, table)) with t(apply(resp, 2, table))[,levels(resp[,1])]. And +1 to you too! $\endgroup$
    – chl
    Commented Mar 2, 2013 at 11:01
  • $\begingroup$ @chl I've tried duplicating your HH graphics (2022), but cannot seem to do so. I've tried version 3.1-47 of HH built under R version 4.0.5. Can you confirm whether your method still works? If so, is there additional code not shown? If not, would you mind updating your answer? Thanks! $\endgroup$ Commented Jul 15, 2022 at 14:43
7
$\begingroup$

I started to write a blog post about recreating many of the charts in the post you mention (Visualizing Likert Item Response Data) in SPSS so I suppose this will be good motivation for finishing it.

As Michelle notes, the fact that you have groups is a new twist compared to the previous questions. And while groups can be taken into account using the stacked bar graphs, IMO they are much more easily incorporated into the dot plot example in chl's original post. I've included the SPSS code to generate this at the end of the post, essentially it entails knowing how to reshape your data in the appropriate format to generate said plot (annotation provided in the code to hopefully clear some of that up). Here I used some redundant encoding (color and shape) to distinguish points coming from the two groups, and made the points semi-transparent so you can tell when they overlap (another option would be to dodge the points when they overlap).

Figure 1: Dot Plots by Group

Why is this better than the stacked bar charts? The stacked bar charts encode information in the length of the bars. When you try to make comparisons between lengths of bars, either within the same axis category or between panels, the stacking precludes the bars from having a common scale. For an example, I have provided an image in Figure 2 in which two bars are placed in a plot in which their beginning location is different, which bar is the wider one (along the horizontal axis)?

Figure 2: Bars Without a Common Scale

Compare that to the plot in Figure 3 below, in which the two bars (of the same length) are plotted from the same beginning point. I've intentionally made the task difficult, but you should be able to tell which one is longer.

Figure 3: Bars With a Common Scale

Stacked bar charts are essentially doing what is displayed in Figure 2. Dot plots can be considered more similar to what is displayed in Figure 3, just replace the bar with a dot at the end of the bar.

I'm not going to say don't generate any particular chart for exploratory data analysis, but I would suggest avoiding the stacked bar charts when using so many categories. The dot plots aren't a panacea either, but I believe making comparisons between panels with the dot plots is much easier than with the stacked bar charts. Consider some of the advice I provide on my blog post here for tables as well, try to order and/or seperate the charts into meaningful categories, and make sure that items you would want to look at in tandem are closer together in the charts. While some of the plotting methods may scale well to many questions (the categorical heat maps are an example), without sorting it will still be difficult to identify any meaningul patterns (besides obvious outliers).

A note on using SPSS. SPSS can generate any of the previous linked to charts, although it frequently entails knowing how to shape your data (the same is true of ggplot, but people have been developing packages to essentially do the reshaping for you). To understand how SPSS's GPL language works better I would actually suggest reading Hadley Wickham's book on ggplot2 in the Use R! series. It lays out the grammar necessary to understand how SPSS's GPL work, and is a much easier read than the GPL programming manual that comes with SPSS! If you have any questions about generating specific charts in SPSS it would be best to ask one question for one chart (I've talked enough here as is!) I will update this answer with a link though if I ever get around to making my blog post replicating some of the other charts. For a proof of concept of the heat maps or fluctuation plots you can see another blog post of mine, Some example Corrgrams in SPSS.

SPSS code used to generate Figure 1

****************************************.
input program. */making fake data similar to yours.
loop #i = 1 to 82.
compute case_num = #i.
end case.
end loop.
end file.
end input program.
execute.
dataset name likert.

*making number in groups.
compute group = 1.
if case_num > 43 group = 2.
value labels group
1 'A'
2 'B'.

*this makes 5 variables with categories between 0 and 5 (similar to Likert data with 5 categories plus missing data).
vector V(5).
do repeat V = V1 to V5.
compute V = TRUNC(RV.UNIFORM(0,6)).
end repeat.
execute.

value labels V1 to V5
0 'missing'
1 'very disagree'
2 'disagree'
3 'neutral'
4 'agree'
5 'very agree'.
formats case_num group V1 to V5 (F1.0).
*****************************************.

*Because I want to panel by variable, I am going to reshape my data so all of the "V" variables are in one column (stacking them in long format).
varstocases
/make V from V1 to V5
/index orig (V).

*I am going to plot the points, so I aggregate that information (you could aggregate total counts as well if you wanted to plot percentages.
DATASET DECLARE agg_lik.
AGGREGATE
  /OUTFILE='agg_lik'
  /BREAK=orig V group
  /count_lik=N.
dataset activate agg_lik.


*now the fun part, generating the chart.
*The X axis, dim(1) is the count of likert responses within each category for each original question.
*The Y axis, dim(2) is the likert responses, and the third axis is used to panel the observations by the original questions, dim(4) here beacause I want to panel
by rows instead of columns.
DATASET ACTIVATE agg_lik.
* Chart Builder.
GGRAPH
  /GRAPHDATASET NAME="graphdataset" VARIABLES=count_lik V group orig 
    MISSING=LISTWISE REPORTMISSING=NO
  /GRAPHSPEC SOURCE=INLINE.
BEGIN GPL
  SOURCE: s=userSource(id("graphdataset"))
  DATA: count_lik=col(source(s), name("count_lik"))
  DATA: V=col(source(s), name("V"), unit.category())
  DATA: group=col(source(s), name("group"), unit.category())
  DATA: orig=col(source(s), name("orig"), unit.category())
  GUIDE: axis(dim(1), label("Count"))
  GUIDE: axis(dim(2))
  GUIDE: axis(dim(4))
  GUIDE: legend(aesthetic(aesthetic.color.exterior), label("group"))
  GUIDE: text.title(label("Figure 1: Dot Plots by Group"))
  SCALE: cat(aesthetic(aesthetic.color.exterior), include("1", "2"))
  SCALE: cat(aesthetic(aesthetic.shape), map(("1", shape.circle), ("2", shape.square)))
  ELEMENT: point(position(count_lik*V*1*orig), color.exterior(group), color.interior(group), transparency.interior(transparency."0.7"), size(size."8px"), shape(group))
END GPL.
*The "SCALE: cat" statements map different shapes which I use to assign to the two groups in the plot, and I plot the interior of the points as partially transparent.
*With some post hoc editing you should be able to make the chart look like what I have in the stats post.
****************************************.
$\endgroup$
1
  • $\begingroup$ Strong plus from me for politely but penetratingly discussing the deficiencies of stacked bar charts, which are easy to understand in principle but often much less easy to decode in practice. $\endgroup$
    – Nick Cox
    Commented Feb 23, 2018 at 14:44
5
$\begingroup$

Oh well, I came up with the code before you clarified. Should have waited but thought I should post it up so that anyone who comes here can reuse this code.

Dummy data for visualizing

# Response for http://stats.stackexchange.com/questions/25109/visualizing-likert-responses-using-r-or-spss
# Load libraries
library(reshape2)
library(ggplot2)

# Functions
CreateRowsColumns <- function(noofrows, noofcolumns) {
createcolumnnames <- paste("Q", 1:noofcolumns, sep ="")
df <- sapply(1:noofcolumns, function(i) assign(createcolumnnames[i], matrix(sample(1:5, noofrows, replace = TRUE))))
df <- sapply(1:noofcolumns, function(i) df[,i] <- as.factor(df[,i]))
colnames(df) <- createcolumnnames
return(df)}

# Generate dummy dataframe
LikertResponse <- CreateRowsColumns(82, 65)
LikertResponse[LikertResponse == 1] <- "Strongly agree"
LikertResponse[LikertResponse == 2] <- "Agree"
LikertResponse[LikertResponse == 3] <- "Neutral"
LikertResponse[LikertResponse == 4] <- "Disagree"
LikertResponse[LikertResponse == 5] <- "Strongly disagree"

Code for heatmap

# Prepare data
LikertResponseSummary <- do.call(rbind, lapply(data.frame(LikertResponse), table))
LikertResponseSummaryPercent <- prop.table(LikertResponseSummary,1)

# Melt data
LikertResponseSummary <- melt(LikertResponseSummary)
LikertResponseSummaryPercent <- melt(LikertResponseSummaryPercent)

# Merge counts with proportions
LikertResponsePlotData <- merge(LikertResponseSummary, LikertResponseSummaryPercent, by = c("Var1","Var2"))

# Plot heatmap!
# Use the "geom_tile(aes(fill = value.y*100), colour = "white")" to control how you want the heatmap colours to map to.
ggplot(LikertResponsePlotData, aes(x = Var2, y = Var1)) +
    geom_tile(aes(fill = value.y*100), colour = "white") +
    scale_fill_gradient(low = "white", high = "steelblue", name = "% of Respondents") +
    scale_x_discrete(name = 'Response') +
    scale_y_discrete(name = 'Questions') +
    geom_text(aes(label = paste(format(round(value.y*100), width = 3), '% (', format(round(value.x), width = 3), ')')), size = 3) 

This is basically a template to the visualising Likert items on a heatmap from Jason Bryon's website.

$\endgroup$
2
  • 1
    $\begingroup$ github.com/jbryer/irutils/blob/master/R/likert.R is the source for the stacked bar charts you want. $\endgroup$
    – RJ-
    Commented Mar 23, 2012 at 4:11
  • $\begingroup$ To clarify, i don't want to compare between groups. Just to present both groups responses in a sophisticated way. This is a great response. Really appreciate it. Thanks. $\endgroup$
    – Adam
    Commented Mar 23, 2012 at 7:31
3
$\begingroup$

@RJ's code produces a plot like this, which is really a table with shaded cells. Its rather busy and a bit tricky to decipher. A plain table without shading might be more effective (and you can put the data in a more meaningful order also).

enter image description here

Of course it depends on what main message you're trying to communicate, but I think this is simpler and a bit easier to make sense of. It also has the questions and responses in a (mostly!) logical order.

    library(stringr)
    LikertResponseSummary$Var1num <- 
      as.numeric(str_extract(LikertResponseSummary$Var1, "[0-9]+"))
    LikertResponseSummary$Var2 <- 
      factor(LikertResponseSummary$Var2, 
      levels =  c("Strongly disagree", "Disagree", "Neutral", "Agree", "Strongly agree"))

ggplot(LikertResponseSummary, 
       aes(factor(Var1num), value, fill = factor(Var2))) + 
       geom_bar(position="fill") +
       scale_x_discrete(name = 'Question', breaks=LikertResponseSummary$Var1num,
                        labels=LikertResponseSummary$Var1) +
       scale_y_continuous(name = 'Proportion') +
       scale_fill_discrete(name = 'Response') +
       coord_flip()

enter image description here

$\endgroup$
1
  • $\begingroup$ Agreed that the chart looks busy. It would be useful however if the questions are grouped in some sort of an order e.g. Q1 - 10 asks about a certain dimension and so on. At a glance if the trends are obvious, the colours would tell. $\endgroup$
    – RJ-
    Commented Mar 23, 2012 at 6:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.