1

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)
}
Darren Tsai
  • 32,117
  • 5
  • 21
  • 51
Borexino
  • 802
  • 8
  • 26
  • 3
    The input to `FUN` will be a vector of 5 numbers, and the output will be 5 `"Yes"` or `"No"` values. That's why you get 5 columns. If you only want one column, you need to write `FUN` to return a single value from a length 5 input. – user2554330 Jul 30 '23 at 08:39

1 Answers1

2

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")
Darren Tsai
  • 32,117
  • 5
  • 21
  • 51