【问题标题】:Speeding up a function: checking NA count before computing mean加速函数:在计算平均值之前检查 NA 计数
【发布时间】:2013-06-09 09:30:40
【问题描述】:

下面的函数计算向量的平均值。但是,它首先检查向量中存在的NA 的比例 如果高于给定阈值,则返回 NA 而不是平均值。

我的问题是我当前的实现效率很低。它比简单地运行 mean(vec, na.rm=TRUE) 花费的时间长 7 倍以上

我尝试了使用na.omit 的替代方法,但速度更慢。

鉴于我的数据大小,执行单个 lapply 需要 40 多分钟。

关于如何更快地完成相同任务的任何建议?


更新 - 回复:@thelatemail 的解决方案和@Arun 的评论:

  • 我在数百组中执行此功能,每组的大小各不相同。这个问题中提供的样本数据(最初)是作为一个简洁的数据框提供的,只是为了便于创建人工数据。

替代样本数据以避免混淆

# Sample Data 
# ------------
  set.seed(1)
  # slightly different sizes for each group
  N1 <- 5e3
  N2 <- N1 + as.integer(rnorm(1, 0, 100))

  # One group has only a moderate amount of NA's
  SAMP1 <- rnorm(N1)
  SAMP1[sample(N1, .25 * N1, FALSE)] <- NA  # add in NA's

  # Another group has many NA's
  SAMP2 <- rnorm(N2)
  SAMP2[sample(N2, .95 * N2, FALSE)] <- NA  # add in large number of NA's

  # put them all in a list
  SAMP.NEW <- list(SAMP1, SAMP2)

  # keep it clean
  rm(SAMP1, SAMP2)

# Execute 
# -------    
  lapply(SAMP.NEW, meanIfThresh)

原始样本数据、函数等

# Sample Data 
# ------------
  set.seed(1)
  rows <- 20000  # actual data has more than 7M rows
  cols <-  1000  

  SAMP <- replicate(cols, rnorm(rows))
  SAMP[sample(length(SAMP), .25 * length(SAMP), FALSE)] <- NA  # add in NA's

  # Select 5 random rows, and have them be 90% NA
  tooSparse <- sample(rows, 5)
  for (r in tooSparse)
    SAMP[r, sample(cols, cols * .9, FALSE)] <- NA

# Function 
# ------------
    meanIfThresh <- function(vec, thresh=12/15) { 
      # Calculates the mean of vec, however, 
      #   if the number of non-NA values of vec is less than thresh, returns NA 

      # thresh : represents how much data must be PRSENT. 
      #          ie, if thresh is 80%, then there must be at least 


      len <- length(vec)

      if( (sum(is.na(vec)) / len) > thresh)
        return(NA_real_)
      # if the proportion of NA's is greater than the threshold, return NA
      # example:  if I'm looking at 14 days, and I have 12 NA's,
      #            my proportion is 85.7 % = (12 / 14)
      #            default thesh is  80.0 % = (12 / 15)
      #            Thus, 12 NAs in a group of 14 would be rejected


    # else, calculate the mean, removing NA's
    return(mean(vec, na.rm=TRUE))       
  }


  # Execute
  # -----------------
  apply(SAMP, 1, meanIfThresh)

  # Compare with `mean`
  #----------------
  plain    <- apply(SAMP, 1, mean, na.rm=TRUE)
  modified <- apply(SAMP, 1, meanIfThresh)

  # obviously different
  identical(plain, modified)
  plain[tooSparse]
  modified[tooSparse]


  microbenchmark( "meanIfThresh"   = apply(SAMP, 1, meanIfThresh)
                , "mean (regular)" = apply(SAMP, 1, mean, na.rm=TRUE)
                , times = 15L)

 #  With the actual data, the penalty is sevenfold
 #  Unit: seconds
 #           expr      min       lq   median       uq      max neval
 #   meanIfThresh 1.658600 1.677472 1.690460 1.751913 2.110871    15
 # mean (regular) 1.422478 1.485320 1.503468 1.532175 1.547450    15

【问题讨论】:

  • 这一行 SAMP[sample(SAMP, .25 * length(SAMP), FALSE)] &lt;- NA 在 R3.0.1 only 0's may be mixed with negative subscripts 中为我抛出错误
  • 也许是SAMP[sample(seq_along(SAMP), .25 * length(SAMP))] &lt;- NA?
  • 谢谢,应该是SAMP[sample(length(SAMP), .25 * length(SAMP), FALSE)] &lt;- NA(在编辑中更正)

标签: r algorithm vectorization apply na


【解决方案1】:

你不能像这样替换高 NA 行的平均值吗?:

# changed `result <- apply(SAMP,1,mean,na.rm=TRUE)`
result <- rowMeans(SAMP, na.rm=TRUE)
NArows <- rowSums(is.na(SAMP))/ncol(SAMP) > 0.8
result[NArows] <- NA

一些基准测试:

Ricardo <- function(vec, thresh=12/15) {
    len <- length(vec)
    if( (sum(is.na(vec)) / len) > thresh)
        return(NA_real_)
    return(mean(vec, na.rm=TRUE))       
}

DanielFischer <- function(vec, thresh=12/15) {

    len <- length(vec)
    nas <- is.na(vec)
    Nna <- sum(nas)
    if( (Nna / len) > thresh)
        return(NA_real_)
    return(sum(vec[!nas])/(len-Nna))
}

thelatemail <- function(mat) {
    result <- rowMeans(mat, na.rm=TRUE)
    NArows <- rowSums(is.na(mat))/ncol(mat) > 0.8
    result[NArows] <- NA
    result
}

require(microbenchmark)
microbenchmark(m1 <- apply(SAMP, 1, Ricardo), 
               m2 <- apply(SAMP, 1, DanielFischer), 
               m3 <- thelatemail(SAMP), times = 5L)

Unit: milliseconds
                                expr       min        lq    median        uq       max neval
       m1 <- apply(SAMP, 1, Ricardo) 2923.7260 2944.2599 3066.8204 3090.8127 3105.4283     5
 m2 <- apply(SAMP, 1, DanielFischer) 2643.4883 2683.1034 2755.7032 2799.5155 3089.6015     5
                m3 <- latemail(SAMP)  337.1862  340.6339  371.6148  376.5517  383.4436     5

all.equal(m1, m2) # TRUE
all.equal(m1, m3) # TRUE

【讨论】:

  • 没错。 @RicardoSaporta,你打电话给sum(is.na(.))nrow 次。您可以改用矢量化的rowSums。调用这些函数的函数开销将超过计算本身。可能,分析 Rprof(); modified &lt;- apply(SAMP, 1, meanIfThresh); Rprof(NULL); summaryRprof() 可能会有所帮助。
  • @latemail,你应该把apply(SAMP, 1, mean, na.rm = TRUE)改成rowMeans(SAMP, na.rm=TRUE)
  • @thelatemail 谢谢你。我的问题应该更明确一点,因为我非常具体地执行了这个函数几百次,因为我有几百个组,每个组的大小都不同。 (混淆可能是我过度简化样本数据的错。)但是......这确实激发了按组融合数据并以这种方式应用您的方法的想法。
【解决方案2】:

是不是你必须在你的函数中通过你的向量vec 两次?如果您可以先存储您的NA,也许它可以加快您的计算速度:

meanIfThresh2 <- function(vec, thresh=12/15) { 

  len <- length(vec)
  nas <- is.na(vec)
  Nna <- sum(nas)
  if( (Nna / len) > thresh)
    return(NA_real_)

  return(sum(vec[!nas])/(len-Nna))
}

编辑:我执行了类似的基准测试,以查看此更改的效果:

> microbenchmark(  "meanIfThresh"   = apply(SAMP, 1, meanIfThresh)
+                 , "meanIfThresh2"   = apply(SAMP, 1, meanIfThresh2)
+                 , "mean (regular)" = apply(SAMP, 1, mean, na.rm=TRUE)
+                 , times = 15L)
Unit: seconds
           expr      min       lq   median       uq      max neval
   meanIfThresh 2.009858 2.156104 2.158372 2.166092 2.192493    15
  meanIfThresh2 1.825470 1.828273 1.829424 1.834407 1.872028    15
 mean (regular) 1.868568 1.882526 1.889852 1.893564 1.907495    15

【讨论】:

  • 你应该做同样的基准测试。我认为这不会改变任何事情。 @thelatemail 做得对,恕我直言。
  • 好点,我在我的帖子中添加了基准 - 似乎存储 NA 会带来一些速度。至少它似乎与常规均值函数一样快。
  • 我应该说,它不会有太大变化。检查后期邮件答案中的编辑。
  • 是的,您使用rowMeans 的建议带来了巨大的变化!但在此之前,我认为解决方案必须更慢,因为它先执行常规均值,然后再执行is.na 步骤。没想到rowMeans这么快……
  • @Daniel,这当然有帮助!它减少了与调用 mean 相关的额外开销。
猜你喜欢
  • 2017-09-26
  • 2021-02-14
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
  • 2021-07-03
  • 1970-01-01
  • 2021-09-05
  • 2016-11-14
相关资源
最近更新 更多