feat: add io_csv module with CSV reader and tests
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+15903
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
|||||||
|
"""io_csv 模块测试。"""
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
import pytest
|
||||||
|
from ts_anomaly_td.io_csv import read_csv
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_csv_basic():
|
||||||
|
"""基本读取:三列完整。"""
|
||||||
|
content = "timestamp,value,label\n2020-01-01 00:00:00,1.0,0\n2020-01-01 01:00:00,2.0,1\n"
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
|
||||||
|
f.write(content)
|
||||||
|
tmp = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ts, vals, labels = read_csv(tmp)
|
||||||
|
assert len(ts) == 2
|
||||||
|
assert len(vals) == 2
|
||||||
|
assert len(labels) == 2
|
||||||
|
assert vals[0] == 1.0
|
||||||
|
assert labels[1] == 1
|
||||||
|
finally:
|
||||||
|
Path(tmp).unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_csv_no_label_column():
|
||||||
|
"""label 列缺失时填 0。"""
|
||||||
|
content = "timestamp,value\n2020-01-01 00:00:00,5.0\n"
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
|
||||||
|
f.write(content)
|
||||||
|
tmp = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
ts, vals, labels = read_csv(tmp)
|
||||||
|
assert labels == [0]
|
||||||
|
finally:
|
||||||
|
Path(tmp).unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_csv_missing_required_column():
|
||||||
|
"""缺少 timestamp 或 value 列时抛出 ValueError。"""
|
||||||
|
content = "time,val\n2020-01-01,1.0\n"
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
|
||||||
|
f.write(content)
|
||||||
|
tmp = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
read_csv(tmp)
|
||||||
|
finally:
|
||||||
|
Path(tmp).unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_csv_file_not_found():
|
||||||
|
"""文件不存在抛出 FileNotFoundError。"""
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
read_csv("/nonexistent/file.csv")
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_csv_finance_001():
|
||||||
|
"""验证真实 finance_001.csv 可读。"""
|
||||||
|
path = Path(__file__).parent.parent / "data" / "finance_001.csv"
|
||||||
|
if not path.exists():
|
||||||
|
pytest.skip("finance_001.csv not found")
|
||||||
|
ts, vals, labels = read_csv(str(path))
|
||||||
|
assert len(ts) == len(vals) == len(labels)
|
||||||
|
assert len(ts) > 0
|
||||||
|
assert all(isinstance(v, float) for v in vals)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""CSV 读取模块:读 timestamp,value,label 三列,返回归一化元组。"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def read_csv(path: str | Path) -> tuple[list[int], list[float], list[int]]:
|
||||||
|
"""读 timestamp,value,label CSV 文件。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: CSV 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(timestamps_ms, values, labels) 三元组
|
||||||
|
- timestamps_ms: 毫秒精度 Unix 时间戳
|
||||||
|
- values: 浮点数值列表
|
||||||
|
- labels: 整数标签列表(0=正常, 1=异常)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: 文件不存在
|
||||||
|
ValueError: 缺少 timestamp 或 value 列
|
||||||
|
"""
|
||||||
|
df = pd.read_csv(path)
|
||||||
|
|
||||||
|
if "timestamp" not in df.columns or "value" not in df.columns:
|
||||||
|
raise ValueError(f"CSV 缺少 timestamp 或 value 列: {path}")
|
||||||
|
|
||||||
|
timestamps = pd.to_datetime(df["timestamp"])
|
||||||
|
timestamps_ms = (timestamps.astype("int64") // 10**6).tolist()
|
||||||
|
|
||||||
|
values = df["value"].astype(float).tolist()
|
||||||
|
|
||||||
|
labels = df["label"].fillna(0).astype(int).tolist() if "label" in df.columns else [0] * len(df)
|
||||||
|
|
||||||
|
return timestamps_ms, values, labels
|
||||||
Reference in New Issue
Block a user