//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 1.6 * @date Sat Jun 13 01:27:00 EST 2017 * @see LICENSE (MIT style license file). * * @title Model: Simple Moving Average */ package scalation.analytics package forecaster import scala.collection.mutable.Set import scala.math.{max, min} import scalation.linalgebra.{MatriD, MatrixD, VectoD, VectorD} import scalation.plot.Plot import scalation.random.{Normal, Random} import scalation.stat.{Statistic, vectorD2StatVector} import scalation.util.banner import Fit._ import ForecasterVec.testInSamp import RollingValidation.trSize //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `MovingAverage` class provides basic time series analysis capabilities. * For a 'MovingAverage' model with the time series data stored in vector 'y', the * next value 'y_t = y(t)' may be predicted based on the mean of prior values * of 'y' and its noise: *

* y_t = mean (y_t-1, ..., y_t-q) + e_t *

* where 'e' is the noise vector and 'q' is the number of prior values used to * compute the mean. * @param y the response vector (time series data) * @param hparam the hyper-parameters */ class MovingAverage (y: VectoD, hparam: HyperParameter = MovingAverage.hp) extends ForecasterVec (y, 1, hparam) with NoFeatureSelectionF { private val DEBUG = true // debug flag private val q = hparam("q").toInt // take mean of last q values //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the model name including its current hyper-parameter, e.g., MovingAverage(3) */ override def modelName: String = s"MovingAverage($q)" //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Train/fit an `MovingAverage` model to the times series data in vector 'y_'. * Note: for `MovingAverage` there are no parameters to train. * @param x_null the data/input matrix (ignored) * @param y_ the response/output vector (currently only works for y) */ override def train (x_null: MatriD, y_ : VectoD): MovingAverage = { super.train (null, y_) this } // train //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the parameter vector (its null). */ def parameter: VectoD = null //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return a vector that is the predictions (zero-centered) of a moving average * model, with side-effect of updating the error. */ def predictAllz (): VectoD = { val zp = new VectorD (m) val sumq = new SumQueue (q) for (i <- z.range) { sumq += z(i); zp(i) = sumq.mean } zp } // predictAllz //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Forecast values for all 'm' time points and all horizons (1 through 'h'-steps ahead). * Record these in the 'yf' matrix, where *

* yf(t, k) = k-steps ahead forecast for y_t *

* Note, 'yf.col(0)' is set to 'y' (the actual time-series values). * @param h the maximum forecasting horizon, number of steps ahead to produce forecasts */ def forecastAll (h: Int): MatriD = { yf = new MatrixD (m, h+1) // forecasts for all time points t & horizons to h yf.setCol (0, y) // first column is actual values, horizon 0 for (k <- 1 to h) { val sumq = new SumQueue (q) // maintain sum of q most recent values sumq += y(0) // put in the first actual value for (t <- 0 until m) { yf(t, k) = sumq.mean // mean of last q values sumq += yf(t, k-1) // replace oldest value with this value } // for if (DEBUG) println (s"forecastAll: yf.col ($k) = ${yf.col (k)}") } // for yf // return matrix of forecasted values } // forecastAll //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Produce a vector of size 'h', of 1 through 'h'-steps ahead forecasts for the model. * @param t the time point from which to make forecasts * @param h the forecasting horizon, number of steps ahead to produce forecasts */ override def forecast (t: Int, h: Int = 1): VectoD = { val yf = new VectorD (h) val sumq = new SumQueue (q) for (i <- max (0, t - q - 1) until t) sumq += y(i) for (i <- yf.range) { yf(i) = sumq.mean sumq += yf(i) } // for yf } // forecast } // MovingAverage class //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `MovingAverage` companion object provides factory methods for the `MovingAverage` class. */ object MovingAverage { /** Base hyper-parameter specification for `MovingAverage` */ val hp = new HyperParameter hp += ("q", 3, 3) // number of prior values for mean //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Create a `MovingAverage` object. * @param y the response vector (time series data) * @param hparam the hyper-parameters */ def apply (y: VectoD, hparam: HyperParameter = null): MovingAverage = { new MovingAverage (y, hparam) } // apply } // MovingAverage object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `MovingAverageTest` object is used to test the `MovingAverage` class. * > runMain scalation.analytics.forecaster.MovingAverageTest */ object MovingAverageTest extends App { val ran = Random () val n = 100 val t = VectorD.range (0, n) val y = VectorD (for (i <- 0 until n) yield t(i) + 10.0 * ran.gen) banner ("Build AR(1) Model") val ar = new AR (y) // time series model ar.train (null, y).eval () // train for AR(1) model println (ar.report) new Plot (t, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner ("Build MovingAverage Model") val rw = new MovingAverage (y) // time series model rw.train (null, y).eval () // train for MovingAverage model println (rw.report) new Plot (t, y, rw.predictAll (), "Plot of y, MovingAverage vs. t", true) } // MovingAverageTest object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `MovingAverageTest2` object is used to test the `MovingAverage` class. * > runMain scalation.analytics.forecaster.MovingAverageTest2 */ object MovingAverageTest2 extends App { val noise = Normal (0.0, 1.0) val n = 30 val t = VectorD.range (0, n) val y = VectorD (for (i <- 0 until n) yield i + noise.gen) banner ("Build AR(1) Model") val ar = new AR (y) // time series model ar.train (null, y).eval () // train for AR(1) model println (ar.report) new Plot (t, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner ("Build MovingAverage Model") val rw = new MovingAverage (y) // time series model rw.train (null, y).eval () // train for MovingAverage model println (rw.report) new Plot (t, y, rw.predictAll (), s"Plot of y, MovingAverage vs. t", true) banner ("Make Forecasts") val steps = 10 // number of steps for the forecasts val rw_f = rw.forecast (steps) println (s"$steps-step ahead forecasts using MovingAverage model = $rw_f") val tf = VectorD.range (n, n + steps) new Plot (tf, rw_f, null, s"Plot MovingAverage forecasts vs. t", true) } // MovingAverageTest2 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `MovingAverageTest3` object is used to test the `MovingAverage` class. * Forecasting lake levels. * @see cran.r-project.org/web/packages/fpp/fpp.pdf * > runMain scalation.analytics.forecaster.MovingAverageTest3 */ object MovingAverageTest3 extends App { import ForecasterVec.y banner ("Build AR(1) Model") val ar = new AR (y) // time series model ar.train (null, y).eval () // train for AR(1) model testInSamp (y, 1, ar, 1) println (ar.summary) banner ("Build MovingAverage Model") val ma = new MovingAverage (y) // time series model ma.train (null, y).eval () // train for MovingAverage model testInSamp (y, 0, ma, 1) // println (ma.summary) banner ("Make Forecasts") val steps = 2 // number of steps for the forecasts val yf = ma.forecast (steps) println (s"$steps-step ahead forecasts using MovingAverage model = $yf") for (h <- 1 to 4) { // h-steps ahead forecast banner (s"Rolling Validation h = $h") val stats = SimpleRollingValidation.crossValidate2 (ma, kt_ = 1, h = h) // val stats = RollingValidation.crossValidate2 (ma, kt_ = 5, h = h) Fit.showQofStatTable (stats) } // for } // MovingAverageTest3 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `MovingAverageTest4` object is used to test the `MovingAverage` class. * > runMain scalation.analytics.forecaster.MovingAverageTest4 */ object MovingAverageTest4 extends App { val path = BASE_DIR + "travelTime.csv" val data = MatrixD (path) val (t, y) = (data.col(0), data.col(1)) banner ("Build AR(1) Model") val ar = new AR (y) // time series model ar.train (null, y).eval () // train for AR(1) model println (ar.report) new Plot (t, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner (s"Build MovingAverage model") val rw = new MovingAverage (y) // time series model rw.train (null, y).eval () // train for MovingAverage model println (rw.report) new Plot (t, y, rw.predictAll (), s"Plot of y, MovingAverage vs. t", true) banner ("Make Forecasts") val steps = 1 // number of steps for the forecasts val rw_f = rw.forecast (steps) println (s"$steps-step ahead forecasts using MovingAverage model = $rw_f") } // MovingAverageTest4 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `MovingAverageTest5` object is used to test the `MovingAverage` class. * > runMain scalation.analytics.forecaster.MovingAverageTest5 */ object MovingAverageTest5 extends App { val sig2 = 10000.0 val noise = Normal (0.0, sig2) val n = 50 val t = VectorD.range (0, n) val y = VectorD (for (i <- 0 until n) yield 40 * (i-1) - (i-2) * (i-2) + noise.gen) banner ("Build AR(1) Model") val ar = new AR (y) // time series model ar.train (null, y).eval () // train for AR(1) model println (ar.report) new Plot (t, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner ("Build MovingAverage model") val rw = new MovingAverage (y) // time series model rw.train (null, y).eval () // train for MovingAverage model println (rw.report) new Plot (t, y, rw.predictAll (), s"Plot of y, MovingAverage vs. t", true) banner ("Make Forecasts") val steps = 2 // number of steps for the forecasts val rw_f = rw.forecast (steps) println (s"$steps-step ahead forecasts using MovingAverage model = $rw_f") } // MovingAverageTest5 object