【问题标题】:Replace NA with mean matching the same ID将 NA 替换为匹配相同 ID 的均值
【发布时间】:2013-05-22 14:52:08
【问题描述】:

我有一个数据框:

id <- c(rep(1, 4), rep(2, 3), rep(3, 2), 4)
rate <- c(rep(1, 3), NA, 0.5, 0.6, NA, 0.7, NA, NA)
df <- data.frame(id, rate)

我需要根据以下条件替换NA

for (i in 1:dim(df)[1]) {
  if (is.na(df$rate[i])) {
    mrate <- round(mean(df$rate[df$id == df$id[i]], na.rm = T), 1)
    if (is.nan(mrate)) {
      df$rate[i] <- 1
    } else {
      df$rate[i] <- mrate
    }
  }
}

显然,for 循环在超过 200K 行的大数据帧上太慢了。如何在不使用for 循环的情况下使用更快的方式?

谢谢!

【问题讨论】:

  • fwiw,for 循环并不慢。相反,我的猜测是代码中最慢的部分是确定mean 时执行的子集操作。如果您使用aggregate 或其他方法预先计算每个组的平均值,您的循环将明显更快......但data.table 解决方案几乎肯定会更清洁和更快!

标签: r


【解决方案1】:

这是使用data.tables 的解决方案:

library(data.table)
dt <- data.table( df, key = "id" )
dt[ , rate := ifelse( is.na(rate), round( mean(rate, na.rm=TRUE), 1), rate ), by = id ]
dt[ is.na(rate), rate := 1 ]
dt 
    id rate
 1:  1  1.0
 2:  1  1.0
 3:  1  1.0
 4:  1  1.0
 5:  2  0.5
 6:  2  0.6
 7:  2  0.6
 8:  3  0.7
 9:  3  0.7
10:  4  1.0

我不确定是否可以/应该避免 ifelse

【讨论】:

    【解决方案2】:

    正如我在评论中提到的,R 中的for 循环并不是特别慢。但是,for 循环通常表明代码中的其他低效率。在这种情况下,为确定mean 对每一行重复的子集操作很可能是最慢的代码位。

    for (i in 1:dim(df)[1]) {
      if (is.na(df$rate[i])) {
        mrate <- round(mean(df$rate[df$id == df$id[i]], na.rm = T), 1)  ## This line!
        if (is.nan(mrate)) {
          df$rate[i] <- 1
        } else {
          df$rate[i] <- mrate
        }
      }
    }
    

    如果相反,这些组平均值是事先确定的,则循环可以进行快速查找。

    foo <- aggregate(df$rate, list(df$id), mean, na.rm=TRUE)
    for (i in 1:dim(df)[1]) {
      if (is.na(df$rate[i])) {
        mrate <- foo$x[foo$Group.1 == df$id[i]]
    ...
    

    但是,我仍在大型 data.frame 上的 df$id[i] 处做一个子集。相反,使用实现拆分-应用-组合策略的工具之一是个好主意。另外,让我们编写一个函数,它采用单个值和预先计算的组平均值并做正确的事情:

    myfun <- function(DF) {
      avg <- avgs$rate[avgs$id == unique(DF$id)]
      if (is.nan(avg)) {
        avg <- 1
      }
      DF$rate[is.na(DF$rate)] <- avg
    
      return (DF)
    }
    

    plyr 版本:

     library(plyr)
     avgs <- ddply(df, .(id), summarise, rate=mean(rate, na.rm=TRUE))
     result <- ddply(df, .(id), myfun)
    

    还有可能更快的data.table 版本:

     library(data.table)
     DT <- data.table(df)
     setkey(DT, id)
    
     DT[, avg := mean(rate, na.rm=TRUE), by=id]
     DT[is.nan(avg), avg := 1]
    
     DT[, rate := ifelse(is.na(rate), avg, rate)]
    

    这样,我们避免了在 leiu 中添加预先计算的列的所有查找子集,现在可以进行快速高效的逐行查找。可以使用以下方法廉价地删除额外的列:

    DT[, avg := NULL]
    

    整个shebang可以写成一个函数或data.table表达式。但是,IMO,这通常是以清晰度为代价的!​​p>

    【讨论】:

    • 只是一个问题:DT[, rate := ifelse(is.na(rate), avg, rate), by=id] 行中需要的by=i 是什么?
    • @Basterfield 啊,不是。这是我先写的东西的保留。已编辑。
    【解决方案3】:

    我不确定这是否完全回答了 OP 的问题,但对于稍后阅读此内容的其他人来说,除了实际对数据进行子集化之外,还有一种不同且更快的方法可以对数据子集执行计算:向量数学。 人群中的工程师会知道我在说什么。

    分配一个非常快速的函数来创建标识向量并将数据乘以标识,而不是子集。

    现在,这并不是对所有情况都更快。在某些情况下,矢量化函数实际上比项目显式函数慢,这完全取决于您的特定应用程序。 [在此处插入您选择的 O 符号咆哮。]

    以下是我们将如何针对这种情况进行矢量数学实现:

    # Create the NA identity vector.
    na_identity <- is.na(df$rate)
    
    # Initialize the final data frame.
    # This is for non-destructive purposes.
    df_revised <- df
    
    # Replace all NA occurrences in final
    # data frame with zero values.
    df_revised$rate[na_identity] <- 0
    
    # Loop through each unique [id]
    # value in the data.
    # Create an identity vector for the
    # current ID, calculate the mean
    # rate for that ID (replacing NaN with 1),
    # and insert the mean for any NA values
    # associated with that ID.
    for (i in unique(df$id)){
        id_identity <- df$id==i
        id_mean <- sum(df_revised$rate * id_identity * !na_identity) / sum(id_identity * !na_identity)
        if(is.nan(id_mean)){id_mean <- 1}
        df_revised$rate <- df_revised$rate + id_mean * id_identity * na_identity
    }
    
    #    id rate
    # 1   1 1.00
    # 2   1 1.00
    # 3   1 1.00
    # 4   1 1.00
    # 5   2 0.50
    # 6   2 0.60
    # 7   2 0.55
    # 8   3 0.70
    # 9   3 0.70
    # 10  4 1.00
    

    从矢量数学的角度来看,这段代码很容易阅读。在这个小例子中,代码非常快,但循环时间直接随着唯一 ID 值的数量而增加。我不确定这是否是 OP 更大应用程序的正确方法,但该解决方案可行且理论上合理,并且无需复杂且难以阅读的逻辑块。

    【讨论】:

      猜你喜欢
      • 2020-03-22
      • 1970-01-01
      • 2016-12-05
      • 1970-01-01
      • 2016-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多