dfm-python 0.3.1__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.1 → dfm_python-0.4.0}/PKG-INFO +11 -9
- {dfm_python-0.3.1 → dfm_python-0.4.0}/README.md +6 -6
- dfm_python-0.4.0/SKTIME_INTEGRATION_PLAN.md +562 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/pyproject.toml +8 -3
- {dfm_python-0.3.1 → 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.1/src/dfm_python/core → dfm_python-0.4.0/src/dfm_python/config}/results.py +96 -71
- {dfm_python-0.3.1 → dfm_python-0.4.0}/src/dfm_python/config/schema.py +260 -331
- {dfm_python-0.3.1/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.1 → 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.1/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.1/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.1/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.1 → dfm_python-0.4.0}/uv.lock +1 -1
- dfm_python-0.3.1/.git +0 -1
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_194140_025b98_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_194248_82a2a3_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_194423_013beb_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_194532_190712_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_195001_ef69d1_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_195110_eb12bc_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_195439_12eea1_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_195547_0ecebd_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_200209_8b6463_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_200319_c62fea_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_200722_7d6a26_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_200838_320d08_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_200934_65c460_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/blocks/spec_20251118_201046_8e7482_blocks.yaml +0 -29
- dfm_python-0.3.1/config/generated/series/spec_20251118_194140_025b98_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_194248_82a2a3_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_194423_013beb_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_194532_190712_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_195001_ef69d1_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_195110_eb12bc_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_195439_12eea1_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_195547_0ecebd_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_200209_8b6463_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_200319_c62fea_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_200722_7d6a26_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_200838_320d08_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_200934_65c460_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/series/spec_20251118_201046_8e7482_series.yaml +0 -749
- dfm_python-0.3.1/config/generated/spec_20251118_194140_025b98.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_194248_82a2a3.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_194423_013beb.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_194532_190712.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_195001_ef69d1.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_195110_eb12bc.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_195439_12eea1.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_195547_0ecebd.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_200209_8b6463.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_200319_c62fea.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_200722_7d6a26.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_200838_320d08.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_200934_65c460.yaml +0 -29
- dfm_python-0.3.1/config/generated/spec_20251118_201046_8e7482.yaml +0 -29
- dfm_python-0.3.1/src/adapters/__init__.py +0 -52
- dfm_python-0.3.1/src/adapters/data_view.py +0 -230
- dfm_python-0.3.1/src/adapters/database.py +0 -161
- dfm_python-0.3.1/src/adapters/model_result.py +0 -200
- dfm_python-0.3.1/src/dfm_python/api/__init__.py +0 -44
- dfm_python-0.3.1/src/dfm_python/api/model_api.py +0 -1148
- dfm_python-0.3.1/src/dfm_python/api/session_api.py +0 -402
- dfm_python-0.3.1/src/dfm_python/config/__init__.py +0 -35
- dfm_python-0.3.1/src/dfm_python/core/__init__.py +0 -139
- dfm_python-0.3.1/src/dfm_python/core/em.py +0 -1385
- dfm_python-0.3.1/src/dfm_python/core/estimation.py +0 -553
- dfm_python-0.3.1/src/dfm_python/core/helpers.py +0 -1338
- dfm_python-0.3.1/src/dfm_python/core/state_space.py +0 -993
- dfm_python-0.3.1/src/dfm_python/dataloader/__init__.py +0 -30
- dfm_python-0.3.1/src/dfm_python/dataloader/loader.py +0 -763
- dfm_python-0.3.1/src/dfm_python/dataloader/preprocess.py +0 -11
- dfm_python-0.3.1/src/dfm_python/models/__init__.py +0 -19
- dfm_python-0.3.1/src/dfm_python/models/ddfm.py +0 -978
- dfm_python-0.3.1/src/dfm_python/models/dfm.py +0 -213
- dfm_python-0.3.1/src/dfm_python/nowcasting/__init__.py +0 -13
- dfm_python-0.3.1/src/dfm_python/utils/__init__.py +0 -38
- dfm_python-0.3.1/src/dfm_python/utils/convert.py +0 -342
- dfm_python-0.3.1/src/dfm_python/utils/loss.py +0 -184
- dfm_python-0.3.1/src/test/test_api.py +0 -466
- dfm_python-0.3.1/src/test/test_core.py +0 -339
- dfm_python-0.3.1/src/test/test_ddfm.py +0 -645
- dfm_python-0.3.1/src/test/test_dfm_regression.py +0 -172
- dfm_python-0.3.1/src/test/test_integration.py +0 -183
- dfm_python-0.3.1/src/test/test_kalman.py +0 -205
- dfm_python-0.3.1/src/test/test_nowcasting.py +0 -1280
- dfm_python-0.3.1/tutorial/basic_tutorial.py +0 -1118
- {dfm_python-0.3.1 → dfm_python-0.4.0}/.gitignore +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/.python-version +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/LICENSE +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/MANIFEST.in +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/blocks/default.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/blocks/sample_spec.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/blocks/sample_spec2.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/default.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/experiment/ddfm_example.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/experiment/exp1.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/experiment/exp2.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/experiment/exp3.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/series/default.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/series/sample_spec.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/config/series/sample_spec2.yaml +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/data/mdfred_snapshot_monthly.csv +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/data/mdfred_snapshot_quarterly.csv +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/data/sample_data.csv +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/data/sample_spec.csv +0 -0
- {dfm_python-0.3.1 → dfm_python-0.4.0}/full_tutorial_example.ipynb +0 -0
- {dfm_python-0.3.1 → 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
|
|
@@ -735,12 +737,12 @@ SQLite data view storage is planned for future releases. Currently, `BasicDataVi
|
|
|
735
737
|
# X_view, Time_view, config = adapter.load_data_view(view_id)
|
|
736
738
|
```
|
|
737
739
|
|
|
738
|
-
### News Decomposition
|
|
740
|
+
### Advanced News Decomposition
|
|
739
741
|
|
|
740
|
-
For advanced users,
|
|
742
|
+
For advanced users, low-level functions are available in the `nowcasting` module:
|
|
741
743
|
|
|
742
744
|
```python
|
|
743
|
-
from dfm_python.
|
|
745
|
+
from dfm_python.nowcasting import para_const
|
|
744
746
|
|
|
745
747
|
# Before new data release
|
|
746
748
|
result_old = model.fit(X_old, config)
|
|
@@ -752,7 +754,7 @@ X_new = ... # Updated data
|
|
|
752
754
|
Res_old = para_const(X_old, result_old, lag=1)
|
|
753
755
|
# ... (see nowcast.py for full implementation)
|
|
754
756
|
|
|
755
|
-
# Recommended: Use Nowcast class
|
|
757
|
+
# Recommended: Use Nowcast class for most use cases (see Nowcasting API section above)
|
|
756
758
|
```
|
|
757
759
|
|
|
758
760
|
### Custom Block Structure
|
|
@@ -1208,7 +1210,7 @@ DATA_VIEW_SOURCE=file # or database
|
|
|
1208
1210
|
|
|
1209
1211
|
## Project Status
|
|
1210
1212
|
|
|
1211
|
-
**Version**: 0.3.
|
|
1213
|
+
**Version**: 0.3.1
|
|
1212
1214
|
**Status**: Stable and production-ready
|
|
1213
1215
|
**Python**: 3.10+
|
|
1214
1216
|
**PyPI**: https://pypi.org/project/dfm-python/
|
|
@@ -1297,7 +1299,7 @@ If you use `dfm-python` in your research, please cite:
|
|
|
1297
1299
|
author = {DFM Python Contributors},
|
|
1298
1300
|
year = {2025},
|
|
1299
1301
|
url = {https://pypi.org/project/dfm-python/},
|
|
1300
|
-
version = {0.
|
|
1302
|
+
version = {0.3.1}
|
|
1301
1303
|
}
|
|
1302
1304
|
```
|
|
1303
1305
|
|
|
@@ -689,12 +689,12 @@ SQLite data view storage is planned for future releases. Currently, `BasicDataVi
|
|
|
689
689
|
# X_view, Time_view, config = adapter.load_data_view(view_id)
|
|
690
690
|
```
|
|
691
691
|
|
|
692
|
-
### News Decomposition
|
|
692
|
+
### Advanced News Decomposition
|
|
693
693
|
|
|
694
|
-
For advanced users,
|
|
694
|
+
For advanced users, low-level functions are available in the `nowcasting` module:
|
|
695
695
|
|
|
696
696
|
```python
|
|
697
|
-
from dfm_python.
|
|
697
|
+
from dfm_python.nowcasting import para_const
|
|
698
698
|
|
|
699
699
|
# Before new data release
|
|
700
700
|
result_old = model.fit(X_old, config)
|
|
@@ -706,7 +706,7 @@ X_new = ... # Updated data
|
|
|
706
706
|
Res_old = para_const(X_old, result_old, lag=1)
|
|
707
707
|
# ... (see nowcast.py for full implementation)
|
|
708
708
|
|
|
709
|
-
# Recommended: Use Nowcast class
|
|
709
|
+
# Recommended: Use Nowcast class for most use cases (see Nowcasting API section above)
|
|
710
710
|
```
|
|
711
711
|
|
|
712
712
|
### Custom Block Structure
|
|
@@ -1162,7 +1162,7 @@ DATA_VIEW_SOURCE=file # or database
|
|
|
1162
1162
|
|
|
1163
1163
|
## Project Status
|
|
1164
1164
|
|
|
1165
|
-
**Version**: 0.3.
|
|
1165
|
+
**Version**: 0.3.1
|
|
1166
1166
|
**Status**: Stable and production-ready
|
|
1167
1167
|
**Python**: 3.10+
|
|
1168
1168
|
**PyPI**: https://pypi.org/project/dfm-python/
|
|
@@ -1251,7 +1251,7 @@ If you use `dfm-python` in your research, please cite:
|
|
|
1251
1251
|
author = {DFM Python Contributors},
|
|
1252
1252
|
year = {2025},
|
|
1253
1253
|
url = {https://pypi.org/project/dfm-python/},
|
|
1254
|
-
version = {0.
|
|
1254
|
+
version = {0.3.1}
|
|
1255
1255
|
}
|
|
1256
1256
|
```
|
|
1257
1257
|
|
|
@@ -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
|
+
|