按组替换R data.table中所有缺失列的第一行

5

我有一个 data.table,想要做类似于 data[ !is.na(variable) ] 的操作。但是,对于完全缺失的组,我希望只保留该组的第一行。因此,我想使用 by 进行子集操作。我在网上做了一些研究,找到了一个解决方案,但我认为它效率低下。

下面提供了一个示例,展示了我希望实现的内容。我想知道是否可以在不创建两个额外列的情况下完成这个操作。

d_sample = data.table( ID = c(1, 1, 2, 2, 3, 3), 
                   Time = c(10, 15, 100, 110, 200, 220), 
                   Event = c(NA, NA, NA, 1, 1, NA))

d_sample[ !is.na(Event), isValidOutcomeRow := T, by = ID]
d_sample[ , isValidOutcomePatient := any(isValidOutcomeRow), by = ID]
d_sample[ is.na(isValidOutcomePatient), isValidOutcomeRow := c(T, rep(NA, .N - 1)), by = ID]
d_sample[ isValidOutcomeRow == T ]

编辑:这里是一些使用包含60K行数据的数据集进行速度比较的thelatemailFrank的解决方案。

d_sample = data.table( ID = sort(rep(seq(1,30000), 2)), 
                   Time = rep(c(10, 15, 100, 110, 200, 220), 10000), 
                   Event = rep(c(NA, NA, NA, 1, 1, NA), 10000) )

在我的电脑上,thelatemail的解决方案运行时间为20.65

system.time(d_sample[, if(all(is.na(Event))) .SD[1] else .SD[!is.na(Event)][1], by=ID])

弗兰克的第一种解决方案运行时间为 0

system.time( unique( d_sample[order(is.na(Event))], by="ID" ) )

弗兰克的第二个解决方案运行时间为0.05

system.time( d_sample[order(is.na(Event)), .SD[1L], by=ID] )
2个回答

7

这看起来是有效的:

unique( d_sample[order(is.na(Event))], by="ID" )

   ID Time Event
1:  2  110     1
2:  3  200     1
3:  1   10    NA

另外一种方法是d_sample[order(is.na(Event)), .SD[1L], by=ID]


扩展原帖的例子,我也发现这两种方法的时间相似:

n = 12e4 # must be a multiple of 6
set.seed(1)
d_sample = data.table( ID = sort(rep(seq(1,n/2), 2)), 
                   Time = rep(c(10, 15, 100, 110, 200, 220), n/6), 
                   Event = rep(c(NA, NA, NA, 1, 1, NA), n/6) )

system.time(rf <- unique( d_sample[order(is.na(Event))], by="ID" ))
# 1.17
system.time(rf2 <- d_sample[order(is.na(Event)), .SD[1L], by=ID] )   
# 1.24
system.time(rt <- d_sample[, if(all(is.na(Event))) .SD[1] else .SD[!is.na(Event)], by=ID])    
# 10.42
system.time(rt2 <- 
    d_sample[ d_sample[, { w = which(is.na(Event)); .I[ if (length(w) == .N) 1L else -w ] }, by=ID]$V1 ] 
)
# .13

# verify
identical(rf,rf2) # TRUE
identical(rf,rt) # FALSE
fsetequal(rf,rt) # TRUE
identical(rt,rt2) # TRUE

@thelatemail的解决方案rt2的变化是迄今为止速度最快的。

聪明 - 有没有任何迹象表明这更快?(我怀疑是的) - thelatemail
1
我不这么认为,根据楼主的预期结果 - 也许他们可以澄清一下? - thelatemail
@Frank 如果相同的ID只有缺失的“Event”值,那么我只想保留第一行(仍然缺失)。这清楚吗?我认为你提供的所有解决方案都可以。 - vryb
1
@Frank 没错,无论如何都是每个ID一行。当ID作为键时,您的最快解决方案也非常有效,因为unique()按顺序返回d_sample。 - vryb
1
@Frank - 没错,我以后会这样做的。感谢您的回复! - vryb
显示剩余6条评论

5

以下是一种可能需要改进的方法,但它依赖于快速的if()逻辑检查来确定返回什么样的结果:

d_sample[, if(all(is.na(Event))) .SD[1] else .SD[!is.na(Event)], by=ID]
#   ID Time Event
#1:  1   10    NA
#2:  2  110     1
#3:  3  200     1

跟随@eddi的小技巧对分组进行子集操作,这样做就可以得到...

d_sample[ d_sample[, { 
  w = which(is.na(Event))
  .I[ if (length(w) == .N) 1L else -w ] 
}, by=ID]$V1 ] 

谢谢您的快速回复!有没有办法避免使用.SD?虽然这段代码看起来很好,但不幸的是它运行得非常慢,我怀疑.SD是其中一些问题的原因。 - vryb
如果您制作一个可以扩展的示例(例如某个n的函数),那么更容易确定是什么导致了减速。 - Frank
使用eddi的技巧,将相同的想法应用于以下代码:d_sample[ d_sample[, { w = which(is.na(Event)); .I[ if (length(w) == .N) 1L else -w ] }, by=ID]$V1 ]。参考链接:https://dev59.com/1WQn5IYBdhLWcg3wxZni#16574176 - Frank

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