当变量名称存储在字符向量中时,选择/分配给data.table


91

data.table如果变量名称存储在字符向量中,如何在a中引用变量?例如,这适用于data.frame

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

如何使用或不使用:=符号对data.table执行相同的操作?显而易见的事情是dt[ , list(colname)]行不通的(我也没想到)。

Answers:


132

以编程方式选择变量的两种方法:

  1. with = FALSE

    DT = data.table(col1 = 1:3)
    colname = "col1"
    DT[, colname, with = FALSE] 
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
    
  2. 'dot dot'(..)前缀:

    DT[, ..colname]    
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
    

有关“点点”(..)表示法的进一步说明,请参见1.10.2中的新功能(帮助文本中目前未对此功能进行说明)。

分配给变量,请将LHS:=括在括号中:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

后者称为列plonk,因为您通过引用替换了整个列向量。如果存在子集i,它将通过引用进行子分配。周围的括号(colname)是CRAN十月版本v1.9.4引入了一个速记2014年这里是新闻项目

现在,在所有情况下都不建议使用with = FALSEwith :=,因为:=在一段时间内首选将LHS加上括号。

colVar = "col1"
DT[, colVar := 1, with = FALSE]                 # deprecated, still works silently
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b)]  # no change
DT[, `:=`(...), by = ...]                       # no change

另请参阅中的“详细信息”部分?`:=`

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

为了回答评论中的其他问题,这是一种方法(照常,有很多方法):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

或者,你可能会发现更容易阅读,编写和调试只是eval一个paste类似于构建一个动态的SQL语句发送到服务器:

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

如果您经常这样做,则可以定义一个辅助函数EVAL

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

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

既然data.table1.8.2会自动优化j效率,那么最好使用该eval方法。例如get(),输入j阻止进行某些优化。

或者,有set()。的低开销,函数形式:=,在这里很好。请参阅?set

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66

1
感谢马修的回复。with = FALSE肯定解决了部分问题。但实际上,我想用该列的总和来替换该列。我能以某种方式通过赋值右侧的变量引用列名吗?
frankc 2012年

从某种意义上说,我只是用一个在dt中不存在的不同名称在外部对这个cumsum进行了匹配,并且可以正常工作。
frankc 2012年

1
但这将是多余的一行!不是很优雅:)但是有时候它很有用。在这种情况下,最好以开头变量名称.,或..避免任何潜在的掩盖,如果DT将来以后确实包含该符号作为列名(并遵循列名不以开头的约定.)。有一些功能要求可以使其更加健壮地解决诸如添加.()和等问题..()
Matt Dowle 2012年

在我注意到您修改了答案之前,我已回复。我的第一个想法是eval(parse()),但是由于某种原因,当我想到从外部进行处理时,我很难使它起作用。对于很多我没有想到的事情,这是一个很好的答案。总体来说,感谢data.table,它是一个很棒的程序包。
frankc 2012年

2
请注意,您可以使用fn$gsubfn包中的准Perl类型的字符串插值 来提高EVAL解决方案的可读性:library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" )
G. Grothendieck 2013年

8

*这并不是真正的答案,但我没有足够的街头信誉来发表评论:/

无论如何,对于任何想在数据表中实际创建一个新列且名称存储在变量中的人来说,我都有以下工作。我不知道它的表现。有什么改进建议吗?是否可以安全地假设一个无名的新列将始终被命名为V1?

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

注意,我可以在sum()中引用它,但是似乎无法在同一步骤中对其进行分配。顺便说一句,我需要这样做的原因是名称,将基于Shiny应用程序中的用户输入。


为工作而+1:我同意这不一定是做到这一点的“方法”,但是花了大约45分钟在该主题的每个SO帖子上花了点时间,这才是我实际上能够获得的唯一解决方案工作-感谢您抽出宝贵时间指出!
Neuropsych

很高兴我能帮助你!不幸的是,尽管这3个衬里并不可怕,但我从未直接使用data.tables找到更优雅的解决方案。在我的场景中,我确实意识到,更简单的选择是使用tidyr将数据设置为“长”而不是“宽”,因为根据用户输入,我始终可以在单个列上进行过滤,而不是从一组中进行选择列。
efh0888 '16

2
假设V1是新名称是不安全的。例如,如果您使用阅读csv,fread并且有一个未命名的列,它将具有V1名称(并read.csv给出X)。因此,您的表可能已经有一个V1。也许是通过names(DT)[length(names(DT))]
dracodoc '16

2

对于多个列,一个函数应用于列值。

从函数更新值时,RHS必须是一个列表对象,因此使用.SDwith循环lapply可以解决问题。

下面的示例将整数列转换为数字列

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 

2

通过变量或函数从data.table中检索多个列:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

全部产生

   that whatever
1:    1        1
2:    2        2

我找到了.SDcols最优雅的方式。


1

你可以试试这个

列名<-as.name(“ COL_NAME”)

DT2 <-DT [,list(COL_SUM = sum(eval(colname,.SD))),by = c(group)]


1
始终建议您在代码中添加说明,而不仅仅是发布代码。
MBorg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.