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

时间:2021-04-21 13:22:40

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

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

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

其中的列告诉我哪个人回答了 q1、q2、... 中的哪个问题。这些问题需要以 4 分的李克特量表来回答(例如,“同意”表示 1,“稍微同意”表示 2,依此类推)。我如何绘制例如两个问题都会产生堆积条形图(以 % 为单位)?

它应该看起来有点像 this

我在网上找到的都是非常复杂的代码,我无法处理或无法理解......难道没有一个简单的函数可以做我想要的吗?

谢谢!

1 个答案:

答案 0 :(得分:2)

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

<块引用>

我在网上找到的都是非常复杂的代码,我无法处理或无法理解......难道没有一个简单的函数可以做我想要的吗?

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

数据集

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 列,那么它将对与每个问题的每个答案的数量相关联的条进行颜色编码。默认情况下,条形按照我们之前为 levels 列的 questions$Answer 参数设置的顺序堆叠在一起。

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

enter image description here

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

  • 添加标题并更改一些轴标签
  • 将配色方案更改为 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')

enter image description here

很酷吧?

至于“是否有一个简单的函数可以满足我的要求?”。答案是不”。您可以编写一个,但这可能取决于您的数据最初的格式。如果您需要经常制作这些图,请设置一个 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')

enter image description here

在顶部获取文本

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

通过指定我们希望如何将数据分组在一起,然后将 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 美学。这个非常重要。对于文本几何体,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')

enter image description here

瞧!

相关问题