19

假设我有一个这样的数据框:

ID,  ID_2, FIRST, VALUE
-----------------------
'a', 'aa', TRUE, 2
'a', 'ab', FALSE, NA
'a', 'ac', FALSE, NA
'b', 'aa', TRUE, 5
'b', 'ab', FALSE, NA

因此,每个 ID 只为 FIRST = TRUE 设置一次 VALUE。ID_2 可能在 ID 之间重复,但不是必须的。

如何将每个 ID 的第一行中的数字放入该 ID 的所有行中,使 VALUE 列变为 2、2、2、5、5?

我知道我可以简单地使用 for 循环遍历所有 ID,但我正在寻找一种更有效的方法。

4

4 回答 4

29

与循环相比,该问题要求效率。以下是四种解决方案的比较:

  1. zoo::na.locf,它引入了包依赖关系,尽管它处理了许多边缘情况,但要求“空白”值为 NA。其他解决方案很容易适应非 NA 空白。

  2. 基础 R 中的一个简单循环。

  3. 基数 R 中的递归函数。

  4. 我自己在基础 R 中的矢量化解决方案。

  5. 0.3.0 版中的新fill()功能tidyr,适用于 data.frames。

请注意,这些解决方案中的大多数都是针对向量,而不是数据帧,因此它们不检查任何 ID 列。如果数据框未按 ID 分组,要填写的值位于每个组的顶部,那么您可以尝试在dplyrdata.table

# A popular solution
f1 <- zoo::na.locf

# A loop, adapted from https://stat.ethz.ch/pipermail/r-help/2008-July/169199.html
f2 <- function(x) {
  for(i in seq_along(x)[-1]) if(is.na(x[i])) x[i] <- x[i-1]
  x
}

# Recursion, also from https://stat.ethz.ch/pipermail/r-help/2008-July/169199.html
f3 <- function(z) { 
  y <- c(NA, head(z, -1))
  z <- ifelse(is.na(z), y, z)
  if (any(is.na(z))) Recall(z) else z }

# My own effort
f4 <- function(x, blank = is.na) {
  # Find the values
  if (is.function(blank)) {
    isnotblank <- !blank(x)
  } else {
    isnotblank <- x != blank
  }
  # Fill down
  x[which(isnotblank)][cumsum(isnotblank)]
}

# fill() from the `tidyr` version 0.3.0
library(tidyr)
f5 <- function(y) {
  fill(y, column)
}
# Test data, 2600 values, ~58% blanks
x <- rep(LETTERS, 100)
set.seed(2015-09-12)
x[sample(1:2600, 1500)] <- NA
x <- c("A", x) # Ensure the first element is not blank
y <- data.frame(column = x, stringsAsFactors = FALSE) # data.frame version of x for tidyr

# Check that they all work (they do)
identical(f1(x), f2(x))
identical(f1(x), f3(x))
identical(f1(x), f4(x))
identical(f1(x), f5(y)$column)

library(microbenchmark)
microbenchmark(f1(x), f2(x), f3(x), f4(x), f5(y))

结果:

Unit: microseconds
  expr      min        lq       mean    median        uq       max neval
 f1(x)  422.762  466.6355  508.57284  505.6760  527.2540   837.626   100
 f2(x) 2118.914 2206.7370 2501.04597 2312.8000 2497.2285  5377.018   100
 f3(x) 7800.509 7832.0130 8127.06761 7882.7010 8395.3725 14128.107   100
 f4(x)   52.841   58.7645   63.98657   62.1410   65.2655   104.886   100
 f5(y)  183.494  225.9380  305.21337  331.0035  350.4040   529.064   100
于 2015-09-12T07:51:06.773 回答
25

如果您只需要结转 VALUE 列中的值,那么我认为您可以使用zoo包中的na.lofc()函数。这是一个例子:

a<-c(1,NA,NA,2,NA)
na.locf(a)
[1] 1 1 1 2 2
于 2012-05-11T16:01:55.660 回答
4

如果特定 ID 的 VALUE 始终出现在第一条记录中,您的数据似乎就是这种情况,您可以使用它match来查找该记录:

df <- read.csv(textConnection("

ID,  ID_2, FIRST, VALUE
'a', 'aa', TRUE, 2
'a', 'ab', FALSE, NA
'a', 'ac', FALSE, NA
'b', 'aa', TRUE, 5
'b', 'ab', FALSE, NA

"))

df$VALUE <- df$VALUE[match(df$ID, df$ID)]
df
#    ID  ID_2  FIRST VALUE
# 1 'a'  'aa'   TRUE     2
# 2 'a'  'ab'  FALSE     2
# 3 'a'  'ac'  FALSE     2
# 4 'b'  'aa'   TRUE     5
# 5 'b'  'ab'  FALSE     5
于 2012-05-11T16:03:19.730 回答
0

+1 @nacnudus 处理前导空白

f4 <- function(x, blank = is.na) {

  # Find the values
  if (is.function(blank)) {
    isnotblank <- !blank(x)
  } else {
    isnotblank <- x != blank
  }

  # Fill down
  xfill <- cumsum(isnotblank) 
  xfill[ xfill == 0 ] <- NA

  # Replace Blanks
  xnew <- x[ which(isnotblank) ][ xfill ]
  xnew[is.na(xnew)] <- blank
  return(xnew)
}
于 2017-01-26T10:06:42.430 回答