代码之家  ›  专栏  ›  技术社区  ›  yrx1702

按组高效填充NAS

  •  8
  • yrx1702  · 技术社区  · 6 年前

    我有一个数据集,在这里我观察一些个体的变量,而不是其他个体的变量。对于那些我观察变量的个体,我只观察一次。然而,每个个体的观察次数以及观察值的位置各不相同。

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

    下面是一个示例数据集:

    #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)
    

    到目前为止还不错,不是什么大问题。从另一个线程获取,我们可以使用 dplyr tidyr :

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

    这完全解决了问题。但是,我必须在80米左右的时间内完成。观察,需要几个小时。有更快的方法吗?我想 data.table 可能是个不错的候选人。

    如果能够调整方法只填充出现在值之前的NAS,这也将是非常好的。

    谢谢!

    3 回复  |  直到 6 年前
        1
  •  1
  •   César Arquero Cabral    6 年前

    这是我用过的代码:你的代码和我的代码。有时动物园不是最快的,但却是最干净的。不管怎样,你可以测试它。

    更新 : 到目前为止,它已经用更多的数据(100.000)和进程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
    
        2
  •  6
  •   talat    6 年前

    您可以对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值精确为0或1次。因此,您不需要最后的观察结转逻辑。只需取第一个非NA值(如果存在)。

        3
  •  2
  •   akrun    6 年前

    我们可以使用 data.table 分配到位。在这里, na.locf zoo 用于用相邻的非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元素。