4

我有一个数据框:

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循环的情况下使用更快的方式?

谢谢!

4

3 回答 3

5

这是使用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可以/应该避免。

于 2013-05-22T15:01:32.530 回答
4

正如我在评论中提到的,forR 中的循环并不是特别慢。但是,循环通常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]]
...

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

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,这通常是以牺牲清晰度为代价的!

于 2013-05-22T15:28:36.440 回答
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 更大应用程序的正确方法,但该解决方案是可行的并且在理论上是合理的,并且不需要复杂且难以阅读的逻辑块。

于 2013-05-22T16:19:41.837 回答