dfm-python 0.3.11__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dfm_python-0.4.0/.git +1 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/PKG-INFO +5 -3
- dfm_python-0.4.0/SKTIME_INTEGRATION_PLAN.md +562 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/pyproject.toml +8 -3
- {dfm_python-0.3.11 → dfm_python-0.4.0}/src/dfm_python/__init__.py +103 -60
- dfm_python-0.4.0/src/dfm_python/config/__init__.py +65 -0
- dfm_python-0.4.0/src/dfm_python/config/params.py +202 -0
- {dfm_python-0.3.11/src/dfm_python/core → dfm_python-0.4.0/src/dfm_python/config}/results.py +96 -71
- {dfm_python-0.3.11 → dfm_python-0.4.0}/src/dfm_python/config/schema.py +260 -331
- {dfm_python-0.3.11/src/dfm_python/core → dfm_python-0.4.0/src/dfm_python/config}/structure.py +1 -1
- dfm_python-0.4.0/src/dfm_python/config/utils.py +93 -0
- dfm_python-0.4.0/src/dfm_python/encoder/__init__.py +33 -0
- dfm_python-0.4.0/src/dfm_python/encoder/base.py +0 -0
- dfm_python-0.4.0/src/dfm_python/encoder/pca.py +157 -0
- dfm_python-0.4.0/src/dfm_python/encoder/vae.py +291 -0
- dfm_python-0.4.0/src/dfm_python/lightning/__init__.py +48 -0
- dfm_python-0.4.0/src/dfm_python/lightning/callbacks.py +0 -0
- dfm_python-0.4.0/src/dfm_python/lightning/data_module.py +364 -0
- dfm_python-0.4.0/src/dfm_python/lightning/ddfm_module.py +553 -0
- dfm_python-0.4.0/src/dfm_python/lightning/dfm_module.py +466 -0
- dfm_python-0.4.0/src/dfm_python/lightning/utils.py +0 -0
- dfm_python-0.4.0/src/dfm_python/logger/__init__.py +51 -0
- dfm_python-0.4.0/src/dfm_python/logger/inference_logger.py +270 -0
- dfm_python-0.4.0/src/dfm_python/logger/logger.py +110 -0
- dfm_python-0.4.0/src/dfm_python/logger/train_logger.py +309 -0
- dfm_python-0.4.0/src/dfm_python/models/__init__.py +36 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/src/dfm_python/models/base.py +24 -28
- dfm_python-0.4.0/src/dfm_python/models/ddfm.py +1120 -0
- dfm_python-0.4.0/src/dfm_python/models/dfm.py +810 -0
- dfm_python-0.4.0/src/dfm_python/models/estimation.py +352 -0
- dfm_python-0.4.0/src/dfm_python/nowcast/__init__.py +28 -0
- dfm_python-0.4.0/src/dfm_python/nowcast/dataview.py +143 -0
- dfm_python-0.4.0/src/dfm_python/nowcast/helpers.py +554 -0
- {dfm_python-0.3.11/src/dfm_python/nowcasting → dfm_python-0.4.0/src/dfm_python/nowcast}/nowcast.py +57 -536
- dfm_python-0.4.0/src/dfm_python/nowcast/utils.py +290 -0
- dfm_python-0.4.0/src/dfm_python/ssm/__init__.py +47 -0
- dfm_python-0.4.0/src/dfm_python/ssm/em.py +488 -0
- dfm_python-0.4.0/src/dfm_python/ssm/kalman.py +604 -0
- dfm_python-0.4.0/src/dfm_python/ssm/utils.py +379 -0
- dfm_python-0.4.0/src/dfm_python/trainer/__init__.py +20 -0
- dfm_python-0.4.0/src/dfm_python/trainer/ddfm.py +196 -0
- dfm_python-0.4.0/src/dfm_python/trainer/dfm.py +153 -0
- dfm_python-0.4.0/src/dfm_python/transformations/__init__.py +35 -0
- dfm_python-0.4.0/src/dfm_python/transformations/sktime.py +40 -0
- dfm_python-0.4.0/src/dfm_python/transformations/transformers.py +320 -0
- dfm_python-0.4.0/src/dfm_python/transformations/utils.py +339 -0
- dfm_python-0.4.0/src/dfm_python/utils/__init__.py +201 -0
- dfm_python-0.4.0/src/dfm_python/utils/data.py +706 -0
- {dfm_python-0.3.11/src/dfm_python/core → dfm_python-0.4.0/src/dfm_python/utils}/diagnostics.py +11 -87
- dfm_python-0.4.0/src/dfm_python/utils/helpers.py +533 -0
- dfm_python-0.4.0/src/dfm_python/utils/statespace.py +1326 -0
- {dfm_python-0.3.11/src/dfm_python/core → dfm_python-0.4.0/src/dfm_python/utils}/time.py +373 -0
- dfm_python-0.4.0/src/test/__init__.py +226 -0
- dfm_python-0.4.0/src/test/test_ddfm.py +681 -0
- dfm_python-0.4.0/src/test/test_dfm.py +450 -0
- dfm_python-0.4.0/src/test/test_lightning_module.py +678 -0
- dfm_python-0.4.0/src/test/test_nowcast.py +516 -0
- dfm_python-0.4.0/src/test/test_trainer.py +54 -0
- dfm_python-0.4.0/src/test/test_transformations.py +1151 -0
- dfm_python-0.4.0/test_comprehensive.py +1 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/uv.lock +1 -1
- dfm_python-0.3.11/.git +0 -1
- dfm_python-0.3.11/.venv_build/bin/Activate.ps1 +0 -247
- dfm_python-0.3.11/.venv_build/bin/activate +0 -70
- dfm_python-0.3.11/.venv_build/bin/activate.csh +0 -27
- dfm_python-0.3.11/.venv_build/bin/activate.fish +0 -69
- dfm_python-0.3.11/.venv_build/bin/docutils +0 -8
- dfm_python-0.3.11/.venv_build/bin/keyring +0 -8
- dfm_python-0.3.11/.venv_build/bin/markdown-it +0 -8
- dfm_python-0.3.11/.venv_build/bin/normalizer +0 -8
- dfm_python-0.3.11/.venv_build/bin/pip +0 -8
- dfm_python-0.3.11/.venv_build/bin/pip3 +0 -8
- dfm_python-0.3.11/.venv_build/bin/pip3.12 +0 -8
- dfm_python-0.3.11/.venv_build/bin/pygmentize +0 -8
- dfm_python-0.3.11/.venv_build/bin/pyproject-build +0 -8
- dfm_python-0.3.11/.venv_build/bin/python +0 -1
- dfm_python-0.3.11/.venv_build/bin/python3 +0 -1
- dfm_python-0.3.11/.venv_build/bin/python3.12 +0 -1
- dfm_python-0.3.11/.venv_build/bin/rst2html +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2html4 +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2html5 +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2latex +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2man +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2odt +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2pseudoxml +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2s5 +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2xetex +0 -8
- dfm_python-0.3.11/.venv_build/bin/rst2xml +0 -8
- dfm_python-0.3.11/.venv_build/bin/twine +0 -8
- dfm_python-0.3.11/.venv_build/pyvenv.cfg +0 -5
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_194140_025b98_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_194248_82a2a3_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_194423_013beb_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_194532_190712_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_195001_ef69d1_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_195110_eb12bc_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_195439_12eea1_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_195547_0ecebd_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_200209_8b6463_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_200319_c62fea_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_200722_7d6a26_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_200838_320d08_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_200934_65c460_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/blocks/spec_20251118_201046_8e7482_blocks.yaml +0 -29
- dfm_python-0.3.11/config/generated/series/spec_20251118_194140_025b98_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_194248_82a2a3_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_194423_013beb_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_194532_190712_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_195001_ef69d1_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_195110_eb12bc_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_195439_12eea1_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_195547_0ecebd_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_200209_8b6463_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_200319_c62fea_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_200722_7d6a26_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_200838_320d08_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_200934_65c460_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/series/spec_20251118_201046_8e7482_series.yaml +0 -749
- dfm_python-0.3.11/config/generated/spec_20251118_194140_025b98.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_194248_82a2a3.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_194423_013beb.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_194532_190712.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_195001_ef69d1.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_195110_eb12bc.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_195439_12eea1.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_195547_0ecebd.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_200209_8b6463.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_200319_c62fea.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_200722_7d6a26.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_200838_320d08.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_200934_65c460.yaml +0 -29
- dfm_python-0.3.11/config/generated/spec_20251118_201046_8e7482.yaml +0 -29
- dfm_python-0.3.11/src/adapters/__init__.py +0 -52
- dfm_python-0.3.11/src/adapters/data_view.py +0 -230
- dfm_python-0.3.11/src/adapters/database.py +0 -161
- dfm_python-0.3.11/src/adapters/model_result.py +0 -200
- dfm_python-0.3.11/src/dfm_python/api/__init__.py +0 -44
- dfm_python-0.3.11/src/dfm_python/api/model_api.py +0 -1148
- dfm_python-0.3.11/src/dfm_python/api/session_api.py +0 -402
- dfm_python-0.3.11/src/dfm_python/config/__init__.py +0 -35
- dfm_python-0.3.11/src/dfm_python/core/__init__.py +0 -139
- dfm_python-0.3.11/src/dfm_python/core/em.py +0 -1385
- dfm_python-0.3.11/src/dfm_python/core/estimation.py +0 -553
- dfm_python-0.3.11/src/dfm_python/core/helpers.py +0 -1338
- dfm_python-0.3.11/src/dfm_python/core/state_space.py +0 -993
- dfm_python-0.3.11/src/dfm_python/dataloader/__init__.py +0 -30
- dfm_python-0.3.11/src/dfm_python/dataloader/loader.py +0 -763
- dfm_python-0.3.11/src/dfm_python/dataloader/preprocess.py +0 -11
- dfm_python-0.3.11/src/dfm_python/models/__init__.py +0 -19
- dfm_python-0.3.11/src/dfm_python/models/ddfm.py +0 -978
- dfm_python-0.3.11/src/dfm_python/models/dfm.py +0 -213
- dfm_python-0.3.11/src/dfm_python/nowcasting/__init__.py +0 -13
- dfm_python-0.3.11/src/dfm_python/utils/__init__.py +0 -38
- dfm_python-0.3.11/src/dfm_python/utils/convert.py +0 -342
- dfm_python-0.3.11/src/dfm_python/utils/loss.py +0 -184
- dfm_python-0.3.11/src/test/test_api.py +0 -466
- dfm_python-0.3.11/src/test/test_core.py +0 -339
- dfm_python-0.3.11/src/test/test_ddfm.py +0 -645
- dfm_python-0.3.11/src/test/test_dfm_regression.py +0 -172
- dfm_python-0.3.11/src/test/test_integration.py +0 -183
- dfm_python-0.3.11/src/test/test_kalman.py +0 -205
- dfm_python-0.3.11/src/test/test_nowcasting.py +0 -1280
- dfm_python-0.3.11/tutorial/basic_tutorial.py +0 -1118
- {dfm_python-0.3.11 → dfm_python-0.4.0}/.gitignore +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/.python-version +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/LICENSE +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/MANIFEST.in +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/README.md +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/blocks/default.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/blocks/sample_spec.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/blocks/sample_spec2.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/default.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/experiment/ddfm_example.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/experiment/exp1.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/experiment/exp2.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/experiment/exp3.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/series/default.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/series/sample_spec.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/config/series/sample_spec2.yaml +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/data/mdfred_snapshot_monthly.csv +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/data/mdfred_snapshot_quarterly.csv +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/data/sample_data.csv +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/data/sample_spec.csv +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/full_tutorial_example.ipynb +0 -0
- {dfm_python-0.3.11 → dfm_python-0.4.0}/src/dfm_python/config/io.py +0 -0
dfm_python-0.4.0/.git
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitdir: ../.git/modules/dfm-python
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dfm-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Dynamic Factor Model (DFM) estimation and nowcasting in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/yourusername/dfm-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
|
|
@@ -24,24 +24,26 @@ Requires-Dist: matplotlib>=3.5.0
|
|
|
24
24
|
Requires-Dist: numpy>=1.24.0
|
|
25
25
|
Requires-Dist: polars>=0.20.0
|
|
26
26
|
Requires-Dist: scipy>=1.10.0
|
|
27
|
+
Requires-Dist: torch>=2.0.0
|
|
27
28
|
Requires-Dist: twine>=6.2.0
|
|
28
29
|
Provides-Extra: all
|
|
29
30
|
Requires-Dist: hydra-core>=1.3.0; extra == 'all'
|
|
30
31
|
Requires-Dist: omegaconf>=2.3.0; extra == 'all'
|
|
31
32
|
Requires-Dist: pytest>=7.0.0; extra == 'all'
|
|
32
33
|
Requires-Dist: scikit-learn>=1.0.0; extra == 'all'
|
|
34
|
+
Requires-Dist: sktime>=0.27.0; extra == 'all'
|
|
33
35
|
Requires-Dist: sqlalchemy>=2.0.0; extra == 'all'
|
|
34
|
-
Requires-Dist: torch>=2.0.0; extra == 'all'
|
|
35
36
|
Provides-Extra: db
|
|
36
37
|
Requires-Dist: sqlalchemy>=2.0.0; extra == 'db'
|
|
37
38
|
Provides-Extra: deep
|
|
38
|
-
Requires-Dist: torch>=2.0.0; extra == 'deep'
|
|
39
39
|
Provides-Extra: dev
|
|
40
40
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
41
41
|
Requires-Dist: scikit-learn>=1.0.0; extra == 'dev'
|
|
42
42
|
Provides-Extra: hydra
|
|
43
43
|
Requires-Dist: hydra-core>=1.3.0; extra == 'hydra'
|
|
44
44
|
Requires-Dist: omegaconf>=2.3.0; extra == 'hydra'
|
|
45
|
+
Provides-Extra: transform
|
|
46
|
+
Requires-Dist: sktime>=0.27.0; extra == 'transform'
|
|
45
47
|
Description-Content-Type: text/markdown
|
|
46
48
|
|
|
47
49
|
# dfm-python: Dynamic Factor Models for Python
|
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
# sktime (Polars 지원) Transformations 통합 계획
|
|
2
|
+
|
|
3
|
+
## 목표
|
|
4
|
+
|
|
5
|
+
sktime의 `ColumnTransformer` (Polars 지원)를 활용하여 dfm-python의 transformation과 standardization을 통합하고, polars DataFrame을 직접 사용할 수 있도록 구조 개선.
|
|
6
|
+
|
|
7
|
+
## 현재 구조 분석
|
|
8
|
+
|
|
9
|
+
### 현재 전처리 위치
|
|
10
|
+
1. **`dataloader/loader.py`**: `transform_data()` - transformation만 적용 (config 기반)
|
|
11
|
+
2. **`core/estimation.py`**: `standardize_data()` - standardization만 적용
|
|
12
|
+
3. **`models/dfm.py`**: `DFMLinear.fit()` - standardization 적용
|
|
13
|
+
4. **`models/ddfm.py`**: `DDFM.fit()` - standardization 적용
|
|
14
|
+
|
|
15
|
+
### 문제점
|
|
16
|
+
- Transformation과 standardization이 분리되어 있음
|
|
17
|
+
- `DFMLinear.fit()`에서는 transformation이 적용되지 않음 (config의 transformation 무시)
|
|
18
|
+
- Polars DataFrame → numpy array 변환 필요
|
|
19
|
+
- 중복 코드 (여러 곳에서 standardization)
|
|
20
|
+
|
|
21
|
+
## 제안 구조
|
|
22
|
+
|
|
23
|
+
### 1. 새로운 모듈 구조
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
dfm-python/src/dfm_python/
|
|
27
|
+
├── transformations/
|
|
28
|
+
│ ├── __init__.py # Public API export
|
|
29
|
+
│ ├── scaler.py # DFMScaler 클래스 (핵심)
|
|
30
|
+
│ ├── transformers.py # Custom transformation 함수들
|
|
31
|
+
│ └── sktime_wrappers.py # sktime optional dependency 처리
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. DFMScaler 클래스 설계
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# transformations/scaler.py
|
|
38
|
+
|
|
39
|
+
class DFMScaler:
|
|
40
|
+
"""DFM Scaler using sktime transformers with Polars support.
|
|
41
|
+
|
|
42
|
+
This class handles:
|
|
43
|
+
1. Series-specific transformations (lin, log, chg, ch1, pch, pc1, pca, cch, cca)
|
|
44
|
+
2. Frequency-aware transformations (based on series frequency)
|
|
45
|
+
3. Global standardization (mean=0, std=1 for entire dataset)
|
|
46
|
+
|
|
47
|
+
Uses sktime's ColumnTransformer with Polars support for efficient processing.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, config: DFMConfig):
|
|
51
|
+
"""Initialize scaler from DFMConfig.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
config : DFMConfig
|
|
56
|
+
DFM configuration containing series transformations and frequencies
|
|
57
|
+
"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def fit(self, X: pl.DataFrame, y=None) -> 'DFMScaler':
|
|
61
|
+
"""Fit the scaler to data.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
X : pl.DataFrame
|
|
66
|
+
Input data (T x N), where T is time periods and N is number of series.
|
|
67
|
+
Column names should match series_ids from config.
|
|
68
|
+
y : optional
|
|
69
|
+
Not used, present for sklearn compatibility
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
self : DFMScaler
|
|
74
|
+
Fitted scaler instance
|
|
75
|
+
"""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
def transform(self, X: pl.DataFrame, y=None) -> pl.DataFrame:
|
|
79
|
+
"""Transform data using fitted scaler.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
X : pl.DataFrame
|
|
84
|
+
Input data (T x N)
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
X_transformed : pl.DataFrame
|
|
89
|
+
Transformed and standardized data
|
|
90
|
+
"""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def fit_transform(self, X: pl.DataFrame, y=None) -> pl.DataFrame:
|
|
94
|
+
"""Fit and transform in one step."""
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
def inverse_transform(self, X: pl.DataFrame, y=None) -> pl.DataFrame:
|
|
98
|
+
"""Inverse transform (for forecasting/backtesting).
|
|
99
|
+
|
|
100
|
+
Note: Some transformations (pch, pc1, etc.) are not fully invertible
|
|
101
|
+
due to division by zero handling. Returns best-effort reconstruction.
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def Mx(self) -> np.ndarray:
|
|
107
|
+
"""Mean values used for standardization (N,)"""
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def Wx(self) -> np.ndarray:
|
|
112
|
+
"""Standard deviation values used for standardization (N,)"""
|
|
113
|
+
pass
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 3. Optional Dependency 처리
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# transformations/sktime_wrappers.py
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
from sktime.transformations.compose import (
|
|
123
|
+
ColumnTransformer,
|
|
124
|
+
TransformerPipeline
|
|
125
|
+
)
|
|
126
|
+
from sktime.transformations.series.log import LogTransformer
|
|
127
|
+
from sktime.transformations.series.difference import Differencer
|
|
128
|
+
from sktime.transformations.series.func_transform import FunctionTransformer
|
|
129
|
+
from sklearn.preprocessing import StandardScaler
|
|
130
|
+
HAS_SKTIME = True
|
|
131
|
+
except ImportError:
|
|
132
|
+
HAS_SKTIME = False
|
|
133
|
+
ColumnTransformer = None
|
|
134
|
+
TransformerPipeline = None
|
|
135
|
+
LogTransformer = None
|
|
136
|
+
Differencer = None
|
|
137
|
+
FunctionTransformer = None
|
|
138
|
+
StandardScaler = None
|
|
139
|
+
|
|
140
|
+
def check_sktime_available():
|
|
141
|
+
"""Check if sktime is available."""
|
|
142
|
+
if not HAS_SKTIME:
|
|
143
|
+
raise ImportError(
|
|
144
|
+
"sktime is required for DFMScaler. "
|
|
145
|
+
"Install it with: pip install dfm-python[transform]"
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 4. 통합 전략
|
|
150
|
+
|
|
151
|
+
#### 전략 A: DFMScaler를 필수로 사용 (권장)
|
|
152
|
+
|
|
153
|
+
**장점**:
|
|
154
|
+
- 일관된 transformation/standardization 처리
|
|
155
|
+
- Polars 직접 지원
|
|
156
|
+
- 코드 중복 제거
|
|
157
|
+
|
|
158
|
+
**구현**:
|
|
159
|
+
1. `DFMLinear.fit()`에서 `DFMScaler` 사용
|
|
160
|
+
2. `core/estimation.py`의 `standardize_data()` 제거 (또는 deprecated)
|
|
161
|
+
3. `dataloader/loader.py`의 `transform_data()` 제거 (또는 deprecated)
|
|
162
|
+
|
|
163
|
+
**단점**:
|
|
164
|
+
- sktime이 optional dependency이므로, 없으면 fallback 필요
|
|
165
|
+
|
|
166
|
+
#### 전략 B: DFMScaler를 선택적으로 사용
|
|
167
|
+
|
|
168
|
+
**장점**:
|
|
169
|
+
- Backward compatibility 유지
|
|
170
|
+
- sktime 없이도 동작 가능
|
|
171
|
+
|
|
172
|
+
**구현**:
|
|
173
|
+
1. `DFMLinear.fit()`에서 `use_sktime` 플래그로 선택
|
|
174
|
+
2. sktime 없으면 기존 방식 사용
|
|
175
|
+
|
|
176
|
+
**단점**:
|
|
177
|
+
- 두 가지 코드 경로 유지 필요
|
|
178
|
+
- 복잡도 증가
|
|
179
|
+
|
|
180
|
+
## 권장 구현: 전략 A (DFMScaler 필수)
|
|
181
|
+
|
|
182
|
+
### 단계별 구현
|
|
183
|
+
|
|
184
|
+
#### Step 1: transformations 모듈 생성
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# transformations/__init__.py
|
|
188
|
+
from .scaler import DFMScaler
|
|
189
|
+
from .transformers import (
|
|
190
|
+
make_pch_transformer,
|
|
191
|
+
make_pc1_transformer,
|
|
192
|
+
make_pca_transformer,
|
|
193
|
+
make_cch_transformer,
|
|
194
|
+
make_cca_transformer,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
__all__ = [
|
|
198
|
+
'DFMScaler',
|
|
199
|
+
'make_pch_transformer',
|
|
200
|
+
'make_pc1_transformer',
|
|
201
|
+
'make_pca_transformer',
|
|
202
|
+
'make_cch_transformer',
|
|
203
|
+
'make_cca_transformer',
|
|
204
|
+
]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Step 2: DFMScaler 구현
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
# transformations/scaler.py
|
|
211
|
+
|
|
212
|
+
import numpy as np
|
|
213
|
+
import polars as pl
|
|
214
|
+
from typing import Optional
|
|
215
|
+
from ..config import DFMConfig
|
|
216
|
+
from .sktime_wrappers import (
|
|
217
|
+
check_sktime_available,
|
|
218
|
+
ColumnTransformer,
|
|
219
|
+
TransformerPipeline,
|
|
220
|
+
LogTransformer,
|
|
221
|
+
Differencer,
|
|
222
|
+
FunctionTransformer,
|
|
223
|
+
StandardScaler,
|
|
224
|
+
)
|
|
225
|
+
from .transformers import (
|
|
226
|
+
make_pch_transformer,
|
|
227
|
+
make_pc1_transformer,
|
|
228
|
+
make_pca_transformer,
|
|
229
|
+
make_cch_transformer,
|
|
230
|
+
make_cca_transformer,
|
|
231
|
+
log_transform,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Frequency → lag 매핑
|
|
235
|
+
FREQ_TO_LAG_YOY = {'m': 12, 'q': 4, 'sa': 2, 'a': 1}
|
|
236
|
+
FREQ_TO_LAG_STEP = {'m': 1, 'q': 3, 'sa': 6, 'a': 12}
|
|
237
|
+
|
|
238
|
+
def get_periods_per_year(freq: str) -> int:
|
|
239
|
+
"""Get number of periods per year for a frequency"""
|
|
240
|
+
return FREQ_TO_LAG_YOY.get(freq, 12)
|
|
241
|
+
|
|
242
|
+
def get_annual_factor(freq: str, step: int) -> float:
|
|
243
|
+
"""Get annualization factor for a frequency and step"""
|
|
244
|
+
periods_per_year = get_periods_per_year(freq)
|
|
245
|
+
return periods_per_year / step
|
|
246
|
+
|
|
247
|
+
class DFMScaler:
|
|
248
|
+
"""DFM Scaler using sktime transformers with Polars support."""
|
|
249
|
+
|
|
250
|
+
def __init__(self, config: DFMConfig):
|
|
251
|
+
check_sktime_available()
|
|
252
|
+
|
|
253
|
+
self.config = config
|
|
254
|
+
self.transformations = [s.transformation for s in config.series]
|
|
255
|
+
self.frequencies = [s.frequency for s in config.series]
|
|
256
|
+
self.series_ids = config.get_series_ids()
|
|
257
|
+
|
|
258
|
+
# 각 series별로 TransformerPipeline 구성
|
|
259
|
+
transformers = []
|
|
260
|
+
for i, (trans, freq) in enumerate(zip(self.transformations, self.frequencies)):
|
|
261
|
+
trafo_pipeline = self._create_transformation_pipeline(trans, freq)
|
|
262
|
+
transformers.append((f'series_{i}', trafo_pipeline, i))
|
|
263
|
+
|
|
264
|
+
# ColumnTransformer 구성 (Polars 지원!)
|
|
265
|
+
self.column_transformer = ColumnTransformer(transformers)
|
|
266
|
+
|
|
267
|
+
# 전체 pipeline: ColumnTransformer → StandardScaler
|
|
268
|
+
self.pipeline = TransformerPipeline([
|
|
269
|
+
("transform", self.column_transformer),
|
|
270
|
+
("scaler", StandardScaler())
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
# Polars 출력 설정
|
|
274
|
+
self.pipeline.set_output(transform="polars")
|
|
275
|
+
|
|
276
|
+
# Standardization 파라미터 (fit 후 설정됨)
|
|
277
|
+
self._Mx: Optional[np.ndarray] = None
|
|
278
|
+
self._Wx: Optional[np.ndarray] = None
|
|
279
|
+
|
|
280
|
+
def _create_transformation_pipeline(self, trans: str, freq: str) -> TransformerPipeline:
|
|
281
|
+
"""Create transformation pipeline for a single series."""
|
|
282
|
+
if trans == 'lin':
|
|
283
|
+
return TransformerPipeline([
|
|
284
|
+
FunctionTransformer(func=lambda x: x)
|
|
285
|
+
])
|
|
286
|
+
elif trans == 'log':
|
|
287
|
+
return TransformerPipeline([
|
|
288
|
+
FunctionTransformer(func=log_transform)
|
|
289
|
+
])
|
|
290
|
+
elif trans == 'chg':
|
|
291
|
+
lag = FREQ_TO_LAG_STEP.get(freq, 1)
|
|
292
|
+
return TransformerPipeline([
|
|
293
|
+
Differencer(lags=[lag])
|
|
294
|
+
])
|
|
295
|
+
elif trans == 'ch1':
|
|
296
|
+
lag = FREQ_TO_LAG_YOY.get(freq, 1)
|
|
297
|
+
return TransformerPipeline([
|
|
298
|
+
Differencer(lags=[lag])
|
|
299
|
+
])
|
|
300
|
+
elif trans == 'pch':
|
|
301
|
+
step = FREQ_TO_LAG_STEP.get(freq, 1)
|
|
302
|
+
return TransformerPipeline([
|
|
303
|
+
make_pch_transformer(step)
|
|
304
|
+
])
|
|
305
|
+
elif trans == 'pc1':
|
|
306
|
+
year_step = FREQ_TO_LAG_YOY.get(freq, 12)
|
|
307
|
+
return TransformerPipeline([
|
|
308
|
+
make_pc1_transformer(year_step)
|
|
309
|
+
])
|
|
310
|
+
elif trans == 'pca':
|
|
311
|
+
step = FREQ_TO_LAG_STEP.get(freq, 1)
|
|
312
|
+
annual_factor = get_annual_factor(freq, step)
|
|
313
|
+
return TransformerPipeline([
|
|
314
|
+
make_pca_transformer(step, annual_factor)
|
|
315
|
+
])
|
|
316
|
+
elif trans == 'cch':
|
|
317
|
+
step = FREQ_TO_LAG_STEP.get(freq, 1)
|
|
318
|
+
return TransformerPipeline([
|
|
319
|
+
make_cch_transformer(step)
|
|
320
|
+
])
|
|
321
|
+
elif trans == 'cca':
|
|
322
|
+
step = FREQ_TO_LAG_STEP.get(freq, 1)
|
|
323
|
+
annual_factor = get_annual_factor(freq, step)
|
|
324
|
+
return TransformerPipeline([
|
|
325
|
+
make_cca_transformer(step, annual_factor)
|
|
326
|
+
])
|
|
327
|
+
else:
|
|
328
|
+
# Default: Identity
|
|
329
|
+
return TransformerPipeline([
|
|
330
|
+
FunctionTransformer(func=lambda x: x)
|
|
331
|
+
])
|
|
332
|
+
|
|
333
|
+
def fit(self, X: pl.DataFrame, y=None) -> 'DFMScaler':
|
|
334
|
+
"""Fit the scaler to data."""
|
|
335
|
+
# Ensure column order matches series_ids
|
|
336
|
+
if list(X.columns) != self.series_ids:
|
|
337
|
+
X = X.select(self.series_ids)
|
|
338
|
+
|
|
339
|
+
# Fit and transform
|
|
340
|
+
X_transformed = self.pipeline.fit_transform(X, y)
|
|
341
|
+
|
|
342
|
+
# Extract StandardScaler parameters
|
|
343
|
+
self.scaler = self.pipeline.steps[1][1] # StandardScaler
|
|
344
|
+
self._Mx = self.scaler.mean_
|
|
345
|
+
self._Wx = self.scaler.scale_
|
|
346
|
+
|
|
347
|
+
return self
|
|
348
|
+
|
|
349
|
+
def transform(self, X: pl.DataFrame, y=None) -> pl.DataFrame:
|
|
350
|
+
"""Transform data using fitted scaler."""
|
|
351
|
+
# Ensure column order matches series_ids
|
|
352
|
+
if list(X.columns) != self.series_ids:
|
|
353
|
+
X = X.select(self.series_ids)
|
|
354
|
+
|
|
355
|
+
return self.pipeline.transform(X, y)
|
|
356
|
+
|
|
357
|
+
def fit_transform(self, X: pl.DataFrame, y=None) -> pl.DataFrame:
|
|
358
|
+
"""Fit and transform in one step."""
|
|
359
|
+
return self.fit(X, y).transform(X, y)
|
|
360
|
+
|
|
361
|
+
def inverse_transform(self, X: pl.DataFrame, y=None) -> pl.DataFrame:
|
|
362
|
+
"""Inverse transform (for forecasting/backtesting)."""
|
|
363
|
+
# Note: Some transformations are not fully invertible
|
|
364
|
+
# This is a best-effort implementation
|
|
365
|
+
return self.pipeline.inverse_transform(X, y)
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def Mx(self) -> np.ndarray:
|
|
369
|
+
"""Mean values used for standardization (N,)"""
|
|
370
|
+
if self._Mx is None:
|
|
371
|
+
raise ValueError("Scaler has not been fitted yet.")
|
|
372
|
+
return self._Mx
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def Wx(self) -> np.ndarray:
|
|
376
|
+
"""Standard deviation values used for standardization (N,)"""
|
|
377
|
+
if self._Wx is None:
|
|
378
|
+
raise ValueError("Scaler has not been fitted yet.")
|
|
379
|
+
return self._Wx
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### Step 3: DFMLinear.fit() 수정
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
# models/dfm.py
|
|
386
|
+
|
|
387
|
+
from ..transformations import DFMScaler
|
|
388
|
+
|
|
389
|
+
class DFMLinear(BaseFactorModel):
|
|
390
|
+
def fit(self, X: Union[np.ndarray, pl.DataFrame], config: Optional[DFMConfig] = None, ...):
|
|
391
|
+
# ... (기존 초기화 코드) ...
|
|
392
|
+
|
|
393
|
+
# Convert to polars DataFrame if needed
|
|
394
|
+
if isinstance(X, np.ndarray):
|
|
395
|
+
X = pl.DataFrame(X, schema=series_ids)
|
|
396
|
+
elif not isinstance(X, pl.DataFrame):
|
|
397
|
+
raise TypeError(f"X must be np.ndarray or pl.DataFrame, got {type(X)}")
|
|
398
|
+
|
|
399
|
+
# Create and fit scaler
|
|
400
|
+
scaler = DFMScaler(config)
|
|
401
|
+
X_transformed = scaler.fit_transform(X)
|
|
402
|
+
|
|
403
|
+
# Convert to numpy for estimation (필요한 경우)
|
|
404
|
+
X_numpy = X_transformed.to_numpy()
|
|
405
|
+
|
|
406
|
+
# Extract standardization parameters
|
|
407
|
+
Mx = scaler.Mx
|
|
408
|
+
Wx = scaler.Wx
|
|
409
|
+
|
|
410
|
+
# ... (기존 estimation 코드) ...
|
|
411
|
+
|
|
412
|
+
# 결과에 scaler 저장 (inverse_transform용)
|
|
413
|
+
result.scaler = scaler
|
|
414
|
+
result.Mx = Mx
|
|
415
|
+
result.Wx = Wx
|
|
416
|
+
|
|
417
|
+
return result
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Step 4: core/estimation.py 수정
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
# core/estimation.py
|
|
424
|
+
|
|
425
|
+
def _dfm_core(X: np.ndarray, config: DFMConfig, ...):
|
|
426
|
+
"""DFM estimation core function.
|
|
427
|
+
|
|
428
|
+
Note: This function now expects pre-transformed and standardized data.
|
|
429
|
+
Use DFMScaler before calling this function.
|
|
430
|
+
"""
|
|
431
|
+
# ... (기존 코드, standardization 제거) ...
|
|
432
|
+
|
|
433
|
+
# X는 이미 transformed + standardized 되어 있다고 가정
|
|
434
|
+
# standardization 코드 제거
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 5. Backward Compatibility
|
|
438
|
+
|
|
439
|
+
#### Option 1: Deprecation Warnings
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
# dataloader/loader.py
|
|
443
|
+
|
|
444
|
+
def transform_data(...):
|
|
445
|
+
"""Transform data using config transformations.
|
|
446
|
+
|
|
447
|
+
.. deprecated:: 0.2.0
|
|
448
|
+
Use DFMScaler instead. This function will be removed in 0.3.0.
|
|
449
|
+
"""
|
|
450
|
+
warnings.warn(
|
|
451
|
+
"transform_data() is deprecated. Use DFMScaler instead.",
|
|
452
|
+
DeprecationWarning,
|
|
453
|
+
stacklevel=2
|
|
454
|
+
)
|
|
455
|
+
# ... (기존 코드) ...
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### Option 2: Fallback 구현
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
# transformations/scaler.py
|
|
462
|
+
|
|
463
|
+
class DFMScaler:
|
|
464
|
+
def __init__(self, config: DFMConfig, use_sktime: bool = True):
|
|
465
|
+
if use_sktime:
|
|
466
|
+
try:
|
|
467
|
+
check_sktime_available()
|
|
468
|
+
self._use_sktime = True
|
|
469
|
+
except ImportError:
|
|
470
|
+
if use_sktime:
|
|
471
|
+
raise
|
|
472
|
+
self._use_sktime = False
|
|
473
|
+
else:
|
|
474
|
+
self._use_sktime = False
|
|
475
|
+
|
|
476
|
+
if not self._use_sktime:
|
|
477
|
+
# Fallback to native implementation
|
|
478
|
+
self._init_native(config)
|
|
479
|
+
else:
|
|
480
|
+
self._init_sktime(config)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## 마이그레이션 가이드
|
|
484
|
+
|
|
485
|
+
### 사용자 관점
|
|
486
|
+
|
|
487
|
+
#### Before (현재)
|
|
488
|
+
```python
|
|
489
|
+
from dfm_python import DFM
|
|
490
|
+
from dfm_python.dataloader import load_data
|
|
491
|
+
|
|
492
|
+
model = DFM()
|
|
493
|
+
X, Time, Z = load_data('data.csv', config)
|
|
494
|
+
# load_data 내부에서 transformation 적용
|
|
495
|
+
result = model.fit(X, config)
|
|
496
|
+
# fit 내부에서 standardization 적용
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### After (새로운 구조)
|
|
500
|
+
```python
|
|
501
|
+
from dfm_python import DFM
|
|
502
|
+
from dfm_python.transformations import DFMScaler
|
|
503
|
+
import polars as pl
|
|
504
|
+
|
|
505
|
+
model = DFM()
|
|
506
|
+
X = pl.read_csv('data.csv') # Polars 직접 사용
|
|
507
|
+
scaler = DFMScaler(config)
|
|
508
|
+
X_transformed = scaler.fit_transform(X)
|
|
509
|
+
result = model.fit(X_transformed, config)
|
|
510
|
+
# standardization은 이미 scaler에서 적용됨
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## 테스트 전략
|
|
514
|
+
|
|
515
|
+
1. **Unit Tests**: DFMScaler의 각 transformation 테스트
|
|
516
|
+
2. **Integration Tests**: DFMLinear.fit()과의 통합 테스트
|
|
517
|
+
3. **Backward Compatibility Tests**: 기존 코드와의 호환성 테스트
|
|
518
|
+
4. **Performance Tests**: Polars vs numpy 변환 성능 비교
|
|
519
|
+
|
|
520
|
+
## 의존성 관리
|
|
521
|
+
|
|
522
|
+
### pyproject.toml
|
|
523
|
+
|
|
524
|
+
```toml
|
|
525
|
+
[project.optional-dependencies]
|
|
526
|
+
transform = [
|
|
527
|
+
"sktime>=0.27.0",
|
|
528
|
+
]
|
|
529
|
+
all = [
|
|
530
|
+
"dfm-python[hydra,db,dev,deep,transform]",
|
|
531
|
+
]
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 설치 방법
|
|
535
|
+
|
|
536
|
+
```bash
|
|
537
|
+
# sktime 포함 설치
|
|
538
|
+
pip install dfm-python[transform]
|
|
539
|
+
|
|
540
|
+
# 또는 전체 설치
|
|
541
|
+
pip install dfm-python[all]
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## 장점 요약
|
|
545
|
+
|
|
546
|
+
1. **Polars 직접 지원**: pandas 변환 불필요
|
|
547
|
+
2. **일관된 처리**: transformation + standardization 통합
|
|
548
|
+
3. **코드 중복 제거**: 여러 곳에서 standardization 하지 않음
|
|
549
|
+
4. **확장성**: 새로운 transformation 쉽게 추가 가능
|
|
550
|
+
5. **표준 인터페이스**: sklearn/sktime 호환
|
|
551
|
+
6. **성능**: Polars의 빠른 처리 속도 활용
|
|
552
|
+
|
|
553
|
+
## 다음 단계
|
|
554
|
+
|
|
555
|
+
1. ✅ 구조 설계 (현재)
|
|
556
|
+
2. ⏳ transformations 모듈 구현
|
|
557
|
+
3. ⏳ DFMScaler 구현
|
|
558
|
+
4. ⏳ DFMLinear.fit() 통합
|
|
559
|
+
5. ⏳ 테스트 작성
|
|
560
|
+
6. ⏳ 문서화
|
|
561
|
+
7. ⏳ 마이그레이션 가이드 작성
|
|
562
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dfm-python"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Dynamic Factor Model (DFM) estimation and nowcasting in Python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -25,6 +25,7 @@ dependencies = [
|
|
|
25
25
|
"numpy>=1.24.0",
|
|
26
26
|
"polars>=0.20.0",
|
|
27
27
|
"scipy>=1.10.0",
|
|
28
|
+
"torch>=2.0.0",
|
|
28
29
|
"twine>=6.2.0",
|
|
29
30
|
]
|
|
30
31
|
|
|
@@ -38,14 +39,18 @@ db = [
|
|
|
38
39
|
# psycopg2-binary for PostgreSQL (optional, install separately if needed)
|
|
39
40
|
]
|
|
40
41
|
deep = [
|
|
41
|
-
|
|
42
|
+
# torch is now a core dependency (required for DFM EM algorithm)
|
|
43
|
+
# This section kept for backward compatibility
|
|
44
|
+
]
|
|
45
|
+
transform = [
|
|
46
|
+
"sktime>=0.27.0",
|
|
42
47
|
]
|
|
43
48
|
dev = [
|
|
44
49
|
"pytest>=7.0.0",
|
|
45
50
|
"scikit-learn>=1.0.0",
|
|
46
51
|
]
|
|
47
52
|
all = [
|
|
48
|
-
"dfm-python[hydra,db,dev,deep]",
|
|
53
|
+
"dfm-python[hydra,db,dev,deep,transform]",
|
|
49
54
|
]
|
|
50
55
|
|
|
51
56
|
[build-system]
|