Modifying vector to contain only ascending elements

Viewed 84

I have a vector that I want to modify so that it contains only elements that are equal too or larger than the previous element. The vector represents a phenomena that should only increase or stay the same (i.e. cumulative deaths by day), but reporting errors result in elements that are less than the previous element. I want to correct this by replacing elements with previous ones until the vector meets the aforementioned criteria.

raw data : 1 3 3 6 8 10 7 9 15 12

desired modified data: 1 3 3 6 6 6 7 9 9 12

library(zoo)
raw <- c(1, 3, 3, 6, 8, 10, 7, 9, 15, 12) 

replace.errors <- function(x){   
x %>% 
replace(diff(x) < 0, NA) %>%
na.locf(na.rm=FALSE)
}

replace.errors(raw)

# [1]  1  3  3  6  8  8  7  9  9 12

My function does not work if multiple sequential elements in a row need to be replaced (8 and 10), as it just pulls forward an element that is still greater than the next one.

3 Answers

A data.table option using nafill along with cummin

nafill(replace(raw, rev(cummin(rev(raw))) != raw, NA), type = "locf")

gives

> nafill(replace(raw, rev(cummin(rev(raw))) != raw, NA), type = "locf")
 [1]  1  3  3  6  6  6  7  9  9 12

Following the similar idea from above approach, your function replace.errors can be defined as

replace.errors <- function(x){   
  x %>%
    replace(rev(cummin(rev(.))) != (.), NA) %>%
    na.locf()
}

such that

> replace.errors(raw)
 [1]  1  3  3  6  6  6  7  9  9 12

Another option is to define a user function like below

f <- function(v) {
  for (k in which(c(FALSE, diff(v) < 0))) {
    p <- max(v[v < v[k]])
    v <- replace(v, tail(which(v == p), 1):(k - 1), p)
  }
  v
}

which gives

> f(raw)
 [1]  1  3  3  6  6  6  7  9  9 12

Base R using @ThomasIsCoding brilliant replace logic:

# Replace values breaching condition with NA: scrubbed => integer vector
scrubbed <- replace(raw, rev(cummin(rev(raw))) != raw, NA_integer_)

# i) Interpolate constants:
res <- na.omit(scrubbed)[cumsum(!is.na(scrubbed))]

# OR 
# ii) Interpolate constants using approx() 
res <- approx(scrubbed, method = "constant", n = length(scrubbed))$y

Or in one expression:

approx(
  replace(raw, rev(cummin(rev(raw))) != raw, NA_integer_),
  method = "constant",
  n = length(raw)
)$y

This smells a bit inefficient, but it may still be the best option:

replace_errors <- function(raw) {
  while (is.unsorted(raw)) {
    raw <- raw[c(TRUE, diff(raw) >= 0)]
  }
  raw
}
Related