【问题标题】:Select / assign to data.table when variable names are stored in a character vector当变量名称存储在字符向量中时选择/分配给 data.table
【发布时间】:2021-12-06 04:03:18
【问题描述】:

如果变量名称存储在字符向量中,您如何引用 data.table 中的变量?例如,这适用于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)] 的显而易见的事情不起作用(我也不希望它起作用)。

【问题讨论】:

    标签: r data.table


    【解决方案1】:

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

    1. with = FALSE:

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

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

    有关“点点”(..) 表示法的进一步说明,请参阅New Features in 1.10.2(目前未在帮助文本中描述)。

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

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

    后者被称为列plonk,因为你通过引用替换了整个列向量。如果存在子集i,它将通过引用进行子分配。 (colname) 周围的括号是 CRAN 2014 年 10 月 v1.9.4 版本中引入的简写。这里是 the news item

    现在在所有情况下都不推荐使用 with = FALSE:=,因为包装 := 带括号的 LHS 已被首选一段时间。

    colVar = "col1"
    
    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 
    

    或者,您可能会发现evalpaste 更容易读取、写入和调试,类似于构造动态 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.table 1.8.2 自动优化j 以提高效率,最好使用eval 方法。例如,j 中的 get() 会阻止一些优化。

    或者,有set():= 的低开销、函数形式,在这里可以。见?set

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

    【讨论】:

    • 感谢马修的回复。 with=FALSE 绝对解决了我的部分问题。但实际上,我想用该列的 cumsum 替换该列。我可以通过赋值右侧的变量以某种方式引用列名吗?
    • 实际上,我只是用一个不同的名称在外部存储了 cumsum,该名称在 dt 中不存在并且工作正常。
    • 但这将是额外的一行!不是很优雅:) 但有时它很有用。在这些情况下,最好以... 开头变量名,以避免任何潜在的屏蔽,如果DT 将来确实包含该符号作为列名(并遵守列名不包含的约定)以. 开头)。有一些功能请求可以使其更健壮地处理此类问题,例如添加 .()..()
    • 在我注意到你编辑了你的答案之前我已经回复了。我的第一个想法是 eval(parse()) 但由于某种原因我无法让它工作,当我意识到只在外部进行时。这是一个很好的答案,有很多我没有想到的事情。总的来说,感谢 data.table,这是一个很棒的包。
    • 请注意,您可以使用 gsubfn 包中 fn$ 的准 perl 类型字符串插值来提高 EVAL 解决方案的可读性:library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" )
    【解决方案2】:

    *这不是一个真正的答案,但我没有足够的街头信誉来发布 cmets:/

    无论如何,对于任何可能希望在数据表中实际创建一个新列并将名称存储在变量中的人,我有以下工作要做。我不知道它的性能。有什么改进的建议吗?假设一个无名的新列总是被命名为 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() 中很好地引用它,但似乎无法在同一步骤中对其进行分配。顺便说一句,我需要这样做的原因是 colname 将基于 Shiny 应用中的用户输入。

    【讨论】:

    • +1 仅用于工作:我同意这一定不是执行此操作的“方法”,但是刚刚花了大约 45 分钟的时间浏览有关此主题的每个 SO 帖子,这是唯一的解决方案我实际上已经能够开始工作了 - 感谢您抽出时间指出!
    • 很高兴我能帮上忙!不幸的是,我从来没有直接使用 data.tables 找到更优雅的解决方案,尽管这 3 班轮并不可怕。在我的场景中,我确实意识到一个更简单的选择是使用 tidyr 来使我的数据“长”而不是“宽”,因为根据用户输入,我总是可以过滤单个列而不是从一组中选择列数。
    • 假设V1 是新名称是不安全的。例如,如果您使用fread 读取csv,并且有一个未命名的列,它将具有V1 名称(而read.csv 将给出X)。因此,您的表可能已经有V1。也许只是通过names(DT)[length(names(DT))]得到名字
    【解决方案3】:

    通过变量或函数从 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 方式最优雅。

    【讨论】:

      【解决方案4】:

      适用于多列和应用于列值的函数。

      从函数更新值时,RHS 必须是一个列表对象,因此在 .SDlapply 上使用循环就可以了。

      以下示例将整数列转换为数字列

      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" 
      

      【讨论】:

        【解决方案5】:

        你可以试试这个:

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

        【讨论】:

        • 始终建议在您的代码中添加解释,而不仅仅是发布代码。
        【解决方案6】:

        随着开发版本 1.14.3, 获得了一个新接口,用于data.table 上的编程,请参阅New Features 中的第 10 项。它使用新的env = 参数。

        library(data.table) # development version 1.14.3 used
        dt <- data.table(col1 = 1:3)
        colname <- "col1"
        
        dt[, cn := cn + 3L, env = list(cn = colname)][]
        
            col1
           <int>
        1:     4
        2:     5
        3:     6
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-07-11
          • 2022-11-01
          • 2016-01-11
          • 2014-09-14
          • 1970-01-01
          • 2018-07-20
          相关资源
          最近更新 更多