2017-04-24 44 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