【问题标题】:Replace NA with value within group for subset将 NA 替换为子集组内的值
【发布时间】:2020-04-09 23:37:24
【问题描述】:

对于具有来自多个来源的数据的子组,我需要在 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

【问题讨论】:

  • 我有点困惑,你能显示所需的输出吗?
  • 抱歉,现在添加。

标签: r performance loops data.table subset


【解决方案1】:
library(tidyverse)
library(data.table)

Data <- data.table(id = c(1,1,1,2,2,2), time = c(2005, 2005, 2005, 2003, 2003, 2003), X = c(67, NA, NA, 85, NA, 85),
                       Y = c(NA, 1.1, 1.1, NA, 0.4, 0.5), Source = c("A", "B", "B", "B", "A", "B"))

Data <- Data %>% 
  group_by(id, Source) %>% 
  fill(time, X, Y) %>%
  fill(time, X, Y, .direction = "up")

Data <- Data %>% 
  group_by(id) %>% 
  fill(time, X, Y) %>%
  fill(time, X, Y, .direction = "up")

我不确定您的意思是始终首选来源“B”还是仅在样本来源也是“B”时才首选(因此,如果该样本的来源是首选来源,则首选来源将是“A”曾经是一个”)。此代码解决了后一种情况的问题。 它需要 tidyverse。

【讨论】:

  • 请包括所有library 行,因为这似乎不是data.table。 OP 要求:如果可能,我希望将其保留在 data.table 框架内
  • 谢谢你!我一直在使用类似的解决方案,但是随着数据集的扩展,它变得太慢了。剩下的数据管理也是在 data.table 框架中进行的,所以把它全部带到那里会很棒。我认为解决方案与其中的 .SDcols 和 (cols) 有关,但还没有完全弄清楚。
【解决方案2】:

这里有 3 个选项:

1) 使用带有getfor 循环:

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(版本>= 1.12.4)代替fcoalesce(即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)

【讨论】:

  • 感谢您的努力!不幸的是,这些解决方案对于我的数据集(包含多种变量类型)来说太慢了,但它们帮助我找到了上面的解决方案。
猜你喜欢
  • 1970-01-01
  • 2012-03-08
  • 2018-09-11
  • 2014-06-13
  • 2020-01-14
  • 2014-06-28
相关资源
最近更新 更多