├── 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 | [](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 |
--------------------------------------------------------------------------------