Search code examples
rfunctionzoorolling-computationrollapply

Rollapply (zoo) gives multiple columns instead of a single column result


I am trying to apply a simple function in a rolling manner to a vector in R using the rollapply function from the zoo package. However, I am encountering an unexpected behavior where the result has multiple columns instead of the expected single column.

Here is my code:

library(zoo)

set.seed(101)
foo <- data.frame("V1" = sample(seq(-5, 5), replace = TRUE, size = 100))

result <- rollapply(foo$V1, 
                    width = 5, 
                    FUN = function(x) ifelse(x < 0, "Yes", "No"),
                    align = "right",
                    by.column = FALSE,
                    fill = NA)
head(result)

I expected result to be a single column vector containing the "Yes" and "No" values for each rolling window of 5 elements in the "V1" column. However, the output I get is a matrix with 6 rows and 5 columns instead.

Can someone please help me understand why I am getting multiple columns in the result and how I can modify the code to obtain a single column result as intended?

Thank you in advance for your assistance!

Dirty way to solve the issue:

# Loop through the elements in foo$V1 using a rolling window of 5
for (i in 1:(length(foo$V1) - 4)) {
  # Check if there is a negative number in the current 5-element window
  foo$V2[i + 4] <- any(foo$V1[i:(i + 4)] < 0)
}

Solution

  • You should use any() to return a single value from a length 5 input.

    rollapply(foo$V1, 
              width = 5, FUN = function(x) ifelse(any(x < 0), "Yes", "No"),
              align = "right", fill = NA)
    
    #  [1] NA    NA    NA    NA    "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes"
    # [21] "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "No"  "No"  "No"  "No" 
    # [41] "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes"
    # [61] "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes"
    # [81] "Yes" "Yes" "Yes" "Yes" "Yes" "No"  "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes" "Yes"
    

    As suggested by @G.Grothendieck, you can use rollapplyr(with suffix r) to skip align = "right", and move ifelse() outside to avoid calling it repeatedly.

    rollapplyr(foo$V1 < 0, width = 5, FUN = any, fill = NA) |>
      ifelse("Yes", "No")