Search code examples
rdataframegraphsurveylikert

How do I plot a neutral centered likert plot in R from percentage value survey data?


To practice with R, I am working on visualising Table 2 from this research paper.

I have managed to get the data as a ggplot 100% stacked barplot with the following code:

ggplot(df_long, aes(fill=answer, y=value, x=statement))+
  geom_bar(position="fill", stat="identity")+
  coord_flip()

I want to plot the data into a likert graph like the one below:

likert plot example

The data only has a three point likert scale, not sure if that matters?

I have tried following along with examples on how to create similar graphics with the likert scale but I'm getting various errors.

Also tried mutating the data to factors but getting an error here too:

> df_long2 <- df_long %>% 
+     mutate_all(
+         funs(factor(case_when(
+             . == 1 ~ "disagree",
+             . == 2 ~ "no opinion",
+             . == 3 ~ "agree",
+             levels = c("disagree", "no opinion", "agree")
+         )))
+     )
Error in `mutate()`:
! Problem while computing `statement = factor(...)`.
Caused by error in `case_when()`:
! Case 4 (`statement == 1 ~ "disagree"`) must be a two-sided formula, not a character vector.
Run `rlang::last_error()` to see where the error occurred.

The data is currently in percentage values of each likert point scale, perhaps I need to expand this?

I know that the statements are very long and would probably need to replace them with shorter variables but for now I'm just trying to plot it nicely and see what it looks like.

I'm a Python programmer but new to R - any help would be appreciated!

-- EDIT:

Adding the output of dput(df_long):


structure(list(statement = c("Wants to be able to offer more intrapartum care", 
"Wants to be able to offer more intrapartum care", "Wants to be able to offer more intrapartum care", 
"Feels sufficiently comptetent", "Feels sufficiently comptetent", 
"Feels sufficiently comptetent", "Discouraged by lack of confidence", 
"Discouraged by lack of confidence", "Discouraged by lack of confidence", 
"Discouraged by lack of facilities", "Discouraged by lack of facilities", 
"Discouraged by lack of facilities", "Discouraged by fear of litigation", 
"Discouraged by fear of litigation", "Discouraged by fear of litigation", 
"Discouraged by rate of remuneration", "Discouraged by rate of remuneration", 
"Discouraged by rate of remuneration", "Discouraged by disruption of personal life", 
"Discouraged by disruption of personal life", "Discouraged by disruption of personal life", 
"Discouraged by attitudes of fellow partners", "Discouraged by attitudes of fellow partners", 
"Discouraged by attitudes of fellow partners", "Discouraged by current workload", 
"Discouraged by current workload", "Discouraged by current workload", 
"Women with uncomplicated pregnancy should be able to book with midwife", 
"Women with uncomplicated pregnancy should be able to book with midwife", 
"Women with uncomplicated pregnancy should be able to book with midwife", 
"Obstetricians should be used primarily during complicated pregnancies", 
"Obstetricians should be used primarily during complicated pregnancies", 
"Obstetricians should be used primarily during complicated pregnancies", 
"Vocational training needs radical alteration", "Vocational training needs radical alteration", 
"Vocational training needs radical alteration", "Women should be able to choose location of birth", 
"Women should be able to choose location of birth", "Women should be able to choose location of birth", 
"The policy of encouraging hospital births cannot be justified on safety grounds", 
"The policy of encouraging hospital births cannot be justified on safety grounds", 
"The policy of encouraging hospital births cannot be justified on safety grounds", 
"There is widespread demand among women for greater choice in type of maternity care they receive", 
"There is widespread demand among women for greater choice in type of maternity care they receive", 
"There is widespread demand among women for greater choice in type of maternity care they receive", 
"There is potential for a damaging demarcation dispute between the professional groups over how labour should be supervised", 
"There is potential for a damaging demarcation dispute between the professional groups over how labour should be supervised", 
"There is potential for a damaging demarcation dispute between the professional groups over how labour should be supervised", 
"Midwives should carry out the routine examination of apparently healthy newborn infants, provided they are well trained in the detection of\ncongenital abnormalities and the subtle signs ofimpending illness", 
"Midwives should carry out the routine examination of apparently healthy newborn infants, provided they are well trained in the detection of\ncongenital abnormalities and the subtle signs ofimpending illness", 
"Midwives should carry out the routine examination of apparently healthy newborn infants, provided they are well trained in the detection of\ncongenital abnormalities and the subtle signs ofimpending illness", 
"The item of service payments to general practitioners for maternity care, as presently operated, should be abandoned and redesigned", 
"The item of service payments to general practitioners for maternity care, as presently operated, should be abandoned and redesigned", 
"The item of service payments to general practitioners for maternity care, as presently operated, should be abandoned and redesigned", 
"New pay system for GPs for maternity care should be heavily weighted towards rewarding those who provide intrapartum care.", 
"New pay system for GPs for maternity care should be heavily weighted towards rewarding those who provide intrapartum care.", 
"New pay system for GPs for maternity care should be heavily weighted towards rewarding those who provide intrapartum care.", 
"It is wrong to remove a woman from a general practitioner list solely because she wishes to have a home confinement or midwifery only care", 
"It is wrong to remove a woman from a general practitioner list solely because she wishes to have a home confinement or midwifery only care", 
"It is wrong to remove a woman from a general practitioner list solely because she wishes to have a home confinement or midwifery only care", 
"GPs should be responsible to have in place arrangements for women to have a home confinement", 
"GPs should be responsible to have in place arrangements for women to have a home confinement", 
"GPs should be responsible to have in place arrangements for women to have a home confinement", 
"Most maternity care should be community based and near to the women's home", 
"Most maternity care should be community based and near to the women's home", 
"Most maternity care should be community based and near to the women's home", 
"GPs who wish to provide care throughout pregnancy, labour, and the puerperium should be enabled to do so", 
"GPs who wish to provide care throughout pregnancy, labour, and the puerperium should be enabled to do so", 
"GPs who wish to provide care throughout pregnancy, labour, and the puerperium should be enabled to do so", 
"Vocational obstetric training at senior house officer level should concentrate on the normal and those aspects of abnormality that can be dealt with by GPs", 
"Vocational obstetric training at senior house officer level should concentrate on the normal and those aspects of abnormality that can be dealt with by GPs", 
"Vocational obstetric training at senior house officer level should concentrate on the normal and those aspects of abnormality that can be dealt with by GPs"
), answer = c("agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree", "agree", "no opinion", "disagree", "agree", "no opinion", 
"disagree"), value = c(23, 11, 66, 28, 8, 64, 49, 11, 40, 33, 
21, 45, 69, 11, 20, 39, 29, 33, 75, 8, 17, 37, 20, 33, 83, 6, 
11, 46, 11, 43, 78, 6, 16, 36, 36, 28, 52, 16, 32, 35, 9, 56, 
58, 15, 28, 69, 20, 11, 41, 12, 47, 33, 34, 33, 27, 18, 54, 71, 
12, 17, 17, 9, 74, 71, 11, 18, 92, 6, 2, 76, 11, 12)), class = c("tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -72L))

Solution

  • It's not possible to copy the linked plot exactly because you have a 3-point Likert scale. Perhaps the best way to handle this is to centre the middle of the 'no opinion' section at 0. This requires taking each question, making the 'disagree' values negative, and splitting the 'no opinion' values into two equal positive and negative halfs, then plotting the whole thing as a bar chart.

    The statements are too long to have in full on the y axis, so I have substituted these for question labels. You may prefer to have these in the order the questions appear rather than in ascending order as per the linked plot for extra clarity:

    library(tidyverse)
    
    df_long %>%
      group_by(statement) %>%
      reframe(value = c(value[answer == 'agree'], 
                        value[answer == 'no opinion']/2,
                        -value[answer == 'no opinion']/2,
                        -value[answer == 'disagree']),
              answer = c('agree', 'no opinion', 'no opinion', 'disagree'),
              overall = sum(value)) %>%
      mutate(question = paste0('Q', as.numeric(factor(statement))),
             question = reorder(question, overall)) %>%
      ggplot(aes(x = value, y = question, fill = answer)) +
      geom_col(orientation = 'y', width = 0.6) +
      geom_vline(xintercept = 0) +
      scale_fill_manual(values = c('#5ab4ac', '#d8b366', 'gray80')) +
      theme_classic()
    

    enter image description here