【问题标题】:Expanding csv data in R在 R 中扩展 csv 数据
【发布时间】:2026-01-07 15:20:02
【问题描述】:

我有以下列标题的数据:

Sheet Number, Year, Term, Class, Debate #, Role in Debate, Gender of Evaluator, Person #1 Clarity, Person #1 Effort, Person #1 Gender,  Person #1 Origin, Debater Number, Person #2 Clarity, Person #2 Effort, Person #2 Gender,    Person #2 Origin, Debater Number, Person #3 Clarity, Person #3 Effort, Person #3 Gender, Person #3 Origin, Debater Number, Person #4 Clarity, Person #4 Effort, Person #4 Gender, Person #4 Origin, Debater Number, Person #5 Clarity, Person #5 Effort, Person #5 Gender, Person #5 Origin, Debater Number, Person #6 Clarity, Person #6 Effort, Person #6 Gender, Person #6 Origin, Debater Number, Person #7 Clarity, Person #7 Effort, Person #7 Gender, Person #7 Origin, Debater Number, Person #8 Clarity, Person #8 Effort, Person #8 Gender, Person #8 Origin, Debater Number, Learned from Team 1, Learned from Team 2, Who won?, Sheet all 10s?, Evaluator Class Year

我想将其转换为具有以下列标题的格式:

Sheet Number, Year, Term, Class, Debate #, Gender of Evaluator, Evaluator Class Year, Role in Debate, Debate Team Member #, Debater Number, Gender of Debate Team Member, Origin of Debate Team Member, Clarity of Debate Team Member, Effort of Debate Team Member, Learned from Team 1, Learned from Team 2, Who won?, Sheet all 10s?, =1 if Gender of Evaluator==Gender of Debater

两者之间的主要区别在于,在第一种格式中,每个工作表编号都有 5-8 个编号的“人员”与之关联。第二种格式,每个表号都有一个与之关联的人(因此每个表号出现多次并且数据被“扩展”)。

我如何在 R 中实现这一点?我一直在尝试使用“重塑”包。谢谢!

【问题讨论】:

  • 欢迎来到 *!尤其是这类数据的“宽度”,我敦促你做一个reproducible minimal working example。在这种情况下,将问题推广到少数几列,您可以在不淹没我们的情况下获得概念。 (此外,提供具有代表性的数据结构非常有帮助。)
  • 所以原始格式类似于Sheet Number, Person #1 Clarity, Person #1 Effort, Person #1 Gender, Person #1 Origin, Debater Number, Person #2 Clarity, Person #2 Effort, Person #2 Gender, Person #2 Origin, Debater Number, Person #3 Clarity, Person #3 Effort, Person #3 Gender, Person #3 Origin, Debater Number,我希望它类似于Sheet Number, Debate Team Member #, Debater Number, Gender of Debate Team Member, Origin of Debate Team Member, Clarity of Debate Team Member, Effort of Debate Team Member(即每个工作表编号仅与一个人相关联)
  • 这不是一个最小的可重现示例。我建议你: 1. 做一个小数据集。 2. 发布您针对数据集运行的 R 代码及其生成的内容。 3. 你期望它产生什么。那么我们应该能够为您提供帮助。

标签: r excel csv reshape


【解决方案1】:

(我借此机会了解了一些关于tidyr 的知识,我很高兴我做到了。)

正如@JamesKing 所建议的,您提供的不是最好的 MWE,因此我创建了一些具有类似结构的数据。不过,我认为这一切都适用于您的示例,因此通过一些解释,您应该能够将其转换为您的数据。话虽如此,由于您似乎是从 Excel 电子表格开始的,因此提出一种简化数据的gathering 和separateing 的命名约定将是有益的。

我的数据:

set.seed(1)
n <- 5
dat <- data.frame(
  sheetNum = 1:n,
  year = sample(2000:2025, size = n),
  roleInDebate = sample(letters, size = n, replace = TRUE),
  Clarity.1 = sample(10, size = n, replace = TRUE),
  Effort.1 = sample(10, size = n, replace = TRUE),
  Clarity.2 = sample(10, size = n, replace = TRUE),
  Effort.2 = sample(10, size = n, replace = TRUE),
  Clarity.3 = sample(10, size = n, replace = TRUE),
  Effort.3 = sample(10, size = n, replace = TRUE))
dat
#   sheetNum year roleInDebate Clarity.1 Effort.1 Clarity.2 Effort.2 Clarity.3
# 1        1 2006            x         3        5        10        4         5
# 2        2 2009            y         2        8         3        1         6
# 3        3 2013            r         7       10         7        4         5
# 4        4 2020            q         4        4         2        9         2
# 5        5 2004            b         8        8         3        4         9

数据类型:

  • 静态列:sheetNumyearroleInDebate。此数据不会在其他任何地方使用,并将复制到每个人的每一行。 gathered、separated 或 spread 基于这些列。

  • 其余的是列名中嵌入了数据的列。我的意思是Clarity.1 有数据1 存储在其中,需要巧妙地分离出来。虽然我每人只有两列,但这很容易转化为更多。

底线,在前面

(如果您不熟悉 dplyrmagrittr 中的 %&gt;% 中缀运算符,我鼓励您在其他地方进行研究。理解这个建议的解决方案既方便又关键。)

现在是解决方案,非常简单:

library(tidyr)
library(dplyr)
dat %>%
  gather(var, val, -sheetNum, -year, -roleInDebate) %>%
  separate(var, c('skill', 'person'), '\\.') %>%
  spread(skill, val)
#    sheetNum year roleInDebate person Clarity Effort
# 1         1 2006            x      1       3      5
# 2         1 2006            x      2      10      4
# 3         1 2006            x      3       5      7
# 4         2 2009            y      1       2      8
# 5         2 2009            y      2       3      1
# 6         2 2009            y      3       6      8
# 7         3 2013            r      1       7     10
# 8         3 2013            r      2       7      4
# 9         3 2013            r      3       5      2
# 10        4 2020            q      1       4      4
# 11        4 2020            q      2       2      9
# 12        4 2020            q      3       2      8
# 13        5 2004            b      1       8      8
# 14        5 2004            b      2       3      4
# 15        5 2004            b      3       9      5

打破它

要查看发生了什么,让我们逐步完成。 gather 步骤只是将未提及的列组合成列的键/值对,如下所示:

dat %>% gather(var, val, -sheetNum, -year, -roleInDebate) %>% head()
#   sheetNum year roleInDebate       var val
# 1        1 2006            x Clarity.1   3
# 2        2 2009            y Clarity.1   2
# 3        3 2013            r Clarity.1   7
# 4        4 2020            q Clarity.1   4
# 5        5 2004            b Clarity.1   8
# 6        1 2006            x  Effort.1   5

请注意我所包含的以- 开头的列是如何保持逐字记录的。接下来,我们需要拆分(或separatevar 列:

dat %>%
  gather(var, val, -sheetNum, -year, -roleInDebate) %>%
  separate(var, c('skill', 'person'), '\\.') %>% head()
#   sheetNum year roleInDebate   skill person val
# 1        1 2006            x Clarity      1   3
# 2        2 2009            y Clarity      1   2
# 3        3 2013            r Clarity      1   7
# 4        4 2020            q Clarity      1   4
# 5        5 2004            b Clarity      1   8
# 6        1 2006            x  Effort      1   5

这里发生的事情不多,但它对于下一步非常重要:扩展数据,或者 spread从不同的键/值对列(现在使用 skillval),这创建名为 ClarityEffort 的新列,正如我们在上面的解决方案中看到的那样。

希望这会有所帮助。

顺便说一句:对于一个好的 MWE,通常建议提供来自 dput(dat) 的输出,其中 dat 是一个但对我们有帮助的代表性数据结构了解起点和您的预期输出。在这里,一个小的data.frame 是合适的。

【讨论】:

  • 很好的答案。 (+1)
  • 感谢set.seed@AnandaMahto!
【解决方案2】:

假设@r2evans 的示例数据在一定程度上代表了您的问题,这里有一些其他选项可供考虑。


选项 1:Base R 的 reshape

奇怪的是,以你描述的任务命名的函数--reshape()--在重塑工具方面似乎是“害群之马”。如果它是您常规工具包的一部分,那么掌握窍门并不难。

对于这个问题,一种方法可能是:

reshape(dat, direction = "long", idvar = 1:3, varying = 4:ncol(dat), sep = ".")

我对@9​​87654327@ 的担忧不是它的语法,而是 (1) 它无法处理不平衡的宽到长转换(例如,如果您有 3 个“Clarity”列但只有 2 个“Effort”列) , 和 (2) 当您开始处理大量行或大量要重新整形的列时,它可能会非常缓慢。因此,我写了merged.stack


选项 2merged.stack

我写了merged.stack 作为我的“splitstackshape”包的一部分来处理与reshape(., direction = "long", ...) 将做的类似的重塑任务(例如,这与来自“reshape”/“reshape2”的melt 不同(随后,来自“tidyr”的gather)确实如此)。我还想通过识别变量“存根”(在本例中为“Clarity”和“Effort”)来简化选择感兴趣变量的过程。

如前所述,merged.stack 也被设计为快速。

library(splitstackshape)
merged.stack(dat, var.stubs = c("Clarity", "Effort"), sep = ".")
#     sheetNum year roleInDebate .time_1 Clarity Effort
#  1:        1 2006            x       1       3      5
#  2:        1 2006            x       2      10      4
#  3:        1 2006            x       3       5      7
#  4:        2 2009            y       1       2      8
#  5:        2 2009            y       2       3      1
#  6:        2 2009            y       3       6      8
#  7:        3 2013            r       1       7     10
#  8:        3 2013            r       2       7      4
#  9:        3 2013            r       3       5      2
# 10:        4 2020            q       1       4      4
# 11:        4 2020            q       2       2      9
# 12:        4 2020            q       3       2      8
# 13:        5 2004            b       1       8      8
# 14:        5 2004            b       2       3      4
# 15:        5 2004            b       3       9      5

选项 3:等待来自“data.table”版本 1.9.8 的 melt

好的。好吧,这可能不是一个真正的选择(但是“data.table”开发人员工作得很快,所以谁知道)但是在“data.table”的 1.9.8 版中,您将能够melt 指定的列列表。 See this issue for more details。或者,如果您更喜欢冒险,请安装 the 1.9.8 branch 并立即尝试 :-)

最终,这可能会使merged.stack 变得多余,因为它具有相似的功能但速度更快(从我所做的一些试验中可以看出)。


更新 -- 基准

我在下面只测试了merged.stack 和@r2evan 的方法。我没有测试reshape(),因为我担心它会减慢我的系统速度。我没有从“data.table”测试melt,因为最好等待生产版本。

以下是一些示例数据:

set.seed(1)
n <- 100000
r <- 6
dat <- data.frame(
  sheetNum = 1:n,
  year = sample(2000:2025, size = n, TRUE),
  roleInDebate = sample(letters, size = n, replace = TRUE),
  matrix(sample(10, n * r * 2, TRUE), nrow = n, 
         dimnames = list(NULL, paste(c("Clarity", "Effort"), 
                                     rep(seq_len(r), each = 2), 
                                     sep = "."))))

这是测试的两个函数:

r2evans <- function() {
  dat %>%
    gather(var, val, -sheetNum, -year, -roleInDebate) %>%
    separate(var, c('skill', 'person'), '\\.') %>%
    spread(skill, val)
}

ananda <- function() {
  merged.stack(dat, var.stubs = c("Clarity", "Effort"), sep = ".")
}

这是 10 次运行的结果:

library(microbenchmark)
microbenchmark(r2evans(), ananda(), times = 10)
# Unit: milliseconds
#       expr       min        lq      mean    median        uq       max neval
#  r2evans() 3514.0961 3603.7102 3839.6097 3713.6705 3959.5320 4380.4601    10
#   ananda()  320.5602  336.2396  363.7165  367.3344  386.3064  417.7994    10

并验证输出是否相同:

out1 <- r2evans()
out2 <- ananda()

library(compare)
compare(out1, out2, allowAll = TRUE)
# TRUE
#   renamed
#   dropped names
#   dropped attributes

【讨论】:

  • @r2evans,我看到了眨眼,但仅供参考,这并不是真正的炫耀,特别是因为我真的认为我的功能会因为 Arun 在 melt 上为“数据”所做的工作而变得多余。桌子”。使用“reshape2”工具(类似于“dpyr”+“tidyr”)的标准方法是将所有内容粉碎成一个细长的数据集并从那里重塑——忽略列类型,然后将它们展开形式。这似乎是不必要的。 merged.stack 试图避免这种情况,melt 的“data.table”实现也是如此。对于更大的数据集,这可能是一笔巨大的交易。
  • 要理解我所说的“忽略列类型是什么”的意思,请参阅this comment,其中有多个列类型同时被熔化。 spread 使用type.convert,这也会增加处理时间。 (我还在cSplit 中使用type.convert 和“splitstackshape”中的相关函数。)
  • 点了,但我既不是在竞争,也不是我认为这个具体问题与简化存储在人工维护的电子表格中的数据有关。我通常更喜欢性能高效的代码,并且没有充分使用这些包中的任何一个来“感受”性能冲击。感谢您的指点,这是一个很好的提醒,“优雅”并不总是更快,“基础”已经在速度方面经过了相当好的审查。