首页 > 解决方案 > 将 NA 替换为子集组内的值

问题描述

对于具有来自多个来源的数据的子组,我需要在 ID 和时间点内替换数据框的所有列中的缺失值。如果不是太复杂,最好优先考虑来自源 B 的数据(例如,在下面数据中变量 Y 的 id 为 2 的情况下)。

使用下面的代码,它目前只针对一列工作(没有优先级),但由于它是一个包含数百万行的大型数据框,因此需要进一步自动化。另外,如果可能,我想将其保留在 data.table 框架内。有什么建议吗?

# Data
id  time  X  Y   Source
1   2005  67 NA  A
1   2005  NA 1.1 B
1   2005  NA 1.1 B
2   2003  85 NA  B
2   2003  NA 0.4 A
2   2003  85 0.5 B

# Desired output
id  time  X  Y   Source
1   2005  67 1.1 A
1   2005  67 1.1 B
1   2005  67 1.1 B
2   2003  85 0.5 B
2   2003  85 0.4 A
2   2003  85 0.5 B

# Find duplicates
dup <- (duplicated(dat[,c('id','time')])|duplicated(dat[,c('id','time')], fromLast=TRUE))

# Replace NA in column X
library(data.table)
dat[dup & is.na(X), X := dat[!is.na(X)][.SD, on=.(id,time), mult="last", X]]

### Solution based on locf and an internal data.table loop (still slower than tidyverse)

    library(data.table)
    library(zoo)

    cols <- colnames(dat)[c(-1,-2)]
    dat <- dat[order(id,time,Source)] # this combined with na.locf0(fromLast=T) takes care of the priority.
    dup <- (duplicated(dat[,c('id','time')])|duplicated(dat[,c('id','time')], fromLast=TRUE))

    t1 <- Sys.time() 
      dat=rbind(
        dat[!dup],
        dat[dup, lapply(.SD, na.locf0,fromLast = TRUE), by=c('id','time'), .SDcols = cols][
            ,lapply(.SD, na.locf0), by=c('id','time'), .SDcols = cols]
      )
    t2 <- Sys.time()
    t2-t1

标签: rperformanceloopsdata.tablesubset

解决方案


这里有 3 个选项:

1)使用for循环get

for (x in updcols) {
    DT0[dup & is.na(get(x)), (x) := DT0[!is.na(get(x))][
        .SD, on=.(id,time), mult="last", get(x)]]   
}
DT0

2)使用for带有非标准评估的循环:

nsef <- function(dat, coln) {
    eval(substitute(
        dat[dup & is.na(V), V := dat[!is.na(V)][.SD, on=.(id,time), mult="last", V]],
        list(V=as.name(coln))
    ))
}
for (x in updcols) {
    nsef(DT1, x)
}
DT1

3) 提取最后一个非 NA 值并执行连接,然后通过引用更新:

lu <- DT2[, lapply(.SD, function(x) last(x[!is.na(x)])), bycols, .SDcols=updcols]
DT2[(dup), (updcols) := 
    lu[.SD, on=bycols, Map(function(x, y) fcoalesce(x, y), 
        mget(paste0("i.", updcols)), mget(updcols))]
]
DT2

您也可以使用fifelse(version >= 1.12.4) 代替fcoalesce(ie fcoalesce(X, Y) == fifelse(is.na(X), Y, X))。

我认为时间将取决于您的实际数据集的特征。

输出:

   id time  X   Y Source
1:  1 2005 67 1.1      A
2:  1 2005 67 1.1      B
3:  1 2005 67 1.1      B
4:  2 2003 85 0.5      B
5:  2 2003 85 0.4      A
6:  2 2003 85 0.5      B

数据:

library(data.table) #data.table_1.12.6
DT <- fread("id  time  X  Y   Source
1   2005  67 NA  A
1   2005  NA 1.1 B
1   2005  NA 1.1 B
2   2003  85 NA  B
2   2003  NA 0.4 A
2   2003  85 0.5 B")
DT0 <- copy(DT)
DT1 <- copy(DT)
DT2 <- copy(DT)
bycols <- c('id','time')
updcols <- c("X", "Y")
dup <- duplicated(DT, by=bycols) | duplicated(DT, by=bycols, fromLast=TRUE)

推荐阅读