├── cd └── codesigning.asc.enc ├── CONTRIBUTORS.md ├── scripts ├── release.sh └── before_release.sh ├── CONTRIBUTING ├── settings.xml ├── LICENSE ├── src ├── test │ └── java │ │ └── com │ │ └── workday │ │ └── insights │ │ └── timeseries │ │ └── arima │ │ ├── InsightsVectorTest.java │ │ ├── InsightsMatrixTest.java │ │ ├── ArimaTest.java │ │ └── Datasets.java └── main │ └── java │ └── com │ └── workday │ └── insights │ ├── timeseries │ ├── arima │ │ ├── YuleWalker.java │ │ ├── struct │ │ │ ├── ArimaModel.java │ │ │ ├── ForecastResult.java │ │ │ ├── BackShift.java │ │ │ └── ArimaParams.java │ │ ├── Arima.java │ │ ├── HannanRissanen.java │ │ └── ArimaSolver.java │ └── timeseriesutil │ │ ├── ForecastUtil.java │ │ └── Integrator.java │ └── matrix │ ├── InsightsVector.java │ └── InsightsMatrix.java ├── .travis.yml ├── README.md └── pom.xml /cd/codesigning.asc.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workday/timeseries-forecast/HEAD/cd/codesigning.asc.enc -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributor List 2 | 3 | Yon-Seo Kim 4 | 5 | Henry Zhang 6 | 7 | Ethan Berl 8 | 9 | Ace Hao 10 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$TRAVIS_BRANCH" = 'master' ] && [ "$TRAVIS_PULL_REQUEST" == 'false' ]; then 4 | mvn deploy -P sign,build-extras --settings settings.xml 5 | fi 6 | 7 | -------------------------------------------------------------------------------- /scripts/before_release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$TRAVIS_BRANCH" = 'master' ] && [ "$TRAVIS_PULL_REQUEST" == 'false' ]; then 4 | openssl aes-256-cbc -K $encrypted_78a08945ac0f_key -iv $encrypted_78a08945ac0f_iv -in cd/codesigning.asc.enc -out cd/codesigning.asc -d 5 | gpg --fast-import cd/codesigning.asc 6 | fi -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Each Contributor (“You”) represents that such You are legally entitled to submit any Contributions 2 | in accordance with these terms and by posting a Contribution, you represent that each of Your Contribution 3 | is Your original creation. 4 | 5 | You are not expected to provide support for Your Contributions, except to the extent You desire to 6 | provide support. You may provide support for free, for a fee, or not at all. Unless required by 7 | applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, 8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, 9 | any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 10 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | 10 | 11 | ossrh 12 | 13 | true 14 | 15 | 16 | gpg 17 | ${env.GPG_KEY_NAME} 18 | ${env.GPG_PASSPHRASE} 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Workday, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /src/test/java/com/workday/insights/timeseries/arima/InsightsVectorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import com.workday.insights.matrix.InsightsVector; 11 | import org.junit.Assert; 12 | import org.junit.Test; 13 | 14 | public class InsightsVectorTest { 15 | 16 | @Test 17 | public void constructorTests() { 18 | InsightsVector iv = new InsightsVector(10, 3.0); 19 | 20 | Assert.assertTrue(iv.size() == 10); 21 | for (int i = 0; i < iv.size(); i++) { 22 | Assert.assertTrue(iv.get(i) == 3.0); 23 | } 24 | 25 | double[] data = {3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0}; 26 | InsightsVector iv3 = new InsightsVector(data, false); 27 | for (int i = 0; i < iv3.size(); i++) { 28 | Assert.assertTrue(iv3.get(i) == 3.0); 29 | } 30 | } 31 | 32 | @Test 33 | public void dotOperationTest() { 34 | InsightsVector rowVec = new InsightsVector(3, 1.1); 35 | InsightsVector colVec = new InsightsVector(3, 2.2); 36 | 37 | double expect = 1.1 * 2.2 * 3; 38 | double actual = rowVec.dot(colVec); 39 | Assert.assertEquals(expect, actual, 0); 40 | } 41 | 42 | @Test (expected = RuntimeException.class) 43 | public void dotOperationExceptionTest(){ 44 | InsightsVector rowVec = new InsightsVector(4, 1); 45 | InsightsVector colVec = new InsightsVector(3, 2); 46 | rowVec.dot(colVec); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/YuleWalker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import com.workday.insights.matrix.InsightsMatrix; 11 | import com.workday.insights.matrix.InsightsVector; 12 | import com.workday.insights.timeseries.timeseriesutil.ForecastUtil; 13 | import java.util.Arrays; 14 | 15 | /** 16 | * Yule-Walker algorithm implementation 17 | */ 18 | public final class YuleWalker { 19 | 20 | private YuleWalker() { 21 | } 22 | 23 | /** 24 | * Perform Yule-Walker algorithm to the given timeseries data 25 | * 26 | * @param data input data 27 | * @param p YuleWalker Parameter 28 | * @return array of Auto-Regressive parameter estimates. Index 0 contains coefficient of lag 1 29 | */ 30 | public static double[] fit(final double[] data, final int p) { 31 | 32 | int length = data.length; 33 | if (length == 0 || p < 1) { 34 | throw new RuntimeException( 35 | "fitYuleWalker - Invalid Parameters" + "length=" + length + ", p = " + p); 36 | } 37 | 38 | double[] r = new double[p + 1]; 39 | for (double aData : data) { 40 | r[0] += Math.pow(aData, 2); 41 | } 42 | r[0] /= length; 43 | 44 | for (int j = 1; j < p + 1; j++) { 45 | for (int i = 0; i < length - j; i++) { 46 | r[j] += data[i] * data[i + j]; 47 | } 48 | r[j] /= (length); 49 | } 50 | 51 | final InsightsMatrix toeplitz = ForecastUtil.initToeplitz(Arrays.copyOfRange(r, 0, p)); 52 | final InsightsVector rVector = new InsightsVector(Arrays.copyOfRange(r, 1, p + 1), false); 53 | 54 | return toeplitz.solveSPDIntoVector(rVector, ForecastUtil.maxConditionNumber).deepCopy(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/struct/ArimaModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima.struct; 9 | 10 | import com.workday.insights.timeseries.arima.ArimaSolver; 11 | 12 | /** 13 | * ARIMA model 14 | */ 15 | public class ArimaModel { 16 | 17 | private final ArimaParams params; 18 | private final double[] data; 19 | private final int trainDataSize; 20 | private double rmse; 21 | 22 | /** 23 | * Constructor for ArimaModel 24 | * 25 | * @param params ARIMA parameter 26 | * @param data original data 27 | * @param trainDataSize size of train data 28 | */ 29 | public ArimaModel(ArimaParams params, double[] data, int trainDataSize) { 30 | this.params = params; 31 | this.data = data; 32 | this.trainDataSize = trainDataSize; 33 | } 34 | 35 | /** 36 | * Getter for Root Mean-Squared Error. 37 | * 38 | * @return Root Mean-Squared Error for the ARIMA model 39 | */ 40 | public double getRMSE() { 41 | return this.rmse; 42 | } 43 | 44 | /** 45 | * Setter for Root Mean-Squared Error 46 | * 47 | * @param rmse source Root Mean-Squared Error 48 | */ 49 | public void setRMSE(final double rmse) { 50 | this.rmse = rmse; 51 | } 52 | 53 | /** 54 | * Getter for ARIMA parameters. 55 | * 56 | * @return ARIMA parameters for the model 57 | */ 58 | public ArimaParams getParams() { 59 | return params; 60 | } 61 | 62 | /** 63 | * Forecast data base on training data and forecast size. 64 | * 65 | * @param forecastSize size of forecast 66 | * @return forecast result 67 | */ 68 | public ForecastResult forecast(final int forecastSize) { 69 | ForecastResult forecastResult = ArimaSolver 70 | .forecastARIMA(params, data, trainDataSize, trainDataSize + forecastSize); 71 | forecastResult.setRMSE(this.rmse); 72 | 73 | return forecastResult; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | before_install: 5 | - chmod +x scripts/before_release.sh 6 | - chmod +x scripts/release.sh 7 | install: 8 | - mvn install -P !build-extras -DskipTests=true -Dmaven.javadoc.skip=true -B -V 9 | script: 10 | - mvn test -P !build-extras -B 11 | cache: 12 | directories: 13 | - ~/.m2/repository 14 | after_success: 15 | - ./scripts/before_release.sh 16 | - ./scripts/release.sh 17 | env: 18 | global: 19 | - secure: LYdA6FZR8c7Js9gAvDd5ZnmeIV3YWPJD8PaKAhwXiuTWHLN1PTildsIevlq9pclmaAmmchP1yUPWqHwDo2fDKM3lOGoG3Fg4Qh4LYwnrlfctaGLI0lEmzGwJ/nlDDXZFLxboWEKqXVj+rRp+nu8ViELEC3f1jzKLaCoMBlzWg6NiymYKsVOpD7ZNW7u1Si1mRJq2WMoD+vTONj+yR1yYSse4UrMwZuq3Tqhm9ZtdCLKXaspaunAnOGPYNkZcqgMzxkQeJgvLYNNC8n0PyjaXQTzpJNnT5diDIFEsKYfdksLtJlOf6tJkAgtH3e1USvb9gHOXCO+aREeP2uRzWawKSbJxZ9KMh7X7qHppgt2F2RTJGpqHwfr01WUJV1Qdf68H5y5dhMkESr+hhvhWCjmvkVApJiVPSD3mAQSxwsSYK2NFR6etPI9PQRwoRzeH3Z0FQqZTJzkMEJ1SWoCC/NuUvJJwc4NBVmA+9QZ5zBrhAjCxTFqIjxqj/ybmTMFh5s/1ziYYKWQqD7BikTlcjDdR0VFE7sFtxpGl7jmH3U4Kjgn5iUfo40TAH64hqnmAxZOJUyukPhm6w//5adXGOzY95q0w6viheBJAg3krQ63VMbFaepCaeX2BPZoQV0wSYWY++0k/+7R6aQZjY4B2KgBpzvkChNo6ZURdaB3PSfV3U34= 20 | - secure: R716QY7lymCAF/WXclB3Ynl4sRPRPUVPHOyK5jyv+eWQkahjfkRj9fLP6NP+M7F1g/By+RSdtS8ZNK8LIOG3bAyMWml83WaFwOZMd9As94/wE4WAyNsuc4pBryv2bi8A8NyvmgOC4qUpERIZ1bejL23Wiv6+7YIlD5vwVyI51LFDlo3ElCDO6Cruy1oqJ/uPlV7PLFRryctRpKbuqEYmchY8odLFJ64RnZvXeDCAYTBuPM7zCfGZwxW1vWFGKzDSvyYKnUdda+zL3GvhVATY+01nfyr+HQnW/bMHx+PMl7Yh6WvpGkI3HlBvgzbyTEIHC3QTeQ1xGIWr9kQBl458M38h6gECic680dXXjkpGlAODcMtck5LRw2jsjFbH0WUb2G1ZAimSfLrWIcpkmU9pAD+NAepBwpDC1j2cl5pg38GCH5BpnrtIAAKxxmRcT2CQgvk/k2shgB+TqE7f6TqDbjW8LnPrIOJJZ92N2mF4UW4N+WpVhU0vb24Pa0yd0Bal8OxV/9loc02ckRgagxcG0rF6AIWomZ4WqYHlw/QoyJ42HiiAC8aQ0KwrFD8SDc/cKOSAk7JcVgCzQpwoGq39dJBzqFwOJIdmvUDEKL0vmlzu/YGrOe6T4Dyhsr4fuWAZTxAK1EqJ66xdPehSablUkV1QJcWN1xm0fwVBsFlx9CE= 21 | - secure: ACXVDwpGplhgA/t3mAMT5WcHsP6933XctNA4xDfgh3J96O7HLnNtkfJ89Mc8fuKUzEtWBh7xkMZL/4ztZamuFmxShGyaf1xojbhBt/qnmZIf+RLrGWCzbxsIN4YL2r0lQjv4IrURVPhN+LL86Oh+ja+HGE37gHW4p/YZWehaKb/otdV5a1x2/u1EIbkEzwK4Qgxc/9Smu7b8JwiXvbIwVTv7hblEt+MoBs+xSdCSgVDNglH+Llqn2NevMd3kOxyzNcIc3z8Nvrbg93knvaM5fIEJE+Ey66LQmkHg762oNN91TOzsW8uYBG4E26wIrDLr/T4YlVLNUuybXiM7EpzgTnDjJfhXYl9zqkDRR+N/gwYMeBmUvM2o0wkkEC9OmbB0tgMnSaMCgHPedFrz/mP8r4EbCk+cqjvzNVHwGFpspJMMu2Qcgq4j97f5Fj9nyyDO23ethpFvRBP7nFXyOanPR1AjoSLZiKByJ+1/nhugRuFOcBL8wuDpImANHtKcboDzBRXrfFCq5ojvUNUXqLrAZIrC1xugFWmrzSFk8Y+lgNat3ikcT56M2A7VVU88gTy0mBctfpZgTt/IRaNQcePNCTmUJvWQ8PmLfHZhyQ5KjaKl6DBwmpAUhCsrCyx4mXWZBFqs9jFNKQEvgbctuWaek8MF2RCbDPsQyan4qDXKOQI= 22 | - secure: W4ywPwoWCU4JHqoE7PJ9ocqti2yfVRjnnjqSf3QCAH2yiKd4+7H+Koxmo6SpGCEHg7N3in+D/nUIh7qJ5rxbJUAKfhoAN0/dwsH6uOsKYtVK9IqpI1fRL3NP1MC2GDzFbrsOvBkAAenxyWmKrZiuFRfxHdTEag8oqE4dL4c4bikcZXi5ry/EEYqMBiC3LJBhE9eq3SRTy81VA4/i+THRLPRAd1VVJuoA1Lt00ZClDdzTRmn4Wn4+k76ZGf6FdLyKEWISQrqns6sJF+3TrgoxNY8ZBflfuqxOdayMFvQb9rZLPykSZX+recJnDnO5w3FqMslvMvWhKtBneQ7bZJ9Fwf+eCfY61FtyJ28OsfKc0aUYSNTmky6AZaSWgzfUyHdoW9/Tt3WJgEtmzwlfDhW/NvjPmH4iqnyjFhzLBHJclM3hJaWGq/uxrrb/IK27Mxt+dxPZqV+H0GjJgexsFCYDpk3UwPmCst08ZOEfw+0CgXUOZe10U4mn7n0W/PxkZt0HYzqyRF+BupDkXcHyUG6ZTNOwTzD+wjLk9pkJuPhZOFtMaC6lETs0H5ncKTIa0NZmMZnjFR0pWg1+TDSIz4qcfFxDuGyk7QnOMAhlSUz2aCj2ODA+UagxnC5jtiPdupf9b3t06wao5d7mBX7cvGAmyhBXNYXB6lZ9wuwoSLgwAWE= 23 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/Arima.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import com.workday.insights.timeseries.arima.struct.ArimaModel; 11 | import com.workday.insights.timeseries.arima.struct.ArimaParams; 12 | import com.workday.insights.timeseries.arima.struct.ForecastResult; 13 | import com.workday.insights.timeseries.timeseriesutil.ForecastUtil; 14 | 15 | /** 16 | * ARIMA implementation 17 | */ 18 | public final class Arima { 19 | 20 | private Arima() { 21 | } // pure static class 22 | 23 | /** 24 | * Raw-level ARIMA forecasting function. 25 | * 26 | * @param data UNMODIFIED, list of double numbers representing time-series with constant time-gap 27 | * @param forecastSize integer representing how many data points AFTER the data series to be 28 | * forecasted 29 | * @param params ARIMA parameters 30 | * @return a ForecastResult object, which contains the forecasted values and/or error message(s) 31 | */ 32 | public static ForecastResult forecast_arima(final double[] data, final int forecastSize, ArimaParams params) { 33 | 34 | try { 35 | final int p = params.p; 36 | final int d = params.d; 37 | final int q = params.q; 38 | final int P = params.P; 39 | final int D = params.D; 40 | final int Q = params.Q; 41 | final int m = params.m; 42 | final ArimaParams paramsForecast = new ArimaParams(p, d, q, P, D, Q, m); 43 | final ArimaParams paramsXValidation = new ArimaParams(p, d, q, P, D, Q, m); 44 | // estimate ARIMA model parameters for forecasting 45 | final ArimaModel fittedModel = ArimaSolver.estimateARIMA( 46 | paramsForecast, data, data.length, data.length + 1); 47 | 48 | // compute RMSE to be used in confidence interval computation 49 | final double rmseValidation = ArimaSolver.computeRMSEValidation( 50 | data, ForecastUtil.testSetPercentage, paramsXValidation); 51 | fittedModel.setRMSE(rmseValidation); 52 | final ForecastResult forecastResult = fittedModel.forecast(forecastSize); 53 | 54 | // populate confidence interval 55 | forecastResult.setSigma2AndPredicationInterval(fittedModel.getParams()); 56 | 57 | // add logging messages 58 | forecastResult.log("{" + 59 | "\"Best ModelInterface Param\" : \"" + fittedModel.getParams().summary() + "\"," + 60 | "\"Forecast Size\" : \"" + forecastSize + "\"," + 61 | "\"Input Size\" : \"" + data.length + "\"" + 62 | "}"); 63 | 64 | // successfully built ARIMA model and its forecast 65 | return forecastResult; 66 | 67 | } catch (final Exception ex) { 68 | // failed to build ARIMA model 69 | throw new RuntimeException("Failed to build ARIMA forecast: " + ex.getMessage()); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/timeseriesutil/ForecastUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.timeseriesutil; 9 | 10 | import com.workday.insights.matrix.InsightsMatrix; 11 | 12 | /** 13 | * Time series forecasting Utilities 14 | */ 15 | public final class ForecastUtil { 16 | 17 | 18 | public static final double testSetPercentage = 0.15; 19 | public static final double maxConditionNumber = 100; 20 | public static final double confidence_constant_95pct = 1.959963984540054; 21 | 22 | private ForecastUtil() { 23 | } 24 | 25 | /** 26 | * Instantiates Toeplitz matrix from given input array 27 | * 28 | * @param input double array as input data 29 | * @return a Toeplitz InsightsMatrix 30 | */ 31 | public static InsightsMatrix initToeplitz(double[] input) { 32 | int length = input.length; 33 | double toeplitz[][] = new double[length][length]; 34 | 35 | for (int i = 0; i < length; i++) { 36 | for (int j = 0; j < length; j++) { 37 | if (j > i) { 38 | toeplitz[i][j] = input[j - i]; 39 | } else if (j == i) { 40 | toeplitz[i][j] = input[0]; 41 | } else { 42 | toeplitz[i][j] = input[i - j]; 43 | } 44 | } 45 | } 46 | return new InsightsMatrix(toeplitz, false); 47 | } 48 | 49 | /** 50 | * Invert AR part of ARMA to obtain corresponding MA series 51 | * 52 | * @param ar AR portion of the ARMA 53 | * @param ma MA portion of the ARMA 54 | * @param lag_max maximum lag 55 | * @return MA series 56 | */ 57 | public static double[] ARMAtoMA(final double[] ar, final double[] ma, final int lag_max) { 58 | final int p = ar.length; 59 | final int q = ma.length; 60 | final double[] psi = new double[lag_max]; 61 | 62 | for (int i = 0; i < lag_max; i++) { 63 | double tmp = (i < q) ? ma[i] : 0.0; 64 | for (int j = 0; j < Math.min(i + 1, p); j++) { 65 | tmp += ar[j] * ((i - j - 1 >= 0) ? psi[i - j - 1] : 1.0); 66 | } 67 | psi[i] = tmp; 68 | } 69 | final double[] include_psi1 = new double[lag_max]; 70 | include_psi1[0] = 1; 71 | for (int i = 1; i < lag_max; i++) { 72 | include_psi1[i] = psi[i - 1]; 73 | } 74 | return include_psi1; 75 | } 76 | 77 | /** 78 | * Simple helper that returns cumulative sum of coefficients 79 | * 80 | * @param coeffs array of coefficients 81 | * @return array of cumulative sum of the coefficients 82 | */ 83 | public static double[] getCumulativeSumOfCoeff(final double[] coeffs) { 84 | final int len = coeffs.length; 85 | final double[] cumulativeSquaredCoeffSumVector = new double[len]; 86 | double cumulative = 0.0; 87 | for (int i = 0; i < len; i++) { 88 | cumulative += Math.pow(coeffs[i], 2); 89 | cumulativeSquaredCoeffSumVector[i] = Math.pow(cumulative, 0.5); 90 | } 91 | return cumulativeSquaredCoeffSumVector; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/workday/insights/timeseries/arima/InsightsMatrixTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import com.workday.insights.matrix.InsightsMatrix; 11 | import com.workday.insights.matrix.InsightsVector; 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | 15 | public class InsightsMatrixTest { 16 | 17 | @Test 18 | public void constructorTests() { 19 | double[][] data = {{3.0, 3.0, 3.0}, 20 | {3.0, 3.0, 3.0}, 21 | {3.0, 3.0, 3.0}}; 22 | 23 | InsightsMatrix im1 = new InsightsMatrix(data, false); 24 | Assert.assertTrue(im1.getNumberOfColumns() == 3); 25 | Assert.assertTrue(im1.getNumberOfRows() == 3); 26 | for (int i = 0; i < im1.getNumberOfColumns(); i++) { 27 | for (int j = 0; j < im1.getNumberOfColumns(); j++) { 28 | Assert.assertTrue(im1.get(i, j) == 3.0); 29 | } 30 | } 31 | im1.set(0, 0, 0.0); 32 | Assert.assertTrue(im1.get(0, 0) == 0.0); 33 | im1.set(0, 0, 3.0); 34 | 35 | InsightsVector iv = new InsightsVector(3, 3.0); 36 | for (int i = 0; i < im1.getNumberOfColumns(); i++) { 37 | Assert.assertTrue(im1.timesVector(iv).get(i) == 27.0); 38 | } 39 | } 40 | 41 | @Test 42 | public void solverTestSimple() { 43 | double[][] A = { 44 | {2.0} 45 | }; 46 | double[] B = {4.0}; 47 | double[] solution = {2.0}; 48 | 49 | InsightsMatrix im = new InsightsMatrix(A, true); 50 | InsightsVector iv = new InsightsVector(B, true); 51 | 52 | InsightsVector solved = im.solveSPDIntoVector(iv, -1); 53 | for (int i = 0; i < solved.size(); i++) { 54 | Assert.assertTrue(solved.get(i) == solution[i]); 55 | } 56 | } 57 | 58 | @Test 59 | public void solverTestOneSolution() { 60 | double[][] A = { 61 | {1.0, 1.0}, 62 | {1.0, 2.0} 63 | }; 64 | 65 | double[] B = {2.0, 16.0}; 66 | double[] solution = {-12.0, 14.0}; 67 | 68 | InsightsMatrix im = new InsightsMatrix(A, true); 69 | InsightsVector iv = new InsightsVector(B, true); 70 | 71 | InsightsVector solved = im.solveSPDIntoVector(iv, -1); 72 | for (int i = 0; i < solved.size(); i++) { 73 | Assert.assertTrue(solved.get(i) == solution[i]); 74 | } 75 | } 76 | 77 | @Test 78 | public void timesVectorTestSimple() { 79 | double[][] A = { 80 | {1.0, 1.0}, 81 | {2.0, 2.0} 82 | }; 83 | 84 | double[] x = {3.0, 4.0}; 85 | double[] solution = {7.0, 14.0}; 86 | 87 | InsightsMatrix im = new InsightsMatrix(A, true); 88 | InsightsVector iv = new InsightsVector(x, true); 89 | 90 | InsightsVector solved = im.timesVector(iv); 91 | for (int i = 0; i < solved.size(); i++) { 92 | Assert.assertTrue(solved.get(i) == solution[i]); 93 | 94 | } 95 | } 96 | 97 | @Test(expected = RuntimeException.class) 98 | public void timesVectorTestIncorrectDimension() { 99 | double[][] A = { 100 | {1.0, 1.0, 1.0}, 101 | {2.0, 2.0, 2.0}, 102 | {3.0, 3.0, 3.0} 103 | }; 104 | 105 | double[] x = {4.0, 4.0, 4.0, 4.0}; 106 | 107 | InsightsMatrix im = new InsightsMatrix(A, true); 108 | InsightsVector iv = new InsightsVector(x, true); 109 | 110 | InsightsVector solved = im.timesVector(iv); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/timeseriesutil/Integrator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.timeseriesutil; 9 | 10 | /** 11 | * Pure Helper Class 12 | */ 13 | public class Integrator { 14 | 15 | private Integrator() { 16 | } // this is pure helper class 17 | 18 | /** 19 | * 20 | * @param src source array of data 21 | * @param dst destination array to store data 22 | * @param initial initial conditions 23 | * @param d length of initial conditions 24 | */ 25 | public static void differentiate(final double[] src, final double[] dst, final double[] initial, 26 | final int d) { 27 | if (initial == null || initial.length != d || d <= 0) { 28 | throw new RuntimeException("invalid initial size=" + initial.length + ", d=" + d); 29 | } 30 | if (src == null || src.length <= d) { 31 | throw new RuntimeException("insufficient source size=" + src.length + ", d=" + d); 32 | } 33 | if (dst == null || dst.length != src.length - d) { 34 | throw new RuntimeException( 35 | "invalid destination size=" + dst.length + ", src=" + src.length + ", d=" + d); 36 | } 37 | 38 | // copy over initial conditions 39 | System.arraycopy(src, 0, initial, 0, d); 40 | // now differentiate source into destination 41 | final int src_len = src.length; 42 | for (int j = d, k = 0; j < src_len; ++j, ++k) { 43 | dst[k] = src[j] - src[k]; 44 | } 45 | } 46 | 47 | /** 48 | * 49 | * @param src source array of data 50 | * @param dst destination array to store data 51 | * @param initial initial conditions 52 | * @param d length of initial conditions 53 | */ 54 | public static void integrate(final double[] src, final double[] dst, final double[] initial, 55 | final int d) { 56 | if (initial == null || initial.length != d || d <= 0) { 57 | throw new RuntimeException("invalid initial size=" + initial.length + ", d=" + d); 58 | } 59 | if (dst == null || dst.length <= d) { 60 | throw new RuntimeException("insufficient destination size=" + dst.length + ", d=" + d); 61 | } 62 | if (src == null || src.length != dst.length - d) { 63 | throw new RuntimeException( 64 | "invalid source size=" + src.length + ", dst=" + dst.length + ", d=" + d); 65 | } 66 | 67 | // copy over initial conditions 68 | System.arraycopy(initial, 0, dst, 0, d); 69 | // now integrate source into destination 70 | final int src_len = src.length; 71 | for (int j = d, k = 0; k < src_len; ++j, ++k) { 72 | dst[j] = dst[k] + src[k]; 73 | } 74 | } 75 | 76 | /** 77 | * Shifting the input data 78 | * 79 | * @param inputData MODIFIED. input data 80 | * @param shiftAmount shift amount 81 | */ 82 | public static void shift(double[] inputData, final double shiftAmount) { 83 | for (int i = 0; i < inputData.length; i++) { 84 | inputData[i] += shiftAmount; 85 | } 86 | } 87 | 88 | /** 89 | * Compute the mean of input data 90 | * 91 | * @param data input data 92 | * @return mean 93 | */ 94 | public static double computeMean(final double[] data) { 95 | final int length = data.length; 96 | if (length == 0) { 97 | return 0.0; 98 | } 99 | double sum = 0.0; 100 | for (int i = 0; i < length; ++i) { 101 | sum += data[i]; 102 | } 103 | return sum / length; 104 | } 105 | 106 | /** 107 | * Compute the variance of input data 108 | * 109 | * @param data input data 110 | * @return variance 111 | */ 112 | public static double computeVariance(final double[] data) { 113 | double variance = 0.0; 114 | double mean = computeMean(data); 115 | for (int i = 0; i < data.length; i++) { 116 | final double diff = data[i] - mean; 117 | variance += diff * diff; 118 | } 119 | return variance / (double) (data.length - 1); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Timeseries-Forecast 2 | 3 | This is a Java open source library which provides a time series forecasting functionality. It is an implementation of the [Hannan-Rissanen algorithm](http://www.jstor.org/stable/2241884?seq=1#page_scan_tab_contents "Paper") for additive [ARIMA](https://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average "Wiki") models. This library is published by the Workday's Syman team, and is used to support basic timeseries forecasting functionalities in some of the Workday products. 4 | 5 | [![Build Status](https://travis-ci.org/Workday/timeseries-forecast.svg?branch=master)](https://travis-ci.org/Workday/timeseries-forecast) 6 | 7 | How to Use 8 | --- 9 | 10 | In order to use this library, you need to provide input timeseries data as well as ARIMA parameters. The ARIMA parameters consist of a) non-seasonal parameters `p,d,q` and b) seasonal parameters `P,D,Q,m`. If `D` or `m` is less than 1, then the model is understood to be non-seasonal and the seasonal parameters `P,D,Q,m` will have no effect. 11 | 12 | ```java 13 | import com.workday.insights.timeseries.arima.Arima; 14 | import com.workday.insights.timeseries.arima.struct.ForecastResult; 15 | 16 | // Prepare input timeseries data. 17 | double[] dataArray = new double[] {2, 1, 2, 5, 2, 1, 2, 5, 2, 1, 2, 5, 2, 1, 2, 5}; 18 | 19 | // Set ARIMA model parameters. 20 | int p = 3; 21 | int d = 0; 22 | int q = 3; 23 | int P = 1; 24 | int D = 1; 25 | int Q = 0; 26 | int m = 0; 27 | int forecastSize = 1; 28 | 29 | // Obtain forecast result. The structure contains forecasted values and performance metric etc. 30 | ForecastResult forecastResult = Arima.forecast_arima(dataArray, forecastSize, p, d, q, P, D, Q, m); 31 | 32 | // Read forecast values 33 | double[] forecastData = forecastResult.getForecast(); // in this example, it will return { 2 } 34 | 35 | // You can obtain upper- and lower-bounds of confidence intervals on forecast values. 36 | // By default, it computes at 95%-confidence level. This value can be adjusted in ForecastUtil.java 37 | double[] uppers = forecastResult.getForecastUpperConf(); 38 | double[] lowers = forecastResult.getForecastLowerConf(); 39 | 40 | // You can also obtain the root mean-square error as validation metric. 41 | double rmse = forecastResult.getRMSE(); 42 | 43 | // It also provides the maximum normalized variance of the forecast values and their confidence interval. 44 | double maxNormalizedVariance = forecastResult.getMaxNormalizedVariance(); 45 | 46 | // Finally you can read log messages. 47 | String log = forecastResult.getLog(); 48 | ``` 49 | 50 | How to Build 51 | --- 52 | This library uses Maven as its build tool. 53 | 54 | ```java 55 | // Compile the source code of the project. 56 | mvn compile 57 | 58 | // To generate javadocs 59 | mvn javadoc:javadoc 60 | 61 | // To generate a site for the current project 62 | mvn site 63 | 64 | // Take the compiled code and package it 65 | mvn package 66 | 67 | // Install the package into the local repository, which can be used as a dependency in other projects locally. 68 | mvn install 69 | ``` 70 | 71 | Dependencies 72 | --- 73 | 74 | The library has the following dependencies: 75 | ``` 76 | JUnit 4.12 77 | ``` 78 | 79 | Authors 80 | --- 81 | 82 | Here is the [Contributors List](CONTRIBUTORS.md) for the timeseries-forecast library. 83 | Please note that the project was developed and ported from an internal repository. Therefore, the commit record does not reflect the full history of the project. 84 | 85 | License 86 | --- 87 | 88 | Copyright 2017 Workday, Inc. 89 | 90 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 91 | 92 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 93 | 94 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 95 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/struct/ForecastResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima.struct; 9 | 10 | import com.workday.insights.timeseries.arima.ArimaSolver; 11 | 12 | /** 13 | * ARIMA Forecast Result 14 | */ 15 | public class ForecastResult { 16 | 17 | private final double[] forecast; 18 | private final double[] forecastUpperConf; 19 | private final double[] forecastLowerConf; 20 | private final double dataVariance; 21 | private final StringBuilder log; 22 | private double modelRMSE; 23 | private double maxNormalizedVariance; 24 | 25 | /** 26 | * Constructor for ForecastResult 27 | * 28 | * @param pForecast forecast data 29 | * @param pDataVariance data variance of the original data 30 | */ 31 | public ForecastResult(final double[] pForecast, final double pDataVariance) { 32 | 33 | this.forecast = pForecast; 34 | 35 | this.forecastUpperConf = new double[pForecast.length]; 36 | System.arraycopy(pForecast, 0, forecastUpperConf, 0, pForecast.length); 37 | 38 | this.forecastLowerConf = new double[pForecast.length]; 39 | System.arraycopy(pForecast, 0, forecastLowerConf, 0, pForecast.length); 40 | 41 | this.dataVariance = pDataVariance; 42 | 43 | this.modelRMSE = -1; 44 | this.maxNormalizedVariance = -1; 45 | 46 | this.log = new StringBuilder(); 47 | } 48 | 49 | /** 50 | * Compute normalized variance 51 | * 52 | * @param v variance 53 | * @return Normalized variance 54 | */ 55 | private double getNormalizedVariance(final double v) { 56 | if (v < -0.5 || dataVariance < -0.5) { 57 | return -1; 58 | } else if (dataVariance < 0.0000001) { 59 | return v; 60 | } else { 61 | return Math.abs(v / dataVariance); 62 | } 63 | } 64 | 65 | /** 66 | * Getter for Root Mean-Squared Error 67 | * 68 | * @return Root Mean-Squared Error 69 | */ 70 | public double getRMSE() { 71 | return this.modelRMSE; 72 | } 73 | 74 | /** 75 | * Setter for Root Mean-Squared Error 76 | * 77 | * @param rmse Root Mean-Squared Error 78 | */ 79 | void setRMSE(double rmse) { 80 | this.modelRMSE = rmse; 81 | } 82 | 83 | /** 84 | * Getter for Max Normalized Variance 85 | * 86 | * @return Max Normalized Variance 87 | */ 88 | public double getMaxNormalizedVariance() { 89 | return maxNormalizedVariance; 90 | } 91 | 92 | /** 93 | * Compute and set confidence intervals 94 | * 95 | * @param constant confidence interval constant 96 | * @param cumulativeSumOfMA cumulative sum of MA coefficients 97 | * @return Max Normalized Variance 98 | */ 99 | public double setConfInterval(final double constant, final double[] cumulativeSumOfMA) { 100 | double maxNormalizedVariance = -1.0; 101 | double bound = 0; 102 | for (int i = 0; i < forecast.length; i++) { 103 | bound = constant * modelRMSE * cumulativeSumOfMA[i]; 104 | this.forecastUpperConf[i] = this.forecast[i] + bound; 105 | this.forecastLowerConf[i] = this.forecast[i] - bound; 106 | final double normalizedVariance = getNormalizedVariance(Math.pow(bound, 2)); 107 | if (normalizedVariance > maxNormalizedVariance) { 108 | maxNormalizedVariance = normalizedVariance; 109 | } 110 | } 111 | return maxNormalizedVariance; 112 | } 113 | 114 | /** 115 | * Compute and set Sigma2 and prediction confidence interval. 116 | * 117 | * @param params ARIMA parameters from the model 118 | */ 119 | public void setSigma2AndPredicationInterval(ArimaParams params) { 120 | maxNormalizedVariance = ArimaSolver 121 | .setSigma2AndPredicationInterval(params, this, forecast.length); 122 | } 123 | 124 | /** 125 | * Getter for forecast data 126 | * 127 | * @return forecast data 128 | */ 129 | public double[] getForecast() { 130 | return forecast; 131 | } 132 | 133 | /** 134 | * Getter for upper confidence bounds 135 | * 136 | * @return array of upper confidence bounds 137 | */ 138 | public double[] getForecastUpperConf() { 139 | return forecastUpperConf; 140 | } 141 | 142 | /** 143 | * Getter for lower confidence bounds 144 | * 145 | * @return array of lower confidence bounds 146 | */ 147 | public double[] getForecastLowerConf() { 148 | return forecastLowerConf; 149 | } 150 | 151 | /** 152 | * Append message to log of forecast result 153 | * 154 | * @param message string message 155 | */ 156 | public void log(String message) { 157 | this.log.append(message + "\n"); 158 | } 159 | 160 | /** 161 | * Getter for log of the forecast result 162 | * 163 | * @return full log of the forecast result 164 | */ 165 | public String getLog() { 166 | return log.toString(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 4.0.0 11 | 12 | 13 | com.workday 14 | timeseries-forecast 15 | 1.1.1 16 | 17 | 18 | junit 19 | junit 20 | 4.10 21 | 22 | 23 | 24 | UTF-8 25 | 1.6 26 | 1.6 27 | 28 | 29 | 30 | 31 | 32 | org.apache.maven.plugins 33 | maven-release-plugin 34 | 2.5.3 35 | 36 | true 37 | false 38 | release 39 | deploy 40 | 41 | 42 | 43 | org.sonatype.plugins 44 | nexus-staging-maven-plugin 45 | 1.6.7 46 | true 47 | 48 | ossrh 49 | https://oss.sonatype.org/ 50 | true 51 | 52 | 53 | 54 | 55 | 56 | timeseries-forecast 57 | This is a Java open source library which provides a time series forecasting functionality. It is an implementation of the Hannan-Rissanen algorithm for additive ARIMA models. 58 | https://github.com/Workday/timeseries-forecast 59 | 60 | 61 | MIT License 62 | 63 | 64 | 65 | git@github.com:Workday/timeseries-forecast.git 66 | scm:git:git@github.com:Workday/timeseries-forecast.git 67 | scm:git:git@github.com:Workday/timeseries-forecast.git 68 | HEAD 69 | 70 | 71 | 72 | ace.hao 73 | Ace Hao 74 | ace.hao@workday.com 75 | 76 | 77 | 78 | 79 | 80 | ossrh 81 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 82 | 83 | 84 | ossrh 85 | https://oss.sonatype.org/content/repositories/snapshots 86 | 87 | 88 | 89 | 90 | 91 | sign 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-gpg-plugin 97 | 1.6 98 | 99 | 100 | sign-artifacts 101 | verify 102 | 103 | sign 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | build-extras 113 | 114 | true 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-source-plugin 121 | 2.4 122 | 123 | 124 | attach-sources 125 | 126 | jar-no-fork 127 | 128 | 129 | 130 | 131 | 132 | org.apache.maven.plugins 133 | maven-javadoc-plugin 134 | 2.10.3 135 | 136 | 137 | attach-javadocs 138 | 139 | jar 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/struct/BackShift.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima.struct; 9 | 10 | /** 11 | * Helper class that implements polynomial of back-shift operator 12 | */ 13 | public final class BackShift { 14 | 15 | private final int _degree; // maximum lag, e.g. AR(1) degree will be 1 16 | private final boolean[] _indices; 17 | private int[] _offsets = null; 18 | private double[] _coeffs = null; 19 | 20 | //Constructor 21 | public BackShift(int degree, boolean initial) { 22 | if (degree < 0) { 23 | throw new RuntimeException("degree must be non-negative"); 24 | } 25 | this._degree = degree; 26 | this._indices = new boolean[_degree + 1]; 27 | for (int j = 0; j <= _degree; ++j) { 28 | this._indices[j] = initial; 29 | } 30 | this._indices[0] = true; // zero index must be true all the time 31 | } 32 | 33 | public BackShift(boolean[] indices, boolean copyIndices) { 34 | if (indices == null) { 35 | throw new RuntimeException("null indices given"); 36 | } 37 | this._degree = indices.length - 1; 38 | if (copyIndices) { 39 | this._indices = new boolean[_degree + 1]; 40 | System.arraycopy(indices, 0, _indices, 0, _degree + 1); 41 | } else { 42 | this._indices = indices; 43 | } 44 | } 45 | 46 | public int getDegree() { 47 | return _degree; 48 | } 49 | 50 | public double[] getCoefficientsFlattened() { 51 | if (_degree <= 0 || _offsets == null || _coeffs == null) { 52 | return new double[0]; 53 | } 54 | int temp = -1; 55 | for (int offset : _offsets) { 56 | if (offset > temp) { 57 | temp = offset; 58 | } 59 | } 60 | final int maxIdx = 1 + temp; 61 | final double[] flattened = new double[maxIdx]; 62 | for (int j = 0; j < maxIdx; ++j) { 63 | flattened[j] = 0; 64 | } 65 | for (int j = 0; j < _offsets.length; ++j) { 66 | flattened[_offsets[j]] = _coeffs[j]; 67 | } 68 | return flattened; 69 | } 70 | 71 | public void setIndex(int index, boolean enable) { 72 | _indices[index] = enable; 73 | } 74 | 75 | public BackShift apply(BackShift another) { 76 | int mergedDegree = _degree + another._degree; 77 | boolean[] merged = new boolean[mergedDegree + 1]; 78 | for (int j = 0; j <= mergedDegree; ++j) { 79 | merged[j] = false; 80 | } 81 | for (int j = 0; j <= _degree; ++j) { 82 | if (_indices[j]) { 83 | for (int k = 0; k <= another._degree; ++k) { 84 | merged[j + k] = merged[j + k] || another._indices[k]; 85 | } 86 | } 87 | } 88 | return new BackShift(merged, false); 89 | } 90 | 91 | public void initializeParams(boolean includeZero) { 92 | _indices[0] = includeZero; 93 | _offsets = null; 94 | _coeffs = null; 95 | int nonzeroCount = 0; 96 | for (int j = 0; j <= _degree; ++j) { 97 | if (_indices[j]) { 98 | ++nonzeroCount; 99 | } 100 | } 101 | _offsets = new int[nonzeroCount]; // cannot be 0 as 0-th index is always true 102 | _coeffs = new double[nonzeroCount]; 103 | int coeffIndex = 0; 104 | for (int j = 0; j <= _degree; ++j) { 105 | if (_indices[j]) { 106 | _offsets[coeffIndex] = j; 107 | _coeffs[coeffIndex] = 0; 108 | ++coeffIndex; 109 | } 110 | } 111 | } 112 | 113 | // MAKE SURE to initializeParams before calling below methods 114 | public int numParams() { 115 | return _offsets.length; 116 | } 117 | 118 | public int[] paramOffsets() { 119 | return _offsets; 120 | } 121 | 122 | public double getParam(final int paramIndex) { 123 | int offsetIndex = -1; 124 | for (int j = 0; j < _offsets.length; ++j) { 125 | if (_offsets[j] == paramIndex) { 126 | return _coeffs[j]; 127 | } 128 | } 129 | throw new RuntimeException("invalid parameter index: " + paramIndex); 130 | } 131 | 132 | public double[] getAllParam() { 133 | return this._coeffs; 134 | } 135 | 136 | public void setParam(final int paramIndex, final double paramValue) { 137 | int offsetIndex = -1; 138 | for (int j = 0; j < _offsets.length; ++j) { 139 | if (_offsets[j] == paramIndex) { 140 | offsetIndex = j; 141 | break; 142 | } 143 | } 144 | if (offsetIndex == -1) { 145 | throw new RuntimeException("invalid parameter index: " + paramIndex); 146 | } 147 | _coeffs[offsetIndex] = paramValue; 148 | } 149 | 150 | public void copyParamsToArray(double[] dest) { 151 | System.arraycopy(_coeffs, 0, dest, 0, _coeffs.length); 152 | } 153 | 154 | public double getLinearCombinationFrom(double[] timeseries, int tsOffset) { 155 | double linearSum = 0; 156 | for (int j = 0; j < _offsets.length; ++j) { 157 | linearSum += timeseries[tsOffset - _offsets[j]] * _coeffs[j]; 158 | } 159 | return linearSum; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/matrix/InsightsVector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.matrix; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * InsightsVector 14 | * 15 | *

Vector of double entries

16 | */ 17 | public class InsightsVector implements Serializable { 18 | 19 | private static final long serialVersionUID = 43L; 20 | 21 | protected int _m = -1; 22 | protected double[] _data = null; 23 | protected boolean _valid = false; 24 | 25 | //===================================================================== 26 | // Constructors 27 | //===================================================================== 28 | 29 | /** 30 | * Constructor for InsightVector 31 | * 32 | * @param m size of the vector 33 | * @param value initial value for all entries 34 | */ 35 | public InsightsVector(int m, double value) { 36 | if (m <= 0) { 37 | throw new RuntimeException("[InsightsVector] invalid size"); 38 | } else { 39 | _data = new double[m]; 40 | for (int j = 0; j < m; ++j) { 41 | _data[j] = value; 42 | } 43 | _m = m; 44 | _valid = true; 45 | } 46 | } 47 | 48 | /** 49 | * Constructor for InsightVector 50 | * 51 | * @param data 1-dimensional double array with pre-populated values 52 | * @param deepCopy if TRUE, allocated new memory space and copy data over 53 | * if FALSE, re-use the given memory space and overwrites on it 54 | */ 55 | public InsightsVector(double[] data, boolean deepCopy) { 56 | if (data == null || data.length == 0) { 57 | throw new RuntimeException("[InsightsVector] invalid data"); 58 | } else { 59 | _m = data.length; 60 | if (deepCopy) { 61 | _data = new double[_m]; 62 | System.arraycopy(data, 0, _data, 0, _m); 63 | } else { 64 | _data = data; 65 | } 66 | _valid = true; 67 | } 68 | } 69 | //===================================================================== 70 | // END of Constructors 71 | //===================================================================== 72 | //===================================================================== 73 | // Helper Methods 74 | //===================================================================== 75 | 76 | /** 77 | * Create and allocate memory for a new copy of double array of current elements in the vector 78 | * 79 | * @return the new copy 80 | */ 81 | public double[] deepCopy() { 82 | double[] dataDeepCopy = new double[_m]; 83 | System.arraycopy(_data, 0, dataDeepCopy, 0, _m); 84 | return dataDeepCopy; 85 | } 86 | //===================================================================== 87 | // END of Helper Methods 88 | //===================================================================== 89 | //===================================================================== 90 | // Getters & Setters 91 | //===================================================================== 92 | 93 | /** 94 | * Getter for the i-th element in the vector 95 | * 96 | * @param i element index 97 | * @return the i-th element 98 | */ 99 | public double get(int i) { 100 | if (!_valid) { 101 | throw new RuntimeException("[InsightsVector] invalid Vector"); 102 | } else if (i >= _m) { 103 | throw new IndexOutOfBoundsException( 104 | String.format("[InsightsVector] Index: %d, Size: %d", i, _m)); 105 | } 106 | return _data[i]; 107 | } 108 | 109 | /** 110 | * Getter for the size of the vector 111 | * 112 | * @return size of the vector 113 | */ 114 | public int size() { 115 | if (!_valid) { 116 | throw new RuntimeException("[InsightsVector] invalid Vector"); 117 | } 118 | 119 | return _m; 120 | } 121 | 122 | /** 123 | * Setter to modify a element in the vector 124 | * 125 | * @param i element index 126 | * @param val new value 127 | */ 128 | public void set(int i, double val) { 129 | if (!_valid) { 130 | throw new RuntimeException("[InsightsVector] invalid Vector"); 131 | } else if (i >= _m) { 132 | throw new IndexOutOfBoundsException( 133 | String.format("[InsightsVector] Index: %d, Size: %d", i, _m)); 134 | } 135 | _data[i] = val; 136 | } 137 | 138 | //===================================================================== 139 | // END of Getters & Setters 140 | //===================================================================== 141 | //===================================================================== 142 | // Basic Linear Algebra operations 143 | //===================================================================== 144 | 145 | /** 146 | * Perform dot product operation with another vector of the same size 147 | * 148 | * @param vector vector of the same size 149 | * @return dot product of the two vector 150 | */ 151 | public double dot(InsightsVector vector) { 152 | if (!_valid || !vector._valid) { 153 | throw new RuntimeException("[InsightsVector] invalid Vector"); 154 | } else if (_m != vector.size()) { 155 | throw new RuntimeException("[InsightsVector][dot] invalid vector size."); 156 | } 157 | 158 | double sumOfProducts = 0; 159 | for (int i = 0; i < _m; i++) { 160 | sumOfProducts += _data[i] * vector.get(i); 161 | } 162 | return sumOfProducts; 163 | } 164 | //===================================================================== 165 | // END of Basic Linear Algebra operations 166 | //===================================================================== 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/HannanRissanen.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import com.workday.insights.matrix.InsightsMatrix; 11 | import com.workday.insights.matrix.InsightsVector; 12 | import com.workday.insights.timeseries.arima.struct.ArimaParams; 13 | import com.workday.insights.timeseries.arima.struct.BackShift; 14 | import com.workday.insights.timeseries.timeseriesutil.ForecastUtil; 15 | 16 | /** 17 | * Hannan-Rissanen algorithm for estimating ARMA parameters 18 | */ 19 | public final class HannanRissanen { 20 | 21 | private HannanRissanen() { 22 | } 23 | 24 | /** 25 | * Estimate ARMA(p,q) parameters, i.e. AR-parameters: \phi_1, ... , \phi_p 26 | * MA-parameters: \theta_1, ... , \theta_q 27 | * Input data is assumed to be stationary, has zero-mean, aligned, and imputed 28 | * 29 | * @param data_orig original data 30 | * @param params ARIMA parameters 31 | * @param forecast_length forecast length 32 | * @param maxIteration maximum number of iteration 33 | */ 34 | public static void estimateARMA(final double[] data_orig, final ArimaParams params, 35 | final int forecast_length, final int maxIteration) { 36 | final double[] data = new double[data_orig.length]; 37 | final int total_length = data.length; 38 | System.arraycopy(data_orig, 0, data, 0, total_length); 39 | final int r = (params.getDegreeP() > params.getDegreeQ()) ? 40 | 1 + params.getDegreeP() : 1 + params.getDegreeQ(); 41 | final int length = total_length - forecast_length; 42 | final int size = length - r; 43 | if (length < 2 * r) { 44 | throw new RuntimeException("not enough data points: length=" + length + ", r=" + r); 45 | } 46 | 47 | // step 1: apply Yule-Walker method and estimate AR(r) model on input data 48 | final double[] errors = new double[length]; 49 | final double[] yuleWalkerParams = applyYuleWalkerAndGetInitialErrors(data, r, length, 50 | errors); 51 | for (int j = 0; j < r; ++j) { 52 | errors[j] = 0; 53 | } 54 | 55 | // step 2: iterate Least-Square fitting until the parameters converge 56 | // instantiate Z-matrix 57 | final double[][] matrix = new double[params.getNumParamsP() + params.getNumParamsQ()][size]; 58 | 59 | double bestRMSE = -1; // initial value 60 | int remainIteration = maxIteration; 61 | InsightsVector bestParams = null; 62 | while (--remainIteration >= 0) { 63 | final InsightsVector estimatedParams = iterationStep(params, data, errors, matrix, r, 64 | length, 65 | size); 66 | final InsightsVector originalParams = params.getParamsIntoVector(); 67 | params.setParamsFromVector(estimatedParams); 68 | 69 | // forecast for validation data and compute RMSE 70 | final double[] forecasts = ArimaSolver.forecastARMA(params, data, length, data.length); 71 | final double anotherRMSE = ArimaSolver 72 | .computeRMSE(data, forecasts, length, 0, forecast_length); 73 | // update errors 74 | final double[] train_forecasts = ArimaSolver.forecastARMA(params, data, r, data.length); 75 | for (int j = 0; j < size; ++j) { 76 | errors[j + r] = data[j + r] - train_forecasts[j]; 77 | } 78 | if (bestRMSE < 0 || anotherRMSE < bestRMSE) { 79 | bestParams = estimatedParams; 80 | bestRMSE = anotherRMSE; 81 | } 82 | } 83 | params.setParamsFromVector(bestParams); 84 | } 85 | 86 | private static double[] applyYuleWalkerAndGetInitialErrors(final double[] data, final int r, 87 | final int length, final double[] errors) { 88 | final double[] yuleWalker = YuleWalker.fit(data, r); 89 | final BackShift bsYuleWalker = new BackShift(r, true); 90 | bsYuleWalker.initializeParams(false); 91 | // return array from YuleWalker is an array of size r whose 92 | // 0-th index element is lag 1 coefficient etc 93 | // hence shifting lag index by one and copy over to BackShift operator 94 | for (int j = 0; j < r; ++j) { 95 | bsYuleWalker.setParam(j + 1, yuleWalker[j]); 96 | } 97 | int m = 0; 98 | // populate error array 99 | while (m < r) { 100 | errors[m++] = 0; 101 | } // initial r-elements are set to zero 102 | while (m < length) { 103 | // from then on, initial estimate of error terms are 104 | // Z_t = X_t - \phi_1 X_{t-1} - \cdots - \phi_r X_{t-r} 105 | errors[m] = data[m] - bsYuleWalker.getLinearCombinationFrom(data, m); 106 | ++m; 107 | } 108 | return yuleWalker; 109 | } 110 | 111 | private static InsightsVector iterationStep( 112 | final ArimaParams params, 113 | final double[] data, final double[] errors, 114 | final double[][] matrix, final int r, final int length, final int size) { 115 | 116 | int rowIdx = 0; 117 | // copy over shifted timeseries data into matrix 118 | final int[] offsetsAR = params.getOffsetsAR(); 119 | for (int pIdx : offsetsAR) { 120 | System.arraycopy(data, r - pIdx, matrix[rowIdx], 0, size); 121 | ++rowIdx; 122 | } 123 | // copy over shifted errors into matrix 124 | final int[] offsetsMA = params.getOffsetsMA(); 125 | for (int qIdx : offsetsMA) { 126 | System.arraycopy(errors, r - qIdx, matrix[rowIdx], 0, size); 127 | ++rowIdx; 128 | } 129 | 130 | // instantiate matrix to perform least squares algorithm 131 | final InsightsMatrix zt = new InsightsMatrix(matrix, false); 132 | 133 | // instantiate target vector 134 | final double[] vector = new double[size]; 135 | System.arraycopy(data, r, vector, 0, size); 136 | final InsightsVector x = new InsightsVector(vector, false); 137 | 138 | // obtain least squares solution 139 | final InsightsVector ztx = zt.timesVector(x); 140 | final InsightsMatrix ztz = zt.computeAAT(); 141 | final InsightsVector estimatedVector = ztz 142 | .solveSPDIntoVector(ztx, ForecastUtil.maxConditionNumber); 143 | 144 | return estimatedVector; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/struct/ArimaParams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima.struct; 9 | 10 | import com.workday.insights.matrix.InsightsVector; 11 | import com.workday.insights.timeseries.timeseriesutil.Integrator; 12 | 13 | /** 14 | * Simple wrapper for ARIMA parameters and fitted states 15 | */ 16 | public final class ArimaParams { 17 | 18 | public final int p; 19 | public final int d; 20 | public final int q; 21 | public final int P; 22 | public final int D; 23 | public final int Q; 24 | public final int m; 25 | // ARMA part 26 | private final BackShift _opAR; 27 | private final BackShift _opMA; 28 | private final int _dp; 29 | private final int _dq; 30 | private final int _np; 31 | private final int _nq; 32 | private final double[][] _init_seasonal; 33 | private final double[][] _diff_seasonal; 34 | private final double[][] _integrate_seasonal; 35 | private final double[][] _init_non_seasonal; 36 | private final double[][] _diff_non_seasonal; 37 | private final double[][] _integrate_non_seasonal; 38 | private int[] lagsAR = null; 39 | private double[] paramsAR = null; 40 | private int[] lagsMA = null; 41 | private double[] paramsMA = null; 42 | // I part 43 | private double _mean = 0.0; 44 | 45 | /** 46 | * Constructor for ArimaParams 47 | * 48 | * @param p ARIMA parameter, the order (number of time lags) of the autoregressive model 49 | * @param d ARIMA parameter, the degree of differencing 50 | * @param q ARIMA parameter, the order of the moving-average model 51 | * @param P ARIMA parameter, autoregressive term for the seasonal part 52 | * @param D ARIMA parameter, differencing term for the seasonal part 53 | * @param Q ARIMA parameter, moving average term for the seasonal part 54 | * @param m ARIMA parameter, the number of periods in each season 55 | */ 56 | public ArimaParams( 57 | int p, int d, int q, 58 | int P, int D, int Q, 59 | int m) { 60 | this.p = p; 61 | this.d = d; 62 | this.q = q; 63 | this.P = P; 64 | this.D = D; 65 | this.Q = Q; 66 | this.m = m; 67 | 68 | // dependent states 69 | this._opAR = getNewOperatorAR(); 70 | this._opMA = getNewOperatorMA(); 71 | _opAR.initializeParams(false); 72 | _opMA.initializeParams(false); 73 | this._dp = _opAR.getDegree(); 74 | this._dq = _opMA.getDegree(); 75 | this._np = _opAR.numParams(); 76 | this._nq = _opMA.numParams(); 77 | this._init_seasonal = (D > 0 && m > 0) ? new double[D][m] : null; 78 | this._init_non_seasonal = (d > 0) ? new double[d][1] : null; 79 | this._diff_seasonal = (D > 0 && m > 0) ? new double[D][] : null; 80 | this._diff_non_seasonal = (d > 0) ? new double[d][] : null; 81 | this._integrate_seasonal = (D > 0 && m > 0) ? new double[D][] : null; 82 | this._integrate_non_seasonal = (d > 0) ? new double[d][] : null; 83 | } 84 | 85 | /** 86 | * ARMA forecast of one data point. 87 | * 88 | * @param data input data 89 | * @param errors array of errors 90 | * @param index index 91 | * @return one data point 92 | */ 93 | public double forecastOnePointARMA(final double[] data, final double[] errors, 94 | final int index) { 95 | final double estimateAR = _opAR.getLinearCombinationFrom(data, index); 96 | final double estimateMA = _opMA.getLinearCombinationFrom(errors, index); 97 | final double forecastValue = estimateAR + estimateMA; 98 | return forecastValue; 99 | } 100 | 101 | /** 102 | * Getter for the degree of parameter p 103 | * 104 | * @return degree of p 105 | */ 106 | public int getDegreeP() { 107 | return _dp; 108 | } 109 | 110 | /** 111 | * Getter for the degree of parameter q 112 | * 113 | * @return degree of q 114 | */ 115 | public int getDegreeQ() { 116 | return _dq; 117 | } 118 | 119 | /** 120 | * Getter for the number of parameters p 121 | * @return number of parameters p 122 | */ 123 | public int getNumParamsP() { 124 | return _np; 125 | } 126 | 127 | /** 128 | * Getter for the number of parameters q 129 | * 130 | * @return number of parameters q 131 | */ 132 | public int getNumParamsQ() { 133 | return _nq; 134 | } 135 | 136 | /** 137 | * Getter for the parameter offsets of AR 138 | * 139 | * @return parameter offsets of AR 140 | */ 141 | public int[] getOffsetsAR() { 142 | return _opAR.paramOffsets(); 143 | } 144 | 145 | /** 146 | * Getter for the parameter offsets of MA 147 | * 148 | * @return parameter offsets of MA 149 | */ 150 | public int[] getOffsetsMA() { 151 | return _opMA.paramOffsets(); 152 | } 153 | 154 | /** 155 | * Getter for the last integrated seasonal data 156 | * 157 | * @return integrated seasonal data 158 | */ 159 | public double[] getLastIntegrateSeasonal() { 160 | return _integrate_seasonal[D - 1]; 161 | } 162 | 163 | /** 164 | * Getter for the last integrated NON-seasonal data 165 | * 166 | * @return NON-integrated NON-seasonal data 167 | */ 168 | public double[] getLastIntegrateNonSeasonal() { 169 | return _integrate_non_seasonal[d - 1]; 170 | } 171 | 172 | /** 173 | * Getter for the last differentiated seasonal data 174 | * 175 | * @return differentiate seasonal data 176 | */ 177 | public double[] getLastDifferenceSeasonal() { 178 | return _diff_seasonal[D - 1]; 179 | } 180 | 181 | /** 182 | * Getter for the last differentiated NON-seasonal data 183 | * 184 | * @return differentiated NON-seasonal data 185 | */ 186 | public double[] getLastDifferenceNonSeasonal() { 187 | return _diff_non_seasonal[d - 1]; 188 | } 189 | 190 | /** 191 | * Summary of the parameters 192 | * 193 | * @return String of summary 194 | */ 195 | public String summary() { 196 | return "ModelInterface ParamsInterface:" + 197 | ", p= " + p + 198 | ", d= " + d + 199 | ", q= " + q + 200 | ", P= " + P + 201 | ", D= " + D + 202 | ", Q= " + Q + 203 | ", m= " + m; 204 | } 205 | 206 | //========================================================== 207 | // MUTABLE STATES 208 | 209 | /** 210 | * Setting parameters from a Insight Vector 211 | * 212 | * It is assumed that the input vector has _np + _nq entries first _np entries are AR-parameters 213 | * and the last _nq entries are MA-parameters 214 | * 215 | * @param paramVec a vector of parameters 216 | */ 217 | public void setParamsFromVector(final InsightsVector paramVec) { 218 | int index = 0; 219 | final int[] offsetsAR = getOffsetsAR(); 220 | final int[] offsetsMA = getOffsetsMA(); 221 | for (int pIdx : offsetsAR) { 222 | _opAR.setParam(pIdx, paramVec.get(index++)); 223 | } 224 | for (int qIdx : offsetsMA) { 225 | _opMA.setParam(qIdx, paramVec.get(index++)); 226 | } 227 | } 228 | 229 | /** 230 | * Create a Insight Vector that contains the parameters. 231 | * 232 | * It is assumed that the input vector has _np + _nq entries first _np entries are AR-parameters 233 | * and the last _nq entries are MA-parameters 234 | * 235 | * @return Insight Vector of parameters 236 | */ 237 | public InsightsVector getParamsIntoVector() { 238 | int index = 0; 239 | final InsightsVector paramVec = new InsightsVector(_np + _nq, 0.0); 240 | final int[] offsetsAR = getOffsetsAR(); 241 | final int[] offsetsMA = getOffsetsMA(); 242 | for (int pIdx : offsetsAR) { 243 | paramVec.set(index++, _opAR.getParam(pIdx)); 244 | } 245 | for (int qIdx : offsetsMA) { 246 | paramVec.set(index++, _opMA.getParam(qIdx)); 247 | } 248 | return paramVec; 249 | } 250 | 251 | public BackShift getNewOperatorAR() { 252 | return mergeSeasonalWithNonSeasonal(p, P, m); 253 | } 254 | 255 | public BackShift getNewOperatorMA() { 256 | return mergeSeasonalWithNonSeasonal(q, Q, m); 257 | } 258 | 259 | public double[] getCurrentARCoefficients() { 260 | return _opAR.getCoefficientsFlattened(); 261 | } 262 | 263 | public double[] getCurrentMACoefficients() { 264 | return _opMA.getCoefficientsFlattened(); 265 | } 266 | 267 | private BackShift mergeSeasonalWithNonSeasonal(int nonSeasonalLag, int seasonalLag, 268 | int seasonalStep) { 269 | final BackShift nonSeasonal = new BackShift(nonSeasonalLag, true); 270 | final BackShift seasonal = new BackShift(seasonalLag * seasonalStep, false); 271 | for (int s = 1; s <= seasonalLag; ++s) { 272 | seasonal.setIndex(s * seasonalStep, true); 273 | } 274 | final BackShift merged = seasonal.apply(nonSeasonal); 275 | return merged; 276 | } 277 | 278 | //================================ 279 | // Differentiation and Integration 280 | 281 | public void differentiateSeasonal(final double[] data) { 282 | double[] current = data; 283 | for (int j = 0; j < D; ++j) { 284 | final double[] next = new double[current.length - m]; 285 | _diff_seasonal[j] = next; 286 | final double[] init = _init_seasonal[j]; 287 | Integrator.differentiate(current, next, init, m); 288 | current = next; 289 | } 290 | } 291 | 292 | public void differentiateNonSeasonal(final double[] data) { 293 | double[] current = data; 294 | for (int j = 0; j < d; ++j) { 295 | final double[] next = new double[current.length - 1]; 296 | _diff_non_seasonal[j] = next; 297 | final double[] init = _init_non_seasonal[j]; 298 | Integrator.differentiate(current, next, init, 1); 299 | current = next; 300 | } 301 | } 302 | 303 | public void integrateSeasonal(final double[] data) { 304 | double[] current = data; 305 | for (int j = 0; j < D; ++j) { 306 | final double[] next = new double[current.length + m]; 307 | _integrate_seasonal[j] = next; 308 | final double[] init = _init_seasonal[j]; 309 | Integrator.integrate(current, next, init, m); 310 | current = next; 311 | } 312 | } 313 | 314 | public void integrateNonSeasonal(final double[] data) { 315 | double[] current = data; 316 | for (int j = 0; j < d; ++j) { 317 | final double[] next = new double[current.length + 1]; 318 | _integrate_non_seasonal[j] = next; 319 | final double[] init = _init_non_seasonal[j]; 320 | Integrator.integrate(current, next, init, 1); 321 | current = next; 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/matrix/InsightsMatrix.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.matrix; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * InsightsMatrix 14 | * 15 | *

16 | * A small set of linear algebra methods to be used in ARIMA 17 | *

18 | */ 19 | public class InsightsMatrix implements Serializable { 20 | 21 | public static final long serialVersionUID = 42L; 22 | 23 | // Primary 24 | protected int _m = -1; 25 | protected int _n = -1; 26 | protected double[][] _data = null; 27 | protected boolean _valid = false; 28 | 29 | // Secondary 30 | protected boolean _cholZero = false; 31 | protected boolean _cholPos = false; 32 | protected boolean _cholNeg = false; 33 | protected double[] _cholD = null; 34 | protected double[][] _cholL = null; 35 | 36 | //===================================================================== 37 | // Constructors 38 | //===================================================================== 39 | 40 | /** 41 | * Constructor for InsightsMatrix 42 | * 43 | * @param data 2-dimensional double array with pre-populated values 44 | * @param makeDeepCopy if TRUE, allocated new memory space and copy data over 45 | * if FALSE, re-use the given memory space and overwrites on it 46 | */ 47 | public InsightsMatrix(double[][] data, boolean makeDeepCopy) { 48 | if (_valid = isValid2D(data)) { 49 | _m = data.length; 50 | _n = data[0].length; 51 | if (!makeDeepCopy) { 52 | _data = data; 53 | } else { 54 | _data = copy2DArray(data); 55 | } 56 | 57 | } 58 | } 59 | 60 | //===================================================================== 61 | // END of Constructors 62 | //===================================================================== 63 | //===================================================================== 64 | // Helper Methods 65 | //===================================================================== 66 | 67 | /** 68 | * Determine whether a 2-dimensional array is in valid matrix format. 69 | * 70 | * @param matrix 2-dimensional double array 71 | * @return TRUE, matrix is in valid format 72 | * FALSE, matrix is not in valid format 73 | */ 74 | private static boolean isValid2D(double[][] matrix) { 75 | boolean result = true; 76 | if (matrix == null || matrix[0] == null || matrix[0].length == 0) { 77 | throw new RuntimeException("[InsightsMatrix][constructor] null data given"); 78 | } else { 79 | int row = matrix.length; 80 | int col = matrix[0].length; 81 | for (int i = 1; i < row; ++i) { 82 | if (matrix[i] == null || matrix[i].length != col) { 83 | result = false; 84 | } 85 | } 86 | } 87 | 88 | return result; 89 | } 90 | 91 | /** 92 | * Create a copy of 2-dimensional double array by allocating new memory space and copy data over 93 | * 94 | * @param source source 2-dimensional double array 95 | * @return new copy of the source 2-dimensional double array 96 | */ 97 | private static double[][] copy2DArray(double[][] source) { 98 | if (source == null) { 99 | return null; 100 | } else if (source.length == 0) { 101 | return new double[0][]; 102 | } 103 | 104 | int row = source.length; 105 | double[][] target = new double[row][]; 106 | for (int i = 0; i < row; i++) { 107 | if (source[i] == null) { 108 | target[i] = null; 109 | } else { 110 | int rowLength = source[i].length; 111 | target[i] = new double[rowLength]; 112 | System.arraycopy(source[i], 0, target[i], 0, rowLength); 113 | } 114 | } 115 | return target; 116 | } 117 | 118 | //===================================================================== 119 | // END of Helper Methods 120 | //===================================================================== 121 | //===================================================================== 122 | // Getters & Setters 123 | //===================================================================== 124 | 125 | /** 126 | * Getter for number of rows of the matrix 127 | * 128 | * @return number of rows 129 | */ 130 | public int getNumberOfRows() { 131 | return _m; 132 | } 133 | 134 | /** 135 | * Getter for number of columns of the matrix 136 | * 137 | * @return number of columns 138 | */ 139 | public int getNumberOfColumns() { 140 | return _n; 141 | } 142 | 143 | /** 144 | * Getter for a particular element in the matrix 145 | * 146 | * @param i i-th row 147 | * @param j j-th column 148 | * @return the element from the i-th row and j-th column from the matrix 149 | */ 150 | public double get(int i, int j) { 151 | return _data[i][j]; 152 | } 153 | 154 | /** 155 | * Setter to modify a particular element in the matrix 156 | * 157 | * @param i i-th row 158 | * @param j j-th column 159 | * @param val new value 160 | */ 161 | public void set(int i, int j, double val) { 162 | _data[i][j] = val; 163 | } 164 | 165 | //===================================================================== 166 | // END of Getters & Setters 167 | //===================================================================== 168 | //===================================================================== 169 | // Basic Linear Algebra operations 170 | //===================================================================== 171 | 172 | /** 173 | * Multiply a InsightMatrix (n x m) by a InsightVector (m x 1) 174 | * 175 | * @param v a InsightVector 176 | * @return a InsightVector of dimension (n x 1) 177 | */ 178 | public InsightsVector timesVector(InsightsVector v) { 179 | if (!_valid || !v._valid || _n != v._m) { 180 | throw new RuntimeException("[InsightsMatrix][timesVector] size mismatch"); 181 | } 182 | double[] data = new double[_m]; 183 | double dotProduc; 184 | for (int i = 0; i < _m; ++i) { 185 | InsightsVector rowVector = new InsightsVector(_data[i], false); 186 | dotProduc = rowVector.dot(v); 187 | data[i] = dotProduc; 188 | } 189 | return new InsightsVector(data, false); 190 | } 191 | 192 | // More linear algebra operations 193 | 194 | /** 195 | * Compute the Cholesky Decomposition 196 | * 197 | * @param maxConditionNumber maximum condition number 198 | * @return TRUE, if the process succeed 199 | * FALSE, otherwise 200 | */ 201 | private boolean computeCholeskyDecomposition(final double maxConditionNumber) { 202 | _cholD = new double[_m]; 203 | _cholL = new double[_m][_n]; 204 | int i; 205 | int j; 206 | int k; 207 | double val; 208 | double currentMax = -1; 209 | // Backward marching method 210 | for (j = 0; j < _n; ++j) { 211 | val = 0; 212 | for (k = 0; k < j; ++k) { 213 | val += _cholD[k] * _cholL[j][k] * _cholL[j][k]; 214 | } 215 | double diagTemp = _data[j][j] - val; 216 | final int diagSign = (int) (Math.signum(diagTemp)); 217 | switch (diagSign) { 218 | case 0: // singular diagonal value detected 219 | if (maxConditionNumber < -0.5) { // no bound on maximum condition number 220 | _cholZero = true; 221 | _cholL = null; 222 | _cholD = null; 223 | return false; 224 | } else { 225 | _cholPos = true; 226 | } 227 | break; 228 | case 1: 229 | _cholPos = true; 230 | break; 231 | case -1: 232 | _cholNeg = true; 233 | break; 234 | } 235 | if (maxConditionNumber > -0.5) { 236 | if (currentMax <= 0.0) { // this is the first time 237 | if (diagSign == 0) { 238 | diagTemp = 1.0; 239 | } 240 | } else { // there was precedent 241 | if (diagSign == 0) { 242 | diagTemp = Math.abs(currentMax / maxConditionNumber); 243 | } else { 244 | if (Math.abs(diagTemp * maxConditionNumber) < currentMax) { 245 | diagTemp = diagSign * Math.abs(currentMax / maxConditionNumber); 246 | } 247 | } 248 | } 249 | } 250 | _cholD[j] = diagTemp; 251 | if (Math.abs(diagTemp) > currentMax) { 252 | currentMax = Math.abs(diagTemp); 253 | } 254 | _cholL[j][j] = 1; 255 | for (i = j + 1; i < _m; ++i) { 256 | val = 0; 257 | for (k = 0; k < j; ++k) { 258 | val += _cholD[k] * _cholL[j][k] * _cholL[i][k]; 259 | } 260 | val = ((_data[i][j] + _data[j][i]) / 2 - val) / _cholD[j]; 261 | _cholL[j][i] = val; 262 | _cholL[i][j] = val; 263 | } 264 | } 265 | return true; 266 | } 267 | 268 | /** 269 | * Solve SPD(Symmetric positive definite) into vector 270 | * 271 | * @param b vector 272 | * @param maxConditionNumber maximum condition number 273 | * @return solution vector of SPD 274 | */ 275 | public InsightsVector solveSPDIntoVector(InsightsVector b, final double maxConditionNumber) { 276 | if (!_valid || b == null || _n != b._m) { 277 | // invalid linear system 278 | throw new RuntimeException( 279 | "[InsightsMatrix][solveSPDIntoVector] invalid linear system"); 280 | } 281 | if (_cholL == null) { 282 | // computing Cholesky Decomposition 283 | this.computeCholeskyDecomposition(maxConditionNumber); 284 | } 285 | if (_cholZero) { 286 | // singular matrix. returning null 287 | return null; 288 | } 289 | 290 | double[] y = new double[_m]; 291 | double[] bt = new double[_n]; 292 | int i; 293 | int j; 294 | for (i = 0; i < _m; ++i) { 295 | bt[i] = b._data[i]; 296 | } 297 | double val; 298 | for (i = 0; i < _m; ++i) { 299 | val = 0; 300 | for (j = 0; j < i; ++j) { 301 | val += _cholL[i][j] * y[j]; 302 | } 303 | y[i] = bt[i] - val; 304 | } 305 | for (i = _m - 1; i >= 0; --i) { 306 | val = 0; 307 | for (j = i + 1; j < _n; ++j) { 308 | val += _cholL[i][j] * bt[j]; 309 | } 310 | bt[i] = y[i] / _cholD[i] - val; 311 | } 312 | return new InsightsVector(bt, false); 313 | } 314 | 315 | /** 316 | * Computu the product of the matrix (m x n) and its transpose (n x m) 317 | * 318 | * @return matrix of size (m x m) 319 | */ 320 | public InsightsMatrix computeAAT() { 321 | if (!_valid) { 322 | throw new RuntimeException("[InsightsMatrix][computeAAT] invalid matrix"); 323 | } 324 | final double[][] data = new double[_m][_m]; 325 | for (int i = 0; i < _m; ++i) { 326 | final double[] rowI = _data[i]; 327 | for (int j = 0; j < _m; ++j) { 328 | final double[] rowJ = _data[j]; 329 | double temp = 0; 330 | for (int k = 0; k < _n; ++k) { 331 | temp += rowI[k] * rowJ[k]; 332 | } 333 | data[i][j] = temp; 334 | } 335 | } 336 | return new InsightsMatrix(data, false); 337 | } 338 | //===================================================================== 339 | // END of Basic Linear Algebra operations 340 | //===================================================================== 341 | } 342 | -------------------------------------------------------------------------------- /src/test/java/com/workday/insights/timeseries/arima/ArimaTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import com.workday.insights.timeseries.arima.struct.ArimaParams; 11 | import com.workday.insights.timeseries.arima.struct.ForecastResult; 12 | import com.workday.insights.timeseries.timeseriesutil.ForecastUtil; 13 | import java.text.DecimalFormat; 14 | import org.junit.Assert; 15 | import org.junit.Test; 16 | 17 | /** 18 | * ARIMA Tests 19 | */ 20 | public class ArimaTest { 21 | 22 | private final DecimalFormat df = new DecimalFormat("##.#####"); 23 | 24 | private double[] commonTestLogic(final String name, final double[] dataSet, 25 | final double forecastRatio, 26 | int p, int d, int q, int P, int D, int Q, int m) { 27 | 28 | //Compute forecast and training size 29 | final int forecastSize = (int) (dataSet.length * forecastRatio); 30 | final int trainSize = dataSet.length - forecastSize; 31 | //Separate data set into training data and test data 32 | final double[] trainingData = new double[trainSize]; 33 | System.arraycopy(dataSet, 0, trainingData, 0, trainSize); 34 | final double[] trueForecastData = new double[forecastSize]; 35 | System.arraycopy(dataSet, trainSize, trueForecastData, 0, forecastSize); 36 | 37 | return commonTestSimpleForecast(name + " (common test)", trainingData, trueForecastData, 38 | forecastSize, p, d, q, 39 | P, D, Q, m); 40 | } 41 | 42 | private double forecastSinglePointLogic(final String name, final double[] dataSet, 43 | int p, int d, int q, int P, int D, int Q, int m) { 44 | //Compute forecast and training size 45 | final int forecastSize = 1; 46 | final int trainSize = dataSet.length - forecastSize; 47 | //Separate data set into training data and test data 48 | final double[] trainingData = new double[trainSize]; 49 | System.arraycopy(dataSet, 0, trainingData, 0, trainSize); 50 | final double[] trueForecastData = new double[forecastSize]; 51 | System.arraycopy(dataSet, trainSize, trueForecastData, 0, forecastSize); 52 | 53 | return commonTestSimpleForecast(name + " (forecast single point)", trainingData, 54 | trueForecastData, forecastSize, p, d, q, 55 | P, D, Q, m)[0]; 56 | } 57 | 58 | private String dbl2str(final double value) { 59 | String rep = df.format(value); 60 | String padded = String.format("%15s", rep); 61 | return padded; 62 | } 63 | 64 | private double[] commonTestSimpleForecast(final String name, final double[] trainingData, 65 | final double[] trueForecastData, final int forecastSize, 66 | int p, int d, int q, int P, int D, int Q, int m) { 67 | 68 | //Make forecast 69 | 70 | final ForecastResult forecastResult = Arima 71 | .forecast_arima(trainingData, forecastSize, new ArimaParams(p, d, q, P, D, Q, m)); 72 | //Get forecast data and confidence intervals 73 | final double[] forecast = forecastResult.getForecast(); 74 | final double[] upper = forecastResult.getForecastUpperConf(); 75 | final double[] lower = forecastResult.getForecastLowerConf(); 76 | //Building output 77 | final StringBuilder sb = new StringBuilder(); 78 | sb.append(name).append(" ****************************************************\n"); 79 | sb.append("Input Params { ") 80 | .append("p: ").append(p) 81 | .append(", d: ").append(d) 82 | .append(", q: ").append(q) 83 | .append(", P: ").append(P) 84 | .append(", D: ").append(D) 85 | .append(", Q: ").append(Q) 86 | .append(", m: ").append(m) 87 | .append(" }"); 88 | sb.append("\n\nFitted Model RMSE: ").append(dbl2str(forecastResult.getRMSE())); 89 | sb.append("\n\n TRUE DATA | LOWER BOUND FORECAST UPPER BOUND\n"); 90 | 91 | for (int i = 0; i < forecast.length; ++i) { 92 | sb.append(dbl2str(trueForecastData[i])).append(" | ") 93 | .append(dbl2str(lower[i])).append(" ").append(dbl2str(forecast[i])) 94 | .append(" ").append(dbl2str(upper[i])) 95 | .append("\n"); 96 | } 97 | 98 | sb.append("\n"); 99 | 100 | //Compute RMSE against true forecast data 101 | double temp = 0.0; 102 | for (int i = 0; i < forecast.length; ++i) { 103 | temp += Math.pow(forecast[i] - trueForecastData[i], 2); 104 | } 105 | final double rmse = Math.pow(temp / forecast.length, 0.5); 106 | sb.append("RMSE = ").append(dbl2str(rmse)).append("\n\n"); 107 | System.out.println(sb.toString()); 108 | return forecast; 109 | } 110 | 111 | private double commonTestCalculateRMSE(final String name, final double[] trainingData, 112 | final double[] trueForecastData, final int forecastSize, 113 | int p, int d, int q, int P, int D, int Q, int m) { 114 | 115 | //Make forecast 116 | 117 | final ForecastResult forecastResult = Arima 118 | .forecast_arima(trainingData, forecastSize, new ArimaParams(p, d, q, P, D, Q, m)); 119 | //Get forecast data and confidence intervals 120 | final double[] forecast = forecastResult.getForecast(); 121 | final double[] upper = forecastResult.getForecastUpperConf(); 122 | final double[] lower = forecastResult.getForecastLowerConf(); 123 | //Building output 124 | final StringBuilder sb = new StringBuilder(); 125 | sb.append(name).append(" ****************************************************\n"); 126 | sb.append("Input Params { ") 127 | .append("p: ").append(p) 128 | .append(", d: ").append(d) 129 | .append(", q: ").append(q) 130 | .append(", P: ").append(P) 131 | .append(", D: ").append(D) 132 | .append(", Q: ").append(Q) 133 | .append(", m: ").append(m) 134 | .append(" }"); 135 | sb.append("\n\nFitted Model RMSE: ").append(dbl2str(forecastResult.getRMSE())); 136 | sb.append("\n\n TRUE DATA | LOWER BOUND FORECAST UPPER BOUND\n"); 137 | 138 | for (int i = 0; i < forecast.length; ++i) { 139 | sb.append(dbl2str(trueForecastData[i])).append(" | ") 140 | .append(dbl2str(lower[i])).append(" ").append(dbl2str(forecast[i])) 141 | .append(" ").append(dbl2str(upper[i])) 142 | .append("\n"); 143 | } 144 | 145 | sb.append("\n"); 146 | 147 | //Compute RMSE against true forecast data 148 | double temp = 0.0; 149 | for (int i = 0; i < forecast.length; ++i) { 150 | temp += Math.pow(forecast[i] - trueForecastData[i], 2); 151 | } 152 | final double rmse = Math.pow(temp / forecast.length, 0.5); 153 | sb.append("RMSE = ").append(dbl2str(rmse)).append("\n\n"); 154 | System.out.println(sb.toString()); 155 | return rmse; 156 | } 157 | 158 | private void commonAssertionLogic(double[] dataSet, double actualValue, double delta) { 159 | double lastTrueValue = dataSet[dataSet.length - 1]; 160 | Assert.assertEquals(lastTrueValue, actualValue, delta); 161 | } 162 | 163 | @Test 164 | public void arma2ma_test() { 165 | double[] ar = {1.0, -0.25}; 166 | double[] ma = {1.0, 2.0}; 167 | int lag = 10; 168 | double[] ma_coeff = ForecastUtil.ARMAtoMA(ar, ma, lag); 169 | double[] true_coeff = {1.0, 2.0, 3.75, 3.25, 2.3125, 1.5, 0.921875, 0.546875, 0.31640625, 170 | 0.1796875}; 171 | 172 | Assert.assertArrayEquals(ma_coeff, true_coeff, 1e-6); 173 | } 174 | 175 | @Test(expected = RuntimeException.class) 176 | public void common_logic_fail_test() { 177 | commonTestLogic("simple12345", Datasets.simple12345, 0.1, 0, 0, 0, 0, 0, 0, 0); 178 | } 179 | 180 | @Test(expected = RuntimeException.class) 181 | public void one_piont_fail_test() { 182 | forecastSinglePointLogic("simple12345", Datasets.simple12345, 0, 0, 0, 0, 0, 0, 0); 183 | } 184 | 185 | @Test 186 | public void a10_test() { 187 | commonTestLogic("a10_test", Datasets.a10_val, 0.1, 3, 0, 0, 1, 0, 1, 12); 188 | double actualValue = forecastSinglePointLogic("a10_test", Datasets.a10_val, 3, 0, 0, 1, 0, 189 | 1, 12); 190 | commonAssertionLogic(Datasets.a10_val, actualValue, 5.05); 191 | } 192 | 193 | @Test 194 | public void usconsumption_test() { 195 | commonTestLogic("usconsumption_test", Datasets.usconsumption_val, 0.1, 1, 0, 3, 0, 0, 1, 196 | 42); 197 | double lastActualData = forecastSinglePointLogic("usconsumption_test", 198 | Datasets.usconsumption_val, 1, 0, 3, 0, 0, 1, 42); 199 | commonAssertionLogic(Datasets.usconsumption_val, lastActualData, 0.15); 200 | } 201 | 202 | @Test 203 | public void euretail_test() { 204 | commonTestLogic("euretail_test", Datasets.euretail_val, 0.1, 3, 0, 3, 1, 1, 0, 0); 205 | double lastActualData = forecastSinglePointLogic("euretail_test", Datasets.euretail_val, 3, 206 | 0, 3, 1, 1, 0, 0); 207 | commonAssertionLogic(Datasets.euretail_val, lastActualData, 0.23); 208 | } 209 | 210 | @Test 211 | public void sunspots_test() { 212 | commonTestLogic("sunspots_test", Datasets.sunspots_val, 0.1, 2, 0, 0, 1, 0, 1, 21); 213 | double actualValue = forecastSinglePointLogic("sunspots_test", Datasets.sunspots_val, 2, 0, 214 | 0, 1, 0, 1, 21); 215 | commonAssertionLogic(Datasets.sunspots_val, actualValue, 11.83); 216 | 217 | } 218 | 219 | @Test 220 | public void ausbeer_test() { 221 | commonTestLogic("ausbeer_test", Datasets.ausbeer_val, 0.1, 2, 0, 1, 1, 0, 1, 8); 222 | double actualValue = forecastSinglePointLogic("ausbeer_test", Datasets.ausbeer_val, 2, 0, 1, 223 | 1, 0, 1, 8); 224 | commonAssertionLogic(Datasets.ausbeer_val, actualValue, 8.04); 225 | 226 | } 227 | 228 | @Test 229 | public void elecequip_test() { 230 | commonTestLogic("elecequip_test", Datasets.elecequip_val, 0.1, 3, 0, 1, 1, 0, 1, 6); 231 | double actualValue = forecastSinglePointLogic("elecequip_test", Datasets.elecequip_val, 3, 232 | 0, 1, 1, 0, 1, 6); 233 | commonAssertionLogic(Datasets.elecequip_val, actualValue, 5.63); 234 | } 235 | 236 | @Test 237 | public void chicago_potholes_test() { 238 | commonTestLogic("chicago_potholes_test", Datasets.chicago_potholes_val, 0.1, 3, 0, 3, 0, 1, 239 | 1, 14); 240 | double actualValue = forecastSinglePointLogic("chicago_potholes_test", 241 | Datasets.chicago_potholes_val, 3, 0, 3, 0, 1, 1, 14); 242 | commonAssertionLogic(Datasets.chicago_potholes_val, actualValue, 25.94); 243 | } 244 | 245 | @Test 246 | public void simple_data1_test() { 247 | final double forecast = forecastSinglePointLogic("simple_data1_test", 248 | Datasets.simple_data1_val, 3, 0, 3, 1, 1, 0, 0); 249 | assert (forecast == 2); 250 | } 251 | 252 | @Test 253 | public void simple_data2_test() { 254 | final double forecast = forecastSinglePointLogic("simple_data2_test", 255 | Datasets.simple_data2_val, 0, 0, 1, 0, 0, 0, 0); 256 | assert (forecast == 2); 257 | } 258 | 259 | @Test 260 | public void simple_data3_test() { 261 | final double[] forecast = commonTestSimpleForecast("simple_data3_test", 262 | Datasets.simple_data3_val, Datasets.simple_data3_answer, 7, 3, 0, 0, 1, 0, 1, 12); 263 | double lastActualData = forecast[forecast.length - 1]; 264 | commonAssertionLogic(Datasets.simple_data3_answer, lastActualData, 0.31); 265 | } 266 | 267 | @Test 268 | public void cscchris_test() { 269 | final int[] params = new int[]{0, 1, 2, 3}; 270 | int best_p, best_d, best_q, best_P, best_D, best_Q, best_m; 271 | best_p = best_d = best_q = best_P = best_D = best_Q = best_m = -1; 272 | double best_rmse = -1.0; 273 | for(int p : params) for(int d : params) for(int q : params) for(int P : params) 274 | for(int D : params) for(int Q : params) for(int m : params) try { 275 | final double rmse = commonTestCalculateRMSE("cscchris_test", 276 | Datasets.cscchris_val, Datasets.cscchris_answer, 6, p, d, q, P, D, Q, m); 277 | if (best_rmse < 0.0 || rmse < best_rmse) { 278 | best_rmse = rmse; best_p = p; best_d = d; best_q = q; 279 | best_P = P; best_D = D; best_Q = q; best_m = m; 280 | System.out.printf( 281 | "Better (RMSE,p,d,q,P,D,Q,m)=(%f,%d,%d,%d,%d,%d,%d,%d)\n", rmse,p,d,q,P,D,Q,m); 282 | } 283 | } catch (final Exception ex) { 284 | System.out.printf("Invalid: (p,d,q,P,D,Q,m)=(%d,%d,%d,%d,%d,%d,%d)\n", p,d,q,P,D,Q,m); 285 | 286 | } 287 | System.out.printf("Best (RMSE,p,d,q,P,D,Q,m)=(%f,%d,%d,%d,%d,%d,%d,%d)\n", 288 | best_rmse,best_p,best_d,best_q,best_P,best_D,best_Q,best_m); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/main/java/com/workday/insights/timeseries/arima/ArimaSolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import com.workday.insights.timeseries.arima.struct.ArimaModel; 11 | import com.workday.insights.timeseries.arima.struct.ArimaParams; 12 | import com.workday.insights.timeseries.arima.struct.ForecastResult; 13 | import com.workday.insights.timeseries.timeseriesutil.ForecastUtil; 14 | import com.workday.insights.timeseries.timeseriesutil.Integrator; 15 | 16 | public final class ArimaSolver { 17 | 18 | private static final int maxIterationForHannanRissanen = 5; 19 | 20 | private ArimaSolver() { 21 | } // pure static helper class 22 | 23 | /** 24 | * Forecast ARMA 25 | * 26 | * @param params MODIFIED. ARIMA parameters 27 | * @param dataStationary UNMODIFIED. the time series AFTER differencing / centering 28 | * @param startIndex the index where the forecast starts. startIndex ≤ data.length 29 | * @param endIndex the index where the forecast stops (exclusive). startIndex endIndex 30 | * @return forecast ARMA data point 31 | */ 32 | public static double[] forecastARMA(final ArimaParams params, final double[] dataStationary, 33 | final int startIndex, final int endIndex) { 34 | 35 | final int train_len = startIndex; 36 | final int total_len = endIndex; 37 | final double[] errors = new double[total_len]; 38 | final double[] data = new double[total_len]; 39 | System.arraycopy(dataStationary, 0, data, 0, train_len); 40 | final int forecast_len = endIndex - startIndex; 41 | final double[] forecasts = new double[forecast_len]; 42 | final int _dp = params.getDegreeP(); 43 | final int _dq = params.getDegreeQ(); 44 | final int start_idx = (_dp > _dq) ? _dp : _dq; 45 | 46 | for (int j = 0; j < start_idx; ++j) { 47 | errors[j] = 0; 48 | } 49 | // populate errors and forecasts 50 | for (int j = start_idx; j < train_len; ++j) { 51 | final double forecast = params.forecastOnePointARMA(data, errors, j); 52 | final double error = data[j] - forecast; 53 | errors[j] = error; 54 | } 55 | // now we can forecast 56 | for (int j = train_len; j < total_len; ++j) { 57 | final double forecast = params.forecastOnePointARMA(data, errors, j); 58 | data[j] = forecast; 59 | errors[j] = 0; 60 | forecasts[j - train_len] = forecast; 61 | } 62 | // return forecasted values 63 | return forecasts; 64 | } 65 | 66 | /** 67 | * Produce forecast result based on input ARIMA parameters and forecast length. 68 | * 69 | * @param params UNMODIFIED. ARIMA parameters 70 | * @param data UNMODIFIED. the original time series before differencing / centering 71 | * @param forecastStartIndex the index where the forecast starts. startIndex ≤ data.length 72 | * @param forecastEndIndex the index where the forecast stops (exclusive). startIndex < endIndex 73 | * @return forecast result 74 | */ 75 | public static ForecastResult forecastARIMA(final ArimaParams params, final double[] data, 76 | final int forecastStartIndex, final int forecastEndIndex) { 77 | 78 | if (!checkARIMADataLength(params, data, forecastStartIndex, forecastEndIndex)) { 79 | final int initialConditionSize = params.d + params.D * params.m; 80 | throw new RuntimeException( 81 | "not enough data for ARIMA. needed at least " + initialConditionSize + 82 | ", have " + data.length + ", startIndex=" + forecastStartIndex + ", endIndex=" 83 | + forecastEndIndex); 84 | } 85 | 86 | final int forecast_length = forecastEndIndex - forecastStartIndex; 87 | final double[] forecast = new double[forecast_length]; 88 | final double[] data_train = new double[forecastStartIndex]; 89 | System.arraycopy(data, 0, data_train, 0, forecastStartIndex); 90 | 91 | //======================================= 92 | // DIFFERENTIATE 93 | final boolean hasSeasonalI = params.D > 0 && params.m > 0; 94 | final boolean hasNonSeasonalI = params.d > 0; 95 | double[] data_stationary = differentiate(params, data_train, hasSeasonalI, 96 | hasNonSeasonalI); // currently un-centered 97 | // END OF DIFFERENTIATE 98 | //========================================== 99 | 100 | //=========== CENTERING ==================== 101 | final double mean_stationary = Integrator.computeMean(data_stationary); 102 | Integrator.shift(data_stationary, (-1) * mean_stationary); 103 | final double dataVariance = Integrator.computeVariance(data_stationary); 104 | //========================================== 105 | 106 | //========================================== 107 | // FORECAST 108 | final double[] forecast_stationary = forecastARMA(params, data_stationary, 109 | data_stationary.length, 110 | data_stationary.length + forecast_length); 111 | 112 | final double[] data_forecast_stationary = new double[data_stationary.length 113 | + forecast_length]; 114 | 115 | System.arraycopy(data_stationary, 0, data_forecast_stationary, 0, data_stationary.length); 116 | System.arraycopy(forecast_stationary, 0, data_forecast_stationary, data_stationary.length, 117 | forecast_stationary.length); 118 | // END OF FORECAST 119 | //========================================== 120 | 121 | //=========== UN-CENTERING ================= 122 | Integrator.shift(data_forecast_stationary, mean_stationary); 123 | //========================================== 124 | 125 | //=========================================== 126 | // INTEGRATE 127 | double[] forecast_merged = integrate(params, data_forecast_stationary, hasSeasonalI, 128 | hasNonSeasonalI); 129 | // END OF INTEGRATE 130 | //=========================================== 131 | System.arraycopy(forecast_merged, forecastStartIndex, forecast, 0, forecast_length); 132 | 133 | return new ForecastResult(forecast, dataVariance); 134 | } 135 | 136 | /** 137 | * Creates the fitted ARIMA model based on the ARIMA parameters. 138 | * 139 | * @param params MODIFIED. ARIMA parameters 140 | * @param data UNMODIFIED. the original time series before differencing / centering 141 | * @param forecastStartIndex the index where the forecast starts. startIndex ≤ data.length 142 | * @param forecastEndIndex the index where the forecast stops (exclusive). startIndex < endIndex 143 | * @return fitted ARIMA model 144 | */ 145 | public static ArimaModel estimateARIMA(final ArimaParams params, final double[] data, 146 | final int forecastStartIndex, final int forecastEndIndex) { 147 | 148 | if (!checkARIMADataLength(params, data, forecastStartIndex, forecastEndIndex)) { 149 | final int initialConditionSize = params.d + params.D * params.m; 150 | throw new RuntimeException( 151 | "not enough data for ARIMA. needed at least " + initialConditionSize + 152 | ", have " + data.length + ", startIndex=" + forecastStartIndex + ", endIndex=" 153 | + forecastEndIndex); 154 | } 155 | 156 | final int forecast_length = forecastEndIndex - forecastStartIndex; 157 | final double[] data_train = new double[forecastStartIndex]; 158 | System.arraycopy(data, 0, data_train, 0, forecastStartIndex); 159 | 160 | //======================================= 161 | // DIFFERENTIATE 162 | final boolean hasSeasonalI = params.D > 0 && params.m > 0; 163 | final boolean hasNonSeasonalI = params.d > 0; 164 | double[] data_stationary = differentiate(params, data_train, hasSeasonalI, 165 | hasNonSeasonalI); // currently un-centered 166 | // END OF DIFFERENTIATE 167 | //========================================== 168 | 169 | //=========== CENTERING ==================== 170 | final double mean_stationary = Integrator.computeMean(data_stationary); 171 | Integrator.shift(data_stationary, (-1) * mean_stationary); 172 | //========================================== 173 | 174 | //========================================== 175 | // FORECAST 176 | HannanRissanen 177 | .estimateARMA(data_stationary, params, forecast_length, 178 | maxIterationForHannanRissanen); 179 | 180 | return new ArimaModel(params, data, forecastStartIndex); 181 | } 182 | 183 | /** 184 | * Differentiate procedures for forecast and estimate ARIMA. 185 | * 186 | * @param params ARIMA parameters 187 | * @param trainingData training data 188 | * @param hasSeasonalI has seasonal I or not based on the parameter 189 | * @param hasNonSeasonalI has NonseasonalI or not based on the parameter 190 | * @return stationary data 191 | */ 192 | private static double[] differentiate(ArimaParams params, double[] trainingData, 193 | boolean hasSeasonalI, boolean hasNonSeasonalI) { 194 | double[] dataStationary; // currently un-centered 195 | if (hasSeasonalI && hasNonSeasonalI) { 196 | params.differentiateSeasonal(trainingData); 197 | params.differentiateNonSeasonal(params.getLastDifferenceSeasonal()); 198 | dataStationary = params.getLastDifferenceNonSeasonal(); 199 | } else if (hasSeasonalI) { 200 | params.differentiateSeasonal(trainingData); 201 | dataStationary = params.getLastDifferenceSeasonal(); 202 | } else if (hasNonSeasonalI) { 203 | params.differentiateNonSeasonal(trainingData); 204 | dataStationary = params.getLastDifferenceNonSeasonal(); 205 | } else { 206 | dataStationary = new double[trainingData.length]; 207 | System.arraycopy(trainingData, 0, dataStationary, 0, trainingData.length); 208 | } 209 | 210 | return dataStationary; 211 | } 212 | 213 | /** 214 | * Differentiate procedures for forecast and estimate ARIMA. 215 | * 216 | * @param params ARIMA parameters 217 | * @param dataForecastStationary stationary forecast data 218 | * @param hasSeasonalI has seasonal I or not based on the parameter 219 | * @param hasNonSeasonalI has NonseasonalI or not based on the parameter 220 | * @return merged forecast data 221 | */ 222 | private static double[] integrate(ArimaParams params, double[] dataForecastStationary, 223 | boolean hasSeasonalI, boolean hasNonSeasonalI) { 224 | double[] forecast_merged; 225 | if (hasSeasonalI && hasNonSeasonalI) { 226 | params.integrateSeasonal(dataForecastStationary); 227 | params.integrateNonSeasonal(params.getLastIntegrateSeasonal()); 228 | forecast_merged = params.getLastIntegrateNonSeasonal(); 229 | } else if (hasSeasonalI) { 230 | params.integrateSeasonal(dataForecastStationary); 231 | forecast_merged = params.getLastIntegrateSeasonal(); 232 | } else if (hasNonSeasonalI) { 233 | params.integrateNonSeasonal(dataForecastStationary); 234 | forecast_merged = params.getLastIntegrateNonSeasonal(); 235 | } else { 236 | forecast_merged = new double[dataForecastStationary.length]; 237 | System.arraycopy(dataForecastStationary, 0, forecast_merged, 0, 238 | dataForecastStationary.length); 239 | } 240 | 241 | return forecast_merged; 242 | } 243 | 244 | /** 245 | * Computes the Root Mean-Squared Error given a time series (with forecast) and true values 246 | * 247 | * @param left time series being evaluated 248 | * @param right true values 249 | * @param startIndex the index which to start evaluation 250 | * @param endIndex the index which to end evaluation 251 | * @param leftIndexOffset the number of elements from @param startIndex to the index which 252 | * evaluation begins 253 | * @return Root Mean-Squared Error 254 | */ 255 | public static double computeRMSE(final double[] left, final double[] right, 256 | final int leftIndexOffset, 257 | final int startIndex, final int endIndex) { 258 | 259 | final int len_left = left.length; 260 | final int len_right = right.length; 261 | if (startIndex >= endIndex || startIndex < 0 || len_right < endIndex || 262 | len_left + leftIndexOffset < 0 || len_left + leftIndexOffset < endIndex) { 263 | throw new RuntimeException( 264 | "invalid arguments: startIndex=" + startIndex + ", endIndex=" + endIndex + 265 | ", len_left=" + len_left + ", len_right=" + len_right + ", leftOffset=" 266 | + leftIndexOffset); 267 | } 268 | double square_sum = 0.0; 269 | for (int i = startIndex; i < endIndex; ++i) { 270 | final double error = left[i + leftIndexOffset] - right[i]; 271 | square_sum += error * error; 272 | } 273 | return Math.sqrt(square_sum / (double) (endIndex - startIndex)); 274 | } 275 | 276 | /** 277 | * Performs validation using Root Mean-Squared Error given a time series (with forecast) and 278 | * true values 279 | * 280 | * @param data UNMODIFIED. time series data being evaluated 281 | * @param testDataPercentage percentage of data to be used to evaluate as test set 282 | * @param params MODIFIED. parameter of the ARIMA model 283 | * @return a Root Mean-Squared Error computed from the forecast and true data 284 | */ 285 | public static double computeRMSEValidation(final double[] data, 286 | final double testDataPercentage, ArimaParams params) { 287 | 288 | int testDataLength = (int) (data.length * testDataPercentage); 289 | int trainingDataEndIndex = data.length - testDataLength; 290 | 291 | final ArimaModel result = estimateARIMA(params, data, trainingDataEndIndex, 292 | data.length); 293 | 294 | final double[] forecast = result.forecast(testDataLength).getForecast(); 295 | 296 | return computeRMSE(data, forecast, trainingDataEndIndex, 0, forecast.length); 297 | } 298 | 299 | /** 300 | * Set Sigma2(RMSE) and Predication Interval for forecast result. 301 | * 302 | * @param params ARIMA parameters 303 | * @param forecastResult MODIFIED. forecast result 304 | * @param forecastSize size of forecast 305 | * @return max normalized variance 306 | */ 307 | public static double setSigma2AndPredicationInterval(final ArimaParams params, 308 | final ForecastResult forecastResult, final int forecastSize) { 309 | 310 | final double[] coeffs_AR = params.getCurrentARCoefficients(); 311 | final double[] coeffs_MA = params.getCurrentMACoefficients(); 312 | return forecastResult 313 | .setConfInterval(ForecastUtil.confidence_constant_95pct, 314 | ForecastUtil.getCumulativeSumOfCoeff( 315 | ForecastUtil.ARMAtoMA(coeffs_AR, coeffs_MA, forecastSize))); 316 | } 317 | 318 | /** 319 | * Input checker 320 | * 321 | * @param params ARIMA parameter 322 | * @param data original data 323 | * @param startIndex start index of ARIMA operation 324 | * @param endIndex end index of ARIMA operation 325 | * @return whether the inputs are valid 326 | */ 327 | private static boolean checkARIMADataLength(ArimaParams params, double[] data, int startIndex, 328 | int endIndex) { 329 | boolean result = true; 330 | 331 | final int initialConditionSize = params.d + params.D * params.m; 332 | 333 | if (data.length < initialConditionSize || startIndex < initialConditionSize 334 | || endIndex <= startIndex) { 335 | result = false; 336 | } 337 | 338 | return result; 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/test/java/com/workday/insights/timeseries/arima/Datasets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017-present, Workday, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the MIT license found in the LICENSE file in the root repository. 6 | */ 7 | 8 | package com.workday.insights.timeseries.arima; 9 | 10 | import java.util.HashMap; 11 | 12 | /** 13 | * Several sample datasets to test with each of these retruns a hash because this 14 | * allows for the general case when data is not evenly spaced or is missing and we have to impute 15 | * before having a nice array to work with. Each type of key will have to be mapped to some sort of 16 | * numbered index before we can actually compute with it. 17 | */ 18 | public final class Datasets { 19 | 20 | /** 21 | * Simple one two tree four five 22 | */ 23 | public static double[] simple12345 = {1.0, 2.0, 3.0, 4.0, 5.0}; 24 | 25 | /** 26 | * a10 Monthly anti-diabetic drug sales in Australia from 1992 to 2008. strong complex seasonal 27 | * component as well as a (super)linear trend 28 | * [Source] https://rdrr.io/cran/fpp/man/a10.html 29 | */ 30 | public static String[] a10_key = {"07-1991", "08-1991", "09-1991", "10-1991", "11-1991", 31 | "12-1991", "01-1992", "02-1992", "03-1992", 32 | "04-1992", "05-1992", "06-1992", "07-1992", "08-1992", "09-1992", "10-1992", "11-1992", 33 | "12-1992", 34 | "01-1993", "02-1993", "03-1993", "04-1993", "05-1993", "06-1993", "07-1993", "08-1993", 35 | "09-1993", 36 | "10-1993", "11-1993", "12-1993", "01-1994", "02-1994", "03-1994", "04-1994", "05-1994", 37 | "06-1994", 38 | "07-1994", "08-1994", "09-1994", "10-1994", "11-1994", "12-1994", "01-1995", "02-1995", 39 | "03-1995", 40 | "04-1995", "05-1995", "06-1995", "07-1995", "08-1995", "09-1995", "10-1995", "11-1995", 41 | "12-1995", 42 | "01-1996", "02-1996", "03-1996", "04-1996", "05-1996", "06-1996", "07-1996", "08-1996", 43 | "09-1996", 44 | "10-1996", "11-1996", "12-1996", "01-1997", "02-1997", "03-1997", "04-1997", "05-1997", 45 | "06-1997", 46 | "07-1997", "08-1997", "09-1997", "10-1997", "11-1997", "12-1997", "01-1998", "02-1998", 47 | "03-1998", 48 | "04-1998", "05-1998", "06-1998", "07-1998", "08-1998", "09-1998", "10-1998", "11-1998", 49 | "12-1998", 50 | "01-1999", "02-1999", "03-1999", "04-1999", "05-1999", "06-1999", "07-1999", "08-1999", 51 | "09-1999", 52 | "10-1999", "11-1999", "12-1999", "01-2000", "02-2000", "03-2000", "04-2000", "05-2000", 53 | "06-2000", 54 | "07-2000", "08-2000", "09-2000", "10-2000", "11-2000", "12-2000", "01-2001", "02-2001", 55 | "03-2001", 56 | "04-2001", "05-2001", "06-2001", "07-2001", "08-2001", "09-2001", "10-2001", "11-2001", 57 | "12-2001", 58 | "01-2002", "02-2002", "03-2002", "04-2002", "05-2002", "06-2002", "07-2002", "08-2002", 59 | "09-2002", 60 | "10-2002", "11-2002", "12-2002", "01-2003", "02-2003", "03-2003", "04-2003", "05-2003", 61 | "06-2003", 62 | "07-2003", "08-2003", "09-2003", "10-2003", "11-2003", "12-2003", "01-2004", "02-2004", 63 | "03-2004", 64 | "04-2004", "05-2004", "06-2004", "07-2004", "08-2004", "09-2004", "10-2004", "11-2004", 65 | "12-2004", 66 | "01-2005", "02-2005", "03-2005", "04-2005", "05-2005", "06-2005", "07-2005", "08-2005", 67 | "09-2005", 68 | "10-2005", "11-2005", "12-2005", "01-2006", "02-2006", "03-2006", "04-2006", "05-2006", 69 | "06-2006", 70 | "07-2006", "08-2006", "09-2006", "10-2006", "11-2006", "12-2006", "01-2007", "02-2007", 71 | "03-2007", 72 | "04-2007", "05-2007", "06-2007", "07-2007", "08-2007", "09-2007", "10-2007", "11-2007", 73 | "12-2007", 74 | "01-2008", "02-2008", "03-2008", "04-2008", "05-2008", "06-2008"}; 75 | 76 | public static double[] a10_val = {3.526591, 3.180891, 3.252221, 3.611003, 3.565869, 4.306371, 77 | 5.088335, 2.814520, 2.985811, 3.204780, 78 | 3.127578, 3.270523, 3.737851, 3.558776, 3.777202, 3.924490, 4.386531, 5.810549, 6.192068, 79 | 3.450857, 80 | 3.772307, 3.734303, 3.905399, 4.049687, 4.315566, 4.562185, 4.608662, 4.667851, 5.093841, 81 | 7.179962, 82 | 6.731473, 3.841278, 4.394076, 4.075341, 4.540645, 4.645615, 4.752607, 5.350605, 5.204455, 83 | 5.301651, 84 | 5.773742, 6.204593, 6.749484, 4.216067, 4.949349, 4.823045, 5.194754, 5.170787, 5.256742, 85 | 5.855277, 86 | 5.490729, 6.115293, 6.088473, 7.416598, 8.329452, 5.069796, 5.262557, 5.597126, 6.110296, 87 | 5.689161, 88 | 6.486849, 6.300569, 6.467476, 6.828629, 6.649078, 8.606937, 8.524471, 5.277918, 5.714303, 89 | 6.214529, 90 | 6.411929, 6.667716, 7.050831, 6.704919, 7.250988, 7.819733, 7.398101, 10.096233, 8.798513, 91 | 5.918261, 92 | 6.534493, 6.675736, 7.064201, 7.383381, 7.813496, 7.431892, 8.275117, 8.260441, 8.596156, 93 | 10.558939, 94 | 10.391416, 6.421535, 8.062619, 7.297739, 7.936916, 8.165323, 8.717420, 9.070964, 9.177113, 95 | 9.251887, 96 | 9.933136, 11.532974, 12.511462, 7.457199, 8.591191, 8.474000, 9.386803, 9.560399, 10.834295, 97 | 10.643751, 9.908162, 11.710041, 11.340151, 12.079132, 14.497581, 8.049275, 10.312891, 98 | 9.753358, 99 | 10.850382, 9.961719, 11.443601, 11.659239, 10.647060, 12.652134, 13.674466, 12.965735, 100 | 16.300269, 101 | 9.053485, 10.002449, 10.788750, 12.106705, 10.954101, 12.844566, 12.196500, 12.854748, 102 | 13.542004, 103 | 13.287640, 15.134918, 16.828350, 9.800215, 10.816994, 10.654223, 12.512323, 12.161210, 104 | 12.998046, 105 | 12.517276, 13.268658, 14.733622, 13.669382, 16.503966, 18.003768, 11.938030, 12.997900, 106 | 12.882645, 107 | 13.943447, 13.989472, 15.339097, 15.370764, 16.142005, 16.685754, 17.636728, 18.869325, 108 | 20.778723, 109 | 12.154552, 13.402392, 14.459239, 14.795102, 15.705248, 15.829550, 17.554701, 18.100864, 110 | 17.496668, 111 | 19.347265, 20.031291, 23.486694, 12.536987, 15.467018, 14.233539, 17.783058, 16.291602, 112 | 16.980282, 113 | 18.612189, 16.623343, 21.430241, 23.575517, 23.334206, 28.038383, 16.763869, 19.792754, 114 | 16.427305, 115 | 21.000742, 20.681002, 21.834890, 23.930204, 22.930357, 23.263340, 25.250030, 25.806090, 116 | 29.665356, 117 | 21.654285, 18.264945, 23.107677, 22.912510, 19.431740}; 118 | 119 | /** 120 | * ausbeer Total quarterly beer production in Australia (in megalitres) from 1956:Q1 to 2008:Q3. 121 | * strong simple seasonal component, part of a trend that then discontinues 122 | * [Source] https://rdrr.io/cran/fpp/man/ausbeer.html 123 | */ 124 | public static String[] ausbeer_key = {"1956:Q1", "1956:Q2", "1956:Q3", "1956:Q4", "1957:Q1", 125 | "1957:Q2", "1957:Q3", "1957:Q4", "1958:Q1", 126 | "1958:Q2", "1958:Q3", "1958:Q4", "1959:Q1", "1959:Q2", "1959:Q3", "1959:Q4", "1960:Q1", 127 | "1960:Q2", 128 | "1960:Q3", "1960:Q4", "1961:Q1", "1961:Q2", "1961:Q3", "1961:Q4", "1962:Q1", "1962:Q2", 129 | "1962:Q3", 130 | "1962:Q4", "1963:Q1", "1963:Q2", "1963:Q3", "1963:Q4", "1964:Q1", "1964:Q2", "1964:Q3", 131 | "1964:Q4", 132 | "1965:Q1", "1965:Q2", "1965:Q3", "1965:Q4", "1966:Q1", "1966:Q2", "1966:Q3", "1966:Q4", 133 | "1967:Q1", 134 | "1967:Q2", "1967:Q3", "1967:Q4", "1968:Q1", "1968:Q2", "1968:Q3", "1968:Q4", "1969:Q1", 135 | "1969:Q2", 136 | "1969:Q3", "1969:Q4", "1970:Q1", "1970:Q2", "1970:Q3", "1970:Q4", "1971:Q1", "1971:Q2", 137 | "1971:Q3", 138 | "1971:Q4", "1972:Q1", "1972:Q2", "1972:Q3", "1972:Q4", "1973:Q1", "1973:Q2", "1973:Q3", 139 | "1973:Q4", 140 | "1974:Q1", "1974:Q2", "1974:Q3", "1974:Q4", "1975:Q1", "1975:Q2", "1975:Q3", "1975:Q4", 141 | "1976:Q1", 142 | "1976:Q2", "1976:Q3", "1976:Q4", "1977:Q1", "1977:Q2", "1977:Q3", "1977:Q4", "1978:Q1", 143 | "1978:Q2", 144 | "1978:Q3", "1978:Q4", "1979:Q1", "1979:Q2", "1979:Q3", "1979:Q4", "1980:Q1", "1980:Q2", 145 | "1980:Q3", 146 | "1980:Q4", "1981:Q1", "1981:Q2", "1981:Q3", "1981:Q4", "1982:Q1", "1982:Q2", "1982:Q3", 147 | "1982:Q4", 148 | "1983:Q1", "1983:Q2", "1983:Q3", "1983:Q4", "1984:Q1", "1984:Q2", "1984:Q3", "1984:Q4", 149 | "1985:Q1", 150 | "1985:Q2", "1985:Q3", "1985:Q4", "1986:Q1", "1986:Q2", "1986:Q3", "1986:Q4", "1987:Q1", 151 | "1987:Q2", 152 | "1987:Q3", "1987:Q4", "1988:Q1", "1988:Q2", "1988:Q3", "1988:Q4", "1989:Q1", "1989:Q2", 153 | "1989:Q3", 154 | "1989:Q4", "1990:Q1", "1990:Q2", "1990:Q3", "1990:Q4", "1991:Q1", "1991:Q2", "1991:Q3", 155 | "1991:Q4", 156 | "1992:Q1", "1992:Q2", "1992:Q3", "1992:Q4", "1993:Q1", "1993:Q2", "1993:Q3", "1993:Q4", 157 | "1994:Q1", 158 | "1994:Q2", "1994:Q3", "1994:Q4", "1995:Q1", "1995:Q2", "1995:Q3", "1995:Q4", "1996:Q1", 159 | "1996:Q2", 160 | "1996:Q3", "1996:Q4", "1997:Q1", "1997:Q2", "1997:Q3", "1997:Q4", "1998:Q1", "1998:Q2", 161 | "1998:Q3", 162 | "1998:Q4", "1999:Q1", "1999:Q2", "1999:Q3", "1999:Q4", "2000:Q1", "2000:Q2", "2000:Q3", 163 | "2000:Q4", 164 | "2001:Q1", "2001:Q2", "2001:Q3", "2001:Q4", "2002:Q1", "2002:Q2", "2002:Q3", "2002:Q4", 165 | "2003:Q1", 166 | "2003:Q2", "2003:Q3", "2003:Q4", "2004:Q1", "2004:Q2", "2004:Q3", "2004:Q4", "2005:Q1", 167 | "2005:Q2", 168 | "2005:Q3", "2005:Q4", "2006:Q1", "2006:Q2", "2006:Q3", "2006:Q4", "2007:Q1", "2007:Q2", 169 | "2007:Q3", 170 | "2007:Q4", "2008:Q1", "2008:Q2", "2008:Q3", "2008:Q4"}; 171 | 172 | public static double[] ausbeer_val = {284.0, 213.0, 227.0, 308.0, 262.0, 228.0, 236.0, 320.0, 173 | 272.0, 233.0, 237.0, 313.0, 261.0, 227.0, 250.0, 314.0, 286.0, 227.0, 260.0, 311.0, 295.0, 174 | 233.0, 257.0, 175 | 339.0, 279.0, 250.0, 270.0, 346.0, 294.0, 255.0, 278.0, 363.0, 313.0, 273.0, 300.0, 370.0, 176 | 331.0, 288.0, 306.0, 386.0, 335.0, 288.0, 308.0, 402.0, 353.0, 316.0, 177 | 325.0, 405.0, 393.0, 319.0, 327.0, 442.0, 383.0, 332.0, 361.0, 446.0, 387.0, 357.0, 374.0, 178 | 466.0, 410.0, 370.0, 379.0, 487.0, 419.0, 378.0, 393.0, 506.0, 458.0, 179 | 387.0, 427.0, 565.0, 465.0, 445.0, 450.0, 556.0, 500.0, 452.0, 435.0, 554.0, 510.0, 433.0, 180 | 453.0, 548.0, 486.0, 453.0, 457.0, 566.0, 515.0, 464.0, 431.0, 588.0, 181 | 503.0, 443.0, 448.0, 555.0, 513.0, 427.0, 473.0, 526.0, 548.0, 440.0, 469.0, 575.0, 493.0, 182 | 433.0, 480.0, 576.0, 475.0, 405.0, 435.0, 535.0, 453.0, 430.0, 417.0, 183 | 552.0, 464.0, 417.0, 423.0, 554.0, 459.0, 428.0, 429.0, 534.0, 481.0, 416.0, 440.0, 538.0, 184 | 474.0, 440.0, 447.0, 598.0, 467.0, 439.0, 446.0, 567.0, 485.0, 441.0, 185 | 429.0, 599.0, 464.0, 424.0, 436.0, 574.0, 443.0, 410.0, 420.0, 532.0, 433.0, 421.0, 410.0, 186 | 512.0, 449.0, 381.0, 423.0, 531.0, 426.0, 408.0, 416.0, 520.0, 409.0, 187 | 398.0, 398.0, 507.0, 432.0, 398.0, 406.0, 526.0, 428.0, 397.0, 403.0, 517.0, 435.0, 383.0, 188 | 424.0, 521.0, 421.0, 402.0, 414.0, 500.0, 451.0, 380.0, 416.0, 492.0, 189 | 428.0, 408.0, 406.0, 506.0, 435.0, 380.0, 421.0, 490.0, 435.0, 390.0, 412.0, 454.0, 416.0, 190 | 403.0, 408.0, 482.0, 438.0, 386.0, 405.0, 491.0, 427.0, 383.0, 394.0, 191 | 473.0, 420.0, 390.0, 410.0}; 192 | /** 193 | * elecequip Manufacture of electrical equipment; Data adjusted by working days; Euro area (16 194 | * countries). Industry new orders index. 2005=100. slight seasonal pattern, no clear trend, 195 | * random on a larger range than the trend can show --- good edge case 196 | * [Source] https://rdrr.io/cran/fpp/man/elecequip.html 197 | */ 198 | public static String[] elecequip_key = {"01-1996", "02-1996", "03-1996", "04-1996", "05-1996", 199 | "06-1996", "07-1996", "08-1996", "09-1996", 200 | "10-1996", "11-1996", "12-1996", "01-1997", "02-1997", "03-1997", "04-1997", "05-1997", 201 | "06-1997", 202 | "07-1997", "08-1997", "09-1997", "10-1997", "11-1997", "12-1997", "01-1998", "02-1998", 203 | "03-1998", 204 | "04-1998", "05-1998", "06-1998", "07-1998", "08-1998", "09-1998", "10-1998", "11-1998", 205 | "12-1998", 206 | "01-1999", "02-1999", "03-1999", "04-1999", "05-1999", "06-1999", "07-1999", "08-1999", 207 | "09-1999", 208 | "10-1999", "11-1999", "12-1999", "01-2000", "02-2000", "03-2000", "04-2000", "05-2000", 209 | "06-2000", 210 | "07-2000", "08-2000", "09-2000", "10-2000", "11-2000", "12-2000", "01-2001", "02-2001", 211 | "03-2001", 212 | "04-2001", "05-2001", "06-2001", "07-2001", "08-2001", "09-2001", "10-2001", "11-2001", 213 | "12-2001", 214 | "01-2002", "02-2002", "03-2002", "04-2002", "05-2002", "06-2002", "07-2002", "08-2002", 215 | "09-2002", 216 | "10-2002", "11-2002", "12-2002", "01-2003", "02-2003", "03-2003", "04-2003", "05-2003", 217 | "06-2003", 218 | "07-2003", "08-2003", "09-2003", "10-2003", "11-2003", "12-2003", "01-2004", "02-2004", 219 | "03-2004", 220 | "04-2004", "05-2004", "06-2004", "07-2004", "08-2004", "09-2004", "10-2004", "11-2004", 221 | "12-2004", 222 | "01-2005", "02-2005", "03-2005", "04-2005", "05-2005", "06-2005", "07-2005", "08-2005", 223 | "09-2005", 224 | "10-2005", "11-2005", "12-2005", "01-2006", "02-2006", "03-2006", "04-2006", "05-2006", 225 | "06-2006", 226 | "07-2006", "08-2006", "09-2006", "10-2006", "11-2006", "12-2006", "01-2007", "02-2007", 227 | "03-2007", 228 | "04-2007", "05-2007", "06-2007", "07-2007", "08-2007", "09-2007", "10-2007", "11-2007", 229 | "12-2007", 230 | "01-2008", "02-2008", "03-2008", "04-2008", "05-2008", "06-2008", "07-2008", "08-2008", 231 | "09-2008", 232 | "10-2008", "11-2008", "12-2008", "01-2009", "02-2009", "03-2009", "04-2009", "05-2009", 233 | "06-2009", 234 | "07-2009", "08-2009", "09-2009", "10-2009", "11-2009", "12-2009", "01-2010", "02-2010", 235 | "03-2010", 236 | "04-2010", "05-2010", "06-2010", "07-2010", "08-2010", "09-2010", "10-2010", "11-2010", 237 | "12-2010", 238 | "01-2011", "02-2011", "03-2011", "04-2011", "05-2011", "06-2011", "07-2011", "08-2011", 239 | "09-2011", 240 | "10-2011", "11-2011"}; 241 | public static double[] elecequip_val = {79.43, 75.86, 86.40, 72.67, 74.93, 83.88, 79.88, 62.47, 242 | 85.50, 83.19, 84.29, 89.79, 78.72, 77.49, 89.94, 243 | 81.35, 78.76, 89.59, 83.75, 69.87, 91.18, 89.52, 91.12, 92.97, 81.97, 85.26, 93.09, 81.19, 244 | 85.74, 91.24, 245 | 83.56, 66.45, 93.45, 86.03, 86.91, 93.42, 81.68, 81.68, 91.35, 79.55, 87.08, 96.71, 98.10, 246 | 79.22, 103.68, 247 | 101.00, 99.52, 111.94, 95.42, 98.49, 116.37, 101.09, 104.20, 114.79, 107.75, 96.23, 123.65, 248 | 116.24, 249 | 117.00, 128.75, 100.69, 102.99, 119.21, 92.56, 98.86, 111.26, 96.25, 79.81, 102.18, 96.28, 250 | 101.38, 251 | 109.97, 89.66, 89.23, 104.36, 87.17, 89.43, 102.25, 88.26, 75.73, 99.60, 96.57, 96.22, 252 | 101.12, 89.45, 253 | 86.87, 98.94, 85.62, 85.31, 101.22, 91.93, 77.01, 104.50, 99.83, 101.10, 109.16, 89.93, 254 | 92.73, 105.22, 255 | 91.56, 92.60, 104.46, 96.28, 79.61, 105.55, 99.15, 99.81, 113.72, 91.73, 90.45, 105.56, 256 | 92.15, 91.23, 257 | 108.95, 99.33, 83.30, 110.85, 104.99, 107.10, 114.38, 99.09, 99.73, 116.05, 103.51, 102.99, 258 | 119.45, 259 | 107.98, 90.50, 121.85, 117.12, 113.66, 120.35, 103.92, 103.97, 125.63, 104.69, 108.36, 260 | 123.09, 108.88, 261 | 93.98, 121.94, 116.79, 115.78, 127.28, 109.35, 105.64, 121.30, 108.62, 103.13, 117.84, 262 | 103.62, 89.22, 263 | 109.41, 103.93, 100.07, 101.15, 77.33, 75.01, 86.31, 74.09, 74.09, 85.58, 79.84, 65.24, 264 | 87.92, 84.45, 265 | 87.93, 102.42, 79.16, 78.40, 94.32, 84.45, 84.92, 103.18, 89.42, 77.66, 95.68, 94.03, 266 | 100.99, 101.26, 267 | 91.47, 87.66, 103.33, 88.56, 92.32, 102.21, 92.80, 76.44, 94.00, 91.67, 91.98}; 268 | /** 269 | * euretail Quarterly retail trade index in the Euro area (17 countries), 1996-2011, (Index: 270 | * 2005 = 100) no seasonal component, strong trend component but then trend gets violated at the 271 | * end, local vs global trend, test overfitting p value (great for grid searching) 272 | * [Source] https://rdrr.io/cran/fpp/man/euretail.html 273 | */ 274 | public static String[] euretail_key = {"1996:Q1", "1996:Q2", "1996:Q3", "1996:Q4", "1997:Q1", 275 | "1997:Q2", "1997:Q3", "1997:Q4", "1998:Q1", 276 | "1998:Q2", "1998:Q3", "1998:Q4", "1999:Q1", "1999:Q2", "1999:Q3", "1999:Q4", "2000:Q1", 277 | "2000:Q2", 278 | "2000:Q3", "2000:Q4", "2001:Q1", "2001:Q2", "2001:Q3", "2001:Q4", "2002:Q1", "2002:Q2", 279 | "2002:Q3", 280 | "2002:Q4", "2003:Q1", "2003:Q2", "2003:Q3", "2003:Q4", "2004:Q1", "2004:Q2", "2004:Q3", 281 | "2004:Q4", 282 | "2005:Q1", "2005:Q2", "2005:Q3", "2005:Q4", "2006:Q1", "2006:Q2", "2006:Q3", "2006:Q4", 283 | "2007:Q1", 284 | "2007:Q2", "2007:Q3", "2007:Q4", "2008:Q1", "2008:Q2", "2008:Q3", "2008:Q4", "2009:Q1", 285 | "2009:Q2", 286 | "2009:Q3", "2009:Q4", "2010:Q1", "2010:Q2", "2010:Q3", "2010:Q4", "2011:Q1", "2011:Q2", 287 | "2011:Q3", 288 | "2011:Q4"}; 289 | public static double[] euretail_val = {89.13, 89.52, 89.88, 90.12, 89.19, 89.78, 90.03, 90.38, 290 | 90.27, 90.77, 91.85, 92.51, 92.21, 92.52, 93.62, 291 | 94.15, 94.69, 95.34, 96.04, 96.30, 94.83, 95.14, 95.86, 95.83, 95.73, 96.36, 96.89, 97.01, 292 | 96.66, 97.76, 293 | 97.83, 97.76, 98.17, 98.55, 99.31, 99.44, 99.43, 99.84, 100.32, 100.40, 99.88, 100.19, 294 | 100.75, 101.01, 295 | 100.84, 101.34, 101.94, 102.10, 101.56, 101.48, 101.13, 100.34, 98.93, 98.31, 97.67, 97.44, 296 | 96.53, 297 | 96.56, 96.51, 96.70, 95.88, 95.84, 95.79, 95.97}; 298 | /** 299 | * usconsumption Percentage changes in quarterly personal consumption expenditure and personal 300 | * disposable income for the US, 1970 to 2010. no seasonal and also no clear trend -- shouldn"t 301 | * find any good fit 302 | * [Source] https://github.com/cran/fpp/blob/master/man/usconsumption.Rd 303 | */ 304 | public static String[] usconsumption_key = {"1970:Q1", "1970:Q2", "1970:Q3", "1970:Q4", 305 | "1971:Q1", "1971:Q2", "1971:Q3", "1971:Q4", "1972:Q1", 306 | "1972:Q2", "1972:Q3", "1972:Q4", "1973:Q1", "1973:Q2", "1973:Q3", "1973:Q4", "1974:Q1", 307 | "1974:Q2", 308 | "1974:Q3", "1974:Q4", "1975:Q1", "1975:Q2", "1975:Q3", "1975:Q4", "1976:Q1", "1976:Q2", 309 | "1976:Q3", 310 | "1976:Q4", "1977:Q1", "1977:Q2", "1977:Q3", "1977:Q4", "1978:Q1", "1978:Q2", "1978:Q3", 311 | "1978:Q4", 312 | "1979:Q1", "1979:Q2", "1979:Q3", "1979:Q4", "1980:Q1", "1980:Q2", "1980:Q3", "1980:Q4", 313 | "1981:Q1", 314 | "1981:Q2", "1981:Q3", "1981:Q4", "1982:Q1", "1982:Q2", "1982:Q3", "1982:Q4", "1983:Q1", 315 | "1983:Q2", 316 | "1983:Q3", "1983:Q4", "1984:Q1", "1984:Q2", "1984:Q3", "1984:Q4", "1985:Q1", "1985:Q2", 317 | "1985:Q3", 318 | "1985:Q4", "1986:Q1", "1986:Q2", "1986:Q3", "1986:Q4", "1987:Q1", "1987:Q2", "1987:Q3", 319 | "1987:Q4", 320 | "1988:Q1", "1988:Q2", "1988:Q3", "1988:Q4", "1989:Q1", "1989:Q2", "1989:Q3", "1989:Q4", 321 | "1990:Q1", 322 | "1990:Q2", "1990:Q3", "1990:Q4", "1991:Q1", "1991:Q2", "1991:Q3", "1991:Q4", "1992:Q1", 323 | "1992:Q2", 324 | "1992:Q3", "1992:Q4", "1993:Q1", "1993:Q2", "1993:Q3", "1993:Q4", "1994:Q1", "1994:Q2", 325 | "1994:Q3", 326 | "1994:Q4", "1995:Q1", "1995:Q2", "1995:Q3", "1995:Q4", "1996:Q1", "1996:Q2", "1996:Q3", 327 | "1996:Q4", 328 | "1997:Q1", "1997:Q2", "1997:Q3", "1997:Q4", "1998:Q1", "1998:Q2", "1998:Q3", "1998:Q4", 329 | "1999:Q1", 330 | "1999:Q2", "1999:Q3", "1999:Q4", "2000:Q1", "2000:Q2", "2000:Q3", "2000:Q4", "2001:Q1", 331 | "2001:Q2", 332 | "2001:Q3", "2001:Q4", "2002:Q1", "2002:Q2", "2002:Q3", "2002:Q4", "2003:Q1", "2003:Q2", 333 | "2003:Q3", 334 | "2003:Q4", "2004:Q1", "2004:Q2", "2004:Q3", "2004:Q4", "2005:Q1", "2005:Q2", "2005:Q3", 335 | "2005:Q4", 336 | "2006:Q1", "2006:Q2", "2006:Q3", "2006:Q4", "2007:Q1", "2007:Q2", "2007:Q3", "2007:Q4", 337 | "2008:Q1", 338 | "2008:Q2", "2008:Q3", "2008:Q4", "2009:Q1", "2009:Q2", "2009:Q3", "2009:Q4", "2010:Q1", 339 | "2010:Q2", 340 | "2010:Q3", "2010:Q4"}; 341 | public static double[] usconsumption_val = {0.61227692, 0.45492979, 0.87467302, -0.27251439, 342 | 1.89218699, 0.91337819, 0.79285790, 1.64999566, 343 | 1.32724825, 1.88990506, 1.53272416, 2.31705777, 1.81385569, -0.05055772, 0.35966722, 344 | -0.29331546, 345 | -0.87877094, 0.34672003, 0.41195356, -1.47820468, 0.83735987, 1.65397369, 1.41431884, 346 | 1.05310993, 347 | 1.97774749, 0.91507218, 1.05074607, 1.29519619, 1.13545889, 0.55153240, 0.95015960, 348 | 1.49616150, 349 | 0.58229978, 2.11467168, 0.41869886, 0.80276430, 0.50412878, -0.05855113, 0.97755597, 350 | 0.26591209, 351 | -0.17368425, -2.29656300, 1.06691983, 1.32441742, 0.54583283, 0.00000000, 0.40482184, 352 | -0.75874883, 353 | 0.64399814, 0.35685950, 0.76412375, 1.80788661, 0.97593734, 1.96559809, 1.75134970, 354 | 1.57374005, 355 | 0.85322727, 1.42002574, 0.76950200, 1.30747803, 1.68128155, 0.90791081, 1.88085044, 356 | 0.21986403, 357 | 0.83153359, 1.05966370, 1.73244172, 0.60006243, -0.15228800, 1.32935729, 1.11041685, 358 | 0.24012547, 359 | 1.65692852, 0.72306031, 0.78681412, 1.17068014, 0.36522624, 0.44694325, 1.03134287, 360 | 0.48794531, 361 | 0.78786794, 0.32958888, 0.37909401, -0.78228237, -0.28358087, 0.75819378, 0.38256742, 362 | -0.04493204, 363 | 1.70442848, 0.59103346, 1.09931218, 1.21261583, 0.40511535, 0.95540152, 1.07908089, 364 | 0.88609934, 365 | 1.10781585, 0.73801073, 0.79832641, 0.98235581, 0.11670364, 0.81643944, 0.89012327, 366 | 0.70025668, 367 | 0.90999511, 1.12517415, 0.59749105, 0.81104981, 1.00231479, 0.40370845, 1.68561876, 368 | 1.13779625, 369 | 0.98935016, 1.70668759, 1.31690105, 1.52238359, 0.98149855, 1.56147049, 1.19479035, 370 | 1.40026421, 371 | 1.50504064, 0.93588274, 0.97432184, 0.88064976, 0.39868539, 0.37651229, 0.43918859, 372 | 1.55369100, 373 | 0.34382689, 0.50665404, 0.67571194, 0.35472465, 0.50387273, 0.98555573, 1.33766670, 374 | 0.54254673, 375 | 0.88074795, 0.44397949, 0.87037870, 1.07395152, 0.79393888, 0.98477889, 0.75627802, 376 | 0.24819787, 377 | 1.01902713, 0.60048219, 0.59799998, 0.92584113, 0.55424125, 0.38257957, 0.43929546, 378 | 0.29465872, 379 | -0.25266521, -0.03553182, -0.97177447, -1.31350400, -0.38748400, -0.47008302, 0.57400096, 380 | 0.10932885, 0.67101795, 0.71771819, 0.65314326, 0.87535215}; 381 | 382 | // Yearly numbers of sunspots 383 | // [Source] https://stat.ethz.ch/R-manual/R-devel/library/datasets/html/sunspot.year.html 384 | public static double[] sunspots_val = {5, 11, 16, 23, 36, 58, 29, 20, 10, 8, 3, 0, 0, 2, 11, 27, 385 | 47, 63, 60, 39, 28, 26, 22, 11, 21, 40, 78, 122, 103, 73, 386 | 47, 35, 11, 5, 16, 34, 70, 81, 111, 101, 73, 40, 20, 16, 5, 11, 22, 40, 60, 80.9, 83.4, 387 | 47.7, 47.8, 30.7, 12.2, 9.6, 10.2, 32.4, 388 | 47.6, 54, 62.9, 85.9, 61.2, 45.1, 36.4, 20.9, 11.4, 37.8, 69.8, 106.1, 100.8, 81.6, 66.5, 389 | 34.8, 30.6, 7, 19.8, 92.5, 154.4, 390 | 125.9, 84.8, 68.1, 38.5, 22.8, 10.2, 24.1, 82.9, 132, 130.9, 118.1, 89.9, 66.6, 60, 46.9, 391 | 41, 21.3, 16, 6.4, 4.1, 6.8, 14.5, 392 | 34, 45, 43.1, 47.5, 42.2, 28.1, 10.1, 8.1, 2.5, 0, 1.4, 5, 12.2, 13.9, 35.4, 45.8, 41.1, 393 | 30.1, 23.9, 15.6, 6.6, 4, 1.8, 8.5, 394 | 16.6, 36.3, 49.6, 64.2, 67, 70.9, 47.8, 27.5, 8.5, 13.2, 56.9, 121.5, 138.3, 103.2, 85.7, 395 | 64.6, 36.7, 24.2, 10.7, 15, 40.1, 396 | 61.5, 98.5, 124.7, 96.3, 66.6, 64.5, 54.1, 39, 20.6, 6.7, 4.3, 22.7, 54.8, 93.8, 95.8, 77.2, 397 | 59.1, 44, 47, 30.5, 16.3, 7.3, 398 | 37.6, 74, 139, 111.2, 101.6, 66.2, 44.7, 17, 11.3, 12.4, 3.4, 6, 32.3, 54.3, 59.7, 63.7, 399 | 63.5, 52.2, 25.4, 13.1, 6.8, 6.3, 400 | 7.1, 35.6, 73, 85.1, 78, 64, 41.8, 26.2, 26.7, 12.1, 9.5, 2.7, 5, 24.4, 42, 63.5, 53.8, 62, 401 | 48.5, 43.9, 18.6, 5.7, 3.6, 1.4, 402 | 9.6, 47.4, 57.1, 103.9, 80.6, 63.6, 37.6, 26.1, 14.2, 5.8, 16.7, 44.3, 63.9, 69, 77.8, 64.9, 403 | 35.7, 21.2, 11.1, 5.7, 8.7, 404 | 36.1, 79.7, 114.4, 109.6, 88.8, 67.8, 47.5, 30.6, 16.3, 9.6, 33.2, 92.6, 151.6, 136.3, 405 | 134.7, 83.9, 69.4, 31.5, 13.9, 4.4, 406 | 38, 141.7, 190.2, 184.8, 159, 112.3, 53.9, 37.6, 27.9, 10.2, 15.1, 47, 93.8, 105.9, 105.5, 407 | 104.5, 66.6, 68.9, 38, 34.5, 408 | 15.5, 12.6, 27.5, 92.5, 155.4, 154.6, 140.4, 115.9, 66.6, 45.9, 17.9, 13.4, 29.4, 100.2, 409 | 157.6, 142.6, 145.7, 94.3, 54.6, 410 | 29.9, 17.5, 8.6, 21.5, 64.3, 93.3, 119.6, 111, 104, 63.7, 40.4, 29.8, 15.2, 7.5, 2.9}; 411 | 412 | // Number of potholes on Chicago streets 413 | public static double[] chicago_potholes_val = {1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 414 | 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 3, 415 | 1, 1, 2, 1, 2, 1, 7, 2, 1, 1, 1, 2, 1, 3, 1, 3, 1, 4, 3, 1, 2, 1, 1, 1, 6, 1, 1, 3, 1, 1, 1, 416 | 2, 2, 3, 3, 1, 2, 2, 1, 4, 2, 417 | 1, 2, 4, 5, 2, 2, 1, 1, 3, 3, 1, 4, 5, 2, 4, 1, 5, 1, 3, 1, 4, 1, 5, 6, 7, 11, 3, 3, 1, 15, 418 | 13, 13, 13, 7, 7, 17, 25, 35, 419 | 61, 84, 93, 58, 57, 367, 451, 396, 383, 288, 34, 51, 387, 336, 287, 253, 293, 39, 48, 54, 420 | 242, 273, 411, 261, 421 | 68, 60, 233, 305, 342, 243, 241, 71, 52, 306, 201, 8, 16, 66, 18, 20, 192, 193, 111, 167, 422 | 204, 46, 64, 453, 423 | 925, 865, 543, 318, 134, 98, 486, 433, 777, 566, 500, 204, 168, 683, 695, 861, 623, 684, 424 | 201, 228, 416, 425 | 878, 1101, 1382, 797, 206, 164, 638, 877, 855, 863, 736, 184, 143, 631, 709, 642, 848, 654, 426 | 143, 123, 672, 427 | 534, 624, 495, 334, 137, 105, 483, 687, 544, 473, 510, 139, 67, 538, 529, 479, 301, 274, 98, 428 | 84, 348, 377, 429 | 291, 500, 446, 131, 86, 427, 519, 497, 651, 313, 117, 122, 447, 452, 496, 345, 320, 114, 77, 430 | 290, 330, 334, 431 | 269, 204, 75, 70, 270, 343, 570, 446, 429, 77, 68, 240, 382, 274, 242, 195, 72, 54, 54, 330, 432 | 291, 279, 221, 433 | 97, 98, 386, 321, 321, 286, 410, 113, 94, 301, 427, 265, 255, 231, 95, 65, 352, 337, 304, 434 | 285, 489, 76, 81, 435 | 327, 346, 274, 242, 114, 42, 41, 45, 244, 192, 329, 214, 60, 69, 162, 186, 159, 178, 201, 436 | 42, 64, 190, 308, 437 | 162, 277, 170, 30, 45, 196, 215, 169, 157, 168, 84, 39, 182, 212, 176, 153, 155, 40, 30, 438 | 142, 176, 119, 196, 439 | 207, 42, 42, 148, 130, 150, 142, 120, 29, 48, 165, 167, 140, 128, 141, 24, 23, 166, 157, 440 | 171, 130, 117, 32, 441 | 24, 27, 136, 105, 132, 132, 28, 21, 140, 131, 115, 134, 83, 27, 24, 137, 153, 101, 173, 115, 442 | 33, 23, 148, 176, 443 | 146, 130, 114, 30, 36, 183, 141, 193, 166, 152, 31, 33, 70, 235, 123, 113, 81, 40, 13, 90, 444 | 148, 181, 202, 124, 445 | 36, 22, 110, 139, 136, 141, 117, 37, 21, 137, 131, 108, 110, 112, 34, 32, 120, 88, 84, 124, 446 | 73, 32, 30, 96, 133, 447 | 105, 88, 83, 26, 20, 96, 129, 110, 6, 79, 23, 17, 136, 95, 120, 188, 137, 22, 14, 162, 155, 448 | 131, 181, 95, 25, 9, 142, 449 | 117, 86, 103, 86, 13, 19, 119, 126, 78, 88, 190, 6, 4, 13, 134, 126, 178, 104, 29, 22, 39, 450 | 65, 192, 155, 156, 11, 451 | 22, 154, 227, 151, 162, 77, 10, 11, 25, 207, 170, 151, 51, 12, 25, 362, 321, 284, 340, 190, 452 | 40, 30, 226, 249, 453 | 312, 238, 191, 26, 16, 323, 212, 275, 231, 278, 23, 16, 162, 318, 243, 315, 296, 22, 16, 454 | 266, 414, 427, 439, 228, 455 | 65, 48, 229, 331, 508, 376, 263, 53, 56, 100, 404, 336, 323, 245, 44, 45, 251, 359, 407, 456 | 373, 203, 38, 74, 360, 457 | 244, 294, 349, 274, 44, 53, 342, 234, 235, 334, 252, 80, 31, 223, 211, 206, 261, 154, 53, 458 | 19, 183, 213, 167, 204, 459 | 152, 37, 26, 155, 201, 222, 250, 171, 34, 40, 174, 285, 126, 137, 203, 30, 32, 201, 150, 460 | 303, 355, 188, 83, 124, 461 | 219, 181, 385, 159, 145, 38, 43, 165, 182, 152, 194, 142, 49, 34, 118, 164, 191, 140, 115, 462 | 19, 14, 22, 116, 163, 463 | 167, 173, 43, 38, 114, 122, 178, 95, 105, 22, 16, 129, 121, 136, 152, 105, 38, 21, 124, 132, 464 | 144, 111, 122, 26, 465 | 22, 90, 108, 113, 135, 133, 15, 26, 97, 211, 23, 101, 141, 21, 31, 110, 122, 115, 118, 80, 466 | 26, 12, 85, 184, 198, 467 | 88, 85, 35, 17, 55, 80, 107, 153, 103, 39, 31, 92, 112, 186, 139, 91, 26, 30, 97, 88, 94, 468 | 47, 64, 29, 13, 96, 138, 469 | 121, 115, 75, 38, 21, 83, 105, 87, 81, 81, 41, 8, 94, 79, 109, 92, 51, 27, 8, 5, 96, 121, 470 | 71, 53, 21, 22, 88, 105, 471 | 54, 90, 99, 41, 17, 98, 72, 141, 136, 79, 17, 11, 107, 155, 111, 96, 75, 15, 14, 196, 62, 472 | 70, 122, 91, 31, 17, 473 | 15, 64, 126, 147, 68, 15, 31, 66, 98, 138, 73, 108, 56, 14, 83, 113, 80, 114, 78, 34, 23, 474 | 93, 76, 115, 64, 60, 475 | 16, 24, 55, 70, 41, 94, 77, 47, 16, 42, 93, 88, 101, 110, 15, 8, 121, 115, 53, 5, 42, 11, 476 | 14, 92, 158, 94, 130, 477 | 108, 6, 27, 90, 59, 90, 219, 38, 13, 19, 96, 104, 84, 215, 115, 19, 40, 114, 148, 68, 113, 478 | 83, 7, 7, 72, 7, 57, 479 | 110, 131, 19, 21, 49, 8, 163, 205, 230, 15, 18, 270, 340, 342, 344, 271, 18, 13, 184, 230, 480 | 276, 403, 199, 10, 481 | 23, 10, 221, 171, 248, 320, 13, 8, 409, 320, 419, 404, 161, 30, 27, 241, 252, 184, 247, 283, 482 | 71, 85, 535, 296, 483 | 383, 316, 400, 62, 55, 226, 363, 408, 426, 203, 72, 60, 284, 326, 288, 439, 351, 135, 102, 484 | 186, 236, 277, 588, 485 | 396, 129, 167, 621, 648, 464, 444, 605, 162, 143, 719, 561, 690, 517, 383, 96, 92, 526, 437, 486 | 555, 467, 428, 487 | 115, 100, 463, 592, 639, 264, 1205, 884, 503, 627, 634, 348, 382, 458, 127, 158, 458, 382, 488 | 321, 257, 431, 489 | 231, 164, 659, 496, 549, 581, 443, 153, 144, 499, 473, 405, 433, 304, 127, 128, 316, 311, 490 | 474, 348, 399, 174, 491 | 85, 288, 327, 340, 276, 232, 117, 79, 269, 258, 293, 274, 239, 76, 33, 58, 313, 217, 223, 492 | 240, 69, 63, 358, 284, 493 | 322, 226, 199, 53, 88, 269, 315, 272, 229, 198, 41, 36, 265, 269, 241, 355, 196, 62, 58, 494 | 260, 210, 199, 191, 495 | 183, 51, 37, 202, 167, 155, 30, 153, 63, 38, 207, 167, 180, 187, 231, 43, 45, 193, 196, 219, 496 | 139, 111, 57, 47, 497 | 116, 209, 101, 196, 124, 56, 40, 192, 180, 185, 132, 144, 33, 27, 229, 182, 179, 141, 155, 498 | 22, 42, 195, 175, 499 | 149, 181, 97, 23, 33, 181, 115, 133, 134, 280, 25, 20, 116, 143, 109, 94, 92, 39, 36, 29, 500 | 141, 134, 114, 100, 501 | 22, 28, 128, 118, 114, 124, 110, 29, 22, 103, 92, 99, 111, 85, 20, 27, 136, 121, 142, 147, 502 | 77, 21, 9, 109, 95, 503 | 119, 120, 67, 15, 24, 136, 120, 149, 110, 107, 17, 12, 43, 73, 81, 92, 74, 18, 20, 122, 92, 504 | 72, 79, 62, 10, 7, 64, 505 | 126, 77, 83, 58, 13, 19, 78, 70, 89, 86, 65, 13, 14, 47, 87, 94, 152, 70, 10, 11, 96, 123, 506 | 106, 102, 62, 14, 9, 104, 507 | 189, 58, 6, 44, 16, 43, 123, 117, 168, 138, 143, 12, 20, 66, 127, 83, 127, 154, 10, 15, 222, 508 | 194, 239, 309, 329, 509 | 47, 68, 398, 175, 12, 214, 147, 51, 47, 131, 165, 20, 253, 327, 70, 23, 96, 230, 422, 383, 510 | 592, 886, 709, 1873, 511 | 1438, 1158, 1055, 1080, 390, 314, 525, 545, 531, 658, 559, 221, 208, 424, 385, 524, 494, 512 | 455, 130, 207, 760, 513 | 708, 357, 614, 529, 208, 268, 652, 555, 538, 578, 482, 177, 148, 285, 590, 995, 1027, 1143, 514 | 990, 1051, 1680, 515 | 1377, 1636, 1190, 1037, 338, 303, 619, 1192, 761, 1218, 1104, 548, 527, 1997, 1581, 678, 516 | 1044, 1022, 512, 517 | 384, 1005, 892, 1219, 974, 1069, 421, 317, 989, 819, 700, 890, 689, 387, 301, 919, 899, 788, 518 | 857, 850, 335, 519 | 395, 1288, 1030, 751, 960, 723, 278, 254, 705, 662, 805, 716, 430, 246, 164, 553, 598, 588, 520 | 662, 512, 521 | 302, 293, 562, 745, 773, 639, 606, 140, 195, 526, 636, 616, 676, 484, 187, 119, 499, 645, 522 | 507, 524, 431, 523 | 189, 176, 546, 531, 652, 495, 477, 166, 123, 115, 716, 547, 596, 460, 188, 148, 433, 539, 524 | 463, 502, 374, 525 | 184, 117, 446, 483, 519, 494, 501, 162, 121, 435, 518, 596, 549, 561, 226, 132, 560, 449, 526 | 358, 396, 398, 527 | 119, 126, 377, 359, 386, 373, 57, 100, 117, 353, 381, 452, 399, 331, 201, 128, 374, 389, 528 | 384, 426, 282, 143, 529 | 93, 290, 350, 347, 371, 305, 121, 89, 343, 330, 327, 275, 202, 105, 61, 272, 351, 354, 305, 530 | 267, 128, 65, 268, 531 | 255, 279, 241, 210, 64, 64, 243, 293, 199, 191, 169, 53, 53, 220, 259, 190, 263, 148, 46, 532 | 68, 50, 245, 211, 204, 533 | 208, 90, 61, 232, 178, 160, 230, 193, 49, 57, 236, 192, 191, 222, 189, 72, 57, 197, 165, 534 | 315, 153, 196, 57, 69, 535 | 181, 155, 162, 156, 153, 63, 34, 170, 201, 201, 167, 152, 114, 27, 57, 172, 186, 148, 133, 536 | 66, 42, 138, 193, 160, 537 | 128, 130, 45, 31, 120, 90, 148, 109, 88, 39, 28, 97, 78, 87, 76, 87, 23, 21, 101, 54, 74, 538 | 98, 65, 26, 13, 56, 58, 54, 539 | 73, 68, 18, 12, 64, 88, 74, 16, 39, 36, 18, 108, 109, 105, 96, 101, 24, 23, 107, 106, 111, 540 | 109, 123, 33, 32, 91, 113, 541 | 112, 65, 43, 17, 16, 112, 123, 51, 14, 50, 24, 22, 101, 138, 61, 13, 84, 31, 19, 156, 102, 542 | 76, 67, 105, 27, 17, 339, 543 | 433, 369, 328, 378, 39, 34, 62, 486, 331, 338, 226, 57, 28, 369, 379, 331, 252, 237, 26, 13, 544 | 58, 89, 215, 309, 301, 545 | 112, 41, 413, 609, 395, 297, 494, 19, 102, 416, 731, 460, 234, 678, 10, 46, 410, 259, 432, 546 | 152, 105, 25, 34, 47, 373, 547 | 246, 317, 279, 38, 65, 648, 504, 612, 514, 330, 61, 96, 578, 429, 308, 290, 374, 91, 243, 548 | 249, 250, 345, 485, 214, 549 | 209, 236, 441, 247, 297, 258, 383, 201, 26, 240, 345, 292, 289, 365, 61, 107, 456, 318, 262, 550 | 239, 236, 73, 75, 323, 551 | 318, 306, 308, 162, 87, 47, 241, 208, 296, 284, 201, 42, 44, 252, 258, 276, 259, 268, 81, 552 | 82, 230, 305, 306, 295, 553 | 184, 123, 57, 191, 231, 210, 254, 174, 74, 33, 56, 231, 213, 194, 155, 59, 36, 256, 242, 554 | 243, 186, 182, 29, 57, 204, 555 | 228, 169, 195, 185, 63, 86, 151, 192, 232, 214, 191, 59, 77, 263, 235, 224, 225, 170, 71, 556 | 67, 270, 244, 188, 186, 557 | 73, 60, 51, 187, 213, 279, 223, 168, 64, 48, 192, 255, 187, 289, 232, 65, 49, 191, 272, 254, 558 | 192, 235, 45, 51, 201, 559 | 188, 215, 149, 128, 41, 57, 183, 147, 170, 142, 105, 41, 52, 152, 154, 178, 160, 141, 47, 560 | 29, 212, 150, 151, 143, 561 | 135, 23, 30, 131, 141, 128, 157, 180, 42, 44, 145, 90, 138, 132, 235, 29, 25, 35, 114, 190, 562 | 137, 134, 48, 27, 159, 563 | 142, 132, 129, 110, 59, 37, 155, 147, 145, 146, 173, 45, 40, 140, 133, 132, 165, 102, 59, 9, 564 | 109, 133, 132, 118, 565 | 171, 44, 31, 53, 85, 162, 99, 96, 22, 40, 115, 109, 78, 114, 84, 12, 24, 136, 72, 109, 115, 566 | 68, 32, 29, 116, 171, 90, 567 | 131, 105, 28, 30, 116, 116, 76, 100, 78, 27, 26, 92, 108, 135, 94, 71, 20, 20, 121, 64, 69, 568 | 20, 47, 31, 47, 152, 101, 569 | 145, 87, 99, 46, 38, 100, 109, 136, 86, 99, 43, 38, 137, 142, 166, 126, 103, 52, 48, 106, 570 | 130, 92, 60, 11, 40, 35, 88, 571 | 127, 95, 94, 28, 55, 45, 113, 161, 195, 105, 135, 80, 60, 154, 306, 160, 188, 168, 26, 34, 572 | 45, 140, 193, 100, 175, 573 | 33, 22, 179, 151, 200, 210, 132, 41, 54, 175, 237, 221, 259, 300, 49, 64, 444, 203, 268, 574 | 139, 201, 26, 21, 136, 305, 575 | 312, 240, 182, 42, 45, 459, 192, 205, 239, 184, 35, 41, 291, 230, 200, 240, 190, 50, 47, 71, 576 | 266, 298, 324, 180, 62, 577 | 31, 335, 466, 275, 305, 240, 73, 53, 303, 438, 313, 139, 207, 67, 76, 353, 324, 254, 317, 578 | 250, 99, 86, 355, 713, 323, 579 | 319, 244, 67, 86, 334, 368, 361, 482, 228, 48, 66, 223, 232, 268, 216, 198, 81, 62, 234, 580 | 240, 371, 272, 252, 47, 160, 581 | 216, 288, 257, 238, 291, 64, 100, 204, 278, 303, 319, 260, 94, 81, 323, 366, 325, 290, 297, 582 | 80, 61, 207, 296, 254, 583 | 292, 221, 89, 55, 54, 229, 274, 408, 188, 68, 79, 250, 240, 204, 251, 191, 117, 68, 206, 584 | 249, 211, 200, 256, 92, 46, 585 | 247, 167, 180, 222, 198, 61, 60, 205, 188, 260, 175, 133, 33, 26, 34, 136, 177, 161, 152, 586 | 45, 57, 195, 200, 158, 144, 587 | 175, 53, 29, 193, 162, 130, 136, 109, 43, 38, 167, 204, 214, 147, 125, 38, 32, 148, 178, 99, 588 | 116, 106, 45, 28, 103, 589 | 123, 143, 130, 118, 31, 48, 142, 191, 287, 103, 97, 33, 24, 90, 120, 120, 152, 109, 71, 36, 590 | 133, 95, 117, 104, 105, 591 | 30, 33, 47, 112, 135, 109, 127, 48, 38, 134, 125, 124, 108, 96, 39, 31, 70, 105, 81, 93, 66, 592 | 22, 22, 136, 96, 95, 92, 593 | 110, 58, 33, 94, 100, 107, 89, 107, 26, 16, 48, 137, 96, 112, 103, 55, 35, 103, 89, 102, 594 | 100, 85, 34, 34, 138, 92, 84, 595 | 91, 71, 30, 34, 71, 103, 77, 89, 55, 31}; 596 | 597 | // some synthetic data to be used for testing 598 | public static double[] simple_data1_val = new double[]{2, 1, 2, 5, 2, 1, 2, 5, 2, 1, 2, 5, 2, 1, 599 | 2, 5, 2, 1, 2, 5, 2, 1, 2, 5, 2, 1, 2, 5, 2, 1, 2, 5, 2}; 600 | public static double[] simple_data2_val = new double[]{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 601 | 2, 2}; 602 | public static double[] simple_data3_val = new double[]{0.984, 1.012, 1.045, 1.109, 1.112, 1.458, 603 | 1.892, 2.018, 2.203, 2.43, 1.897, 0.894, 1.02, 604 | 1.082, 1.109, 1.201, 1.254, 1.562, 2.093, 2.104, 2.540, 2.893, 1.990, 1.035, 1.309, 1.403, 605 | 1.498, 1.73, 1.789, 1.897, 2.309, 2.543, 2.783, 3.093, 2.345, 1.473, 1.234, 1.320, 1.293, 606 | 1.523, 1.432, 1.435, 1.987, 2.235, 2.453, 2.765, 2.097, 1.291, 1.192, 1.109, 1.146, 1.356, 607 | 1.472, 1.592, 2.395, 2.483, 2.698, 2.895, 2.197, 1.303}; 608 | public static double[] simple_data3_answer = new double[]{1.435, 1.532, 1.563, 1.436, 1.578, 609 | 1.799, 2.538}; 610 | 611 | /** 612 | * austa Total international visitors to Australia. 1980-2010. (millions) no seasonal, strong 613 | * trend with a bit random 614 | * [Source] https://rdrr.io/cran/fpp/man/austa.html 615 | */ 616 | public static HashMap austa() { 617 | HashMap data = new HashMap(); 618 | String[] keys = {"1980", "1981", "1982", "1983", "1984", "1985", "1986", "1987", "1988", 619 | "1989", "1990", "1991", "1992", 620 | "1993", "1994", "1995", "1996", "1997", "1998", "1999", "2000", "2001", "2002", "2003", 621 | "2004", "2005", 622 | "2006", "2007", "2008"}; 623 | 624 | Double[] values = {0.8298943, 0.8595109, 0.8766892, 0.8667072, 0.9320520, 1.0482636, 625 | 1.3111932, 1.6375623, 2.0641074, 626 | 1.9126828, 2.0354457, 2.1772113, 2.3896834, 2.7505921, 3.0906664, 3.4266403, 3.8306491, 627 | 3.9719086, 628 | 3.8316004, 4.1431010, 4.5665510, 4.4754100, 4.4627960, 4.3848290, 4.7968610, 5.0462110, 629 | 5.0987590, 630 | 5.1965190, 5.1668430, 5.1747440, 5.4408940}; 631 | 632 | for (int i = 0; i < keys.length; i++) { 633 | data.put(keys[i], values[i]); 634 | } 635 | 636 | return data; 637 | } 638 | 639 | /** 640 | * Question from cscchris 641 | */ 642 | public static double[] cscchris_val = new double[]{ 643 | 2674.8060304978917, 3371.1788109723193, 2657.161969121835, 2814.5583226655367, 3290.855749923403, 3103.622791045206, 3403.2011487950185, 2841.438925235243, 2995.312700153925, 3256.4042898633224, 2609.8702933486843, 3214.6409110870877, 2952.1736018157644, 3468.7045537306344, 3260.9227206904898, 2645.5024256492215, 3137.857549381811, 3311.3526531674556, 2929.7762119375716, 2846.05991810631, 2606.47822546165, 3174.9770937667918, 3140.910443979614, 2590.6601484185085, 3123.4299821259915, 2714.4060964141136, 3133.9561758319487, 2951.3288157912752, 2860.3114228342765, 2757.4279640677833}; 644 | public static double[] cscchris_answer = new double[]{ 645 | 3147.816496825682, 3418.2300802476093, 2856.905414401418, 3419.0312162705545, 3307.9803365878442, 3527.68377555284}; 646 | } 647 | --------------------------------------------------------------------------------