R数据表格有序列查找

3

我有一个R data.table,其中包含一个id列和多个列,指定了一个有序的阈值级别和相应的值。我想要做的是查找每一行中大于或等于该id参数的第一个级别,并返回相应的值。

以下是一个示例数据集。

DT<-data.table(id=c("Obs1","Obs2"),
    level.1=c(1,1),level.2=c(2,4),level.3=c(3,8),
    val.1=c(10,10),val.2=c(20,30),val.3=c(30,50))

DT
     id level.1 level.2 level.3 val.1 val.2 val.3
1: Obs1       1       2       3    10    20    30
2: Obs2       1       4       8    10    30    50

所以,如果查找参数为:
params<-list("Obs1"=2.5,"Obs2"=1) 

返回的值应为:
c(30,10).

我希望级别和值的数量可以是任意的,但它们需要满足类似于示例的命名惯例。我可以通过几个步骤解决这个问题,但这种方法非常丑陋,而且可能计算效率不高。
level.names<-colnames(DT)[grep("level",colnames(DT))]
val.names<-colnames(DT)[grep("val",colnames(DT))]
setkey(DT,id)

idx<-DT[,grep(TRUE,lapply(.SD,function(y)((params[[id]] <= y))))[1],
        .SDcols=level.names,by=id]

values<-ifelse(is.na(idx$V1),as.numeric(NA),DT[,get(val.names[idx[id,V1]]),by=id]$V1)

我之前使用data.frames更加干净地解决了这个问题,使用plyr::ddply和我可以在data.frame中使用变量名作为列的事实。(为了简洁起见,我不在此处包括该解决方案。)

欢迎任何改进建议。

2个回答

5

我会使用滚动连接来完成这个操作:

DT_m = melt(DT, measure=patterns("^level", "^val"), value.name=c("level", "val"))
query = list(id=c("Obs1", "Obs2"), level=c(2.5, 1))
DT_m[query, val, on=c("id", "level"), roll=-Inf]

roll=-Inf 执行一个 NOCB 连接(next observation carried backward)。当要连接的值(这里是 query)落在空隙中时,下一个观察结果会被作为匹配行返回。例如,2.5 位于 24 之间。因此匹配行是 4(下一个观察结果)。相应的 val30


2
谢谢!这两个解决方案都是巨大的改进。了解melt和rolling joins将对未来非常有益。 - mjreed

2

这里有一种方法:

mDT = melt(DT, measure.var = patterns("level","val"), value.name = c("level","val"))
setkey(mDT, id)

#      id variable level val
# 1: Obs1        1     1  10
# 2: Obs1        2     2  20
# 3: Obs1        3     3  30
# 4: Obs2        1     1  10
# 5: Obs2        2     4  30
# 6: Obs2        3     8  50

params2 <- list(id = c("Obs1","Obs2"), v=c(2.5,1)) 
mDT[params2,{
  i = findInterval(v, level, rightmost.closed=TRUE)
  val[ i + (v != level[i]) ]
}, by=.EACHI]

#      id V1
# 1: Obs1 30
# 2: Obs2 10

如果您在顶层设置params$v,将返回NA
params3 <- list(id = c("Obs1","Obs2"), v=c(5, 1)) 
mDT[params3, {i = findInterval(v, level, rightmost.closed=TRUE); val[ i + (v != level[i])]}, by=.EACHI]

#      id V1
# 1: Obs1 NA
# 2: Obs2 10

评论。 我认为保持数据处于长/融合形式比玩弄列名称更好。

如果你想将参数输入为键值对,stacksetNames很有帮助:

p0      = list(Obs1 = 1, Obs2 = 2.5)
params0 = setNames(stack(p0), c("v","id"))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接