2017-05-24 113 views
2

我正在使用美妙的R data.table包。但是,訪問(即通過引用操作)具有變量名稱的列非常笨拙:如果我們給出一個data.table dt,它有兩列x和y,並且我們想要添加兩列並將其命名爲z,那麼該命令是R data.table:訪問變量名稱列

dt = dt[, z := x + y] 

現在讓我們寫一個函數add這需要作爲參數(參照)data.table dt三列名summand1Namesummand2NameresultName,它是supossed如上爲只執行相同的指令與一般列名稱。我現在使用的解決方案是反射,即

add = function(dt, summand1Name, summand2Name, resultName) { 
    cmd = paste0('dt = dt[, ', resultName, ' := ', summand1Name, ' + ', summand2Name, ']') 
    eval(parse(text=cmd)) 
    return(dt) # optional since manipulated by reference 
} 

但是我絕對不滿意這個解決方案。首先它笨拙,它不會讓這樣的代碼變得有趣。這很難調試,它只是讓我生氣並燒傷時間。其次,閱讀和理解比較困難。這是我的問題:

我們可以用更好的方式來編寫這個函數嗎?

我知道的一個事實,即一個可以訪問的變量名稱的列像這樣:dt[[resultName]]但是當我寫

dt[[resultName]] = dt[[summand1Name]] + dt[[summand2Name]] 

然後data.table開始抱怨不得不採取的副本,而不是由工作參考。我不想那樣。另外我喜歡語法dt = dt[<all 'database related operations'>],這樣我所做的一切就會粘在一對括號中。是不是可以使用像反引號這樣的特殊符號來表示當前使用的名稱不是引用數據表的實際列,而是作爲實際列的名稱的佔位符?

+0

你或許應該看看'GET'和'mget' –

+0

參見[這](https://stackoverflow.com/questions/27677283/evaluating-both-column-name-and-the-target -d) –

+0

'add = function(dt,summand1Name,summand2Name,resultName)dt [,(resultName):= .SD [[summand1Name]] + .SD [[summand2Name ]]]'?另一個選項可以是'add2 = function(dt,summand1Name,summand2Name,resultName)dt [,(resultName):= eval(as.name(summand1Name))+ eval(as.name(summand2Name))]''得到'如上所述。 –

回答

1

您可以結合使用的LHS中()以及with = FALSE引用RHS上的變量。

dt <- data.table(a = 1:5, b = 10:14) 
my_add <- function(dt, summand1Name, summand2Name, resultName) { 
    dt[, (resultName) := dt[, summand1Name, with = FALSE] + 
     dt[, summand1Name, with = FALSE]] 
} 
my_add(dt, 'a', 'b', 'c') 
dt 

編輯:

相比三個版本。我的效率最低......(但將保留僅供參考)。

set.seed(1) 
dt <- data.table(a = rnorm(10000), b = rnorm(10000)) 
original_add <- function(dt, summand1Name, summand2Name, resultName) { 
    cmd = paste0('dt = dt[, ', resultName, ' := ', summand1Name, ' + ', summand2Name, ']') 
    eval(parse(text=cmd)) 
    return(dt) # optional since manipulated by reference 
} 
my_add <- function(dt, summand1Name, summand2Name, resultName) { 
    dt[, (resultName) := dt[, summand1Name, with = FALSE] + 
     dt[, summand1Name, with = FALSE]] 
} 
list_access_add <- function(dt, summand1Name, summand2Name, resultName) { 
    dt[, (resultName) := dt[[summand1Name]] + dt[[summand2Name]]] 
} 
david_add <- function(dt, summand1Name, summand2Name, resultName) { 
    dt[, (resultName) := .SD[[summand1Name]] + .SD[[summand2Name]]] 
} 

microbenchmark::microbenchmark(
    original_add(dt, 'a', 'b', 'c'), 
    my_add(dt, 'a', 'b', 'c'), 
    list_access_add(dt, 'a', 'b', 'c'), 
    david_add(dt, 'a', 'b', 'c')) 

## Unit: microseconds 
##        expr  min  lq  mean median  uq  max 
##  original_add(dt, "a", "b", "c") 604.397 659.6395 784.2206 713.0315 776.1295 5070.541 
##   my_add(dt, "a", "b", "c") 1063.984 1168.6140 1460.5329 1247.7990 1486.9730 6134.959 
## list_access_add(dt, "a", "b", "c") 272.822 310.9680 422.6424 334.3110 380.6885 3620.463 
##  david_add(dt, "a", "b", "c") 389.389 431.9080 542.7955 454.5335 493.4895 3696.992 
## neval 
## 100 
## 100 
## 100 
## 100 

EDIT2:

一個百萬行,結果看起來是這樣的。正如預期的那樣,原來的方法執行得很好,因爲一旦eval完成,這將工作得很快。

## Unit: milliseconds 
##        expr  min  lq  mean median  uq  max 
##  original_add(dt, "a", "b", "c") 2.493553 3.499039 6.585651 3.607101 4.390051 114.0612 
##   my_add(dt, "a", "b", "c") 11.821820 14.512878 28.387841 17.412433 19.642231 117.6359 
## list_access_add(dt, "a", "b", "c") 2.161276 3.133110 6.874885 3.218185 3.407776 107.6853 
##  david_add(dt, "a", "b", "c") 2.237089 3.313133 6.047832 3.381757 3.788558 103.7532 
## neval 
## 100 
## 100 
## 100 
## 100 
+0

你也可以使用'substitute'和'eval',或潛在的Hadley更好的'quo'和'UQ'函數從'rlang'或者'dplyr'的開發版來做,而不需要回想'dt'理想的 –

+0

即''summand1 = substitute(summand1Name)...'在函數內部的起點和終點處'dt [,(resultName):= eval(summand1)+ eval(summand2)]''。在這裏,您傳遞的是空列名稱,而不是加數字符串。 –

+0

首先'with = FALSE'也需要拷貝,所有'dt [[summand1Name]]'的秒會比'dt [,summand1Name,with = FALSE]'效率更高。 –

0

這是另一種使用substitute的解決方案。我通常儘量避免使用substitute,但我認爲這是使用快速data.table:=代碼而不是本地列表訪問的唯一方式。

我一直保留到amatsuo_net的接口。

set.seed(1) 
dt <- data.table(a = rnorm(10000), b = rnorm(10000)) 

snaut_add <- function(dt, summand1, summand2, resultName){ 
    eval(substitute(
    dt[, z := x + y], 
    list(
     z=as.symbol(resultName), 
     x=as.symbol(summand1), 
     y=as.symbol(summand2) 
    ) 
)) 
} 

snaut_add(dt, "a", "b", "c") 
dt