首页 > 解决方案 > 在R中绘制李克特变量的堆积条形图

问题描述

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

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

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

它应该看起来像这样

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

谢谢!

标签: rggplot2bar-chartstacked-chartlikert

解决方案


我敢肯定,我不是唯一一个对你问题的这一部分提出异议的人:

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

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

数据集

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

你可以通过几种方式做到这一点。这里我使用了dplyrandtidyr包和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- 这允许您重新映射1to be"Approve"2to be"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()which 将绘制一个条形图(总计每个项目的数量/计数),并且x只需要美学。如果我们将fill每个条的颜色设置为与Answer列相同,那么它将对条进行颜色编码,以与每个问题的每个答案的数量相关联。默认情况下,条形图按照我们之前为列levels参数设置的顺序堆叠在一起questions$Answer

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

在此处输入图像描述

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

  • 添加标题并更改一些轴标签
  • 将配色方案更改为布鲁尔比例之一
  • 删除 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_text。该y值需要再次表示为比例。就像geom_bar上面的原因和原因一样,我们必须使用"fill"位置。我还想确保将每个条的位置垂直设置为“中间”,因此我们必须通过使用position_fill(vjust=0.5)而不是 just 来进一步指定"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')

在此处输入图像描述

瞧!


推荐阅读