mirror of
https://github.com/wassname/pytorch-ts.git
synced 2026-07-10 05:54:59 +08:00
113 lines
3.9 KiB
Python
113 lines
3.9 KiB
Python
from functools import lru_cache
|
|
from typing import Callable, List, cast
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
from pandas.tseries.offsets import Tick
|
|
|
|
from .common import DataEntry
|
|
|
|
|
|
class ProcessStartField:
|
|
def __init__(self, name: str, freq: str) -> None:
|
|
self.name = name
|
|
self.freq = freq
|
|
|
|
def __call__(self, data: DataEntry) -> DataEntry:
|
|
try:
|
|
value = ProcessStartField.process(data[self.name], self.freq)
|
|
except (TypeError, ValueError) as e:
|
|
raise Exception(
|
|
f'Error "{e}" occurred when reading field "{self.name}"')
|
|
|
|
data[self.name] = value
|
|
|
|
return data
|
|
|
|
@staticmethod
|
|
@lru_cache(maxsize=10000)
|
|
def process(string: str, freq: str) -> pd.Timestamp:
|
|
timestamp = pd.Timestamp(string, freq=freq)
|
|
|
|
# operate on time information (days, hours, minute, second)
|
|
if isinstance(timestamp.freq, Tick):
|
|
return pd.Timestamp(timestamp.floor(timestamp.freq),
|
|
timestamp.freq)
|
|
|
|
# since we are only interested in the data piece, we normalize the
|
|
# time information
|
|
timestamp = timestamp.replace(hour=0,
|
|
minute=0,
|
|
second=0,
|
|
microsecond=0,
|
|
nanosecond=0)
|
|
|
|
return timestamp.freq.rollforward(timestamp)
|
|
|
|
|
|
class ProcessTimeSeriesField:
|
|
def __init__(self, name, is_required: bool, is_static: bool,
|
|
is_cat: bool) -> None:
|
|
self.name = name
|
|
self.is_required = is_required
|
|
self.req_ndim = 1 if is_static else 2
|
|
self.dtype = np.int32 if is_cat else np.float32
|
|
|
|
def __call__(self, data: DataEntry) -> DataEntry:
|
|
value = data.get(self.name, None)
|
|
|
|
if value is not None:
|
|
value = np.asarray(value, dtype=self.dtype)
|
|
dim_diff = self.req_ndim - value.ndim
|
|
if dim_diff == 1:
|
|
value = np.expand_dims(a=value, axis=0)
|
|
elif dim_diff != 0:
|
|
raise Exception(
|
|
f"JSON array has bad shape - expected {self.req_ndim} dimensions got {dim_diff}"
|
|
)
|
|
|
|
data[self.name] = value
|
|
return data
|
|
elif not self.is_required:
|
|
return data
|
|
else:
|
|
raise Exception(
|
|
f"JSON object is missing a required field `{self.name}`")
|
|
|
|
|
|
class ProcessDataEntry:
|
|
def __init__(self, freq: str, one_dim_target: bool = True) -> None:
|
|
self.trans = cast(
|
|
List[Callable[[DataEntry], DataEntry]],
|
|
[
|
|
ProcessStartField("start", freq=freq),
|
|
ProcessTimeSeriesField("target",
|
|
is_required=True,
|
|
is_cat=False,
|
|
is_static=one_dim_target),
|
|
ProcessTimeSeriesField("feat_dynamic_cat",
|
|
is_required=False,
|
|
is_cat=True,
|
|
is_static=False),
|
|
ProcessTimeSeriesField(
|
|
"feat_dynamic_real",
|
|
is_required=False,
|
|
is_cat=False,
|
|
is_static=False,
|
|
),
|
|
ProcessTimeSeriesField("feat_static_cat",
|
|
is_required=False,
|
|
is_cat=True,
|
|
is_static=True),
|
|
ProcessTimeSeriesField("feat_static_real",
|
|
is_required=False,
|
|
is_cat=False,
|
|
is_static=True),
|
|
],
|
|
)
|
|
|
|
def __call__(self, data: DataEntry) -> DataEntry:
|
|
for t in self.trans:
|
|
data = t(data)
|
|
return data
|