【问题标题】:Plot stacked bar chart of likert variables in R在R中绘制李克特变量的堆积条形图
【发布时间】:2021-04-21 13:22:40
【问题描述】:

假设我有一个如下所示的数据框:

  P   Q1  Q2 ...
  1   1   4    1
  2   2   3    4
  3   1   1    4

这些列告诉我哪个人相应地回答了 q1、q2、... 中的哪个问题。这些问题需要一个 4 点李克特量表的答案(例如,“批准”表示 1,“稍微批准”表示 2,依此类推)。我如何绘制例如这两个问题都会导致堆积条形图(以 % 为单位)?

它应该有点像this

我在网上找到的都是非常复杂的代码,我无法处理或无法理解......难道不是只有一个简单的函数可以满足我的需求吗?

谢谢!

【问题讨论】:

    标签: r ggplot2 bar-chart stacked-chart likert


    【解决方案1】:

    我确信我不是唯一一个对你的这部分问题提出异议的人:

    我在网上找到的都是非常复杂的代码,我无法处理或无法理解......难道不是只有一个简单的函数可以满足我的需求吗?

    “非常复杂的代码”是相当主观的。但是,我可以理解,学习代码并试图弄清楚如何去做你想做的事情(起初可能看起来很简单)可能会令人生畏和沮丧。我将尝试以非常合乎逻辑和清晰的方式向您展示如何处理此问题,以便您了解此处显示的代码实际上并不太复杂。

    数据集

    OP 没有提供数据集,但我将在这里随机演示一个。这也是展示如何通过代码生成此类数据(并使其具有可扩展性)的好机会。假设我们有 20 个人回答 20 个问题。我将首先只提供一列人员,然后在其中添加 20 列问题,从而在数据框结构中创建数据。问题答案的每个单元格都会从 1 到 5 中随机选择一个答案。

    library(dplyr)
    library(tidyr)
    library(ggplot2)
    
    # make the dataset
    set.seed(8675309)
    questions <- data.frame(Person = 1:20)
    
    for (i in 1:20) {
      questions[[paste0('Q',i)]] <- sample(1:5, 20, replace=TRUE)
    }
    

    这给了我们一个 20 行和 21 列的数据框(1 列用于人员 + 20 列用于问题)。

    准备数据

    在准备生成绘图时,您几乎总是需要以某种方式准备数据。在我们开始绘图之前,我只想先在这里做两件事。第一步是将我们的数据转换为一种称为Tidy Data 的格式。以我们现在的格式......在 Excel 中绘图是可以的,但如果我们想要有一种组织和汇总这些数据的高质量方式,我们希望将其组织为“更长”的表格格式。我们需要的是以一种将列组织为的方式进行组织:

    Person | Question_num | Answer
    

    您可以通过几种方式做到这一点。这里我使用dplyrtidyr 包和gather() 函数,但也存在其他方式(即使用pivot_longer()):

    questions <- questions %>% gather(key='Question_num', value='Answer', -Person)
    

    我在这里要做的最后一件事是将我们的列questions$Answer 转换为分类变量,而不是连续数字。为什么?好吧,参与者只能回答 1、2、3、4 或 5。“3.4”的答案没有意义,所以我们的数据应该是离散的,而不是连续的。我们将通过将questions$Answer 转换为因子来做到这一点。这也让我们可以同时做两件在这里非常有用的事情:

    1. 设置levels - 这表明您希望因子水平的顺序。
    2. 设置labels - 这允许您将1 重新映射为"Approve" 并将2 重新映射为"Slightly Approve" 等等。

    然后您可以检查之后的数据,发现 questions$Answer 列现在由我们的 labels() 值组成,而不是数字。

    questions$Answer <- factor(questions$Answer,
        levels=1:5,
        labels=c('Approve','Slightly Approve','Neutral','Slightly Disapprove','Disapprove'))
    

    制定剧情

    然后我们可以使用ggplot2 包来制作绘图。 GGplot 使用geoms 将您的数据绘制到绘图区域。在这种情况下,我们可以使用geom_bar() 来绘制条形图(总计每个项目的数量/计数),并且只需要x 美学。如果我们将每个条形的fill 颜色设置为等于Answer 列,那么它将对条形进行颜色编码,以与每个问题的每个答案的数量相关联。默认情况下,条形图按照我们之前为questions$Answer 列的levels 参数设置的顺序堆叠在一起。

    ggplot(questions, aes(x=Question_num)) +
      geom_bar(aes(fill=Answer))
    

    这个情节有很多合适的地方,总体布局看起来不错。剩下的就是以几种方式改变外观。我们可以通过扩展我们的情节代码来改变情节的这些方面来做到这一点。即,我想做以下事情:

    • 添加标题并更改一些轴标签
    • 将配色方案更改为 Brewer 比例之一
    • 删除 y 轴上的空格
    • 简化主题并将图例移至其他位置

    完整的情节代码现在如下所示。您应该能够确定代码的哪些部分正在执行上面提到的每项操作。

    ggplot(questions, aes(x=Question_num)) +
      geom_bar(aes(fill=Answer)) +
      scale_fill_brewer(palette='Spectral', direction=-1) +
      scale_y_continuous(expand=expansion(0)) +
      labs(
        title='My Likert Plot', subtitle='Twenty Questions!',
        x='Questions', y='Number Answered'
      ) +
      theme_classic() +
      theme(legend.position='top')
    

    很酷,嗯?

    至于“是否有一个简单的功能可以满足我的需求?”。答案是不”。您可以编写一个,但这可能取决于您的数据最初是如何格式化的。如果您需要经常绘制这些图,请设置一个 R 脚本来自动为您执行此操作:)。

    编辑:百分比可能???

    OP 在评论中要求通过百分比显示相同的信息。这也相当简单,通常是人们想要用李克特情节做的事情......所以让我们开始吧!我们将分两个阶段将计数转换为百分比。首先,我们将设置轴和条来执行此操作。其次,我们将在每个栏的顶部覆盖文本,以显示每个问题以这种方式回答的百分比。

    首先,让我们将条形图和 y 轴设置为百分比,而不是计数。我们绘制条形几何的线是geom_bar(aes(fill=Answer))。在该函数中,position = "stack" 也有一个隐藏的默认值(我们不必指定)。 position 参数处理ggplot 应该如何处理需要在特定 x 值处绘制多个条形的情况。在这种情况下,它决定如何处理与每个问题对应的questions$Answer 的每个值对应的 5 个条形。

    “堆栈”,正如您可能假设的那样,只是将它们堆叠在一起。由于我们有 20 个人回答每个问题,因此我们所有的条形图对于每个问题都是相同的总高度 (20)。如果您只有 19 个人回答第 3 个问题怎么办?好吧,总的条形高度会比其他的要短。

    通常,李克特图都以相同的高度显示条形,因为它们是根据它们在总中所占的整体比例堆叠的。在这种情况下,我们希望每个条形图的总和最多为 1。这意味着应将 10 个人以一种方式回答应该映射到 0.5 (50%) 的条形高度。

    这是其他position 值发挥作用的地方。我们想用position = "fill"来引用我们想要在同一个x轴位置绘制的条形图……但不是按照它们的值,而是按照那个x的总值的比例轴位置。

    最后,我们要修正我们的规模。如果我们只使用position="fill",我们的 y 轴刻度将具有“0、0.25、0.50、0.75 和 1.0”或类似的值。我们希望它看起来像“0%、25%、50%、75%、100%”。您可以在 scale_y_continuous() 函数中执行此操作并指定 labels 参数。在这种情况下,scales 包有一个方便的percent_format() 函数用于此目的。将这些放在一起,您会得到以下结果:

    ggplot(questions, aes(x=Question_num)) +
      geom_bar(aes(fill=Answer), position="fill") +
      scale_fill_brewer(palette='Spectral', direction=-1) +
      scale_y_continuous(expand=expansion(0), labels=scales::percent_format()) +
      labs(
        title='My Likert Plot', subtitle='Twenty Questions!',
        x='Questions', y='Number Answered'
      ) +
      theme_classic() +
      theme(legend.position='top')
    

    在顶部获取文本

    要将文本以百分比的形式放在首位,不幸的是,这并不那么简单。为此,我们需要汇总数据,在这种情况下,最简单的方法是预先汇总到单独的数据集中,然后使用映射到汇总数据框的文本几何图形来标记文本。

    通过指定我们希望如何将数据组合在一起,然后将n() 或每个答案的计数分配为freq 列值来创建摘要数据框。

    questions_summary <- questions %>%
      group_by(Question_num, Answer) %>%
      summarize(freq = n()) %>% ungroup()
    

    然后我们使用它来映射到一个新的 geom:geom_texty 值需要再次表示为比例。就像geom_bar 和上述原因一样,我们必须使用"fill" 位置。我还想确保将每个条的位置垂直设置为“中间”,因此我们必须通过使用position_fill(vjust=0.5) 而不仅仅是"fill" 来进一步指定。

    您会注意到最后一个关键部分是我们使用了group 美学。这个非常重要。对于文本 geom,ggplot 需要知道如何对数据进行分组。在条形几何的情况下,“很明显”(可以这么说)由于条的颜色不同,条的每种颜色都是分隔符。对于文本,这始终需要指定(如何拆分值),我们通过group 审美来做到这一点。

    ggplot(questions, aes(x=Question_num)) +
      geom_bar(aes(fill=Answer), position="fill") +
      geom_text(
        data=questions_summary,
        aes(y=freq, label=percent(freq/20,1), group=Answer),
        position=position_fill(vjust=0.5),
        color='gray25', size=3.5
      ) +
      scale_fill_brewer(palette='Spectral', direction=-1) +
      scale_y_continuous(expand=expansion(0), labels=scales::percent_format()) +
      labs(
        title='My Likert Plot', subtitle='Twenty Questions!',
        x='Questions', y='Number Answered'
      ) +
      theme_classic() +
      theme(legend.position='top')
    

    瞧!

    【讨论】:

    • 很高兴听到它并很高兴能提供帮助。刚开始很难,甚至不知道要问什么问题,所以我从不介意回答这样的问题。 :)
    • 哇!这是一个应该在某种初学者教程中进行的解释!最后,我真的能理解发生了什么,以及为什么事情会这样发生。非常感谢您的努力和时间!我已经 - 多亏了你 - 终于掌握了它。:D 假设我希望这个图在 y 轴上使用 % 并且每个问题都有不同数量的答案 - 我该如何实现这个?我不知何故需要在每列中找到“真实”答案(没有“NA”)的数量,对吗?或者是否有实现这个百分比图的“短”方法?我认为情况并非如此?
    • 是的,你当然可以。要将条形显示为 %,只需对 y 轴应用格式(您可以通过 scales 包执行此操作),然后将条形几何线更改为 position="fill" 而不是 position="stack"。这种强制所有东西以总计等于 1 的方式堆叠。一起,这改变了这一点。如果您 想要添加覆盖在条形顶部的文本......它会涉及更多,但如果您遵循逻辑并不太难。我会做一个编辑供你跟进。
    • 我只是试图用我的数据框解决这个问题,我发现了最后一个问题:在绘图(绝对值)中,我不仅显示了 1 到 5 的级别,而且还显示了“NA” (在传说中)。我怎样才能告诉 ggplot 只看“不是 NA”?如果我正确理解了您的帮助,则在 % 部分中,可以使用“填充”来解决此问题-但是将百分比值写入条形的最后一位我们除以 20-这并不认为存在“NA”是正确的?.你介意帮我(我们)解决最后一个问题吗?非常感谢!!
    【解决方案2】:

    没有代表发表评论,但只想添加到给定的答案。要为每个问题的答案数量不同的数据添加百分比标签(在顶部获取文本),请使用以下代码(而不是给定的)获取 questions_summary

    questions_summary <- questions %>%
     group_by(Question_num, Answer) %>%
     dplyr::summarize(freq = length(Person)) %>%
     ungroup %>% group_by(Question_num) %>% 
     mutate(proportion = freq / sum(freq))
    

    然后,将 geom_text() 中的label=percent(freq/20,1) 更改为label=percent(proportion),如下所示:

    ggplot(questions, aes(x=Question_num)) +
      geom_bar(aes(fill=Answer), position="fill") +
      geom_text(
        data=questions_summary,
        aes(y=freq, label=percent(proportion), group=Answer),
        position=position_fill(vjust=0.5),
        color='gray25', size=3.5
      ) +
      scale_fill_brewer(palette='Spectral', direction=-1) +
      scale_y_continuous(expand=expansion(0), labels=scales::percent_format()) +
      labs(
        title='My Likert Plot', subtitle='Twenty Questions!',
        x='Questions', y='Number Answered'
      ) +
      theme_classic() +
      theme(legend.position='top')
    

    另外,如果您的数据中有不希望在图表中显示的 NA,只需使用 questions &lt;- na.omit(questions) 在准备数据时将答案转换为因子之前。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-17
      • 2021-12-27
      • 1970-01-01
      相关资源
      最近更新 更多