使用字符串访问data.table列

12

非常抱歉,这个问题可能表明我通常使用Python/pandas,但我现在却遇到了困难。我如何使用字符串选择一个 data.table 的列?

dt$"string"
dt$as.name("string")
dt$get("string")

我相信这非常简单,但我不理解。非常感谢任何帮助!


-------------- 编辑以添加 ----------------------

在下面一些有用的评论和提示之后,我认为我已经将问题缩小了,并且有了一个可重现的例子。请看:

dt = data.table(ID = c("a","a","a","b","b","b"), col1=rnorm(6), col2=rnorm(6)*100)

假设我们想要将 col2 中的值分配给 col1。根据我所学,data.table 的语法应该是 dt[,col1:=col2],这很简单明了。但当 j 参数中的一个或两个变量为字符串时,则出现问题。我找到了以下信息:

dt[, "col1":=col2] 像预期的那样工作

dt[, "col1":="col2"] 失败,因为它试图将字符 col2 分配给双精度向量 col1

dt[, "col1":=get("col2")] 像预期的那样工作

dt[, get("col1")] 像预期的那样返回 col1

但是:dt[, get("col1"):=col2] 或任何其他赋值操作均失败。

一些背景:我这样做的原因是,我正在循环中构造字符串,以访问名为 colname_colnumber 的大量列,即我循环遍历 colnamecolnumber,然后访问列paste0(colname,colnumber)


2
顺便说一下,dt$"string"应该完美地工作,具体是什么错误?虽然$右侧的表达式永远不会起作用,请参见此FAQ - David Arenburg
你说得有道理 - 我想我之所以出错是因为我在循环内部构建字符串,所以使用了类似 $paste(x,y) 的东西,然后在提问之前忘记尝试不使用表达式! - Nils Gudat
1
目前还不太清楚为什么 dt[,"col1":=get("col2")] 这个选项不能满足您的需求,例如 for (ii in 1:2) dt[,paste0("col",ii):=get("col2")] 看起来是您想要的。也许您需要扩大示例的范围。 - MichaelChirico
明白了 - 你提到的选项可行(我会这样做)。问题出现在我先存储了构建的字符串,因为我还在其他地方使用它。如果我执行 var<-paste0("col","1"),我将不得不再次依赖于 get() 方法,但它不起作用。所以虽然你的建议在我的情况下有效,但我仍然想了解为什么 data.table 的行为在简单的 get() 调用和带有赋值的 get() 调用之间发生了变化。 - Nils Gudat
@Nilsgudat 这是一个合理的问题。我现在不在电脑旁,但我的直觉告诉我,一旦你使用了 get,赋值选项就不再可用了,因为已经返回了一个向量。你可以将一个向量分配给列名,但你不能将一个名称向量分配给另一个向量(除非更加小心)。 - MichaelChirico
3个回答

16

您可以使用单个方括号并将get()作为j参数:

library(data.table)
dt <- data.table(iris)
dt[, get("Species")]

结果:

[1] setosa     setosa     setosa     setosa     setosa     setosa .....

您也可以直接在双括号操作符中使用字符串,就像这样:

dt[["Species"]]

get()可以解决问题,但由于某种原因,在赋值操作中它不起作用。我做错了什么? 假设我有var1 <- "string1"var2 <- "string2", 我的数据表中有名为string1string2的列。 dt[, get(var1)]dt[,get(var2)]按预期工作,但如果我想使用dt[,get(var1)] <- dt[,get(var2)]将列2的值分配给列1,则会出现Error in get(var1): object 'string1' not found错误。这里发生了什么? - Nils Gudat
甚至更简单的是:dt[, get(var1)] 可以工作,而 dt[,get(var1)] <- 0 则会抛出“对象未找到”的错误。 - Nils Gudat
5
尽量避免在data.table中使用<-赋值操作符。可以尝试使用dt[,(var1):=var2],或者对于另一个示例,可以使用dt[,(var1):=0]。我建议查看一些入门文档,例如starter vignettes,其中包含一些易于理解的基本操作示例。 - MichaelChirico
那个语法确实干净了很多,但它仍然不起作用 - dt[,get(var1):=0] 抛出相同的错误。我想不出为什么一个列可以通过简单调用访问,但在赋值调用中不可用,但我一定会阅读你提供的链接文档! - Nils Gudat
@NilsGudat 嗯,在这种情况下,我不知道该告诉你什么。如果你已经“读完了”,问题仍然存在,请在你的帖子中编辑一个可重现的例子。我不确定为什么你专注于使用 get,因为有几个替代方案应该可以工作,但也许需要更多的上下文--如果是这样,请将其添加到你的帖子中。 - MichaelChirico
当然,我看到我的问题在可重现性方面并不好 - 我正在循环中创建变量名称并连接字符串,因此我需要使用get来实际访问变量,而且似乎get是引起问题的原因,因为dt [,string1:= 0]没有问题。我会再考虑一下并根据需要编辑我的问题。 - Nils Gudat

7
我想补充一点,如果你需要许多列,你可能希望使用这样的东西:
dt[ , c("id", paste0("col", 1:10)), with = FALSE]

正如@Arun所述,在获取多列数据方面,其他选项包括:

dt[ , mget(c("id", paste0("col", 1:5)))]

并且

dt[ , .SD, .SDcols = c("id", paste0("col", 1:5))]

在最近的data.table版本(例如当前CRAN)中,您还可以使用“上一级”符号,如下所示:

keep_cols = c('id', paste0('col', 1:5))
dt[ , ..keep_cols]

供参考,mget 似乎非常慢;.SDcols 最快,但与 with = FALSE 相当竞争;我个人认为在不同的情况下所有方法都很有用/最自然。

这是一个简单的基准测试:

NN <- 10000L
MM <- 100L
mm <- 10L

DT = data.table(id = 1:NN)
DT[ , paste0("col", 1:MM) := lapply(integer(MM), function(x) runif(NN))]

sdcols = function(...) DT[ , .SD, .SDcols = paste0("col", sample(MM, size = mm))]
m.get = function(...) DT[ , mget(paste0("col", sample(MM, size=mm)))]
withF = function(...) DT[ , paste0("col", sample(MM, size = mm)), with = FALSE]

library(microbenchmark)
microbenchmark(times=100L, sdcols(), m.get(), withF())
# Unit: microseconds
#      expr      min        lq      mean    median        uq      max neval cld
#  sdcols()  780.201  810.4350  865.3564  827.4970  853.4875 2354.577   100 a  
#   m.get() 2792.293 2864.1225 3052.3872 2899.9370 3031.9260 4831.963   100   c
#   withF()  897.822  927.7105 1005.3166  945.9495  981.0580 2600.445   100  b 

@Arun,之前不知道mget。真是太棒了! - Sumit

5

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