//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller * @version 1.6 * @date Sat Jun 13 01:27:00 EST 2017 * @see LICENSE (MIT style license file). * * @title Model: Random Walk */ 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, Uniform} import scalation.stat.{Statistic, vectorD2StatVector} import scalation.util.banner import Fit._ import ForecasterVec.testInSamp import RollingValidation.trSize //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NullModel` class provides basic time series analysis capabilities. * For a 'NullModel' model with the time series data stored in vector 'y', the * next value 'y_t = y(t)' may be predicted based on the prior value of 'y' and its noise: *

* y_t = ym + e_t *

* where 'e' is the noise vector. * Random Walk is a special case of AR(1) with the parameter set to one. *------------------------------------------------------------------------------ * @param y the response vector (time series data) * @param hparam the hyper-parameters */ class NullModel (y: VectoD, hparam: HyperParameter = null) extends ForecasterVec (y, 1, hparam) with NoFeatureSelectionF { private val DEBUG = true // debug flag //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the model name including its current hyper-parameter. */ override def modelName: String = "NullModel" //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Train/fit an `NullModel` model to the times series data in vector 'y_'. * Note: for `NullModel` there are no parameters to train. * @param x_null the data/input matrix (ignored) * @param y_ the response/output vector (training/full) */ override def train (x_null: MatriD, y_ : VectoD): NullModel = { super.train (null, y_) this } // train //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return the parameter vector (the training mean). */ def parameter: VectoD = VectorD (stats.mu) //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Return a vector that is the predictions (zero-centered) of a Null Model. */ def predictAllz (): VectoD = new VectorD (m) //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** 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) { for (t <- 0 until m) { // forecast all values yf(t, k) = stats.mu } // for if (DEBUG) println (s"forecastAll: yf.col ($k) = ${yf.col (k)}") } // for yf // return matrix of forecasted values } // forecastAll //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Produce h-steps ahead forecast on the testing data during cross validation. * @param y the current response vector * @param t the time point/index to be forecast * @param h the forecasting horizon, number of steps ahead to produce forecast */ override def forecastX (y: VectoD, t: Int, h: Int = 1): Double = stats.mu } // NullModel class //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NullModel` companion object provides factory methods for the `NullModel` class. */ object NullModel { //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Create a `NullModel` object. * @param y the response vector (time series data) * @param hparam the hyper-parameters */ def apply (y: VectoD, hparam: HyperParameter = null): NullModel = { new NullModel (y, hparam) } // apply } // NullModel object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NullModelTest` object is used to test the `NullModel` class. * > runMain scalation.analytics.forecaster.NullModelTest */ object NullModelTest extends App { val m = 100 val noise = Uniform (-5, 5) val y = VectorD (for (i <- 0 until m) 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 (null, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner ("Build NullModel Model") val nm = new NullModel (y) // time series model nm.train (null, y).eval () // train for NullModel model println (nm.report) new Plot (null, y, nm.predictAll (), "Plot of y, NullModel vs. t", true) } // NullModelTest object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NullModelTest2` object is used to test the `NullModel` class. The order 'p' hyper-parameter * is re-assigned. * > runMain scalation.analytics.forecaster.NullModelTest2 */ object NullModelTest2 extends App { val m = 30 val noise = Normal (0, 1) val y = VectorD (for (i <- 0 until m) 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 (null, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner ("Build NullModel Model") val nm = new NullModel (y) // time series model nm.train (null, y).eval () // train for NullModel model println (nm.report) new Plot (null, y, nm.predictAll (), "Plot of y, NullModel vs. t", true) banner ("Make Forecasts") val steps = 10 // number of steps for the forecasts val yf = nm.forecast (steps) println (s"$steps-step ahead forecasts using NullModel model = $yf") new Plot (null, yf, null, s"Plot NullModel forecasts vs. t", true) } // NullModelTest2 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NullModelTest3` object is used to test the `NullModel` class. Forecasting lake levels. * @see cran.r-project.org/web/packages/fpp/fpp.pdf * > runMain scalation.analytics.forecaster.NullModelTest3 */ object NullModelTest3 extends App { import ForecasterVec.y var nm: NullModel = null for (h <- 1 to 2) { // forecasting horizon banner (s"Build NullModel Model for h = $h") nm = new NullModel (y) // create model for time series data nm.train (null, y).eval () // train model and evaluate val yf = nm.forecastAll (h) val yf2 = testInSamp (y, 0, nm, h) // in-sample test } // for banner ("Make Forecasts") val steps = 2 // number of steps for the forecasts val yf = nm.forecast (steps) println (s"$steps-step ahead forecasts using NullModel model = $yf") banner ("Rolling Validation") val stats = SimpleRollingValidation.crossValidate2 (nm, kt_ = 2) // val stats = RollingValidation.crossValidate2 (nm, kt_ = 2) Fit.showQofStatTable (stats) } // NullModelTest3 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NullModelTest4` object is used to test the `NullModel` class. * > runMain scalation.analytics.forecaster.NullModelTest4 */ object NullModelTest4 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 NullModel model") val rw = new NullModel (y) // time series model rw.train (null, y).eval () // train for NullModel model println (rw.report) new Plot (t, y, rw.predictAll (), s"Plot of y, NullModel 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 NullModel model = $rw_f") } // NullModelTest4 object //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `NullModelTest5` object is used to test the `NullModel` class. * > runMain scalation.analytics.forecaster.NullModelTest5 */ object NullModelTest5 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 (null, y) // train for AR(1) model println (ar.report) new Plot (t, y, ar.predictAll (), "Plot of y, AR(1) vs. t", true) banner ("Build NullModel model") val rw = new NullModel (y) // time series model rw.train (null, y).eval () // train for NullModel model println (rw.report) new Plot (t, y, rw.predictAll (), s"Plot of y, NullModel 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 NullModel model = $rw_f") } // NullModelTest5 object