Metadata-Version: 2.4
Name: echoss-timeseries
Version: 1.0.0
Summary: echoss AI Bigdata Center 시계열 데이터/모델 기반 라이브러리 (TSData, TSModel, 시계열 피처 및 진단)
Author: echoss AI Bigdata Center, 12CM Inc.
License-Expression: Apache-2.0
Keywords: timeseries,forecasting,feature-engineering,diagnostics,seasonality
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: echoss-fileformat>=1.3.2
Requires-Dist: pandas>=3.0.0
Requires-Dist: numpy>=2.5.3
Requires-Dist: scipy>=1.18.1
Requires-Dist: scikit-learn>=1.9.0
Requires-Dist: statsmodels>=0.15.0
Requires-Dist: holidays>=0.104
Requires-Dist: window_ops>=0.0.15
Requires-Dist: numba>=0.68.0
Provides-Extra: plot
Requires-Dist: bokeh>=3.10.0; extra == "plot"
Requires-Dist: matplotlib>=3.11.1; extra == "plot"
Provides-Extra: forecast
Requires-Dist: darts>=0.47.0; extra == "forecast"
Requires-Dist: statsforecast>=2.0.1; extra == "forecast"
Requires-Dist: lightgbm>=4.7.0; extra == "forecast"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# echoss-timeseries

echoss AI Bigdata Center 시계열 데이터/모델 기반 라이브러리.

여러 도메인 프로젝트가 함께 쓰는 공통 패키지다. 통계 / 기계학습 / 딥러닝 / 트랜스포머 계열
시계열 모델을 한 구조 위에서 다루는 것을 목표로 한다.

도메인 프로젝트가 `TSData` / `TSModel` 을 **상속**해서 사용하는 것을 전제로 설계되었다.
데이터 로딩·리샘플링·시계열 피처 엔지니어링·진단은 기반 클래스가 담당하고,
도메인 고유의 전처리와 학습 로직만 자식 클래스에 구현한다.

## 설치

```bash
pip install echoss-timeseries              # 데이터 · 피처 · 진단 · 모델 골격
pip install 'echoss-timeseries[plot]'      # 플롯까지
```

## 요구사항

- Python >= 3.12

기본 설치:

| 패키지 | 버전 | 용도 |
|---|---|---|
| echoss-fileformat | >=1.3.2 | logger, config/파일 IO |
| pandas | >=3.0.0 | DataFrame |
| numpy | >=2.5.3 | 수치 연산 |
| scipy | >=1.18.1 | BoxCox, 통계 |
| scikit-learn | >=1.9.0 | scaler, encoder, metric |
| statsmodels | >=0.15.0 | STL, ADF/KPSS, AutoReg |
| holidays | >=0.104 | 공휴일 피처 |
| window_ops | >=0.0.15 | seasonal rolling 연산 |
| numba | >=0.68.0 | window_ops 실행 (0.67 이하는 numpy 2.5 거부) |

`[plot]` extras:

| 패키지 | 버전 | 용도 |
|---|---|---|
| bokeh | >=3.10.0 | 인터랙티브 플롯 |
| matplotlib | >=3.11.1 | 정적 플롯 |

`[forecast]` extras — darts 예측 모델 (`naive_seasonal`, `exponential_smoothing`, `theta`, `auto_arima`, `lightgbm`):

| 패키지 | 버전 | 용도 |
|---|---|---|
| darts | >=0.47.0 | 예측 모델 (0.46.x 의 Theta 는 statsmodels 0.15 에서 실패) |
| statsforecast | >=2.0.1 | darts AutoARIMA (2.1.0 부터 pandas<3 을 요구해 pandas 3 과 함께 설치되지 않음) |
| lightgbm | >=4.7.0 | darts LightGBM |

판매 없는 날(0)이 있는 자료에서 `theta` 는 `hyperparameters: {season_mode: additive}` 로 쓴다 (darts 기본값인 곱셈은 0 을 거부한다). 계절성·추세 방식은 `additive` / `multiplicative` / `none` 문자열로 지정한다. `exponential_smoothing` 은 계절 주기를 자료 간격으로 정한다(일 7, 주 52, 월 12). 7일처럼 배수 간격이거나, 분 단위처럼 주기를 정할 수 없는 간격이거나, 자료가 그 주기의 두 바퀴보다 짧으면 학습 전에 멈추므로, `seasonal_periods` 를 지정하거나 `seasonal: none` 으로 쓴다.

extras 없이 플롯·예측 모델을 쓰면 무엇을 설치해야 하는지 알려주는 오류가 난다.

하한은 pandas 3.0.5 조합에서 시험 통과와 청정 환경 설치를 확인한 값이며, 각 패키지의
최소 요구 버전을 조사한 값이 아니다. pandas 2.x 환경에는 설치되지 않는다.

**필요한 것만 적재된다.** `import echoss_timeseries` 는 이름을 실제로 쓸 때까지 하위 모듈을
읽지 않는다. 진단 함수 하나만 쓰면 플롯 라이브러리는 올라오지 않는다.

## 기능

| 모듈 | 주요 export |
|---|---|
| `echoss_tsdata` | `TSData` — 데이터 로딩/캐시, split, 리샘플링, 피처 추가 진입점 |
| `echoss_tsmodel` | `TSModel` — `run_models()` 파이프라인 골격, forecast/classification 스코어 |
| `tsdiag` | `stationarity_tests`, `lag_diagnostics`, `try_autoreg`, `naive_cv_scores`, `seasonal_strength`, `is_constant_series`, `analyze_series`, `ensure_month_start_col`, `validate_month_ms`, `ensure_datetime_freq` |
| `autoregressive_features` | `add_lag_features`, `add_rolling_features`, `add_seasonal_rolling_features`, `add_ewma_features` |
| `temporal_features` | `add_temporal_features`, `add_fourier_features`, `bulk_add_fourier_features` |
| `decomposition` | `BaseDecomposition`, `MultiSeasonalDecomposition`, `FourierDecomposition`, `DecomposeResult` |
| `outlier` | `detect_outlier_sd`, `detect_outlier_iqr`, `detect_outlier_isolation_forest` |
| `echoss_plot_bokeh` **[plot]** | `plot_lines`, `plot_datetime`, `plot_forecast`, `plot_train_test_forecast` 등 |
| `echoss_plot_matplot` **[plot]** | `plot_sales_intensity_heatmap` |
| `engines` | `ModelEngine`, `DataShape`, `EngineRegistry`, `StatsmodelsArimaEngine`, `SklearnTabularEngine`, `DartsSeriesEngine`, `HoldoutComparison`, `Ensemble`, `WeightedEnsemble`, `Screener`, `Candidate` |

## 사용법

### 1. TSData 상속 — 도메인 데이터 레이어

```python
from echoss_timeseries import TSData, EventDayFeatures


class BakeryData(TSData):
    """제과점 판매 데이터. 월별 Excel 원천 -> 일별 dense DataFrame."""

    def __init__(self, data_path='data', config='config/bakery_data.yaml', use_cache=False):
        super().__init__(data_path, config, use_cache=use_cache)
        self.id_col = self.config.get('id_col')
        self.time_col = self.config.get('time_col')
        self.target_col = self.config.get('target_col')

    def load_raw(self):
        """도메인 원천(Excel/DB) 파싱. 기반 클래스의 add_data() 로 등록한다."""
        raw_df = ...  # 도메인 파싱
        self.add_data(raw_df, data_key='raw_df')

    def prepare_xy_data(self):
        """기반 클래스의 피처 메서드와 EventDayFeatures 를 조합한다. 각 호출은 (df, 추가된_컬럼명) 을 반환한다."""
        df = self.get_dataframe('raw_df')
        df = self.fill_to_frequency(df, 'D', time_col=self.time_col, id_col=self.id_col)
        df, _ = self.add_temporal_features(df, time_col=self.time_col, frequency='D')
        df, _ = EventDayFeatures(country="KR").add_features(df, time_col=self.time_col)
        df, _ = self.add_lag_features(df, lags=[1, 7, 14], column=self.target_col, ts_id=self.id_col)
        df, _ = self.add_rolling_features(df, rolls=[7, 28], column=self.target_col, ts_id=self.id_col)
        self.add_data(df, data_key='daily_dense_df')
```

데이터는 항상 cache 에 쓰고, `use_cache=True` 일 때만 cache 에서 읽는다. cache 는 `cache_dir`(기본 `cached`, 실행 폴더 기준)에
`{config 파일명}_{data_key}` 로 저장되며, 같은 `cache_dir` 의 인스턴스는 cache 를 공유한다(config 가 dict 이면 이름은 `cache`).
나누려면 `cache_dir` 나 `cache_suffix` 를 지정한다.

### 2. TSModel 상속 — 도메인 모델 레이어

`run_models()` 가 `prepare_dataset()` -> `training_model()` -> `evaluation_model()` 순서로
자식 클래스 구현을 호출한다. 단계 간 데이터 전달은 `self.runtime_dict` 를 사용한다.

```python
from echoss_timeseries import TSModel


class BakeryModel(TSModel):
    """2단계 예측: has_sales 분류 -> 수량 회귀."""

    def prepare_dataset(self, df):
        # split 은 데이터 레이어 책임 — self.ts_data 를 통해 호출한다
        train_df, test_df = self.ts_data.split_data(df, split_date=self.config.get('split_date'))
        self.runtime_dict['train_df'] = train_df
        self.runtime_dict['test_df'] = test_df

    def training_model(self):
        train_df = self.runtime_dict['train_df']
        # 도메인 학습 엔진 호출 (AutoGluon, pycaret 등)
        self.runtime_dict['predictor'] = ...
        return self.runtime_dict

    def evaluation_model(self):
        test_df = self.runtime_dict['test_df']
        pred_df = self.runtime_dict['pred_df']
        return self.calculate_forecast_score(
            test_df[self.config['target_col']], pred_df['pred_y'],
        )


ts_data = BakeryData(data_path='data', config='config/bakery_data.yaml')
ts_data.load_raw()
ts_data.prepare_xy_data()

model = BakeryModel(ts_data, config='config/bakery_model.yaml', result_save_path='results/')
model.run_models(data='daily_dense_df', training_kwargs={'year_month': '2026-05'})
```

인스턴스는 원하는 만큼 만들 수 있다. `TSData` 와 `TSModel` 의 가변 상태(`data_map`,
`runtime_dict`, scaler·encoder 맵 등)는 인스턴스마다 분리된다.

`show_plot` 같은 불변 플래그만 클래스 레벨에 남아 있어, 자식 클래스에서 클래스 레벨로
기본값을 바꿀 수 있다.

### 3. 시계열 진단 (tsdiag)

상속 없이 모듈 함수로 바로 사용한다.

```python
from echoss_timeseries import analyze_series, stationarity_tests, naive_cv_scores

result = analyze_series(ts, freq='D', seasonal_period=7, max_ar=12, horizon=14)
print(result['predictability_score'], result['seasonal_strength'])

print(stationarity_tests(ts))              # adf_pvalue, kpss_pvalue
print(naive_cv_scores(ts, horizon=14, seasonal_period=7))
```

도메인 컬럼명은 라이브러리 기본값으로 두지 않는다. `validate_month_ms(df, time_col='날짜')`
처럼 호출부가 명시 전달한다.

## 알려진 한계 (1.0.0)

고치지 않고 공개한 동작이다. 고치면 동작이 바뀌므로 다음 major 에서 다룬다.

| 대상 | 한계 | 우회 |
|---|---|---|
| `TSData.set_dataframe` | 등록되지 않은 `data_key` 로 부르면 예외 없이 지나가고 cache 에만 쓴다 | 새 키 등록은 `add_data`, `set_dataframe` 은 기존 키 갱신에만 쓴다 |
| `EventDayFeatures.add_features` | 넘겨받은 DataFrame 에 컬럼을 직접 더한다 | 원본을 지켜야 하면 `df.copy()` 를 넘긴다 |
| `HoldoutComparison.run` | 시계열 하나라도 결측이 있으면 비교 전체를 `ValueError` 로 거부한다 | 결측을 미리 채우거나 시계열마다 따로 `run()` 한다 |
| `analyze_series` 의 `predictability_score` | 실측 판매 자료(상품 71개)에서 실제 예측 오차와 반대 방향으로 움직였다 | 모델 선택이나 예측 난이도 판정에 쓰지 않는다 |
| `MultiSeasonalDecomposition('fourier', ...)` | 요일 주기(`day_of_week`) 성분의 세기가 항상 0 으로 나온다 | 요일 주기는 STL 이나 averages 방식으로 분해한다 |
| `seasonal_esd` | 원 알고리즘(S-H-ESD)대로 추세를 빼지 않아 추세가 있는 자료에서 이상치를 놓친다 | 추세를 먼저 빼고 넘긴다 |
| `is_constant_series` | 넘파이 불리언을 돌려준다 | `is True` 대신 진리값으로 비교한다 |

## 문서

| 문서 | 내용 |
|---|---|
| [`prior-work.jsonl`](prior-work.jsonl) | 작업원장. 이전 작업의 결론과 다시 하지 말 것, 백로그 |
| [`docs/known-issues.md`](docs/known-issues.md) | 사용자용. 알려진 문제와 우회 방법 |
| [`docs/backlog.md`](docs/backlog.md) | 개발자용. 결함 대장(B4~B13), config 주도 실행 통제 미비(G1~G4), 참조 프로젝트 결함(S1~S2) |
| [`docs/design/engine-abstraction.md`](docs/design/engine-abstraction.md) | 학습 엔진 추상화 ABC 설계 (구현 전) |

## 앞으로

알고리즘을 교체하고 결합할 수 있는 계층을 만들고 있다 — 후보 알고리즘을 추리는 **선별기**,
알고리즘 하나를 감싸는 **모델 엔진**, 여러 모델을 합치는 **앙상블** 셋으로 나눈다.

**모델 엔진과 앙상블은 동작한다. 선별기는 ABC 만 있다** — AutoML 어댑터는 아직 없다.

```python
from echoss_timeseries import EngineRegistry, WeightedEnsemble

# 알고리즘은 config 로 고른다. 이름은 알고리즘 이름이다 (라이브러리 이름이 아니다)
models = EngineRegistry.build_models({"models": [
    {"name": "arima", "hyperparameters": {"order": [1, 1, 1]}},
    {"name": "random_forest", "problem_type": "regression", "target_col": "y"},
]})

engine = models[0].fit(series)
pred = engine.predict(future_index)   # 예측 길이는 넘긴 인덱스의 길이로 정해진다

# 여러 모델을 검증오차 역수로 가중 평균한다
ensemble = WeightedEnsemble().fit(fitted_models, (valid_x, valid_y))
print(ensemble.weights())             # 어느 모델이 얼마나 기여했는지 설명된다
```

config 에 모르는 이름이나 키를 적으면 **예외**가 난다. 조용히 무시하면 그 설정이 적용되지
않은 채 실행되고, 결과만 보고는 원인을 찾을 수 없다.

설계는 `docs/design/engine-abstraction.md` 에 있다.

## License

Apache-2.0
