26
$\begingroup$

I have a vector of values that I would like to report the average in windows along a smaller slide.

For example, for a vector of the following values:

4, 5, 7, 3, 9, 8

A window size of 3 and a slide of 2 would do the following:

(4+5+7)/3 = 5.33
(7+3+9)/3 = 6.33
(9+8)/3 = 5.67

And return a vector of these values:

5.33, 6.33, 5.67

Is there a simple function that will do this for me? If it also returned the indices of the window starts that would be an added bonus. In this example that would be 1,3,5

$\endgroup$
5
  • 4
    $\begingroup$ Have you seen this? $\endgroup$ Commented Sep 24, 2010 at 14:52
  • $\begingroup$ Can you give some background on this "slide" idea? $\endgroup$
    – Shane
    Commented Sep 24, 2010 at 15:01
  • $\begingroup$ @J.M - I hadn't! Thank you! I'm about to see how it works. $\endgroup$
    – T-Burns
    Commented Oct 6, 2010 at 15:36
  • $\begingroup$ @Shane - Yes! I'm sorry that wasn't clear. The slide is the number of positions/indices you move to start computing the next window of averages. So rather than the next window starting after the end of the last there is some overlap when the slide is smaller than your window size. The idea is to smooth out the data points a bit. $\endgroup$
    – T-Burns
    Commented Oct 6, 2010 at 15:39
  • $\begingroup$ Thanks, I had the same question. Now, I found it useful the function "rollapply". $\endgroup$
    – angelous
    Commented Aug 6, 2014 at 9:21

8 Answers 8

34
$\begingroup$

Function rollapply in package zoo gets you close:

> require(zoo)
> TS <- zoo(c(4, 5, 7, 3, 9, 8))
> rollapply(TS, width = 3, by = 2, FUN = mean, align = "left")
       1        3 
5.333333 6.333333

It just won't compute the last value for you as it doesn't contain 3 observations. Maybe this will be sufficient for your real problem? Also, note that the returned object has the indices you want as the names of the returned vector.

Your example is making an assumption that there is an unobserved 0 in the last window. It might be more useful or realistic to pad with an NA to represent the missing information and tell mean to handle missing values. In this case we will have (8+9)/2 as our final windowed value.

> TS <- zoo(c(4, 5, 7, 3, 9, 8, NA))
> rollapply(TS, width = 3, by = 2, FUN = mean, na.rm = TRUE, align = "left")
       1        3        5 
5.333333 6.333333 8.500000
$\endgroup$
5
  • $\begingroup$ BTW, I once wrote about a usage of this function to implement the notion of "quantile loess" : r-statistics.com/2010/04/… $\endgroup$
    – Tal Galili
    Commented Sep 24, 2010 at 22:11
  • $\begingroup$ You may add a 0 at the end of x (x<-c(x,0)) to get the last element of answer. $\endgroup$
    – user88
    Commented Sep 25, 2010 at 8:36
  • 1
    $\begingroup$ @mbq; that is making a strong assumption that the observation is 0. I had been mulling this point and T-Burns is making the same assumption (an unobserved 0). I would prefer perhaps to pad with NA and pass in the na.rm = TRUE argument to mean. The answer won't be the same as what the OP requested, but it seems more useful. I'll edit my answer to include this. $\endgroup$ Commented Sep 25, 2010 at 8:59
  • $\begingroup$ @ucfagls Yet this is easy to change and as you said this assumption was made by the OP. On the other hand, I would be even more restrictive and removed the last average. $\endgroup$
    – user88
    Commented Sep 25, 2010 at 9:24
  • $\begingroup$ Thanks! Especially for noting the last value as zero assumption, I hadn't considered that. I definitely care about that last window!! $\endgroup$
    – T-Burns
    Commented Oct 6, 2010 at 15:59
16
$\begingroup$

Rollapply works great with a small dataset. However, if you are working with several million rows (genomics) it is quite slow.

The following function is super fast.

data <- c(runif(100000, min=0, max=.1),runif(100000, min=.05, max=.1),runif(10000, min=.05, max=1), runif(100000, min=0, max=.2))

slideFunct <- function(data, window, step){
  total <- length(data)
  spots <- seq(from=1, to=(total-window), by=step)
  result <- vector(length = length(spots))
  for(i in 1:length(spots)){
    result[i] <- mean(data[spots[i]:(spots[i]+window)])
  }
  return(result)
}

http://coleoguy.blogspot.com/2014/04/sliding-window-analysis.html

$\endgroup$
2
  • $\begingroup$ Quite helpful. But be aware, that window=3 will return the average of 4 (!) values, unless you add a -1 (to the range) and a +1 (to the loop). $\endgroup$
    – BurninLeo
    Commented Sep 11, 2019 at 11:42
  • $\begingroup$ Just as a hint, this function is not as fast as you might expect: I modified it to calculate a median instead of the mean and used it for a 17 million row data set with a window size of 3600 (step=1). It took 25 minutes to complete. In addition, I wrote a Go program for the same task and it finished within 21 seconds. But the problem isn't the language, it is the algorithm. For this algorithm, the window size is of critical importance. I think the options from the TTR package are a much better option if you are looking for a simple moving mean calculation (look at the other answers). $\endgroup$
    – JepZ
    Commented May 17, 2020 at 17:03
5
$\begingroup$

This simple line of code does the thing:

((c(x,0,0) + c(0,x,0) + c(0,0,x))/3)[3:(length(x)-1)]

if x is the vector in question.

$\endgroup$
2
  • $\begingroup$ This does not return what the asker wanted, but 5.33 5.00 6.33. However, it looks quite interesting. Can you explain your idea, because I don't get it. $\endgroup$
    – Henrik
    Commented Sep 24, 2010 at 18:14
  • 1
    $\begingroup$ @Henric I use this trick frequently, yet user1414's code return this roll with slide 1, not 2, as intended by OP. Check out (c(0,0,x)+c(0,x,0)+c(x,0,0))/3 to see what I mean (and how does it work). The proper formula would be: (c(0,0,x)+c(0,x,0)+c(x,0,0))[1:(length(x)-3)*2+1]/3 (we must cut 0-padding at the beginning and select even elements then. $\endgroup$
    – user88
    Commented Sep 25, 2010 at 8:00
4
$\begingroup$

I can do this easily in Matlab and duck while you downvote me:

%given vector x, windowsize, slide 
idx1 = 1:slide:numel(x);
idx2 = min(numel(x) + 1,idx1 + windowsize);  %sic on +1 here and no -1;
cx = [0;cumsum(x(:))];  %pad out a zero, perform a cumulative sum;
rv = (cx(idx2) - cx(idx1)) / windowsize; %tada! the answer!

as a side effect, idx1 is the index of the element in the sum. I am sure this can be easily translated into R. The idiom first:skip:last in Matlab gives the array first, first+skip, first+2skip, ..., first + n skip, where the last element in the array is no greater than last.

edit: I had omitted the averaging part (divide by windowsize).

$\endgroup$
4
  • $\begingroup$ +1 Not tada, rv/windowsize ;-) $\endgroup$
    – user88
    Commented Sep 25, 2010 at 8:24
  • 1
    $\begingroup$ This marg... comment box is too narrow for this code, so I've posted a new answer. $\endgroup$
    – user88
    Commented Sep 25, 2010 at 8:31
  • 1
    $\begingroup$ Thanks, but MATLAB isn't free!! $\endgroup$
    – T-Burns
    Commented Oct 6, 2010 at 15:33
  • $\begingroup$ @T-Burns: octave is free, however; also R is close enough to Matlab that this code can easily be translated. In fact, @mbq did that.. $\endgroup$
    – shabbychef
    Commented Oct 6, 2010 at 17:24
4
$\begingroup$
library(zoo)
x=c(4, 5, 7, 3, 9, 8)
rollmean(x,3)

or

library(TTR)
x=c(4, 5, 7, 3, 9, 8)
SMA(x,3)
$\endgroup$
2
  • $\begingroup$ Does this work for 2D matrices? Like how? If the window size is 3*3 as an example $\endgroup$
    – Mona Jalal
    Commented May 12, 2014 at 6:32
  • $\begingroup$ it is only one direction $\endgroup$ Commented May 15, 2014 at 4:26
3
$\begingroup$

shabbychef's answer in R:

slideMean<-function(x,windowsize=3,slide=2){
 idx1<-seq(1,length(x),by=slide);
 idx1+windowsize->idx2;
 idx2[idx2>(length(x)+1)]<-length(x)+1;
 c(0,cumsum(x))->cx;
 return((cx[idx2]-cx[idx1])/windowsize);
}

EDIT: Indices you're looking for are just idx1... this function can be easily modified to return them also, but it is almost equally fast to recreate them with another call to seq(1,length(x),by=slide).

$\endgroup$
2
  • $\begingroup$ thanks for translating. I figured it would be an easy exercise, and I learned some R from it $\endgroup$
    – shabbychef
    Commented Sep 26, 2010 at 15:59
  • $\begingroup$ My updated answer is use fromo::running_mean from the bleeding edge version of my fromo package. $\endgroup$
    – shabbychef
    Commented Jun 9, 2017 at 4:44
1
$\begingroup$

This will get you the window means and the index of the first value of the window:

#The data
x <- c(4, 5, 7, 3, 9, 8)

#Set window size and slide
win.size <- 3
slide <- 2

#Set up the table of results
results <- data.frame(index = numeric(), win.mean = numeric())

#i indexes the first value of the window (the sill?)
i <- 1
#j indexes the row of the results to be added next
j <- 1
while(i < length(x)) {
    #This mean preserves the denominator of 3
    win.mean <- sum(x[i:(i+2)], na.rm = TRUE)/win.size
    #Insert the results
    results[j, ] <- c(i, win.mean)
    #Increment the indices for the next pass
    i <- i + slide
    j <- j + 1
    }

Various caveats apply: haven't tested this against anything but your sample data; I believe that appending to data frames like this can get really slow if you have lots of values (because it'll copy the data.frame each time); etc. But it does produce what you asked for.

$\endgroup$
6
  • $\begingroup$ Please don't downvote without providing a comment. How am I supposed to know what's wrong? $\endgroup$ Commented Sep 24, 2010 at 19:59
  • $\begingroup$ It wasn't me, but this is slow (but not much slower than rollapply). $\endgroup$
    – user88
    Commented Sep 25, 2010 at 8:06
  • 2
    $\begingroup$ wasn't me either, but as mentioned by yourself, pre-allocation of the result object will help with the speed issue. One trick, if you don't know, or it is tedious/difficult to determine, the size of the result object you need. Allocate something reasonable, perhaps pre-filling with NA. Then fill in with your loop, but add a check that if you are approaching the limit of the preallocated object, allocate another big chunk, and continue filling. $\endgroup$ Commented Sep 25, 2010 at 9:43
  • 1
    $\begingroup$ @mbq; Speed of results, whilst important, isn't the only consideration. Instead of having to reinvent the while and handle all the indexes etc in the custom solutions, the one-linear that is rollapply is much easier to understand and grep the intention of. Also, rollapply is likely to have had many more eyeballs checking its code than something I might cook up one afternoon. Horses for courses. $\endgroup$ Commented Sep 25, 2010 at 9:45
  • 1
    $\begingroup$ Changing [i:(i+2)] to [i:(i+win.size-1)] would make the code more general, I think. $\endgroup$
    – Jota
    Commented Jul 10, 2014 at 0:54
1
$\begingroup$

You are doing a convolution operation. The implementation in R uses FFT internally and you are unlikely to beat it with loops and such things.

> vals=c(4, 5, 7, 3, 9, 8, 0)
> convolve(x=vals, y=c(1, 1, 1)/3, type="filter")
[1] 5.33 5.00 6.33 6.67 5.67

If you want to extract every second result.

> tmp <- convolve(x=vals, y=c(1, 1, 1)/3, type="filter")
> tmp[0:2*2+1]
[1] 5.33 6.33 5.67
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.