Rolling z-score function in R I have written a rolling z-score function myself. Is my implementation of rolling z-score function correct?

#x is an xts object and y duration eg rollz(x,25)
rollz&lt;-function(x,y){
  avg=rollapply(x, y, mean)
  std=rollapply(x, y, sd)
  z=(x-avg)/std
  return(z)
}


EDIT

Here is the description of z-score. A rolling (moving) z-score calculates z-score at a particular width like how we calculate simple moving averages.
This is very old, but I'd like to give a &quot;modern way&quot; of implementing this.

Everything should be a project, e.g. usethis::create_package(&quot;roll_util&quot;)

Every function should be in its own R-file, e.g. usethis::use_r(&quot;roll_zscore&quot;)

First version:
roll_zscore &lt;- function(x, ...) {
avg &lt;- RcppRoll::roll_mean(x, ...)
std &lt;- RcppRoll::roll_sd(x, ...)

(x - avg) / std
}

Here, instead of y, we are inheriting all the rolling-operation behaviour
that is provided by {RcppRoll} (remember to add it as a dependency,
usethis::use_package(&quot;RcppRoll&quot;))

Documentation, second version
#' Rolling Z-scores
#'
#'
#' @param x a time-series `xts` object
#' @inheritDotParams RcppRoll::roll_mean
#'
roll_zscore &lt;- function(x, ...) {
#omitted


Next, example/test/etc.

Testing framework: usethis::use_testthat()
Unit-test for this function: usethis::use_test(&quot;roll_zscore&quot;)
We want it to work for {xts} objects, so we add it as a suggestion, because really this function could work with other stuff. usethis::use_package(&quot;xts&quot;, &quot;Suggests&quot;)


The test looks like this:
test_that(&quot;standard example of roll_zscore&quot;, {
    requireNamespace(&quot;xts&quot;, quietly = TRUE)

    data(sample_matrix, package = &quot;xts&quot;)
    sample.xts &lt;- xts::as.xts(sample_matrix, descr='my new xts object')

    roll_zscore(sample.xts$Open, n = 2, fill = NA)
    roll_zscore(sample.xts$Open, n = 25, NA)
})

From this test, we discover some crucial problems with this implementation.

It doesn't work for n = 1. This is probably due to sd.
attached to it.
In order, to do the last calculation, the fill argument must be set to
something, otherwise the vectors won't be of equal length.

Either decide to handle these in the function, in the documentation, or elsewhere.


To properly handle ellipsis arguments, I've made use of do.call. Honestly, if I
wasn't trying to make this as simple as possible, I'd have used rlang and purrr::exec.
Final version of the code:
#' Rolling Z-scores
#'
#'
#' @param x a time-series `xts` object
#' @inheritDotParams RcppRoll::roll_mean
#'
roll_zscore &lt;- function(x, ...) {
  args &lt;- list(...)

  # either use provided `fill` or `NA`
  args$fill &lt;- if (is.null(args$fill)) NA else args$fill
  args &lt;- append(args, list(x = x))

  avg &lt;- do.call(RcppRoll::roll_mean, args)
  std &lt;- do.call(RcppRoll::roll_sd, args)

  (x - avg) / std
}

And final test file:
test_that(&quot;standard example of roll_zscore&quot;, {
  requireNamespace(&quot;xts&quot;, quietly = TRUE)

  data(sample_matrix, package = &quot;xts&quot;)
  sample.xts &lt;- xts::as.xts(sample_matrix, descr='my new xts object')

  roll_zscore(sample.xts, n = 3)
  roll_zscore(sample.xts$Open, n = 25)
  roll_zscore(sample.xts, n = 3, fill = 0)
  roll_zscore(sample.xts, n = 3, fill = NA)
  roll_zscore(sample.xts$Open, n = 2, fill = NA)
  roll_zscore(sample.xts$Open, n = 25, fill = NA)
})