【问题标题】:Efficiently fill NAs by group按组有效填充 NA
【发布时间】:2019-01-09 12:33:52
【问题描述】:

我有一个数据集,我在其中观察某些人的变量,而不是其他人。对于那些我观察变量的人,我只观察一次。但是,每个个体的观察次数以及观察值的位置会有所不同。

如果有非 NA 值,我想用非 NA 值填充给定个体的所有 NA 值。否则,NA 应该保持 NA。

这是一个示例数据集:

#data.frame of 100 individuals with 10 observations each
data <- data.frame(group = rep(1:100,each=10),value = NA)

#first 50 individuals get a value at the fifth observation, others don't have value
data$value[seq(5,500,10)] <- rnorm(50)

到目前为止一切顺利,不是什么大问题。取自另一个线程,我们可以使用dplyrtidyr 做这样的事情:

data <- data %>% 
  group_by(group) %>% #by group
  fill(value) %>% #default direction down
  fill(value, .direction = "up") #also fill NAs upwards

这很好地解决了这个问题。但是,我必须为大约 80mio 执行此操作。观察,这需要几个小时。有更快的方法吗?我认为data.table 可能是一个不错的候选人。

如果可以调整该方法以仅填充出现在值之前的 NA,那就太好了。

谢谢!

【问题讨论】:

    标签: r performance na


    【解决方案1】:

    您可以对 data.table 和 dplyr 使用一种非常简单的方法,我相信这会非常快速和高效:

    在数据表中:

    library(data.table)
    setDT(data)
    data[, value := value[!is.na(value)][1L], by = group]
    

    或 dplyr:

    library(dplyr)
    data <- data %>% 
      group_by(group) %>% 
      mutate(value = value[!is.na(value)][1L])
    

    关键是你有一个非 NA 值,每组恰好 o 或 1 次。因此,您不需要 last-observation-carried-forward 逻辑。只需取第一个非 NA 值(如果存在)。

    【讨论】:

      【解决方案2】:

      这是我使用的代码:你的代码 vs akrun vs 我的。有时动物园不是最快的过程,但它是最干净的。无论如何,你可以测试它。

      更新: 它已经用更多数据 (100.000) 进行了测试,到目前为止,Process 03(子集和合并)胜出。

      上次更新 与rbenchmark的功能对比:

      library(dplyr)
      library(tidyr)
      library(base)
      library(data.table)
      library(zoo)
      library(rbenchmark)
      
      #data.frame of 100 individuals with 10 observations each
      data <- data.frame(group = rep(1:10000,each=10),value = NA)
      data$value[seq(5,5000,10)] <- rnorm(50) #first 50 individuals get a value at the fifth observation, others don't have value
      
      #Process01
      P01 <- function (data){
          data01 <- data %>% 
              group_by(group) %>% #by group
                  fill(value) %>% #default direction down
                  fill(value, .direction = "up") #also fill NAs upwards
          return(data01)
      }
      
      #Process02
      P02 <- function (data){
          data02 <- setDT(data)[, value := na.locf(na.locf(value, na.rm = FALSE), 
                                                   fromLast = TRUE), group]
          return(data02)
      }
      
      #Process03
      P03 <- function (data){
          dataU <- subset(unique(data), value!='NA') #keep row number
          dataM <- merge(data, dataU, by = "group", all=T) #merge tables
          data03 <- data.frame(group=dataM$group, value = dataM$value.y) #idem shape of data
          return(data03)
      }
      
      benchmark("P01_dplyr" = {data01 <- P01(data)},
                "P02_zoo" = {data02 <- P02(data)},
                "P03_data.table" = {data03 <- P03(data)},
                replications = 10,
                columns = c("test", "replications", "elapsed")
                )
      

      数据=10.000、10 次重复和 I5 7400 的结果:

          test replications elapsed
      1      P01_dplyr           10  257.78
      2        P02_zoo           10   10.35
      3 P03_data.table           10    0.09
      

      【讨论】:

      • 可能会考虑研究bench 包或microbenchmark。此外,有时您包括定义示例数据,有时不包括,OP 提到他有非常大的数据。我想在进行基准测试时应该考虑到这一点。
      • 我已经尝试增加数据和处理 03 到目前为止的胜利。
      【解决方案3】:

      我们可以使用data.table 就地分配。这里,zoo中的na.locf用于用相邻的非NA元素填充NA元素

      library(data.table)
      library(zoo)
      setDT(data)[, value := na.locf(na.locf(value, na.rm = FALSE), fromLast = TRUE), group]
      

      基准测试

      set.seed(24)
      data1 <- data.frame(group = rep(1:1e6,each=10),value = NA)
      data1$value[seq(5,1e6,10)] <- rnorm(100000)
      
      data2 <- copy(data1)
      
      system.time({setDT(data2)[, value := na.locf(na.locf(value, 
                   na.rm = FALSE), fromLast = TRUE), group]})
      #   user  system elapsed 
      # 70.681   0.294  70.917 
      
      
      system.time({
      
      data1 %>% 
        group_by(group) %>% #by group
        fill(value) %>% #default direction down
        fill(value, .direction = "up")
      
      })
      # 17% ~33 m remaining 
      

      注意:这花了很多时间。所以必须中止会话。

      注意 2:这种方法是基于我们希望用非 NA 相邻元素替换 NA 元素并且每组有多个非 NA 元素的假设

      【讨论】:

      • 非常感谢!我也进行了基准测试(10 万次观察),发现您的方法大约需要 tidyverse 方法的 1/5 时间。看看它是如何扩展的会很有趣!
      • @Mr.Zen 对我来说,dplyr 方法只完成了 27% 和更多时间
      猜你喜欢
      • 2015-02-06
      • 1970-01-01
      • 2021-12-18
      • 1970-01-01
      • 2022-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多