在 R 中绘制 Likert 变量的堆积条形图

man*_*459 2 r bar-chart ggplot2 stacked-chart likert

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

  P   Q1  Q2 ...
  1   1   4    1
  2   2   3    4
  3   1   1    4
Run Code Online (Sandbox Code Playgroud)

其中的列告诉我哪个人相应地回答了问题 q1、q2、... 中的哪一个。这些问题需要按照 4 分李克特量表进行回答(例如,“批准”意味着 1,“稍微批准”意味着 2,等等)。我如何绘制两个问题结果的堆积条形图(以%为单位)?

它应该看起来有点像这样

我在网上找到的都是非常复杂的代码,我无法处理或无法理解......不是只有一个简单的函数可以完成我想要的事情吗?

谢谢你!

che*_*123 9

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

我在网上找到的都是非常复杂的代码,我无法处理或无法理解......不是只有一个简单的函数可以完成我想要的事情吗?

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

数据集

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)
}
Run Code Online (Sandbox Code Playgroud)

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

准备数据

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

Person | Question_num | Answer
Run Code Online (Sandbox Code Playgroud)

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

questions <- questions %>% gather(key='Question_num', value='Answer', -Person)
Run Code Online (Sandbox Code Playgroud)

我想做的最后一件事是将我们的列转换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'))
Run Code Online (Sandbox Code Playgroud)

制定情节

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

ggplot(questions, aes(x=Question_num)) +
  geom_bar(aes(fill=Answer))
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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

  • 添加标题并更改一些轴标签
  • 将配色方案更改为布鲁尔等级之一
  • 删除 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')
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

很酷,是吗?

至于“有没有一个简单的函数可以完成我想要的功能?”。答案是不”。您可以编写一个,但这可能取决于数据的最初格式化方式。如果您需要经常绘制这些图,请设置一个 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')
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

将文本置于顶部

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

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

questions_summary <- questions %>%
  group_by(Question_num, Answer) %>%
  summarize(freq = n()) %>% ungroup()
Run Code Online (Sandbox Code Playgroud)

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

您会注意到最后一个关键部分是我们正在使用group美学。这个非常重要。对于文本几何,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')
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

瞧!