【问题标题】:Apply a function across groups and columns in data.table and/or dplyr在 data.table 和/或 dplyr 中跨组和列应用函数
【发布时间】:2021-06-11 10:20:44
【问题描述】:

我想合并两个不等行的data.tables或dataframes,其中dt2的行数与dt1的组数相同。这是一个可重现的示例:

a <- 1:10; b <- 2:11; c <- 3:12
groupVar <- c(1,1,1,2,2,2,3,3,3,3)
dt1 <- data.table(a,b,c,groupVar)
a2 <- c(10,20,30); b2 <- c(20,30,40); c2 <- c(30,40,50)
dt2 <- data.table(a2,b2,c2)

实际情况涉及大量列,因此我使用变量来引用它们。 使用循环或应用,我希望将 dt2 的每一行添加到组成每组 dt1 的行中。这是失败的众多尝试之一:

for (ic in 1:3) {
  c1 <- dt2[,(ic), with=FALSE]
  c2 <- dt2[,(ic), with=FALSE]
  dt1[,(ic) := .(c1 + c2[.G]), by = "groupVar"]
}

我对如何在 data.table 语法和 dplyr 语法中“按组和按列”执行这种操作很感兴趣。到位(如上所述)并不重要。

想要的结果:

dt1 (or dt3) = 
a   b   c   groupVar
11  22  33  1
12  23  34  1
13  24  35  1
24  35  46  2 
...
40  51  62  3

【问题讨论】:

标签: r dplyr data.table


【解决方案1】:

问题提供的示例数据集表明,数据集之间的列名称可能不同,例如,dt1 的列 bdt2 的列 b2 应该被添加。

以下两种方法应该适用于任意数量的任意命名的列对:

  1. 以长格式工作
  2. 编辑: 使用 get() 更新联接
  3. 编辑 2: 计算语言

1。以长格式工作

可以在查找表翻译表中提供对应列的信息:

library(data.table)
lut <- data.table(vars1 = c("a", "b", "c"), vars2 = c("a2", "b2", "c2"))

lut
   vars1 vars2
1:     a    a2
2:     b    b2
3:     c    c2

如果列名被视为数据并且列数据属于相同的数据类型,我的第一种方法是重塑为长格式。

# reshape to long format
mdt1 <- melt(dt1[, rn := .I], measure.vars = lut$vars1)
mdt2 <- melt(dt2[, groupVar := .I], measure.vars = lut$vars2)
# update join to translate variable names
mdt2[lut, on = .(variable = vars2), variable := vars1]
# update join to add corresponding values of both tables 
mdt1[mdt2, on = .(groupVar, variable), value := x.value + i.value]
# reshape backe to wide format
dt3 <- dcast(mdt1, rn + groupVar ~ ...)[, rn := NULL][]
dt3
    groupVar  a  b  c
 1:        1 11 22 33
 2:        1 12 23 34
 3:        1 13 24 35
 4:        2 24 35 46
 5:        2 25 36 47
 6:        2 26 37 48
 7:        3 37 48 59
 8:        3 38 49 60
 9:        3 39 50 61
10:        3 40 51 62

2。使用get() 更新连接

再想一想,这是一种类似于 OP 提出的for 循环的方法,并且需要更少的编码:

vars1 <- c("a", "b", "c")
vars2 <- c("a2", "b2", "c2")
dt2[, groupVar := .I]
   
for (iv in seq_along(vars1)) {
  dt1[dt2, on = .(groupVar), 
      (vars1[iv]) := get(paste0("x.", vars1[iv])) + get(paste0("i.", vars2[iv]))][]
}

dt1[]
     a  b  c groupVar
 1: 11 22 33        1
 2: 12 23 34        1
 3: 13 24 35        1
 4: 24 35 46        2
 5: 25 36 47        2
 6: 26 37 48        2
 7: 37 48 59        3
 8: 38 49 60        3
 9: 39 50 61        3
10: 40 51 62        3

请注意,dt1通过引用更新的,即不进行复制。

:=的右侧将变量名vars1[iv]添加到"x."vars2[iv]前添加"i."是为了确保来自dt1dt2的右列分别,在列名重复的情况下选择。请参阅help("data.table")j 参数的高级: 部分。

3。语言计算

这在Matt Dowle's advice 之后创建一个要评估的表达式,“类似于构造动态 SQL 语句以发送到服务器”。有关另一个用例,请参阅 here

library(glue) # literal string interpolation
library(magrittr) # piping used to improve readability

EVAL <- function(...) eval(parse(text = paste0(...)), envir = parent.frame(2))

data.table(vars1 = c("a", "b", "c"), vars2 = c("a2", "b2", "c2")) %>% 
  glue_data("{vars1} = x.{vars1} + i.{vars2}") %>% 
  glue_collapse( sep = ", ") %>% 
  {glue("dt1[dt2[, groupVar := .I], on = .(groupVar), `:=`({.})][]")} %>% 
  EVAL()
     a  b  c groupVar
 1: 11 22 33        1
 2: 12 23 34        1
 3: 13 24 35        1
 4: 24 35 46        2
 5: 25 36 47        2
 6: 26 37 48        2
 7: 37 48 59        3
 8: 38 49 60        3
 9: 39 50 61        3
10: 40 51 62        3

它以一个查找表开始,它是动态创建的,随后被操作以形成一个完整的data.table语句

dt1[dt2[, groupVar := .I], on = .(groupVar), `:=`(a = x.a + i.a2, b = x.b + i.b2, c = x.c + i.c2)][]

作为字符串。然后一次性评估和执行该字符串;不需要for 循环。

由于辅助函数EVAL() 已经使用paste0(),所以可以省略对glue() 的调用:

data.table(vars1 = c("a", "b", "c"), vars2 = c("a2", "b2", "c2")) %>% 
  glue_data("{vars1} = x.{vars1} + i.{vars2}") %>% 
  glue_collapse( sep = ", ") %>% 
  {EVAL("dt1[dt2[, groupVar := .I], on = .(groupVar), `:=`(", ., ")][]")}

请注意,点 . 和大括号 {} 在不同的上下文中具有不同的含义,这可能看起来有些混乱。

【讨论】:

    【解决方案2】:

    假设列名是一致的(例如,您想要 a + a2、b + b2...等),这里有一个 tidyverse 解决方案,它以与@dclarson 类似的方式开始,然后使用 bang-bang 运算符选择要添加的列。

    这就是你所追求的吗?

    ## Create tibbles and join
    dt1 <- tibble(groupVar,a,b,c)
    dt2 <- tibble(groupVar = 1:3,a2,b2,c2)
    dt3 <- inner_join(dt1,dt2)
    
    ## Define the column starters you are interested in
    cols <- c("a","b","c")
    ## Or in case of many columns
    cols <- colnames(dt1[-1])
    
    ## Create function to add columns with the same starting letters
    add_cols <- function(col){
      dt3 %>% select(starts_with(!!col)) %>% 
        transmute(!!(sym(col)) :=  !!(sym(col)) +  !!(sym(paste0(col,"2")))) 
    }
    ## map the function and add groupVar
     map_dfc(cols,add_cols) %>% mutate(groupVar = dt3$groupVar)
    
        # A tibble: 10 x 4
           a     b     c groupVar
       <dbl> <dbl> <dbl>    <dbl>
     1    11    22    33        1
     2    12    23    34        1
     3    13    24    35        1
     4    24    35    46        2
     5    25    36    47        2
     6    26    37    48        2
     7    37    48    59        3
     8    38    49    60        3
     9    39    50    61        3
    10    40    51    62        3
    

    【讨论】:

      【解决方案3】:

      如果把groupVar加到dt2就很简单了:

      dt2 <- data.table(a2, b2, c2, groupVar=1:3)
      dt3 <- merge(dt1, dt2)
      dt4 <- with(dt3, data.table(a=a+a2, b=b+b2, c=c+c2, groupVar))
      dt4
      #      a  b  c groupVar
      #  1: 11 22 33        1
      #  2: 12 23 34        1
      #  3: 13 24 35        1
      #  4: 24 35 46        2
      #  5: 25 36 47        2
      #  6: 26 37 48        2
      #  7: 37 48 59        3
      #  8: 38 49 60        3
      #  9: 39 50 61        3
      # 10: 40 51 62        3
      

      【讨论】:

      • 谢谢,但这并不是通过变量间接引用列名。虽然我现在很欣赏它会更容易合并公共键,然后使用 'with' 或 data.table := 添加两组列,而不是参考组合来自两个不同 data.tables 的选择。
      【解决方案4】:

      这应该可以解决您的愿望:

      1. dt2 中使用unique groupVardt1 创建一个groupVar
      2. right_joingroupVar
      3. 使用mutate 创建新列abc
      4. abcgroupVar 保留为所需的输出
      library(dplyr)
      
      dt3 <- dt2 %>% 
        mutate(groupVar = unique(dt1$groupVar)) %>% 
        right_join(dt1, by="groupVar") %>% 
        mutate(a = a + a2,
               b = b + b2,
               c = c + c2) %>% 
        select(a, b, c, groupVar)
      

      数据:

      library(data.table)
      a <- 1:10; b <- 2:11; c <- 3:12
      groupVar <- c(1,1,1,2,2,2,3,3,3,3)
      dt1 <- data.table(a,b,c,groupVar)
      a2 <- c(10,20,30); b2 <- c(20,30,40); c2 <- c(30,40,50)
      dt2 <- data.table(a2,b2,c2)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-28
        • 2021-07-30
        • 1970-01-01
        • 2015-05-06
        • 2014-02-03
        相关资源
        最近更新 更多