Decayed Value (aka. moving average)

A DecayedValue can be approximately computed into a moving average. Please see an explanation of different averages here: The Decaying Average.

@johnynek mentioned that:

a DecayedValue is approximately like a moving average value with window size of the half-life. It is EXACTLY a sample of the Laplace transform of the signal of values. Therefore, if we normalize a decayed value with halflife/ln(2), which is the integral of exp(-t(ln(2))/halflife) from 0 to infinity. We get a moving average of the window size equal to halflife.

See the related issue: https://github.com/twitter/algebird/issues/235

Here is the code example for computing a DecayedValue average:

import com.twitter.algebird._
// import com.twitter.algebird._

val data = {
  val rnd = new scala.util.Random
  (1 to 10).map { _ => rnd.nextInt(1000).toDouble }.toSeq
}
// data: scala.collection.immutable.Seq[Double] = Vector(540.0, 447.0, 4.0, 480.0, 155.0, 44.0, 582.0, 139.0, 270.0, 841.0)

val HalfLife = 5.0
// HalfLife: Double = 5.0

val normalization = HalfLife / math.log(2)
// normalization: Double = 7.213475204444817

implicit val dvMonoid = DecayedValue.monoidWithEpsilon(1e-3)
// dvMonoid: com.twitter.algebird.Monoid[com.twitter.algebird.DecayedValue] = DecayedValueMonoid(0.001)

data.zipWithIndex.scanLeft(Monoid.zero[DecayedValue]) { (previous, data) =>
  val (value, time) = data
  val decayed = Monoid.plus(previous, DecayedValue.build(value, time, HalfLife))
  println("At %d: decayed=%f".format(time, (decayed.value / normalization)))
  decayed
}
// At 0: decayed=74.859896
// At 1: decayed=127.136682
// At 2: decayed=111.233428
// At 3: decayed=163.376453
// At 4: decayed=163.715026
// At 5: decayed=148.621903
// At 6: decayed=210.065213
// At 7: decayed=202.141881
// At 8: decayed=213.404676
// At 9: decayed=302.366917
// res0: scala.collection.immutable.Seq[com.twitter.algebird.DecayedValue] = Vector(DecayedValue(0.0,-Infinity), DecayedValue(540.0,0.0), DecayedValue(917.097304179907,0.13862943611198905), DecayedValue(802.3795747511749,0.2772588722239781), DecayedValue(1178.51199077694,0.4158883083359671), DecayedValue(1180.9542774221018,0.5545177444479562), DecayedValue(1072.080411436778,0.6931471805599453), DecayedValue(1515.3002060750277,0.8317766166719343), DecayedValue(1458.1454479613483,0.9704060527839233), DecayedValue(1539.389341090431,1.1090354888959124), DecayedValue(2181.116258018324,1.2476649250079015))

Running the above code in comparison with a simple decayed average:

data.zipWithIndex.scanLeft(0.0) { (previous, data) =>
  val (value, time) = data
  val avg = (value + previous * (HalfLife - 1.0)) / HalfLife
  println("At %d: windowed=%f".format(time, avg))
  avg
}
// At 0: windowed=108.000000
// At 1: windowed=175.800000
// At 2: windowed=141.440000
// At 3: windowed=209.152000
// At 4: windowed=198.321600
// At 5: windowed=167.457280
// At 6: windowed=250.365824
// At 7: windowed=228.092659
// At 8: windowed=236.474127
// At 9: windowed=357.379302
// res1: scala.collection.immutable.Seq[Double] = Vector(0.0, 108.0, 175.8, 141.44, 209.152, 198.3216, 167.45728, 250.36582399999998, 228.09265919999999, 236.47412735999995, 357.379301888)

You’ll see that the averages are pretty close to each other.

DecayedValue FAQ

Is a DecayedValue average better than a moving average?

DecayedValue gives an exponential moving average. A standard windowed moving average of N points requires O(N) memory. An exponential moving average takes O(1) memory. That’s the win. Usually one, or a few, exponential moving averages gives you what you need cheaper than the naive approach of keeping 100 or 1000 recent points.

Is a DecayedValue average better than a simple decayed average?

A simple decayed average looks like this:

val avg = (value + previousAvg * (HaflLife - 1.0)) / HalfLife

In a way, a DecayedValue average is a simple decayed average with different scaling factor.

Documentation Help

We’d love your help fleshing out this documentation! You can edit this page in your browser by clicking this link. These links might be helpful: