Task-adapter layer over the TSData dataset library. TSData handles storage,
catalog, and raw arrays; ts_data turns those into windowed, split, scaled
torch datasets for forecasting, imputation, generation, classification, and
anomaly detection.
cd TSLib-tool/ts_data
pip install -e .ts_data reads the TSData library through a self-contained reader. Root
resolution order:
root=argument toload(...)/TSDataSource(...)TSDATA_ROOTenvironment variable- built-in default path
export TSDATA_ROOT=/data/songzy/workshop/data/timeseries/TSDataimport ts_data
# discovery
ts_data.list_datasets() # all dataset names
ts_data.list_datasets(task="forecasting") # filter by task/domain/format
ts_data.info("etth1") # full meta.json
# one-liner: name + task -> Splits(train, val, test) of torch datasets
train, val, test = ts_data.load("appliances", task="forecasting",
window=96, horizon=96)
from torch.utils.data import DataLoader
from ts_data import collate
loader = DataLoader(train, batch_size=32, shuffle=True, collate_fn=collate)
sample = train[0] # {"x", "y", "mask", "x_mark", "y_mark", "idx"} (unused -> None)val/test may be None for formats without such a split (see below).
87 datasets, indexed by the TSData catalog. Browse them with
ts_data.list_datasets(...) and ts_data.info(name).
| task | count | on-disk formats |
|---|---|---|
| forecasting | 66 | matrix (42), sequences (24) |
| classification | 15 | samples |
| anomaly_detection | 6 | anomaly |
Forecasting datasets span domains such as weather, energy, traffic, finance, health, cloud, web, and forecasting-competition collections. Use the filters to narrow down:
ts_data.list_datasets(task="forecasting", domain="traffic")
ts_data.list_datasets(format="samples")Each sample is a dict with a fixed set of keys (x, y, mask, x_mark, y_mark, idx); fields a task does not use are None (e.g. a forecast sample
has mask=None). Because the default torch collate cannot stack None, use
the bundled ts_data.collate, which drops all-None keys and stacks the rest:
from torch.utils.data import DataLoader
from ts_data import collate
loader = DataLoader(train, batch_size=32, shuffle=True, collate_fn=collate)
batch = next(iter(loader)) # {"x": [B,C,L], "y": [B,C,H], "x_mark": ..., "idx": ...}To control what each sample looks like, pass a transform(sample) -> Any
callable. It receives the fixed-key dict and can return anything — a renamed
dict, a tuple, tensors on a device, etc. Attach it via load(..., transform=),
create_dataset(..., transform=), or dataset.with_transform(fn). With a
transform that returns pure tensors/tuples you can use the default collate.
# return (x, y) tuples instead of dicts
train, val, test = ts_data.load(
"etth1", task="forecasting", window=96, horizon=96,
transform=lambda s: (s["x"], s["y"]),
)
x, y = train[0]
# or rename / restructure
def to_tslib(s):
return {"batch_x": s["x"], "batch_y": s["y"], "batch_x_mark": s["x_mark"]}
train, _, _ = ts_data.load("etth1", task="forecasting", transform=to_tslib)Every dataset returns a dict with the same fixed keys; a field a task does not
use is None.
| key | meaning |
|---|---|
x |
input window, [C, L] (default) or [L, C] if channel_last=True |
y |
target (task-dependent; None for generation / anomaly-train) |
mask |
observation mask (imputation; None otherwise) |
x_mark, y_mark |
calendar time features from freq+start_time; None when there is no time axis |
idx |
window start index in the original series |
ts_data.load(
name, task=None, *,
root=None,
window=96, # input window (also the window size for imputation/generation/classification)
horizon=96, # prediction length (forecast)
label_len=0, # teacher-forcing label length (forecast)
stride=1,
scale=True,
scaler="standard", # "standard" | "minmax" | "robust" | None (or an sklearn scaler)
norm_each_channel=True, # False -> one global statistic across channels
channel_last=False, # False -> [C, L]; True -> [L, C]
split_ratio=None, # matrix ratio splitting, e.g. (0.7, 0.1, 0.2)
split_mode=None, # "ratio" | "standard" (ETT auto-detected)
val_ratio=0.0, # samples: carve a val split off train
mmap=True,
)If task is omitted, a sensible default is chosen from the dataset's format.
TSData stores each dataset in one of four on-disk formats; load routes
automatically:
| TSData format | typical task | splits returned | notes |
|---|---|---|---|
matrix (T, N) |
forecasting / imputation / generation | train / val / test | ratio or ETT-standard split; x_mark derived from freq |
samples (n, L, C) + labels |
classification / generation | train / (val) / test | uses the archive's n_train; val_ratio carves val from train |
anomaly |
anomaly_detection | train / — / test | train is all-normal & unlabeled; test carries pointwise 0/1 labels |
sequences (ragged) |
forecasting / generation | single dataset / — / — | windows cut within each series; global standardization |
Task names match the TSData catalog vocabulary:
forecasting, imputation, generation, classification, anomaly_detection.
# forecasting
train, val, test = ts_data.load("etth1", task="forecasting",
window=96, horizon=96, label_len=48)
# imputation
train, val, test = ts_data.load("appliances", task="imputation", window=96,
mask_ratio=0.25, mask_mode="random")
# generation
train, _, _ = ts_data.load("appliances", task="generation", window=64)
# classification (samples format)
train, val, test = ts_data.load("atrial_fibrillation", task="classification",
val_ratio=0.2)
# anomaly detection (anomaly format)
train, _, test = ts_data.load("msl", task="anomaly_detection", window=100)The scaler is fit on the training split and reused for val/test:
scaler |
transform | typical use |
|---|---|---|
"standard" (default) |
z-score (x-μ)/σ |
forecasting |
"minmax" |
scale to [0, 1] |
generation (GAN/diffusion) |
"robust" |
median / IQR | outlier-heavy data, anomaly detection |
None |
no scaling | pre-normalized data |
You can also pass any sklearn-like scaler instance. Set scale=False to disable
entirely. Use norm_each_channel=False to fit a single global statistic across
all channels instead of per-channel.
train, val, test = ts_data.load("appliances", task="generation",
window=64, scaler="minmax")Every returned split exposes the fitted scaler via .scaler and an
.inverse_transform(pred, mask=None) helper to map predictions back to the
original scale for metrics. It expects features on the last axis ([..., C]),
so transpose channel-first outputs first. mask (imputation) keeps the original
value where False.
train, val, test = ts_data.load("etth1", task="forecasting", window=96, horizon=96)
pred = model(...) # normalized space, e.g. [B, C, L]
pred_real = test.inverse_transform(pred.transpose(-1, -2)) # -> [..., C] in original unitsfrom ts_data import DataModule, TSDataSource
# raw arrays + metadata straight from TSData
src = TSDataSource()
data, meta = src.load_matrix("etth1")
X, y, meta = src.load_samples("atrial_fibrillation")
train, test, test_label, meta = src.load_anomaly("msl")
values, offsets, meta = src.load_sequences("air")
# manual matrix splitting / windowing
dm = DataModule.from_tsdata("etth1", split_ratio=(0.7, 0.1, 0.2))
ds = dm.create_dataset("train", "forecasting", input_len=96, pred_len=96)
# your own CSV/NPY files still work
dm = DataModule(data="my.csv", date_col="date", freq="h")新数据不一定要写进全局 TSData 库(catalog + 默认 root)。两条路:
DataModule 直接接受 (T, F) numpy 数组,完全绕过 catalog:
import numpy as np
from torch.utils.data import DataLoader
from ts_data import DataModule, collate
from ts_data.utils import marks_from_freq
data = np.load("my_data.npy") # (T, F) 连续多变量序列
marks = marks_from_freq(len(data), "h", "2020-01-01") # 可选时间特征
dm = DataModule(
data,
time_marks=marks, # 可为 None:样本里 x_mark/y_mark 为 None
split_ratio=(0.7, 0.1, 0.2), # ratio 切分;名字为 etth1 等时自动走 ETT 标准切分
scale=True, scaler="standard", # scaler 只在 train 段 fit
)
train_ds = dm.create_dataset("train", "forecasting",
input_len=96, pred_len=24, label_len=0)
test_ds = dm.create_dataset("test", "forecasting",
input_len=96, pred_len=24, label_len=0, stride=5)
loader = DataLoader(train_ds, batch_size=32, shuffle=True, collate_fn=collate)
# batch: {"x": (B,F,96), "y": (B,F,24), "x_mark", "y_mark", "idx"} —— 直接喂 ts_trainercreate_dataset(flag, task, **kwargs):
flag:"train"/"val"/"test"。task:forecasting/imputation/generation/classification/anomaly_detection。- 其余参数透传给对应 Dataset:预测用
input_len/pred_len/label_len; 其余任务用window_size,插补另有mask_ratio/mask_mode/seed, 分类需构造时给labels,异常检测 test 段需点级labels。 - 正
stride只对 test 段降采样,train/val 恒为 1;负数对三段都生效。
还原尺度用 dm.inverse_transform(sklearn 风格,2D、特征在最后一轴);
样本结构、scaler 语义与 ts_data.load 完全一致。
想要 load() 的全套参数(test_stride、transform、mmap 等),可以搭一个
最小私有库,不动全局默认库:
my_root/
├── catalog.json # 空 {} 即可,索引实际在 task 文件里
└── forecasting/
├── metadata.json # 数据集索引(见下)
└── mydata/
├── data.npy # (T, F)
└── meta.json # "freq": "h", "start_time": "2020-01-01" 等
forecasting/metadata.json:
{"mydata": {"path": "forecasting/mydata", "format": "matrix", "domain": "custom"}}splits = ts_data.load("mydata", task="forecasting", root="my_root",
window=96, horizon=24, test_stride=5)也可以 export TSDATA_ROOT=my_root 省掉每次传 root=。
samples / anomaly / sequences 格式同理,按 source.py 的文件约定落盘即可。
ts_data/
├── __init__.py # public API re-exports
├── api.py # high-level load() + discovery
├── source.py # self-contained TSData reader (catalog + raw arrays)
├── datamodule.py # matrix splitting + create_dataset routing
├── utils.py # CSV/NPY loading + time-feature extraction
└── datasets/ # torch Dataset implementations
├── base.py # sliding-window base dataset
├── forecast.py # forecast (matrix / continuous)
├── imputation.py # imputation
├── generation.py # generation
├── classification.py # classification (continuous)
├── anomaly.py # anomaly detection (train/test/test_label)
├── samples.py # pre-windowed (n, L, C) datasets
└── sequences.py # variable-length (values + offsets) windowing
MIT