【问题标题】:Manipulating data.frame format in BASE R在 BASE R 中操作 data.frame 格式
【发布时间】:2020-04-16 20:27:07
【问题描述】:

我已经看到here 给出的答案,但我想知道在我的情况下,BASE R 中是否有一种有效的方法可以将我的 input data.frame 更改为我想要的 output data.frame 如下图?

input <- data.frame(id = c(1,3,6), school = LETTERS[1:3], read_1 = c(20,22,24),
               read_1_sp = c(T,F,T), read_2 =c(45,47,49),read_2_sp = c(F,F,F),  
               math_1 =c(20,22,NA), math_1_sp = c(T,F,NA), math_2 = c(NA,35,37),
               math_2_sp =c(NA,F,F))


output <- data.frame(id = c(rep(1,3),rep(3,4), rep(6, 3)),school = c(rep("A",3),rep("B",4), rep("C", 3)),   
                      subject = c("read","read","math","read","read","math", "math","read","read","math"),  
                      no.= c(1,2,1,1,2,1,2,1,2,2), score = c(20,45,20,22,47,22,35,24,49,37),    
                      sp = c(T,F,T,T,F,T,T,T,F,T))

【问题讨论】:

  • 你试过stats::reshape吗?它不是最直观的使用方式,但它可以重塑你所寻求的。
  • 由于您正在尝试缩小几列,我怀疑这将是对reshape 的几次独立调用,然后merge 将它们放在一起。
  • IMO,这是tidyr::pivot_longer 的强大动力......虽然我很欣赏你坚持使用基础 R 的动力,但便利功能如此......便利是有原因的 :-)跨度>
  • 您是否考虑过sqldf 或其他一些基于sql 的解决方案?这与您的 base-R 约束相反,但如果您已经在工作流程的其他地方使用了 sqldfDBIRSQLite 或其他一些 DBMS 之一,则可以利用它。

标签: r dataframe


【解决方案1】:

1) Base - reshape 创建一个列表 varying,其中包含两个元素,每个元素都是名称的字符向量 - 第一个元素是 score 名称的向量,然后第二个是sp 名称的向量。将其与基数 reshape 一起使用。然后按idvar 变量排序(如果不需要,则省略执行排序的两行)并使用na.omit 删除带有NA 的行。 reshape 生成一个 subject 列,其中包含 read_1 等条目。转换语句将其分成两列,subjectno

varying <- lapply(c("\\d$", "sp$"), grep, names(input), value = TRUE)

r <- reshape(input, dir = "long", idvar = c("id", "school"), 
  varying = varying, v.names = c("score", "sp"),
  times = varying[[1]], timevar = "subject")  

o <- with(r, order(id, school))
r <- r[o, ]
r <- na.omit(r)

transform(r, subject = sub("_.*", "", subject), no = as.numeric(sub(".*_", "", subject)))

给予:

           id school subject score    sp no
1.A.read_1  1      A    read    20  TRUE  1
1.A.read_2  1      A    read    45 FALSE  2
1.A.math_1  1      A    math    20  TRUE  1
3.B.read_1  3      B    read    22 FALSE  1
3.B.read_2  3      B    read    47 FALSE  2
3.B.math_1  3      B    math    22 FALSE  1
3.B.math_2  3      B    math    35 FALSE  2
6.C.read_1  6      C    read    24  TRUE  1
6.C.read_2  6      C    read    49 FALSE  2
6.C.math_2  6      C    math    37 FALSE  2

2) data.table - melt 该问题要求提供基本解决方案,但只是为了比较,我们还展示了在 data.table 中使用 melt 的解决方案。

input 转换为 data.table 并使用键和指示的模式将其融化。 meltreshape 中的times= 没有对应物,而是在variable.name 列中提供索引号,在这种情况下为subject。我们用它来索引times。这会产生诸如read_1 这样的元素,因此我们使用freadsubject 拆分为两列,subjectno。最后使用 na.omit 删除具有 NA 的行并通过指定键进行排序。

library(data.table)

input2 <- as.data.table(input, key = c("id", "school"))
times <- grep("\\d$", names(input2), value = TRUE)  # score col names

melt(input2, measure = patterns(sp = "sp", score = "\\d$"), variable.name = "subject")[, 
  c("subject", "no"):= fread(text = times[subject], sep = "_")][, 
  na.omit(.SD), key = key(input2)]

给予:

    id school    sp score subject no
 1:  1      A  TRUE    20    read  1
 2:  1      A FALSE    45    read  2
 3:  1      A  TRUE    20    math  1
 4:  3      B FALSE    22    read  1
 5:  3      B FALSE    47    read  2
 6:  3      B FALSE    22    math  1
 7:  3      B FALSE    35    math  2
 8:  6      C  TRUE    24    read  1
 9:  6      C FALSE    49    read  2
10:  6      C FALSE    37    math  2

【讨论】:

  • 稍微简化了 (1) 并添加了 (2)。
【解决方案2】:

tidyverse,我们可以做

library(tidyr)
library(stringr)
input %>%
   rename_at(vars(matches('\\d+$')), ~ str_c(., '_score')) %>%
   pivot_longer(cols = -c(id, school), names_to = c('subject', 'no', '.value'),
          names_sep="_", values_drop_na = TRUE)
# A tibble: 10 x 6
#      id school subject no    score sp   
#   <dbl> <fct>  <chr>   <chr> <dbl> <lgl>
# 1     1 A      read    1        20 TRUE 
# 2     1 A      read    2        45 FALSE
# 3     1 A      math    1        20 TRUE 
# 4     3 B      read    1        22 FALSE
# 5     3 B      read    2        47 FALSE
# 6     3 B      math    1        22 FALSE
# 7     3 B      math    2        35 FALSE
# 8     6 C      read    1        24 TRUE 
# 9     6 C      read    2        49 FALSE
#10     6 C      math    2        37 FALSE

或者base R的选项

 i1 <-grep("_\\d+$", names(input))
 names(input)[i1] <- paste0(names(input)[i1], "_score")
 lst1 <- lapply(split.default(input[-(1:2)], sub(".*_", "", names(input)[-(1:2)])), function(dat) {
         dat1 <- stack(dat)
          transform(dat1, no = sub("^[^_]+_(\\d+)_.*", "\\1", ind), ind = sub("_.*", "", ind))})
out <- setNames(cbind(lst1[[1]], lst1[[2]][1])[c(2:3, 1, 4)], c('subject', 'no', 'score', 'sp'))
na.omit(cbind(input[rep(seq_len(nrow(input)), each = 4), 1:2], out))

【讨论】:

  • @rnorouzian 我添加了一个base R
  • @rnorouzian 我删除了该解决方案,因为它可以收集一些 dv。无论如何,你得到了另一个在基础 R 中工作的解决方案
  • @Reza 我将重塑更改为拆分,因为已经有解决方案。
猜你喜欢
  • 2020-08-03
  • 1970-01-01
  • 2020-04-09
  • 1970-01-01
  • 2021-08-03
  • 1970-01-01
  • 1970-01-01
  • 2022-08-02
  • 2020-10-08
相关资源
最近更新 更多