mirror of
https://github.com/wassname/pytorch-ts.git
synced 2026-07-10 07:32:20 +08:00
341 lines
11 KiB
Python
341 lines
11 KiB
Python
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License").
|
|
# You may not use this file except in compliance with the License.
|
|
# A copy of the License is located at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# or in the "license" file accompanying this file. This file is distributed
|
|
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
# express or implied. See the License for the specific language governing
|
|
# permissions and limitations under the License.
|
|
|
|
# Standard library imports
|
|
import unittest
|
|
from typing import cast
|
|
|
|
# Third-party imports
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
# First-party imports
|
|
from pts.dataset import DataEntry, Dataset
|
|
from pts.dataset.stat import (
|
|
DatasetStatistics,
|
|
ScaleHistogram,
|
|
calculate_dataset_statistics,
|
|
)
|
|
|
|
|
|
def make_dummy_dynamic_feat(target, num_features) -> np.ndarray:
|
|
# gives dummy dynamic_feat constructed from the target
|
|
return np.vstack([target * (i + 1) for i in range(num_features)])
|
|
|
|
|
|
# default values for TimeSeries field
|
|
start = pd.Timestamp("1985-01-02", freq="1D")
|
|
target = np.random.randint(0, 10, 20)
|
|
fsc = [0, 1]
|
|
fsr = [0.1, 0.2]
|
|
|
|
|
|
def make_time_series(
|
|
start=start,
|
|
target=target,
|
|
feat_static_cat=fsc,
|
|
feat_static_real=fsr,
|
|
num_feat_dynamic_cat=1,
|
|
num_feat_dynamic_real=1,
|
|
) -> DataEntry:
|
|
feat_dynamic_cat = (
|
|
make_dummy_dynamic_feat(target, num_feat_dynamic_cat).astype("int64")
|
|
if num_feat_dynamic_cat > 0
|
|
else None
|
|
)
|
|
feat_dynamic_real = (
|
|
make_dummy_dynamic_feat(target, num_feat_dynamic_real).astype("float")
|
|
if num_feat_dynamic_real > 0
|
|
else None
|
|
)
|
|
data = {
|
|
"start": start,
|
|
"target": target,
|
|
"feat_static_cat": feat_static_cat,
|
|
"feat_static_real": feat_static_real,
|
|
"feat_dynamic_cat": feat_dynamic_cat,
|
|
"feat_dynamic_real": feat_dynamic_real,
|
|
}
|
|
return data
|
|
|
|
|
|
def ts(
|
|
start,
|
|
target,
|
|
feat_static_cat=None,
|
|
feat_static_real=None,
|
|
feat_dynamic_cat=None,
|
|
feat_dynamic_real=None,
|
|
) -> DataEntry:
|
|
d = {"start": start, "target": target}
|
|
if feat_static_cat is not None:
|
|
d["feat_static_cat"] = feat_static_cat
|
|
if feat_static_real is not None:
|
|
d["feat_static_real"] = feat_static_real
|
|
if feat_dynamic_cat is not None:
|
|
d["feat_dynamic_cat"] = feat_dynamic_cat
|
|
if feat_dynamic_real is not None:
|
|
d["feat_dynamic_real"] = feat_dynamic_real
|
|
return d
|
|
|
|
|
|
class DatasetStatisticsTest(unittest.TestCase):
|
|
def test_dataset_statistics(self) -> None:
|
|
|
|
n = 2
|
|
T = 10
|
|
|
|
# use integers to avoid float conversion that can fail comparison
|
|
np.random.seed(0)
|
|
targets = np.random.randint(0, 10, (n, T))
|
|
|
|
scale_histogram = ScaleHistogram()
|
|
for i in range(n):
|
|
scale_histogram.add(targets[i, :])
|
|
|
|
scale_histogram.add([])
|
|
|
|
expected = DatasetStatistics(
|
|
integer_dataset=True,
|
|
num_time_series=n + 1,
|
|
num_time_observations=targets.size,
|
|
mean_target_length=T * 2 / 3,
|
|
min_target=targets.min(),
|
|
mean_target=targets.mean(),
|
|
mean_abs_target=targets.mean(),
|
|
max_target=targets.max(),
|
|
feat_static_real=[{0.1}, {0.2, 0.3}],
|
|
feat_static_cat=[{1}, {2, 3}],
|
|
num_feat_dynamic_real=2,
|
|
num_feat_dynamic_cat=2,
|
|
num_missing_values=0,
|
|
scale_histogram=scale_histogram,
|
|
)
|
|
|
|
# FIXME: the cast below is a hack to make mypy happy
|
|
timeseries = cast(
|
|
Dataset,
|
|
[
|
|
make_time_series(
|
|
target=targets[0, :],
|
|
feat_static_cat=[1, 2],
|
|
feat_static_real=[0.1, 0.2],
|
|
num_feat_dynamic_cat=2,
|
|
num_feat_dynamic_real=2,
|
|
),
|
|
make_time_series(
|
|
target=targets[1, :],
|
|
feat_static_cat=[1, 3],
|
|
feat_static_real=[0.1, 0.3],
|
|
num_feat_dynamic_cat=2,
|
|
num_feat_dynamic_real=2,
|
|
),
|
|
make_time_series(
|
|
target=np.array([]),
|
|
feat_static_cat=[1, 3],
|
|
feat_static_real=[0.1, 0.3],
|
|
num_feat_dynamic_cat=2,
|
|
num_feat_dynamic_real=2,
|
|
),
|
|
],
|
|
)
|
|
|
|
found = calculate_dataset_statistics(timeseries)
|
|
|
|
assert expected == found
|
|
|
|
def test_dataset_histogram(self) -> None:
|
|
|
|
# generates 2 ** N - 1 timeseries with constant increasing values
|
|
N = 6
|
|
n = 2 ** N - 1
|
|
T = 5
|
|
targets = np.ones((n, T))
|
|
for i in range(0, n):
|
|
targets[i, :] = targets[i, :] * i
|
|
|
|
# FIXME: the cast below is a hack to make mypy happy
|
|
timeseries = cast(
|
|
Dataset, [make_time_series(target=targets[i, :]) for i in range(n)]
|
|
)
|
|
|
|
found = calculate_dataset_statistics(timeseries)
|
|
|
|
hist = found.scale_histogram.bin_counts
|
|
for i in range(0, N):
|
|
assert i in hist
|
|
assert hist[i] == 2 ** i
|
|
|
|
|
|
class DatasetStatisticsExceptions(unittest.TestCase):
|
|
def test_dataset_statistics_exceptions(self) -> None:
|
|
def check_error_message(expected_regex, dataset) -> None:
|
|
with self.assertRaisesRegex(Exception, expected_regex):
|
|
calculate_dataset_statistics(dataset)
|
|
|
|
check_error_message("Time series dataset is empty!", [])
|
|
|
|
check_error_message(
|
|
"Only empty time series found in the dataset!",
|
|
[make_time_series(target=np.random.randint(0, 10, 0))],
|
|
)
|
|
|
|
# infinite target
|
|
# check_error_message(
|
|
# "Target values have to be finite (e.g., not inf, -inf, "
|
|
# "or None) and cannot exceed single precision floating "
|
|
# "point range.",
|
|
# [make_time_series(target=np.full(20, np.inf))]
|
|
# )
|
|
|
|
# different number of feat_dynamic_{cat, real}
|
|
check_error_message(
|
|
"Found instances with different number of features in "
|
|
"feat_dynamic_cat, found one with 2 and another with 1.",
|
|
[
|
|
make_time_series(num_feat_dynamic_cat=2),
|
|
make_time_series(num_feat_dynamic_cat=1),
|
|
],
|
|
)
|
|
check_error_message(
|
|
"Found instances with different number of features in "
|
|
"feat_dynamic_cat, found one with 0 and another with 1.",
|
|
[
|
|
make_time_series(num_feat_dynamic_cat=0),
|
|
make_time_series(num_feat_dynamic_cat=1),
|
|
],
|
|
)
|
|
check_error_message(
|
|
"feat_dynamic_cat was found for some instances but not others.",
|
|
[
|
|
make_time_series(num_feat_dynamic_cat=1),
|
|
make_time_series(num_feat_dynamic_cat=0),
|
|
],
|
|
)
|
|
check_error_message(
|
|
"Found instances with different number of features in "
|
|
"feat_dynamic_real, found one with 2 and another with 1.",
|
|
[
|
|
make_time_series(num_feat_dynamic_real=2),
|
|
make_time_series(num_feat_dynamic_real=1),
|
|
],
|
|
)
|
|
check_error_message(
|
|
"Found instances with different number of features in "
|
|
"feat_dynamic_real, found one with 0 and another with 1.",
|
|
[
|
|
make_time_series(num_feat_dynamic_real=0),
|
|
make_time_series(num_feat_dynamic_real=1),
|
|
],
|
|
)
|
|
check_error_message(
|
|
"feat_dynamic_real was found for some instances but not others.",
|
|
[
|
|
make_time_series(num_feat_dynamic_real=1),
|
|
make_time_series(num_feat_dynamic_real=0),
|
|
],
|
|
)
|
|
|
|
# infinite feat_dynamic_{cat,real}
|
|
inf_dynamic_feat = np.full((2, len(target)), np.inf)
|
|
check_error_message(
|
|
"Features values have to be finite and cannot exceed single "
|
|
"precision floating point range.",
|
|
[
|
|
ts(
|
|
start,
|
|
target,
|
|
feat_dynamic_cat=inf_dynamic_feat,
|
|
feat_static_cat=[0, 1],
|
|
)
|
|
],
|
|
)
|
|
check_error_message(
|
|
"Features values have to be finite and cannot exceed single "
|
|
"precision floating point range.",
|
|
[
|
|
ts(
|
|
start,
|
|
target,
|
|
feat_dynamic_real=inf_dynamic_feat,
|
|
feat_static_cat=[0, 1],
|
|
)
|
|
],
|
|
)
|
|
|
|
# feat_dynamic_{cat, real} different length from target
|
|
check_error_message(
|
|
"Each feature in feat_dynamic_cat has to have the same length as the "
|
|
"target. Found an instance with feat_dynamic_cat of length 1 and a "
|
|
"target of length 20.",
|
|
[
|
|
ts(
|
|
start=start,
|
|
target=target,
|
|
feat_static_cat=[0, 1],
|
|
feat_dynamic_cat=np.ones((1, 1)),
|
|
)
|
|
],
|
|
)
|
|
check_error_message(
|
|
"Each feature in feat_dynamic_real has to have the same length as the "
|
|
"target. Found an instance with feat_dynamic_real of length 1 and a "
|
|
"target of length 20.",
|
|
[
|
|
ts(
|
|
start=start,
|
|
target=target,
|
|
feat_static_cat=[0, 1],
|
|
feat_dynamic_real=np.ones((1, 1)),
|
|
)
|
|
],
|
|
)
|
|
|
|
# feat_static_{cat, real} different length
|
|
check_error_message(
|
|
"Not all feat_static_cat vectors have the same length 2 != 1.",
|
|
[
|
|
ts(start=start, target=target, feat_static_cat=[0, 1]),
|
|
ts(start=start, target=target, feat_static_cat=[1]),
|
|
],
|
|
)
|
|
check_error_message(
|
|
"Not all feat_static_real vectors have the same length 2 != 1.",
|
|
[
|
|
ts(start=start, target=target, feat_static_real=[0, 1]),
|
|
ts(start=start, target=target, feat_static_real=[1]),
|
|
],
|
|
)
|
|
|
|
calculate_dataset_statistics(
|
|
# FIXME: the cast below is a hack to make mypy happy
|
|
cast(
|
|
Dataset,
|
|
[
|
|
make_time_series(num_feat_dynamic_cat=2),
|
|
make_time_series(num_feat_dynamic_cat=2),
|
|
],
|
|
)
|
|
)
|
|
|
|
calculate_dataset_statistics(
|
|
# FIXME: the cast below is a hack to make mypy happy
|
|
cast(
|
|
Dataset,
|
|
[
|
|
make_time_series(num_feat_dynamic_cat=0),
|
|
make_time_series(num_feat_dynamic_cat=0),
|
|
],
|
|
)
|
|
)
|