假设有一个data.table,在其他四列中包含id和integer值。如何有效地找到其他四列中四个值中至少有两个相同的行?
fooTbl = data.table(id = c('a', 'b'), ind1=c(1,2), ind2=c(3,4), ind3=c(2,3), ind4=c(2,1))
fooTbl
# id ind1 ind2 ind3 ind4
# 1: a 1 3 2 2
# 2: b 2 4 3 1
我已经有两个解决方案了。第一个比第二个快得多,但第一个要求对所有组合进行硬编码并检查每个组合的相等性。随着列数的增加,这似乎是不可取的并且难以维护:
fooTbl[, uniq := (ind1 != ind2 & ind1 != ind3 & ind1 != ind4 & ind2 != ind3 & ind2 != ind4 & ind3 != ind4)]
fooTbl
# id ind1 ind2 ind3 ind4 uniq
# 1: a 1 3 2 2 FALSE
# 2: b 2 4 3 1 TRUE
第二种方法是使用data.table并在表格的长形式上操作。这个更易于维护(没有所有组合的硬编码),但速度要慢得多:
fooTbl[, uniq := NULL]
fooTbl
# id ind1 ind2 ind3 ind4
# 1: a 1 3 2 2
# 2: b 2 4 3 1
fooTbl = melt(fooTbl, measure=c('ind1', 'ind2', 'ind3', 'ind4'))
fooTbl
# id variable value
# 1: a ind1 1
# 2: b ind1 2
# 3: a ind2 3
# 4: b ind2 4
# 5: a ind3 2
# 6: b ind3 3
# 7: a ind4 2
# 8: b ind4 1
fooTbl[, N := length(unique(value)), by=id]
fooTbl[, uniq := N == 4][, N := NULL]
fooTbl
id variable value uniq
1: a ind1 1 FALSE
2: b ind1 2 TRUE
3: a ind2 3 FALSE
4: b ind2 4 TRUE
5: a ind3 2 FALSE
6: b ind3 3 TRUE
7: a ind4 2 FALSE
8: b ind4 1 TRUE
fooTbl = dcast(fooTbl, id + uniq ~ variable, value.var='value')
fooTbl
id uniq ind1 ind2 ind3 ind4
1 a FALSE 1 3 2 2
2 b TRUE 2 4 3 1
有没有办法可以在不对所有检查组合进行硬编码的情况下获得第一个(宽)解决方案的速度?
对于我的实际表格来说,N是可管理的(~3M),但是大到足以在第二个解决方案中感受到操作的重量。
答案 0 :(得分:1)
这假设id
是每行的唯一键:
> (ind <- paste0("ind",1:4))
[1] "ind1" "ind2" "ind3" "ind4"
> fooTbl[,u := length(ind) == length(unique(unlist(.SD[,ind,with=F]))),by="id"]
或
> fooTbl[,u := !any(duplicated(unlist(.SD[,ind,with=F]))),by="id"]
或没有by
:
> fooTbl[, u := apply(fooTbl[,ind,with=F],1,function(x) !any(duplicated(x)))]
现在:
> fooTbl
id ind1 ind2 ind3 ind4 u
1: a 1 3 2 2 FALSE
2: b 2 4 3 1 TRUE
答案 1 :(得分:1)
我最终选择了@ Arun的建议并以编程方式构建了表达式并对其进行了评估。这是一个data.table特定实现。我不得不求助于字符串操作(而不是仅使用bquote对符号进行操作),所以它不像我想的那样干净,但它有效。
allColUniqExpr <- function(colNames, resColName) {
makeExpr = function(x) sprintf('%s != %s', x[1], x[2])
expr = apply(combn(colNames, 2), 2, makeExpr)
expr = paste(expr, sep='', collapse=' & ')
expr = sprintf('%s := %s', resColName, expr)
expr = parse(text=expr)
expr
}
使用:
fooTbl[, eval(allColUniqExpr(c('ind1', 'ind2', 'ind3', 'ind4'), 'uniq'))]
答案 2 :(得分:0)
这是另一种可能性:
fooTbl$uniq = apply(fooTbl[,2:ncol(fooTbl)],1,function(x) {any(duplicated(x))})