【问题标题】:How to efficiently draw lots of graphs in R from data in a wide format?如何从宽格式的数据中有效地在 R 中绘制大量图形?
【发布时间】:2019-07-21 12:52:39
【问题描述】:

我正在尝试使用 R 和 ggplot2 包绘制 18 个图形。我的数据如下所示:

v1 v2 v3 ... v18 subject group
534 543 512 ... 410 1 (6.5, 18]
437 576 465 ... 420 2 (0, 6.5]
466 487 492 ... 501 3 (18, 55]

我需要创建一个“分面”直方图,显示一帧中所有组的分布(即方便地显示所有子组的分布),如下所示:

我为一个情节想出了这段代码:

ggplot(data = df, aes (x = v1)) + geom_histogram (boundary = 500) + facet_wrap(~Group, nrow = 2)

但由于有 18 个变量(v1、v2、...),我正在寻找一种方法来编写一个高效的函数/循环/命令来绘制所有 18 个图形,而无需我复制/粘贴和更改变量名 18 次。像这样:

ggplot(data = df, aes (x = **v1**)) + geom_histogram (boundary = 500) + facet_wrap(~Group, nrow = 2)
ggplot(data = df, aes (x = **v2**)) + geom_histogram (boundary = 500) + facet_wrap(~Group, nrow = 2)
ggplot(data = df, aes (x = **v3**)) + geom_histogram (boundary = 500) + facet_wrap(~Group, nrow = 2)

我知道解决方案可能在于循环,它似乎是一项有用的技能,所以我也借此机会学习这个权利。

谢谢,感谢您的帮助! (感谢迄今为止的所有建议!)

在以下用户的善意帮助下,到目前为止,这是我所取得的成就:

for (v in c(v1,v2)) {
pdf("plots.pdf") 
histograms <- ggplot(data = data, aes (x = v)) + geom_histogram (boundary = 500) + facet_wrap(~Group, nrow = 2) 
print(histograms)
} 
dev.off()

【问题讨论】:

  • 打开一个输出连接,例如pdf。然后在for 循环中运行代码。在循环结束时,仍在循环内,添加行 print(histograms) 以将图形输出到 pdf。在循环下方,添加行 dev.off() 以关闭 pdf 连接。
  • 我运行起来了,谢谢!现在只有打印很糟糕——它只有一页打印了一个 ggplots。而且它们看起来不像直方图。
  • Petr,您的问题可以通过提供更多详细信息和(有限)数据示例来改进。我猜到了您的要求,并提供了一些建议的答案。祝你好运。
  • 太棒了!我猜数据集太复杂了。这是一种将名为wide 的data.frame 中的数据转换为长格式的简单方法(假设唯一的名称是'v1'、'v2'、...和'group')。新数据应该在我提供的答案中起作用。 sel &lt;- which(names(wide) == "group"); dat &lt;- stack(wide[-sel]); names(dat) &lt;- c("v", "Group")
  • 我现在看到您的数据似乎有两个级别的分组。第一个由名为“v1”、“v2”和“v3”的变量定义。第二个是通过切割名称似乎是从cut() 函数生成的值生成的变量:(0, 6.50], (6.5, 18], (18, 55] 等。后一组似乎是在您的原始问题中命名为Group 的变量。同样,使用有关您的数据的信息提出解决方案会更容易。我稍后会回来看看我们是否可以再做一次!:)

标签: r loops ggplot2


【解决方案1】:

编辑在澄清需求后提供了一个经过重大修改的答案。

这个问题提出了几个常见问题,每个问题都在其他帖子中解决。不过,也许这个建议可以为这些常见问题提供一站式解决方案。

我的第一个建议是将数据重新格式化为“长”格式。有许多资源描述了这一点,并提供了帮助包。许多用户都接受“tidyverse”工具集,我将把它留给其他人。我将演示一个使用基本函数的简单方法。我推荐stats 包中的reshape() 函数。我发现它对于将时间作为变量之一的重复测量很有用,但对于其他数据来说却相当复杂。

将以“宽”格式生成一个大型假数据集,其中包含人口统计数据(id, sex, weight, age, group) 和 18 个变量,名为“v01”、“v02”、...、“v18”,它们是 400 到 500 之间的随机整数。

# Set random number generator and number of "individuals" in fake data
  set.seed(1234) # to ensure reproducibility
  N <- 936 # number of "individuals" in the fake data

# Create typical fake demographic data and divide the age into 4 groups
  id <- factor(sample(1e4:9e4, N, replace = FALSE))
  age <- rpois(N, 36)
  sex <- sample(c("F","M"), N, replace = TRUE)
  weight <- 16 * log(age)
  group <- cut(age, breaks = c(12, 32, 36, 40, 62))

为宽格式的每个人生成 18 个假值,然后创建假的“宽”data.frame。

# 18 variable measurements for wide format
  V <- replicate(18, sample(400:600, N, replace = TRUE), simplify = FALSE)
  names(V) <- sprintf("v%02d", 1:18)

# Add a little variation to the fake data
  adj <- sample(1:6, 18, replace = TRUE)
  V <- Map("/", V, adj) # divide each value by the number in 'adj'
  V <- lapply(V, round, 1) # simplify

# Create data.frame with variable data in wide format
  vars <- as.data.frame(V)
  names(vars)

# Assemble demographic and variable data into a typical "wide" data set
  wide <- data.frame(id, sex, weight, age, group, vars)
  names(wide)
  head(wide)

在“宽”格式中,每一行对应一个具有人口统计信息和 18 个变量的 18 个值的唯一个体。这将被更改为“长”格式,每个值由一行表示。新的“长”数据框将有两个新的数据变量 (values) 和一个指示数据来自的组的因子 (ind)。通常它们会被重命名,但我将在这里使用默认名称。

如上所述,简单的基函数stack() 将用于将变量堆叠成单个向量。与cbind() 相比,data.frame() 函数将复制值,只要它们是彼此的偶数倍。以下代码利用此属性来构建“长”数据帧。

# Identify those variables to be stacked (they all start with 'v')
  sel <- grepl("^v", names(wide))
  long <- data.frame(wide[!sel], stack(wide[sel]))
  head(long)

我的第二个建议是使用“应用”函数之一来创建ggplot 对象的列表。通过将绘图存储在此变量中,您可以选择以不同的格式绘制它们,而无需每次都运行绘图代码。

代码为 18 个不同变量中的每一个创建了一个图,这些变量由新变量 ind 标识。我将boundary = 500 更改为bins = 10,因为我不知道您的实际数据是什么样的。我还在每个图中添加了一个“标题”来标识原始变量。

  library(ggplot2) # to use ggplot...
  plotList <- lapply(levels(long$ind), function(i)
    ggplot(data = subset(long, ind == i), aes(x = values))
    + geom_histogram(bins = 10)
    + facet_wrap(~ group, nrow = 2)
    + labs(caption = paste("Variable", i)))
  names(plotList) <- levels(long$ind) # name the list elements for convenience

现在检查 18 个图中的每一个(这在 RStudio 中可能不起作用):

  opar <- par(ask = TRUE)
  plotList # This is the same as print(plotList)
  par(opar) # turn off the 'ask' option

要将绘图保存到文件中,Imo 的建议很好。但明智的做法是控制文件输出的大小和性质。我建议您查看pdf()dev.print() 的帮助文件。该答案的最后一部分显示了pdf() 函数使用for 循环生成单页图的一种可能性。

  for (v in levels(long$ind)) {
    fname <- paste(v, "pdf", sep = ".")
    fname <- file.path("~", fname) # change this to specify a directory
    pdf(fname, width = 6.5, height = 7, paper = "letter")
    print(plotList[[v]])
    dev.off()
  }

为了添加另一种可能的方法,这里有一个lattice 的解决方案,每个图显示 6 组变量。 (就我个人而言,我喜欢这种更简单的方法。)

  library(lattice)
  idx <- split(levels(long$ind), gl(3, 6, 18))
  opar <- par(ask = TRUE)
  for (i in idx)
    plot(histogram(~values | group + ind, data = long,
      subset = ind %in% i, as.table = TRUE))
  par(opar)

【讨论】:

  • 非常感谢您的帮助!代码看起来很棒。但是,即使在我澄清了问题之后,它仍然成立吗?我不确定你所说的“配对”是什么意思,我猜我无法写出好的问题导致了误解。
  • 我明白了。我的猜测来自代码中的 nrows = 2 选项。我们还可以说清楚!您的新数据显示了三个组,其值分别命名为 v1、v2 和 v3,尽管我仍然有点不清楚。如果您的数据集不是太大,也许您可​​以使用dput(dat)head(dat) 来显示您的数据结构。
  • 我想在编辑中这样做,但输出根本没有帮助。这就是我选择口头描述的原因。它基本上只是每人(v1 等)的几个 (18) 测量值,并且这些人根据他们的年龄(group var)分为几组。而且我需要分别显示每个测量中每个组的分布。
  • 我们越来越近了!我很欣赏对隐私的需求,但这将有助于模拟数据。所以,也许还有一个问题。然后我们就可以做到了!我知道每个人只有 18 次测量值,其值大致在 400 到 500 之间。这些人分为四个年龄段(如您的图片所示)。我的最后一个问题是,大约有多少人在你的数据集?我假设 v1 到 v32,但它看起来要高得多。有了这个,我可以用一些修改后的建议来编辑我的回复。
  • 你太棒了。 N = 936。每个人都有人口统计变量(我们在这里不关心),每个人都有一个存储在“组”变量中的年龄段,每个人都有 18 个变量中的 18 个测量值(经典宽格式)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
  • 2020-02-09
  • 1970-01-01
  • 2016-04-26
  • 2020-04-17
  • 1970-01-01
相关资源
最近更新 更多