2017-04-24 34 views
2

我有一個包含一些邏輯列的數據集,並且希望用相應的列名稱替換「TRUE」的值。我問了一個類似的問題here,並且能夠藉助其他S/O用戶的一些建議來確定合適的解決方案。但是,該解決方案不使用data.table語法,而是複製整個數據集而不是按引用進行替換,這非常耗時。R使用data.table語法將邏輯列中的正值替換爲列名稱

什麼是使用data.table語法來做到這一點的最合適的方法?

我嘗試這樣做:

# Load library  
library(data.table) 

# Create dummy data.table: 
mydt <- data.table(id = c(1,2,3,4,5), 
        ptname = c("jack", "jill", "jo", "frankie", "claire"), 
        sex = c("m", "f", "f", "m", "f"), apple = c(T,F,F,T,T), 
        orange = c(F,T,F,T,F), 
        pear = c(T,T,T,T,F)) 

# View dummy data: 
> mydt 
    id ptname sex apple orange pear 
1: 1 jack m TRUE FALSE TRUE 
2: 2 jill f FALSE TRUE TRUE 
3: 3  jo f FALSE FALSE TRUE 
4: 4 frankie m TRUE TRUE TRUE 
5: 5 claire f TRUE FALSE FALSE 

# Function to recode values in a data.table: 
recode.multi <- function(datacol, oldval, newval) { 
    trans <- setNames(newval, oldval) 
    trans[ match(datacol, names(trans)) ] 
} 

# Get a list of all the logical columns in the data set: 
logicalcols <- names(which(mydt[, sapply(mydt, is.logical)] == TRUE)) 

# Apply the function to convert 'TRUE' to the relevant column names: 
mydt[, (logicalcols) := lapply(.SD, recode.multi, 
           oldval = c(FALSE, TRUE), 
           newval = c("FALSE", names(.SD))), .SDcols = logicalcols] 

# View the result: 
> mydt 
    id ptname sex apple orange pear 
1: 1 jack m apple FALSE apple 
2: 2 jill f FALSE apple apple 
3: 3  jo f FALSE FALSE apple 
4: 4 frankie m apple apple apple 
5: 5 claire f apple FALSE FALSE 

這是不正確的作爲,而不是通過對替換值每列名迭代,它只是回收的第一個(在這種情況下,「蘋果」)。

而且,如果我扭轉新舊值的順序,函數忽略我的字符串替換爲第二個值,並使用了前兩個列名作爲替代品在所有情況下:

# Apply the function with order of old and new values reversed: 
mydt[, (logicalcols) := lapply(.SD, recode.multi, 
           oldval = c(TRUE, FALSE), 
           newval = c(names(.SD), "FALSE")), .SDcols = logicalcols] 

# View the result: 
> mydt 
    id ptname sex apple orange pear 
1: 1 jack m apple orange apple 
2: 2 jill f orange apple apple 
3: 3  jo f orange orange apple 
4: 4 frankie m apple apple apple 
5: 5 claire f apple orange orange 

我m確定我可能錯過了一些簡單的東西,但有人知道爲什麼函數不會遍歷列名(以及如何編輯它來做到這一點)?

我的預期產出將如下所示:

> mydt 
    id ptname sex apple orange pear 
1: 1 jack m apple FALSE pear 
2: 2 jill f FALSE orange pear 
3: 3  jo f FALSE FALSE pear 
4: 4 frankie m apple orange pear 
5: 5 claire f apple FALSE FALSE 

或者簡潔data.table語法的任何其他建議,以實現這一目標將非常感激。

+2

使用字符,而不是邏輯將在以後的任何分析我想是痛苦的。關於你的方式不起作用的原因,'lapply'一次迭代一件事(這裏的.SD)。如果您需要它遍歷.SD和名稱(.SD),請嘗試使用Map。 – Frank

+0

謝謝 - 找不到'地圖'的語法示例,但幫助說它是一個適用於mapply的包裝 - 這樣做'mydt [,(logicalcols):= mapply(recode.multi, datacol = .SD, oldval = c(TRUE,FALSE), newval = c(names(.SD),「FALSE」), SIMPLIFY = FALSE)。SDcols = logicalcols] '除了將FALSE值轉換爲NAs之外,幾乎可以讓我在那裏。 –

回答

2

我們可以用一個melt/dcast方法

dcast(melt(mydt, id.var = c("id", "ptname", "sex"))[, 
    value1 := as.character(value)][(value), value1 := variable], 
      id + ptname + sex~variable, value.var = "value1") 
# id ptname sex apple orange pear 
#1: 1 jack m apple FALSE pear 
#2: 2 jill f FALSE orange pear 
#3: 3  jo f FALSE FALSE pear 
#4: 4 frankie m apple orange pear 
#5: 5 claire f apple FALSE FALSE 

或者另一種選擇是set這將是更有效的

nm1 <- which(unlist(mydt[, lapply(.SD, class)])=="logical") 
for(j in nm1){ 
    i1 <- which(mydt[[j]]) 
    set(mydt, i=NULL, j=j, value = as.character(mydt[[j]])) 
    set(mydt, i = i1, j=j, value = names(mydt)[j]) 
} 

mydt 
# id ptname sex apple orange pear 
#1: 1 jack m apple FALSE pear 
#2: 2 jill f FALSE orange pear 
#3: 3  jo f FALSE FALSE pear 
#4: 4 frankie m apple orange pear 
#5: 5 claire f apple FALSE FALSE 

或者在評論中提到的另一種選擇是

mydt[, (nm1) := Map(function(x,y) replace(x, x, y), .SD, names(mydt)[nm1]), .SDcols = nm1] 
mydt 
# id ptname sex apple orange pear 
#1: 1 jack m apple FALSE pear 
#2: 2 jill f FALSE orange pear 
#3: 3  jo f FALSE FALSE pear 
#4: 4 frankie m apple orange pear 
#5: 5 claire f apple FALSE FALSE 

更新:比較選項二和三(一個是不可能的,由於非邏輯列的數量)與包含18573行和650列的數據集,其中252列是邏輯運行與以下時間:

# Option 2: 
    nm1 <- which(unlist(mydt[, lapply(.SD, is.logical)])) 
    system.time( 
    for(j in nm1){ 
    i1 <- which(mydt[[j]]) 
    set(mydt, i=NULL, j=j, value = as.character(mydt[[j]])) 
    set(mydt, i = i1, j=j, value = names(mydt)[j]) 
    } 
    ) 
# user system elapsed 
# 0.61 0.00 0.61 

# Option 3: 
system.time( 
    mydt[, (nm1) := Map(function(x,y) replace(x, x, y), .SD, names(mydt)[nm1]), .SDcols = nm1] 

    ) 
#user system elapsed 
#0.65 0.00 0.66 

兩者都是比不使用data.table語法原來的做法顯著快:

# Original approach: 
logitrue <- which(mydt == TRUE, arr.ind = T) 
system.time(
    mydt[logitrue, ] <- colnames(mydt)[logitrue[,2]] 
) 
    # user system elapsed 
    # 1.22 0.03 4.22 
+0

謝謝,但之後我不知道在哪些數據集中要更改的列(但可以通過創建所有邏輯列的列表來識別要更改的列,已將我的帖子編輯爲包括這個)。是否有可能在融化/ dcast中使用.SD?在上面的recode.multi函數中,從邏輯到字符的轉換已經被考慮到了,爲此使用set是否更快? –

+0

@AmyM在將其重新編碼爲字符值之前,您必須將邏輯更改爲字符 – akrun

+0

@AmyM在列名稱中應該有一些模式或某些內容來指定。如果沒有模式,您如何知道要更改哪些列? – akrun