【问题标题】:R: Create multiple columns in list of data.tables and modify them with condition on a grouping variableR:在 data.tables 列表中创建多个列,并根据分组变量的条件修改它们
【发布时间】:2016-10-27 07:47:13
【问题描述】:

我有一个如下所示的数据表列表:

group1 <- data.table(
    group = rep(x = c("group1"), each = 16),
    amount = rep(x = 7:4, each = 4),
    subgr = rep(x = 1:2, each = 8),    
    ind = rep(x = 0:1, each = 4, times = 2)
  )

group2 <- data.table(
    group = rep(x = c("group2"), each = 36),
    amount = rep(x = 13:8, each = 6),
    subgr = rep(x = 1:3, each = 12),
    ind = rep(x = 0:1, each = 6, times = 3)
  )

mydt <- rbind(group1, group2)

mydt <- lapply(X = split(x = 1:nrow(mydt), f = mydt[["group"]]),
FUN = function(i)mydt[i])

上面显示的对象过于简单,实际列表包含越来越多的data.tables,每个在subgr 中分布的行数和subgr 本身的数量方面的结构略有不同。我想要实现的是:

  1. 在列表中的每个data.table 中创建多个列,该列等于subgr 中唯一值的数量。每个新列都是amount 的副本。复制的列数将等于 subgr 中唯一值的数量。
  2. 修改每个subgr 中新创建的列(如果ind == 1amount*4 如果ind ==0 则说amount*4),使subgr 中子组中的其余值不受影响。

也就是说,有这样的东西(这里只显示mydt$group1,但它适用于所有表格):

$group1
     group amount subgr ind am1 am2
 1: group1      7     1   0  28   7
 2: group1      7     1   0  28   7
 3: group1      7     1   0  28   7
 4: group1      7     1   0  28   7
 5: group1      6     1   1  12   6
 6: group1      6     1   1  12   6
 7: group1      6     1   1  12   6
 8: group1      6     1   1  12   6
 9: group1      5     2   0   5  20
10: group1      5     2   0   5  20
11: group1      5     2   0   5  20
12: group1      5     2   0   5  20
13: group1      4     2   1   4   8
14: group1      4     2   1   4   8
15: group1      4     2   1   4   8
16: group1      4     2   1   4   8

我知道将data.table 拆分为data.tables 列表不是一个好主意,正如this post 中所述,但对象就是这样。除此之外,拆分与我需要执行的任务有关:

  1. 数据表包含不同的行数。
  2. 行被分组为由subgr 定义的子组,并且它们的数量在不同的数据表中也不同,即新列的数量在整个列表中会有所不同。

也就是说,整个data.table 无法一次处理,因为group 变量中的每个组将创建不同数量的列。

到目前为止,我尝试的是使用this post 接受的答案中的第二种解决方案编写一个函数:

myfun <- function(data, quantity, region, index) {
  data <- lapply(data, function(i) {
    i[ , eval(paste0("am", unique(i[[region]]))) := i[[quantity]]]
  })
  data <- lapply(X = data, FUN = function(i) {
    rep.names <- paste0("am", unique(i[[region]]))
    i[ , eval(rep.names) := lapply(.SD, function(j) {
      ifelse(i[["ind"]] == 1L, j*2L, j*4L)
      }), by = region, .SDcols = rep.names]
  })
  return(data)
}

myfun(mydt, quantity = "amount", region = "subgr", index = "ind")

它没有按预期工作,它会根据条件修改所有变量中的整个值范围。但是,它会发出警告,指出问题所在。这里只是第一个警告,其他都一样:

Warning messages:
1: In `[.data.table`(i, , `:=`(eval(rep.names), lapply(.SD,  ... :
  RHS 1 is length 16 (greater than the size (8) of group 1). The last
8 element(s) will be discarded.

也就是说,它只使用 LHS 上必须使用的行,然后将整个列用于 RHS。显然我在这里遗漏了一些重要的东西。与 [this post][3] 中接受的答案的第二种解决方案的区别在于,有多个列可供使用,而在我的情况下只有一个 (amount)。

有人可以帮忙吗?

【问题讨论】:

  • 从 data.table 1.9.7 开始,您可以使用split(mydt, by="group") 进行拆分。如果您有超过 ~1000 个唯一值 subgr,您还应该在创建 := 之前使用 alloc.col
  • @jangorecki:谢谢,这是有用的信息。版本 1.9.7 尚未发布,想了解更多有关此功能的信息。你能提供更多关于alloc.col的细节吗?
  • 也许是?alloc.col

标签: r data.table conditional subset multiple-columns


【解决方案1】:

我建议这是适合 for 循环的任务。您可以遍历列表并就地修改每个 data.table,而无需重建列表,lapply() 就是这样做的。

此外,我建议您先在矩阵中构造am* 列,然后再将它们分配给目标data.table。通过将amount 作为基础数据向量传递,我们可以一次完成所有am* 列,因为大多数单元格直接从amount 列获取它们的值而没有任何更改,特别是如果有许多独特的 subgr 值。之后,我们可以通过为数据矩阵分配索引矩阵来选择性地修改必须更改的单元格。构建索引矩阵将相当容易,因为我们知道每行只需更改一个单元格。基本上,我们可以cbind() 行索引序列.I 使用从match(subgr,grs) 计算的所需列索引,其中grssubgr 值的唯一集合。这将比对每个 am* 列进行相等比较(如 j==i[[region]])更有效。

for (i in seq_along(mydt)) {
    grs <- unique(mydt[[i]]$subgr);
    mydt[[i]][,paste0('am',grs):={
        m <- matrix(amount,.N,length(grs));
        m[cbind(.I,match(subgr,grs))] <- amount*ifelse(ind==1L,2L,4L);
        as.data.frame(m);
    }];
}; ## end for
mydt;
## $group1
##      group amount subgr ind am1 am2
##  1: group1      7     1   0  28   7
##  2: group1      7     1   0  28   7
##  3: group1      7     1   0  28   7
##  4: group1      7     1   0  28   7
##  5: group1      6     1   1  12   6
##  6: group1      6     1   1  12   6
##  7: group1      6     1   1  12   6
##  8: group1      6     1   1  12   6
##  9: group1      5     2   0   5  20
## 10: group1      5     2   0   5  20
## 11: group1      5     2   0   5  20
## 12: group1      5     2   0   5  20
## 13: group1      4     2   1   4   8
## 14: group1      4     2   1   4   8
## 15: group1      4     2   1   4   8
## 16: group1      4     2   1   4   8
##
## $group2
##      group amount subgr ind am1 am2 am3
##  1: group2     13     1   0  52  13  13
##  2: group2     13     1   0  52  13  13
##  3: group2     13     1   0  52  13  13
##  4: group2     13     1   0  52  13  13
##  5: group2     13     1   0  52  13  13
##  6: group2     13     1   0  52  13  13
##  7: group2     12     1   1  24  12  12
##  8: group2     12     1   1  24  12  12
##  9: group2     12     1   1  24  12  12
## 10: group2     12     1   1  24  12  12
## 11: group2     12     1   1  24  12  12
## 12: group2     12     1   1  24  12  12
## 13: group2     11     2   0  11  44  11
## 14: group2     11     2   0  11  44  11
## 15: group2     11     2   0  11  44  11
## 16: group2     11     2   0  11  44  11
## 17: group2     11     2   0  11  44  11
## 18: group2     11     2   0  11  44  11
## 19: group2     10     2   1  10  20  10
## 20: group2     10     2   1  10  20  10
## 21: group2     10     2   1  10  20  10
## 22: group2     10     2   1  10  20  10
## 23: group2     10     2   1  10  20  10
## 24: group2     10     2   1  10  20  10
## 25: group2      9     3   0   9   9  36
## 26: group2      9     3   0   9   9  36
## 27: group2      9     3   0   9   9  36
## 28: group2      9     3   0   9   9  36
## 29: group2      9     3   0   9   9  36
## 30: group2      9     3   0   9   9  36
## 31: group2      8     3   1   8   8  16
## 32: group2      8     3   1   8   8  16
## 33: group2      8     3   1   8   8  16
## 34: group2      8     3   1   8   8  16
## 35: group2      8     3   1   8   8  16
## 36: group2      8     3   1   8   8  16
##      group amount subgr ind am1 am2 am3
##

基准测试

library(microbenchmark);
library(data.table);

hubert <- function(mydt) { myfun <- function(data, quantity, region, index) lapply(data, function(i) i[ , eval(paste0("am", unique(i[[region]]))) := lapply(unique(i[[region]]), function(j) {i[[quantity]]*ifelse(j==i[[region]],ifelse(ind==1, 2, 4), 1)})] ); myfun(mydt, quantity = "amount", region = "subgr", index = "ind"); };
bgoldst <- function(mydt) { for (i in seq_along(mydt)) { grs <- unique(mydt[[i]]$subgr); mydt[[i]][,paste0('am',grs):={ m <- matrix(amount,.N,length(grs)); m[cbind(.I,match(subgr,grs))] <- amount*ifelse(ind==1L,2L,4L); as.data.frame(m); }]; }; mydt; };

## OP's example
group1 <- data.table(group=rep(x=c("group1"),each=16),amount=rep(x=7:4,each=4),subgr=rep(x=1:2,each=8),ind=rep(x=0:1,each=4,times=2));
group2 <- data.table(group=rep(x=c("group2"),each=36),amount=rep(x=13:8,each=6),subgr=rep(x=1:3,each=12),ind=rep(x=0:1,each=6,times=3));
mydt <- rbind(group1,group2);
mydt <- lapply(X=split(x=1:nrow(mydt),f=mydt[["group"]]),FUN=function(i)mydt[i]);

ex <- hubert(lapply(mydt,copy));
all.equal(ex,bgoldst(lapply(mydt,copy)));
## [1] TRUE

microbenchmark(hubert(lapply(mydt,copy)),bgoldst(lapply(mydt,copy)));
## Unit: milliseconds
##                         expr      min       lq     mean   median       uq      max neval
##   hubert(lapply(mydt, copy)) 2.579173 2.632417 2.837445 2.669621 2.736549 6.555914   100
##  bgoldst(lapply(mydt, copy)) 2.603977 2.683092 2.880715 2.723078 2.781025 4.376168   100

## scale test
set.seed(1L);
NR <- 1e5L; NGRP <- 1e3L; NAMT <- 30L; NSUBGR <- 30L;
mydt <- data.table(group=paste0('group',sample(NGRP,NR,T)),amount=sample(NAMT,NR,T),subgr=sample(NSUBGR,NR,T),ind=sample(0:1,NR,T));
mydt <- split(mydt,mydt$group);

ex <- hubert(lapply(mydt,copy));
all.equal(ex,bgoldst(lapply(mydt,copy)));
## [1] TRUE

microbenchmark(hubert(lapply(mydt,copy)),bgoldst(lapply(mydt,copy)));
## Unit: seconds
##                         expr      min       lq     mean   median       uq      max neval
##   hubert(lapply(mydt, copy)) 2.831080 2.899419 2.938751 2.935096 2.970701 3.110481   100
##  bgoldst(lapply(mydt, copy)) 1.571023 1.647102 1.674666 1.671877 1.709434 1.845174   100

【讨论】:

  • 谢谢bgoldst,你的回答解释得很好。
【解决方案2】:

您的错误来自 i[["ind"]] 的长度,其中包含数据集中的所有行,而 j 仅包含组中的行:

ifelse(i[["ind"]] == 1L, j*2L, j*4L)

有几种可能性可以解决这个问题并实现您的目标,这就是我的做法:

myfun <- function(data, quantity, region, index) {
        lapply(data, function(i) {
                i[ , eval(paste0("am", unique(i[[region]]))) := lapply(unique(i[[region]]), function(j)
                        {i[[quantity]]*ifelse(j==i[[region]],ifelse(ind==1, 2, 4), 1)})]
        })
}
myfun(mydt, quantity = "amount", region = "subgr", index = "ind")
$group1
     group amount subgr ind am1 am2
 1: group1      7     1   0  28   7
 2: group1      7     1   0  28   7
 3: group1      7     1   0  28   7
 4: group1      7     1   0  28   7
 5: group1      6     1   1  12   6
 6: group1      6     1   1  12   6
 7: group1      6     1   1  12   6
 8: group1      6     1   1  12   6
 9: group1      5     2   0   5  20
10: group1      5     2   0   5  20
11: group1      5     2   0   5  20
12: group1      5     2   0   5  20
13: group1      4     2   1   4   8
14: group1      4     2   1   4   8
15: group1      4     2   1   4   8
16: group1      4     2   1   4   8

$group2
     group amount subgr ind am1 am2 am3
 1: group2     13     1   0  52  13  13
 2: group2     13     1   0  52  13  13
 3: group2     13     1   0  52  13  13
 4: group2     13     1   0  52  13  13
 5: group2     13     1   0  52  13  13
 6: group2     13     1   0  52  13  13
 7: group2     12     1   1  24  12  12
 8: group2     12     1   1  24  12  12
 9: group2     12     1   1  24  12  12
10: group2     12     1   1  24  12  12
11: group2     12     1   1  24  12  12
12: group2     12     1   1  24  12  12
13: group2     11     2   0  11  44  11
14: group2     11     2   0  11  44  11
15: group2     11     2   0  11  44  11
16: group2     11     2   0  11  44  11
17: group2     11     2   0  11  44  11
18: group2     11     2   0  11  44  11
19: group2     10     2   1  10  20  10
20: group2     10     2   1  10  20  10
21: group2     10     2   1  10  20  10
22: group2     10     2   1  10  20  10
23: group2     10     2   1  10  20  10
24: group2     10     2   1  10  20  10
25: group2      9     3   0   9   9  36
26: group2      9     3   0   9   9  36
27: group2      9     3   0   9   9  36
28: group2      9     3   0   9   9  36
29: group2      9     3   0   9   9  36
30: group2      9     3   0   9   9  36
31: group2      8     3   1   8   8  16
32: group2      8     3   1   8   8  16
33: group2      8     3   1   8   8  16
34: group2      8     3   1   8   8  16
35: group2      8     3   1   8   8  16
36: group2      8     3   1   8   8  16

【讨论】:

  • 非常感谢。它按预期进行。
  • 其实你提供的函数的第四行应该是{i[[quantity]]*ifelse(j==i[[region]],ifelse(i[[index]]==1, 2, 4), 1)})]。现在它使用实际的变量名并且不将其作为函数参数匹配。
猜你喜欢
  • 1970-01-01
  • 2023-02-07
  • 2016-02-28
  • 1970-01-01
  • 2021-11-29
  • 2019-02-28
  • 2021-03-19
  • 2021-11-03
  • 2022-08-13
相关资源
最近更新 更多