使用 data.table 按组进行有限制的最后一次观察

Carrying forward last observation with a limit, by group, using data.table

我正在尝试以 2 为限,以大 data.table 分组进行最后一次观察。这里有很多复杂的解决方案,但其中 none 似乎包含了所有 3 个元素:一个具有最大限制的类似 na.locf 的函数,按组,在 data.table.[=13 中=]

我的数据如下:

df <- structure(list(country = c("USA", "USA", "USA", "USA", "USA", 
"FR", "FR", "FR", "FR", "FR"), values = c(2, 1, NA, NA, NA, 2, 
1, 2, NA, NA)), class = c("data.table", "data.frame"), row.names = c(NA, 
-10L))

      country values
 1:     USA      2
 2:     USA      1
 3:     USA     NA
 4:     USA     NA
 5:     USA     NA
 6:      FR      2
 7:      FR      1
 8:      FR      2
 9:      FR     NA
10:      FR     NA

我希望它看起来像这样:

     country values
1      USA      2
2      USA      1
3      USA      1
4      USA      1
5      USA     NA
6       FR      2
7       FR      1
8       FR      2
9       FR      2
10      FR      2

您可以借助功能并按组应用它-

library(data.table)
library(zoo)

replace_NA_with_limit <- function(a, n) {
  r <- rle(is.na(a))
  a <- na.locf(a)
  is.na(a) <- sequence(r$lengths) > n & rep(r$values, r$lengths)
  a
}

setDT(df)[, values := replace_NA_with_limit(values, 2), country]
df

#    country values
# 1:     USA      2
# 2:     USA      1
# 3:     USA      1
# 4:     USA      1
# 5:     USA     NA
# 6:      FR      2
# 7:      FR      1
# 8:      FR      2
# 9:      FR      2
#10:      FR      2

请注意,通常在处理较长的 NA 时,要么全部填充它们,要么填充其中的 none 并且 na.locf 已经使用 maxgap 参数处理了该问题,该参数仅填充不超过指定的。这个想法是插值只在短时间内可靠,所以你不应该在较长的时间段内进行插值。尽管如此,下面显示了如何实现问题中的方案,但请考虑是否应该更改策略并改用 maxgap。

1) 使用 na.locf0 给出 locf 计算 na.locf 并为 NA 和非 NA 的延伸创建一个分组变量,g。然后,对于每个 运行 的 NA,取 na.locf 列的前两个元素,并用 NA 的起始值填充其余部分。这不会覆盖 df,因此它可以在没有副作用的管道中使用。

library(data.table)
library(zoo)

df[, .(values, locf = na.locf0(values), g = rleid(is.na(values))), by = country][
   , .(values = c(head(locf, 2), tail(values, -2))), by = .(country, g)][
   , .(country, values)]

给予:

    country values
 1:     USA      2
 2:     USA      1
 3:     USA      1
 4:     USA      1
 5:     USA     NA
 6:      FR      2
 7:      FR      1
 8:      FR      2
 9:      FR      2
10:      FR      2

2) 下面是一个稍微修改过的公式,它仍然使用相同的基本思想。它也不会覆盖。

library(data.table)
library(zoo)

# like na.locf0 but only specifies vector, x, and limit to fill, k
na.locf2 <- function(x, k) {
  nalocf <- na.locf0(x)
  f <- function(ix) c(head(nalocf[ix], k), tail(x[ix], -k))
  unlist(tapply(seq_along(x), rleid(is.na(x)), f))
}
df[, .(values = na.locf2(values, 2)), by = country]

给予:

    country values
 1:     USA      2
 2:     USA      1
 3:     USA      1
 4:     USA      1
 5:     USA     NA
 6:      FR      2
 7:      FR      1
 8:      FR      2
 9:      FR      2
10:      FR      2

这是另一个选项:

library(data.table)
setDT(df)[, ri := rowid(country, values)]
df[!is.na(values) | ri <= 2L, values := nafill(values, "locf")]