使用data.table重编一个变量

15

我正在尝试使用data.table重新编码一个变量。我已经谷歌搜索了近2个小时,但是没有找到答案。

假设我有一个如下所示的data.table:

DT <- data.table(V1=c(0L,1L,2L),
                 V2=LETTERS[1:3],
                 V4=1:12)

我希望重新编码V1和V2。对于V1,我想将1重新编码成0,将2重新编码成1。

对于V2,我想将A重新编码为T,B重新编码为K,C重新编码为D。

如果我使用dplyr,这很简单。

library(dplyr)
DT %>% 
  mutate(V1 = recode(V1, `1` = 0L, `2` = 1L)) %>% 
  mutate(V2 = recode(V2, A = "T", B = "K", C = "D"))

但是我不知道如何在data.table中实现这一点。
DT[V1==1, V1 := 0]
DT[V1==2, V1 := 1]
DT[V2=="A", V2 := "T"]
DT[V2=="B", V2 := "K"]
DT[V2=="C", V2 := "D"]

以下是我认为最好的代码。但一定有更好、更有效率的方法来完成这个任务。
编辑 我改变了重构V2的方式,以使我的示例更加通用。

我认为你的V2可能要是字符型的,因为你想要将“C”改成“D”,对吗?虽然我可能对您如何重新编码V2的方式有所误解。 - Mike H.
@MikeH。是的,V2 很可能需要是字符类型。 - user3077008
3个回答

20

使用 data.table,可以通过 update on join 解决重编码问题:

DT[.(V1 = 1:2, to = 0:1), on = "V1", V1 := i.to]
DT[.(V2 = LETTERS[1:3], to = c("T", "K", "D")), on = "V2", V2 := i.to]

DT 转换为
    V1 V2 V4
 1:  0  T  1
 2:  0  K  2
 3:  1  D  3
 4:  0  T  4
 5:  0  K  5
 6:  1  D  6
 7:  0  T  7
 8:  0  K  8
 9:  1  D  9
10:  0  T 10
11:  0  K 11
12:  1  D 12

编辑: @Frank建议使用i.to来确保安全。

解释

表达式.(V1 = 1:2, to = 0:1).(V2 = LETTERS[1:3], to = c("T", "K", "D"))分别在运行时创建查找表格即时生成

或者,也可以预先设置查找表格。

lut1 <- data.table(V1 = 1:2, to = 0:1)
lut2 <- data.table(V2 = LETTERS[1:3], to = c("T", "K", "D"))

lut1
   V1 to
1:  1  0
2:  2  1
lut2
   V2 to
1:  A  T
2:  B  K
3:  C  D
然后,更新连接变为
DT[lut1, on = "V1", V1 := i.to]
DT[lut2, on = "V2", V2 := i.to]

编辑2: 关于“我如何动态使用这段代码?”的答案

mat asked “我如何动态使用这段代码?”

因此,这里提供了一个修改后的版本,其中要更新的列的名称作为字符变量my_var_name提供,但查找表仍然是即时创建的:

my_var_name <- "V1"
DT[.(from = 1:2, to = 0:1), on = paste0(my_var_name, "==from"), 
   (my_var_name) := i.to]
my_var_name <- "V2"
DT[.(from = LETTERS[1:3], to = c("T", "K", "D")), on = paste0(my_var_name, "==from"), 
   (my_var_name) := i.to]

有三点需要注意:
  1. 查找表的第一列不再使用动态命名,而是固定为from。这需要在不同命名的列之间进行连接(外键连接)。要连接的列的名称必须通过on参数指定。
  2. on参数接受字符字符串,用于形如"V1==from"的外键连接。此字符串是使用paste0()动态创建的。
  3. 在表达式(my_var_name) := i.to中,括号将变量my_var_name强制使用其内容。

使用预定义的查找表进行动态代码

现在,虽然要重新编码的列由变量动态指定,但要使用的查找表仍然在语句中硬编码,这意味着我们只完成了一半:我们还需要动态选择适当的查找表。

可以通过将查找表存储在列表中来实现这一点,其中每个列表元素的名称都根据应该重新编码的DT列命名:

 lut_list  <- list(
  V1 = data.table(from = 1:2, to = 0:1),
  V2 = data.table(from = LETTERS[1:3], to = c("T", "K", "D"))
)
lut_list
$V1
    from    to
   <int> <int>
1:     1     0
2:     2     1

$V2
     from     to
   <char> <char>
1:      A      T
2:      B      K
3:      C      D
现在,我们也可以动态地从列表中选择适当的查找表:
my_var_name <- "V1"
DT[lut_list[[my_var_name]], on = paste0(my_var_name, "==from"), 
   (my_var_name) := i.to]

进一步地,我们可以通过一个循环来重新编码DT的所有相关列:
for (v in intersect(names(lut_list), colnames(DT))) {
  DT[lut_list[[v]], on = paste0(v, "==from"), (v) := i.to]
}

请注意,DT是通过引用更新的,即只有受影响的元素被替换,而不是复制整个对象。因此,for循环在同一数据对象上进行迭代应用。这是的特殊之处,无法与data.frames或tibbles一起使用。

我该如何动态地使用这段代码?例如,使用 my_var_name <- "V1"。我尝试过 DT[.(get(my_var_name) = 1:2, to = 0:1), on = my_var_name", get(my_var_name) := i.to],但没有成功。 - mat
@mat,请查看我的编辑。 - Uwe
太棒了,谢谢! - mat
太棒了!可惜在 data.table 的文档/vignettes 中没有找到这样详细的解释。 - iago

7

我认为这可能是你要找的。在等号:=的左侧,我们命名要更新的变量,在右侧,我们有要使用对应变量更新的表达式。

DT[, c("V1","V2") := .(as.numeric(V1==2), sapply(V2, function(x) {if(x=="A") "T" 
                                                     else if (x=="B") "K" 
                                                     else if (x=="C") "D" }))]

 #   V1 V2 V4
 #1:  0  T  1
 #2:  0  K  2
 #3:  1  D  3
 #4:  0  T  4
 #5:  0  K  5
 #6:  1  D  6
 #7:  0  T  7
 #8:  0  K  8
 #9:  1  D  9
#10:  0  T 10
#11:  0  K 11
#12:  1  D 12

或者,您可以在 data.table 内使用 recode

library(dplyr)
DT[, c("V1","V2") := .(as.numeric(V1==2), recode(V2, "A" = "T", "B" = "K", "C" = "D"))]

谢谢!我一直在尝试寻找一个类似于dplyr中的recode函数的data.table等效函数。因此,我将我的示例更改为一个更一般的情况。 - user3077008
1
我不知道data.table中是否有像recode这样的特定函数。但是你可以在data.table中使用recode,例如:DT[, c("V1","V2") := .(as.numeric(V1==2), recode(V2, "A" = "T", "B" = "K", "C" = "D"))] - Mike H.
1
或者,您可以使用其他函数,如sapplyDT [,c(“V1”,“V2”):= .(as.numeric(V1 == 2),sapply(V2,function(x){if(x ==”A“)”T“ 否则,如果(x ==”B“)”K“ 否则,如果(x ==”C“)”D“ }))] - Mike H.
1
只是提醒一下:使用 as.numeric(V1==2)2L 转换为 1L,并将任何其他数字转换为 0L,这是一个非常聪明的 R 技巧。但这个技巧只在这种特殊情况下有效。例如,如果 V1 包含任何其他数字,它将会出错。 - Uwe

2

plyr中的mapvalues()与data.table结合使用效果非常好。

我将其用于较大的数据(5000万-4亿行)。虽然我没有将其与其他可能性进行基准测试,但我发现清晰的语法非常有价值,因为它意味着在复杂的重新编码操作中减少了错误。

library(data.table)
library(plyr)


DT <- data.table(V1=c(0L,1L,2L),
                 V2=LETTERS[1:3],
                 V4=1:12)


DT[, V1 := mapvalues(V1, from=c(1, 2), to=c(0, 1))]
DT[, V2 := mapvalues(V2, from=c('A', 'B', 'C'), to=c('T', 'K', 'D'))]

对于更复杂的记录操作,我通常会首先创建一个带有NA值的新变量,并使用另一个包含from-to向量变量的数据表。

在某些用例中更像是bug的功能是mapvalues()会保留不在from参数中的旧变量的值。如果您确定所有正确的值都在from向量中,那么数据表中任何不在此向量中的值都应该是NA,这将成为一个问题。

DT <- data.table(V1=c(LETTERS[1:3], 'i dont want this value transfered'),
                 V4=1:12)
map_DT <- data.table(from=c('A', 'B', 'C'), to=c('T', 'K', 'D'))


# NA variable to begin with is good practice because it is clearer to spot an error  
DT[, V1_new := NA_character_]
DT[V1 %in% map_DT$from , V1_new := mapvalues(V1, from=map_DT$from, to=map_DT$to)][]

请注意,plyr已被弃用,因此mapvalues函数在未来可能会有一定的风险消失。虽然我认为mapvalues更易于阅读,但提出的update-joins方法可能是一种更好的方法。尽管mapvalues可能需要很多年才会被弃用,但在决定是否将其用作工具时仍需谨慎考虑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接