删除被一定数量的 NA 包围的值

Remove values which are surrounded by a certain number of NAs

我希望删除时间序列中被特定最小长度的 NA 块包围的值。

一些玩具数据:

x = seq(0,10,length.out = 100)
y = sin(x) + rnorm(length(x), mean=0, sd=0.1)
y[20:21] = rep(NA, 2)
y[50:54] = rep(NA, 5)
y[55:59] = seq(-0.1, -0.8, length.out = 5)
y[60:64] = rep(NA, 5)
y[90:91] = rep(NA, 2)

df <- data.frame(x, y)

我希望删除任何长度小于 10 且前后有 5 个或更多 NA 个值的 y 值序列。

在我的玩具数据中,索引 55-59 处的 y 值 (a) 少于 10 个连续值,并且 (b) 在 both[=32= 上有 5 NA ] 双方。因此,应删除此值块。

其他值由较长的值块组成 and/or 被较短的 NA (< 5) 包围,应该保留。

用红色绘制要删除的值:

library(ggplot2)
ggplot(data = df, aes(x, y)) +
  geom_line() +
  geom_line(data = df[55:59, ], color = "red")

首先,我们将定义您指定的两个阈值。 (我将第二个设置为 4,这样我们就可以一致地使用“<”和“>”,而不是容易出错的“<”和“>=”)。

threshold.data <- 10
threshold.NA <- 4

现在,关键是在 is.na(y) 上使用 运行 长度编码。看看?rle.

foo <- rle(is.na(y))
foo

首先,我们通过检查原始数据NA的位置来提取可能的"candidate runs of NAs"(因此foo$values将是TRUE 我们有指定的最小 运行 长度 NAs:

candidate.runs.NA <- which(foo$values & foo$lengths>threshold.NA)

我们只有在超过阈值至少两个 NA 运行 时才想继续:

if ( diff(range(candidate.runs.NA)) >= 2 ) {

我们的目标是找到我们要删除的非 NA 数据的索引。为此,我们找到 "candidate runs of (non-NA) data"。在第一步中,这包括上面标识的第一个和最后一个 NA 运行 之间的所有 运行:

    candidate.runs.data <- seq(candidate.runs.NA[1]+1,tail(candidate.runs.NA,1)-1)

我们通过两个标准对其进行细化。一方面,我们只想要非NA的序列,另一方面,这些序列的长度应该低于阈值:

    candidate.runs.data <- candidate.runs.data[!foo$values[candidate.runs.data] &
      foo$lengths[candidate.runs.data]<threshold.data]

在您的示例中,candidate.runs.data 现在将只有一个条目 5。这意味着我们需要删除 is.na 序列的第 5 个 运行 中的所有数据。为此,我们需要恢复实际索引:

    indices.to.remove <- as.vector(sapply(candidate.runs.data,function(kk)
      seq(sum(foo$lengths[1:(kk-1)])+1,sum(foo$lengths[1:kk]))))

这有点复杂,因为我将它包装在一个 sapply() 调用中,以防我们要删除 multiple candidate.runs.data。最后,我们删除这些数据:

    y[indices.to.remove] <- NA
}
plot(x,y,"l")

现在,这似乎可以满足您对特定示例的要求。您可能需要考虑在边界情况下您希望发生什么。例如,这假设您的系列以非 NA 开头。如果你没有 two 运行 五个或更多 NA,但是 three 应该会发生什么,或者 5?在 "long" 运行 之间有或没有更短的 NA 运行s?此脚本将第一个和最后一个 "long" 运行 之间最多九个非 NA 的任何 运行 视为公平游戏。

complete.cases() 适合您吗? 此函数使所有带有 NA 的行消失.. 也许对你来说太过分了...

您可以将您的时间序列视为一个字符串,并在这里利用正则表达式的优势。借助 stringr 包中的函数 str_locate_all 很容易解决问题。

st <- paste0(as.integer(is.na(df$y)), collapse = '')
# [1] "0000000000000000000110000000000000000000000000000111110000011111000000000000000000000000011000000000"
require("stringr")
str_locate_all(st, "1{5,}0{,10}1{5,}") 
# pattern of at least 5 ones, then not more than 10 zeros, then again not less than 5 ones

# output will be:
# [[1]]
#      start end
# [1,]    50  64

另一种rle可能性:

运行 NA 的长度:

r <- rle(is.na(y))

values (sensu rle) of non-NA (FALSE) 应从数据中删除(运行时间短于 10,并且先行和后行通过运行 NA 超过 4) 被替换为 TRUE:

r$values[!r$values & r$lengths < 10 &
           c(0, head(r$lengths, -1)) > 4 &
           c(tail(r$lengths, -1), 0) > 4] <- TRUE

更新后的 rle values 然后与 lengths 一起使用生成布尔索引,用 NA 替换相关的 y 值:

y[rep(r$values, r$lengths)] <- NA

使用OP的绘图代码: