用 R 中的最后一个已知值替换离群值

Replace outlier with last known value in R

示例数据: 2个 4个 6个 10 99 150 14 15 45

这个问题的先验知识,我知道任何高于 35 的东西都是异常值,但是因为数据取决于时间,所以我想用最近已知的小于 35 的数字替换所有高于 35 的值。数据集包含超过数百万行,所以我需要自动执行此操作,而不是一一替换。

需要的结果:2 4 6 10 10 10 14 15 15

x <- c(2, 4, 6, 10, 99, 150, 14, 15, 45)

#set outliers to NA
x[x > 35] <- NA

#fill NA values with Last Observation Carried Forward
library(zoo)
x <- na.locf(x)
#[1]  2  4  6 10 10 10 14 15 15

对于那些不想要 zoo 包依赖性的人,这里有一个使用来自 base 的 运行 级别编码的简单版本。这个想法很简单,我们只使用 rle() 并将索引中的 NA 替换为左边的任何内容(即前面的值),我们跳过任何第一个 NA 因为左边没有任何价值。然后我们使用 inverse.rle() 得到一个全长向量。逆向操作(,我们只是把vector前后颠倒一下。我没有做过benchmarking,但是因为所有的操作都是vector化的,所以应该很快。

rle() 出于某种原因没有将 NA 分组。文档状态 "Missing values are regarded as unequal to the previous value, even if that is also missing."。因此,为什么我将 NA 重新编码为临时字符串值,并且必须将向量转换回正确的 class。不完美,但适用于大多数情况。

#' Last observation carried forward
#'
#' @param x A vector
#' @param reverse Whether to do it in reverse
#'
#' @return A vector
#' @export
#'
#' @examples
#' c(NA, 1, NA, 2, NA) %>% locf()
#' c(NA, 1, NA, 2, NA) %>% locf(reverse = T)
locf = function(x, reverse = F) {
  #reverse?
  if (reverse) x = rev(x)

  #recode NA
  #these are kept distinct by rle() by default for same reason ???
  x_class = class(x)
  x[is.na(x)] = "___tmp"

  #run level encoding
  x_rle = rle(x)

  #swap values for NAs
  which_na = which(x_rle$values == "___tmp")

  #skip 1st
  which_na = setdiff(which_na, 1)

  #replace values
  x_rle$values[which_na] = x_rle$values[which_na - 1]

  #back to normal
  y = inverse.rle(x_rle)

  #NA recode
  y[y == "___tmp"] = NA

  #fix type/class
  if (x_class[1] == "logical") y = as.logical(y)
  if (x_class[1] == "integer") y = as.integer(y)
  if (x_class[1] == "numeric") y = as.double(y)
  if (x_class[1] == "factor") y = factor(y, levels = levels(x))
  if (x_class[1] == "ordered") y = ordered(y, levels = levels(x))

  #reverse?
  if (reverse) y = rev(y)

  y
}

测试:

> c(NA, 1, NA, 2, NA) %>% rle()
Run Length Encoding
  lengths: int [1:5] 1 1 1 1 1
  values : num [1:5] NA 1 NA 2 NA
> c(NA, 1, NA, 2, NA) %>% rle() %>% str()
List of 2
 $ lengths: int [1:5] 1 1 1 1 1
 $ values : num [1:5] NA 1 NA 2 NA
 - attr(*, "class")= chr "rle"
> #swap the values for one to left
> #reverse rle
> c(NA, 1, NA, 2, NA) %>% locf()
[1] NA  1  1  2  2
> c(NA, 1, NA, 2, NA) %>% locf(reverse = T)
[1]  1  1  2  2 NA
> c(NA, 1, NA, 2, NA, NA, NA) %>% locf()
[1] NA  1  1  2  2  2  2