From ggplot to plotly: Likert Plots in R

A short tutorial on how to use geom_segment() to create a Likert-type plot with ggplot, and then convert this to an interactive plotly chart.

Like ggplot, plotly does not easily accomodate Likert-type charts, and for many of the same reasons – namely that negative bar heights make stacking more complicated. This tutorial uses geom_segment() rather than geom_bar() to achieve the desired layout in ggplot.

We’re using the same pre-summarized data from the Arab Barometer III survey as used in this post, and the data can be downloaded here in CSV format. The data concern public opinion on the future economic outlook in eleven Arab countries. Survey respondnets were asked, “What do you think will be the economic situation in your country during the next few years (3-5 years) compared to the current situation?,” with the response options: “Much worse,” “Somewhat worse,” “Almost the same,” “Somewhat better,” or “Much better.”

Here’s the chart in plotly:

First we get the data and load four required packages.


We difference the data from a zero center-point to obtain staring values for our line segments. The center-point for each country is half the neutral category (“Almost the same”). We then order the factor by the sum of negative responses to obtain the classic “cascade” of Likert-type plots. Finally, we multiply the entire data frame by 100 to get the data into a percent format.

#starting coordinates for segments
#order by worse categories
data$Country<-factor(data$Country, levels = data$Country[order(-(data$s.Much.worse))])
#to percents

Next, we melt the data frame into a long format. The first half of the melted data frame contains response values, and the second half of the melted data frame contains the starting values we calcuated previously. We need these to be on the same rows in our data frame, because geom_segment requires starting and ending coordinates for each segment. We use the cbind() function to reshape our data frame as required. We assign nicely formatted levels to our factor. And finally, we define a custom color palette loosly based on the pallete from this website; alternatively, one might use a palette from RColorBrewer.

mdfr <- melt(data, id=c("Country"))
#remove dot in levels
mylevels<-c("Much worse","Somewhat worse","Almost the same","Somewhat better","Much better")
#custom color palette
pal<-c("#DF4949", "#E27A3F", "#BEBEBE","#45B29D", "#334D5C")

The call to ggplot() is rather strait-forward. From each coordinate, we draw a segment of length and thickness size 6. We also specify a custom string that will be used for plotly’s hovertext.

p<-ggplot(data=mdfr) +
  geom_segment(aes(x = Country, y = start, xend = Country, yend = start+value, colour = variable,
  text=paste("Country: ",Country,"<br>Percent: ",value,"%")), size = 6) +
  geom_hline(yintercept = 0, color =c("#646464")) +
  coord_flip() +
  scale_color_manual("Response", labels = mylevels, values = pal, guide="legend") +
  labs(title="", y="Percent",x="") +
  scale_y_continuous(breaks=seq(-100,100,25), limits=c(-100,100)) +
  theme(panel.background = element_rect(fill = "#ffffff"),
        panel.grid.major = element_line(colour = "#CBCBCB"))

The plot looks good enough in ggplot, and after we render this with plotly, we can still add a title and customize many features. It’s best to have a fairly simple ggplot object to pass to plotly, because not all ggplot features are currently supported by plotly. For example, some ggthemes render poorly in plotly, and some options, like a horizontal legend, are not yet available.

This final conversion to plotly requires a free subscription to post, but omit the last line here, and the chart will still render locally in RStudio’s viewer or in a browser. Transform a ggplot object to a plotly object with plotly_build(), and then edit the plotly object as necessary. I found this easiest to do with the pipe oberator (%>%). I added a multiline title, using <br> rather than \n for line breaks. I increased the top margin, because of the multiline title. And, I specified a height and width rather than use the autosize option, because the width of my bars (i.e., line segments) are determined by the size I specified in the previous call to ggplot and will not auto-scale with plotly.

## note: plot not shown here; see top of post
m<-list(l = 100, r=100, b = 100, t = 140)
f <- list(family = "", size = 12, color = "rgba(0,0,0,1)")
p2 %>%
  layout(title="What do you think will be the economic situation in your<br>country during the next few years (3-5 years)<br>compared to the current situation?",
         autosize = FALSE, height=600, width=800, margin=m, font = f, hoverinfo="text", text=text) %>%
plotly_POST("Economic Outlook in Arab Countries")

Dialogue & Discussion