/*
* Copyright 2015, Yahoo Inc.
* Copyrights licensed under the GPL License.
* See the accompanying LICENSE file for terms.
*/
// Olympic scoring model considers the average of the last k weeks
// (dropping the b highest and lowest values) as the current prediction.
package com.yahoo.egads.models.tsmm;
import com.yahoo.egads.data.*;
import com.yahoo.egads.data.TimeSeries.Entry;
import java.util.Properties;
import java.util.ArrayList;
import java.util.Collections;
import com.yahoo.egads.utilities.FileUtils;
public class OlympicModel extends TimeSeriesAbstractModel {
// methods ////////////////////////////////////////////////
private static final long serialVersionUID = 1L;
public int getNumWeeks() {
return numWeeks;
}
public int getNumToDrop() {
return numToDrop;
}
public int[] getTimeShifts() {
return timeShifts;
}
public int[] getBaseWindows() {
return baseWindows;
}
public ArrayList getModel() {
return model;
}
// Number of weeks to look back when computing the
// estimate.
protected int numWeeks;
// Number of lowest and highest points to drop.
protected int numToDrop;
// Stores the historical values.
protected TimeSeries.DataSequence data;
// Stores the possible time-shifts.
// time-shifts are used to fix the time-series
// that has been shifted due to day-light savings.
protected int[] timeShifts;
// Stores the possible base windows.
// The default base window is 1 week, however
// trying multiple possible windows seems to improve
// performance.
protected int[] baseWindows;
// The actual model that stores the expectations.
protected ArrayList model;
public OlympicModel(Properties config) {
super(config);
if (config.getProperty("NUM_WEEKS") == null) {
throw new IllegalArgumentException("NUM_WEEKS is NULL");
}
if (config.getProperty("NUM_TO_DROP") == null) {
throw new IllegalArgumentException("NUM_TO_DROP is NULL");
}
if (config.getProperty("TIME_SHIFTS") == null) {
throw new IllegalArgumentException("TIME_SHIFTS is NULL");
}
if (config.getProperty("BASE_WINDOWS") == null) {
throw new IllegalArgumentException("BASE_WINDOWS is NULL");
}
this.numWeeks = new Integer(config.getProperty("NUM_WEEKS"));
this.numToDrop = new Integer(config.getProperty("NUM_TO_DROP"));
this.timeShifts = FileUtils.splitInts(config.getProperty("TIME_SHIFTS"));
this.baseWindows = FileUtils.splitInts(config.getProperty("BASE_WINDOWS"));
model = new ArrayList();
}
public void reset() {
// At this point, reset does nothing.
}
public void train(TimeSeries.DataSequence data) {
this.data = data;
int n = data.size();
java.util.Arrays.sort(baseWindows);
java.util.Arrays.sort(timeShifts);
float precision = (float) 0.000001;
for (int i = 0; i < n; i++) {
float baseVal = Float.POSITIVE_INFINITY;
float tmpbase = (float) 0.0;
// Cannot compute the expected value if the time-series
// is too short preventing us form getting the reference
// window.
if ((i - baseWindows[0]) < 0) {
model.add(data.get(i).value);
continue;
}
// Attempt to shift the time-series.
for (int w = 0; w < baseWindows.length; w++) {
for (int j = 0; j < timeShifts.length; j++) {
if (timeShifts[j] == 0) {
tmpbase = computeExpected(i, baseWindows[w]);
if ((Math.abs(tmpbase - data.get(i).value) - Math.abs(baseVal - data.get(i).value)) < precision) {
baseVal = tmpbase;
}
} else {
if (i + timeShifts[j] < n) {
tmpbase = computeExpected(i + timeShifts[j], baseWindows[w]);
if ((Math.abs(tmpbase - data.get(i).value) - Math.abs(baseVal - data.get(i).value)) < precision) {
baseVal = tmpbase;
}
}
if (i - timeShifts[j] >= 0) {
tmpbase = computeExpected(i - timeShifts[j], baseWindows[w]);
if ((Math.abs(tmpbase - data.get(i).value) - Math.abs(baseVal - data.get(i).value)) < precision) {
baseVal = tmpbase;
}
}
}
}
}
model.add(baseVal);
}
initForecastErrors(model, data);
logger.debug(getBias() + "\t" + getMAD() + "\t" + getMAPE() + "\t" + getMSE() + "\t" + getSAE() + "\t" + 0 + "\t" + 0);
}
public void update(TimeSeries.DataSequence data) {
}
public String getModelName() {
return "OlympicModel";
}
private Float sum(ArrayList list) {
float sum = 0;
for (float i : list) {
sum = sum + i;
}
return sum;
}
private float computeExpected(int i, int pl) {
ArrayList vals = new ArrayList();
float precision = (float) 0.000001;
int j = 1;
if ((i - pl * j) < 0) {
return Float.POSITIVE_INFINITY;
}
while (j <= this.numWeeks && (i - pl * j) >= 0) {
float lastWeeksVal = data.get(i - pl * j).value;
// If dynamic parameters are turned on,
// then we check if our error improved from last time,
// if not, then we stop and use the old result.
if (dynamicParameters == 1 && vals.size() > 0) {
float withNewVal = (sum(vals) + lastWeeksVal) / (vals.size() + 1);
float withoutNewVal = (sum(vals)) / (vals.size());
if ((Math.abs(withNewVal - data.get(i).value) - Math.abs(withoutNewVal - data.get(i).value)) > precision) {
break;
}
}
vals.add(lastWeeksVal);
j++;
}
Collections.sort(vals);
j = 0;
if (vals.size() > (2 * this.numToDrop)) {
while (j < this.numToDrop) {
vals.remove(vals.size() - 1);
vals.remove(0);
j++;
}
}
float baseVal = sum(vals) / vals.size();
return baseVal;
}
public void predict(TimeSeries.DataSequence sequence) throws Exception {
int n = data.size();
for (int i = 0; i < n; i++) {
sequence.set(i, (new Entry(data.get(i).time, model.get(i))));
logger.info(data.get(i).time + "," + data.get(i).value + "," + model.get(i));
}
}
}
Ads help maintain this website.