dfc-kit 1.0.1__tar.gz → 1.0.2__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.
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/CHANGELOG.md +13 -1
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/CITATION.cff +2 -2
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/MANIFEST.in +0 -3
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/PKG-INFO +1 -1
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/api.md +8 -1
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/cli.md +25 -0
- dfc_kit-1.0.2/docs/granger.md +35 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/nbs.md +30 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/states.md +60 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/storage.md +31 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/mkdocs.yml +1 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/pyproject.toml +1 -1
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfc_kit.egg-info/PKG-INFO +1 -1
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfc_kit.egg-info/SOURCES.txt +6 -3
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/__init__.py +1 -1
- dfc_kit-1.0.2/src/dfckit/_feature_selection.py +43 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/models.py +14 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/commands/parser.py +37 -1
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/commands/reporting.py +6 -2
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/commands/states.py +38 -3
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/__init__.py +8 -0
- dfc_kit-1.0.2/src/dfckit/connectivity/granger.py +243 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/data.py +82 -16
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/interpretation.py +3 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/kmeans.py +103 -3
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/streaming.py +2 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/storage/__init__.py +2 -1
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/storage/store.py +158 -9
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/storage/summary.py +40 -24
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_cli.py +148 -0
- dfc_kit-1.0.2/tests/unit/test_feature_selection.py +117 -0
- dfc_kit-1.0.2/tests/unit/test_granger.py +73 -0
- dfc_kit-1.0.2/tests/unit/test_granger_optional_dependency.py +49 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_model_io.py +30 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_states.py +76 -1
- dfc_kit-1.0.2/tests/unit/test_store_summary.py +355 -0
- dfc_kit-1.0.1/ENGINEERING_PLAN.md +0 -42
- dfc_kit-1.0.1/ROADMAP.md +0 -26
- dfc_kit-1.0.1/SECURITY.md +0 -12
- dfc_kit-1.0.1/tests/unit/test_store_summary.py +0 -137
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/CONTRIBUTING.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/LICENSE +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/README.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/benchmarks/README.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/benchmarks/profile_core.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/benchmarks/profile_information.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/architecture.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/correlation.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/datasets.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/getting_started.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/group_inference.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/hmm.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/index.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/inference.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/inference_matching.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/information.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/instantaneous_edges.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/leida.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/lowrank.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/method_inventory.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/model_artifacts.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/partition_graphs.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/reference.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/release.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/state_alignment.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/state_scoring.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/state_selection.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/state_stability.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/tutorial_xcpd_to_states.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/docs/xcpd_input.md +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/setup.cfg +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfc_kit.egg-info/dependency_links.txt +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfc_kit.egg-info/entry_points.txt +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfc_kit.egg-info/requires.txt +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfc_kit.egg-info/top_level.txt +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/_arrays.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/_preprocessing.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/_validation.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/_fields.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/_json.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/_numpy.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/state_alignment.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/state_results.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/state_scoring.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/artifacts/state_stability.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/cli.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/commands/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/commands/source.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/commands/stability.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/_edge_products.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/correlation.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/instantaneous.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/leida.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/lowrank.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/partition.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/connectivity/windows.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/data.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/endpoints.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/hc3.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/independent_endpoints.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/matching.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/multiple_testing.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/nbs.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/nbs_endpoints.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/paired.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/inference/state_metrics.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/information/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/information/_artifact.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/information/estimators.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/information/fixed.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/information/summary.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/io/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/io/xcpd.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/reference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/segments.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/alignment.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/cap.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/cross_validation.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/hmm.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/metrics.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/scoring.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/selection.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/stability.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/states/streaming_hmm.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/storage/_statistics.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/src/dfckit/storage/builders.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/fixtures/audited_kernels.json +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_leida_state_pipeline.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_model_artifact_pipeline.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_xcpd_information.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_xcpd_lowrank.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_xcpd_sliding_window.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_xcpd_state_pipeline.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_xcpd_static_nbs.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/integration/test_xcpd_window_pattern.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/regression/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/regression/test_audited_kernels.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/regression/test_information_reference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/regression/test_nbs_independent_reference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/regression/test_nbs_reference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/__init__.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_artifact_helpers.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_correlation.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_cross_validation.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_data.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_endpoint_inference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_ets.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_hmm.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_independent_endpoint_inference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_inference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_inference_matching.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_information.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_information_io.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_information_parallel.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_information_summary.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_internal_helpers.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_leida.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_lowrank.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_mtd.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_nbs.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_nbs_endpoints.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_outofcore.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_outofcore_hmm.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_partition_graph.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_preprocessing.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_public_api.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_reference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_sliding_window.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_stability.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_alignment_io.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_interpretation.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_metric_inference.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_results.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_scoring_io.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_selection.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_selection_io.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_state_stability_io.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_storage.py +0 -0
- {dfc_kit-1.0.1 → dfc_kit-1.0.2}/tests/unit/test_xcpd.py +0 -0
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.0.
|
|
3
|
+
## 1.0.2 - 2026-09-11
|
|
4
|
+
|
|
5
|
+
- Added exact-feature selection for in-memory and disk-backed state models,
|
|
6
|
+
including clustering on a fixed NBS component's edges.
|
|
7
|
+
- Added conditional Granger predictability with censor-bounded lagged samples
|
|
8
|
+
and VAR diagnostics. SciPy is loaded only when those diagnostics are used,
|
|
9
|
+
preserving the NumPy-only base installation.
|
|
10
|
+
- FeatureStore summaries can now aggregate a named feature set within each
|
|
11
|
+
sample before computing acquisition-level mean, variance, standard deviation,
|
|
12
|
+
and extrema. The CLI exposes `--feature-keys` and `--feature-mean NAME` for
|
|
13
|
+
fixed-network summaries, including sparse NBS components.
|
|
14
|
+
|
|
15
|
+
## 1.0.1
|
|
4
16
|
|
|
5
17
|
- Fixed-length MI/CMI now shares one process pool across acquisition, window
|
|
6
18
|
length, and draw combinations. Bounded task submission avoids materializing
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
include CHANGELOG.md
|
|
2
2
|
include CITATION.cff
|
|
3
3
|
include CONTRIBUTING.md
|
|
4
|
-
include ENGINEERING_PLAN.md
|
|
5
4
|
include LICENSE
|
|
6
5
|
include README.md
|
|
7
|
-
include ROADMAP.md
|
|
8
|
-
include SECURITY.md
|
|
9
6
|
include mkdocs.yml
|
|
10
7
|
recursive-include benchmarks *.md *.py
|
|
11
8
|
recursive-include docs *.md
|
|
@@ -60,10 +60,17 @@ store = FeatureStore.create(
|
|
|
60
60
|
sample_interval_seconds=result.sample_interval_seconds,
|
|
61
61
|
)
|
|
62
62
|
store.append_dataset(dataset)
|
|
63
|
+
|
|
64
|
+
# Select exact edges without duplicating the disk-backed store.
|
|
65
|
+
selected = store.select_features(
|
|
66
|
+
feature_keys=(("V1_L", "PUT-DP_L"), ("V1_R", "PUT-DP_R"))
|
|
67
|
+
)
|
|
63
68
|
```
|
|
64
69
|
|
|
65
70
|
FeatureStore chunks are memory-mappable and retain sequence/acquisition
|
|
66
|
-
boundaries.
|
|
71
|
+
boundaries. A selected feature view preserves those boundaries and is accepted
|
|
72
|
+
by the store-based state fitters. FeatureStore objects are an execution format,
|
|
73
|
+
not a provenance database.
|
|
67
74
|
|
|
68
75
|
## State models
|
|
69
76
|
|
|
@@ -32,6 +32,13 @@ dfc-kit fit-states /path/to/features.store /path/to/models/k4.model \
|
|
|
32
32
|
--max-iter 100 --streaming-tol 1e-4 \
|
|
33
33
|
--streaming-patience 3 --streaming-min-passes 2
|
|
34
34
|
|
|
35
|
+
# Exact ordered edge subset with equal participant-session influence.
|
|
36
|
+
dfc-kit fit-states /path/to/features.store /path/to/models/k2.model \
|
|
37
|
+
--method kmeans --n-states 2 --seed 20260907 \
|
|
38
|
+
--feature-keys /path/to/edges.json \
|
|
39
|
+
--fitting-mode materialized --algorithm lloyd \
|
|
40
|
+
--sample-weight-mode subject_session_balanced
|
|
41
|
+
|
|
35
42
|
dfc-kit predict-states /path/to/features.store /path/to/models/k4.model \
|
|
36
43
|
/path/to/results/k4.labels --subject sub-010
|
|
37
44
|
|
|
@@ -177,6 +184,24 @@ and `maximum`. Variance and standard deviation use all retained samples with
|
|
|
177
184
|
`ddof=0`. Censor-delimited segments remain distinct during feature generation,
|
|
178
185
|
then their valid samples are combined by sample count within the acquisition.
|
|
179
186
|
|
|
187
|
+
To summarize a fixed NBS component, supply its exact ROI-pair keys as a JSON
|
|
188
|
+
array (for example, `[["V1_L", "PUT-DP_L"], ["V1_R", "PUT-DP_R"]]`):
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
dfc-kit summarize-store features/window-fc.store results/nbs24-summary.json \
|
|
192
|
+
--feature-keys nbs24-edges.json --feature-mean NBS24 \
|
|
193
|
+
--statistic mean --statistic standard_deviation
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`--feature-keys` selects columns. `--feature-mean NAME` averages those columns
|
|
197
|
+
equally within each sample, then summarizes the resulting network time series
|
|
198
|
+
under `NAME.mean`, `NAME.standard_deviation`, and any other requested statistics.
|
|
199
|
+
With no key file, the mean uses all store features; with no `--feature-mean`,
|
|
200
|
+
selected features are summarized separately. Signed values and source units
|
|
201
|
+
are preserved. Network standard deviation is computed **after** averaging
|
|
202
|
+
edges, not by averaging their individual standard deviations. The resulting
|
|
203
|
+
endpoint file can be used with `infer-paired-endpoints`.
|
|
204
|
+
|
|
180
205
|
Use `summarize-states`, `summarize-information`, `describe-states`,
|
|
181
206
|
`infer-state-metrics`, `infer-paired-endpoints`, and `infer-paired-nbs` for
|
|
182
207
|
reporting and inference. Every command writes finite JSON and refuses
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Conditional Granger predictability
|
|
2
|
+
|
|
3
|
+
Install the inference dependency before running this analysis:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
python -m pip install 'dfc-kit[inference]'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`conditional_granger()` fits pooled linear VAR models while constructing every
|
|
10
|
+
lagged row inside one contiguous retained-frame segment. Its Geweke statistic is
|
|
11
|
+
the log ratio of restricted to unrestricted target-residual variance (or
|
|
12
|
+
generalized variance for a multivariate target). The restricted model removes
|
|
13
|
+
source lags and retains target and conditioning lags.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from dfckit.connectivity import conditional_granger
|
|
17
|
+
|
|
18
|
+
result = conditional_granger(
|
|
19
|
+
run,
|
|
20
|
+
source=[0],
|
|
21
|
+
target=[1],
|
|
22
|
+
conditioning=[2, 3],
|
|
23
|
+
lag_order=2,
|
|
24
|
+
)
|
|
25
|
+
print(result.geweke, result.spectral_radius, result.whiteness.min_ljung_box_p)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The result reports residual degrees of freedom, VAR spectral radius, and a
|
|
29
|
+
Ljung--Box residual-autocorrelation diagnostic. Segment standardization is
|
|
30
|
+
enabled by default. These estimates describe lagged linear predictability in the
|
|
31
|
+
processed time series and do not by themselves establish neural causality.
|
|
32
|
+
|
|
33
|
+
References: Geweke (1982), *Journal of the American Statistical Association*,
|
|
34
|
+
doi:10.1080/01621459.1982.10477803; Barnett and Seth (2014), *Journal of
|
|
35
|
+
Neuroscience Methods*, doi:10.1016/j.jneumeth.2013.10.018.
|
|
@@ -98,6 +98,36 @@ tests reconstruct the OLS statistics with NumPy and the graph components with
|
|
|
98
98
|
SciPy, and compare observed components and complete null arrays for both sign
|
|
99
99
|
modes and for unadjusted and confound-adjusted analyses.
|
|
100
100
|
|
|
101
|
+
## Summarize a component's connection strength
|
|
102
|
+
|
|
103
|
+
NBS reports component size or a sum of edge t statistics; neither is its mean
|
|
104
|
+
FC. To measure a fixed component in every acquisition, take its exact
|
|
105
|
+
`component_edges` from an `infer-paired-nbs` result and select those keys in
|
|
106
|
+
the FC store:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from dfckit.storage import FeatureStore, summarize_store_statistics
|
|
110
|
+
|
|
111
|
+
store = FeatureStore.open("features/window-fc.store")
|
|
112
|
+
selected = store.select_features(feature_keys=component_edges)
|
|
113
|
+
network_endpoints = summarize_store_statistics(
|
|
114
|
+
selected,
|
|
115
|
+
statistics=("mean", "standard_deviation"),
|
|
116
|
+
feature_mean="NBS_component",
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This averages the selected signed edge values in each window, then computes
|
|
121
|
+
their network mean and temporal variability within each acquisition. Use the
|
|
122
|
+
same fixed edge set in every condition. The stored edge names, not node
|
|
123
|
+
membership alone, define the component: adding all connections between its
|
|
124
|
+
nodes would include edges that NBS did not select. A mean of window FC is also
|
|
125
|
+
distinct from whole-acquisition static FC.
|
|
126
|
+
|
|
127
|
+
The generic endpoint output supports paired inference. Testing the contrast
|
|
128
|
+
that selected a component on the same participants is a descriptive follow-up,
|
|
129
|
+
not an independent confirmation of the NBS discovery.
|
|
130
|
+
|
|
101
131
|
## References
|
|
102
132
|
|
|
103
133
|
- Zalesky A, Fornito A, Bullmore ET. Network-based statistic: identifying
|
|
@@ -40,6 +40,16 @@ directly in the original edge-feature space:
|
|
|
40
40
|
window FC edges -> training-set feature scaling -> KMeans
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
The default `sample_weight_mode="uniform"` gives every window equal weight and
|
|
44
|
+
preserves the original fitting behavior. For cohorts with unequal usable scan
|
|
45
|
+
lengths, `sample_weight_mode="subject_session_balanced"` first gives every
|
|
46
|
+
participant equal total weight, then divides that weight equally among the
|
|
47
|
+
participant's sessions and their retained windows. The weighted mean and
|
|
48
|
+
variance are used for feature scaling, and the same weights enter the Lloyd
|
|
49
|
+
KMeans objective. The model records the weight sums and Kish effective row
|
|
50
|
+
count; this diagnostic is not a count of statistically independent samples.
|
|
51
|
+
Balanced weighting currently requires materialized Lloyd KMeans without PCA.
|
|
52
|
+
|
|
43
53
|
For a lower-dimensional sensitivity path, set `n_pca_components`. PCA is fitted
|
|
44
54
|
only on the training participants, and the frozen scaler and PCA basis are
|
|
45
55
|
applied before KMeans:
|
|
@@ -123,6 +133,56 @@ The prediction artifact retains the original sequence identity and sample
|
|
|
123
133
|
indices. The summary command applies the same gap-safe
|
|
124
134
|
`summarize_state_assignments` implementation used by the Python API.
|
|
125
135
|
|
|
136
|
+
## Clustering a specified feature or edge set
|
|
137
|
+
|
|
138
|
+
State fitting uses every input feature by default. Restrict an in-memory
|
|
139
|
+
feature dataset by exact feature identity before fitting:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
selected = training_sequences.select_features(
|
|
143
|
+
feature_keys=(
|
|
144
|
+
("V1_L", "PUT-DP_L"),
|
|
145
|
+
("V1_R", "PUT-DP_R"),
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
fit = fit_kmeans_states(selected, n_states=2, seed=20260907)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For a disk-backed store, `select_features` returns a read-only column view. It
|
|
152
|
+
does not rewrite the store and works with materialized or streaming state
|
|
153
|
+
fitting:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
store = FeatureStore.open("/path/to/window_fc.store")
|
|
157
|
+
selected_store = store.select_features(feature_mask=nbs_edge_mask)
|
|
158
|
+
fit = fit_minibatch_kmeans_store(
|
|
159
|
+
selected_store,
|
|
160
|
+
n_states=2,
|
|
161
|
+
seed=20260907,
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`feature_keys` are exact identities and determine output order. A Boolean
|
|
166
|
+
`feature_mask` must have one entry per source feature and preserves source
|
|
167
|
+
order. Fitted models record only the selected keys, so prediction must use the
|
|
168
|
+
same selected view.
|
|
169
|
+
|
|
170
|
+
The command line accepts the same exact ordered selection as a JSON array of
|
|
171
|
+
feature-key arrays:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
dfc-kit fit-states window-fc.store models/nbs-k2.model \
|
|
175
|
+
--method kmeans --n-states 2 --feature-keys nbs-edges.json \
|
|
176
|
+
--fitting-mode materialized --algorithm lloyd \
|
|
177
|
+
--sample-weight-mode subject_session_balanced
|
|
178
|
+
dfc-kit predict-states window-fc.store models/nbs-k2.model labels \
|
|
179
|
+
--feature-keys nbs-edges.json --allow-fit-subjects
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Participant-session-balanced weighting gives every acquisition sequence equal
|
|
183
|
+
total influence regardless of its number of windows. It is supported only by
|
|
184
|
+
materialized Lloyd KMeans; incompatible engines are rejected explicitly.
|
|
185
|
+
|
|
126
186
|
## CAP
|
|
127
187
|
|
|
128
188
|
```python
|
|
@@ -176,6 +176,37 @@ or `feature_12.variance`. For edge FeatureStores, one selected statistic at a
|
|
|
176
176
|
time can be reshaped into a participants-by-edges matrix for NBS. Mixing two
|
|
177
177
|
statistics in one NBS graph would duplicate every edge and is invalid.
|
|
178
178
|
|
|
179
|
+
### Mean strength and variability of a fixed network
|
|
180
|
+
|
|
181
|
+
Select exact edge keys, then name the feature mean to summarize the network
|
|
182
|
+
instead of each edge separately:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
selected = store.select_features(feature_keys=component_edges)
|
|
186
|
+
payload = summarize_store_statistics(
|
|
187
|
+
selected,
|
|
188
|
+
statistics=("mean", "standard_deviation"),
|
|
189
|
+
feature_mean="NBS24",
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
For each sample, the selected edges are averaged with equal weights. The
|
|
194
|
+
resulting network time series is then summarized separately for each
|
|
195
|
+
participant, session, and acquisition. `NBS24.mean` is its temporal mean;
|
|
196
|
+
`NBS24.standard_deviation` is its population standard deviation, **not** the
|
|
197
|
+
average of the individual edges' standard deviations. Each retained sample
|
|
198
|
+
has equal weight, including when segments or storage chunks differ in length.
|
|
199
|
+
|
|
200
|
+
Values keep the store's scale and sign: a Fisher-z window-FC store produces
|
|
201
|
+
means of signed Fisher-z values, without taking absolute values or applying
|
|
202
|
+
another transform. These are mean-window FC summaries, not whole-acquisition
|
|
203
|
+
static FC. A store with one static FC row per acquisition instead gives its
|
|
204
|
+
across-edge mean and a zero temporal standard deviation.
|
|
205
|
+
|
|
206
|
+
The output lists `source_feature_keys` and marks `feature_type="feature_set"`.
|
|
207
|
+
It can enter `infer-paired-endpoints`, but is no longer an edge matrix for
|
|
208
|
+
`infer-paired-nbs`. Omitting `feature_mean` retains per-feature summaries.
|
|
209
|
+
|
|
179
210
|
## Stream MiniBatchKMeans
|
|
180
211
|
|
|
181
212
|
Feature stores can be fitted directly without calling `read_dataset()`:
|
|
@@ -16,6 +16,7 @@ nav:
|
|
|
16
16
|
- Architecture: architecture.md
|
|
17
17
|
- Connectivity methods:
|
|
18
18
|
- Correlation and sliding-window FC: correlation.md
|
|
19
|
+
- Conditional Granger predictability: granger.md
|
|
19
20
|
- Instantaneous edges (ETS and MTD): instantaneous_edges.md
|
|
20
21
|
- LEiDA: leida.md
|
|
21
22
|
- Low-rank covariance: lowrank.md
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
CHANGELOG.md
|
|
2
2
|
CITATION.cff
|
|
3
3
|
CONTRIBUTING.md
|
|
4
|
-
ENGINEERING_PLAN.md
|
|
5
4
|
LICENSE
|
|
6
5
|
MANIFEST.in
|
|
7
6
|
README.md
|
|
8
|
-
ROADMAP.md
|
|
9
|
-
SECURITY.md
|
|
10
7
|
mkdocs.yml
|
|
11
8
|
pyproject.toml
|
|
12
9
|
benchmarks/README.md
|
|
@@ -18,6 +15,7 @@ docs/cli.md
|
|
|
18
15
|
docs/correlation.md
|
|
19
16
|
docs/datasets.md
|
|
20
17
|
docs/getting_started.md
|
|
18
|
+
docs/granger.md
|
|
21
19
|
docs/group_inference.md
|
|
22
20
|
docs/hmm.md
|
|
23
21
|
docs/index.md
|
|
@@ -49,6 +47,7 @@ src/dfc_kit.egg-info/requires.txt
|
|
|
49
47
|
src/dfc_kit.egg-info/top_level.txt
|
|
50
48
|
src/dfckit/__init__.py
|
|
51
49
|
src/dfckit/_arrays.py
|
|
50
|
+
src/dfckit/_feature_selection.py
|
|
52
51
|
src/dfckit/_preprocessing.py
|
|
53
52
|
src/dfckit/_validation.py
|
|
54
53
|
src/dfckit/cli.py
|
|
@@ -73,6 +72,7 @@ src/dfckit/commands/states.py
|
|
|
73
72
|
src/dfckit/connectivity/__init__.py
|
|
74
73
|
src/dfckit/connectivity/_edge_products.py
|
|
75
74
|
src/dfckit/connectivity/correlation.py
|
|
75
|
+
src/dfckit/connectivity/granger.py
|
|
76
76
|
src/dfckit/connectivity/instantaneous.py
|
|
77
77
|
src/dfckit/connectivity/leida.py
|
|
78
78
|
src/dfckit/connectivity/lowrank.py
|
|
@@ -138,6 +138,9 @@ tests/unit/test_cross_validation.py
|
|
|
138
138
|
tests/unit/test_data.py
|
|
139
139
|
tests/unit/test_endpoint_inference.py
|
|
140
140
|
tests/unit/test_ets.py
|
|
141
|
+
tests/unit/test_feature_selection.py
|
|
142
|
+
tests/unit/test_granger.py
|
|
143
|
+
tests/unit/test_granger_optional_dependency.py
|
|
141
144
|
tests/unit/test_hmm.py
|
|
142
145
|
tests/unit/test_independent_endpoint_inference.py
|
|
143
146
|
tests/unit/test_inference.py
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Shared validation for selecting named feature columns."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable, Sequence
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy.typing import ArrayLike, NDArray
|
|
9
|
+
|
|
10
|
+
FeatureKey = tuple[str, ...]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def resolve_feature_selection(
|
|
14
|
+
available_keys: Sequence[FeatureKey],
|
|
15
|
+
*,
|
|
16
|
+
feature_keys: Iterable[Sequence[str]] | None,
|
|
17
|
+
feature_mask: ArrayLike | None,
|
|
18
|
+
) -> tuple[NDArray[np.int64], tuple[FeatureKey, ...]]:
|
|
19
|
+
"""Resolve one exact-key or Boolean-mask selection against known features."""
|
|
20
|
+
if (feature_keys is None) == (feature_mask is None):
|
|
21
|
+
raise ValueError("provide exactly one of feature_keys or feature_mask")
|
|
22
|
+
|
|
23
|
+
available = tuple(tuple(str(part) for part in key) for key in available_keys)
|
|
24
|
+
if feature_keys is not None:
|
|
25
|
+
requested = tuple(tuple(str(part) for part in key) for key in feature_keys)
|
|
26
|
+
if not requested or any(not key for key in requested):
|
|
27
|
+
raise ValueError("feature_keys must select at least one non-empty key")
|
|
28
|
+
if len(set(requested)) != len(requested):
|
|
29
|
+
raise ValueError("feature_keys must be unique")
|
|
30
|
+
positions = {key: index for index, key in enumerate(available)}
|
|
31
|
+
missing = tuple(key for key in requested if key not in positions)
|
|
32
|
+
if missing:
|
|
33
|
+
raise KeyError(f"selected feature keys are absent: {missing}")
|
|
34
|
+
indices = np.asarray([positions[key] for key in requested], dtype=np.int64)
|
|
35
|
+
return indices, requested
|
|
36
|
+
|
|
37
|
+
mask = np.asarray(feature_mask)
|
|
38
|
+
if mask.dtype.kind != "b" or mask.shape != (len(available),):
|
|
39
|
+
raise ValueError(f"feature_mask must be a Boolean vector with shape ({len(available)},)")
|
|
40
|
+
indices = np.flatnonzero(mask).astype(np.int64, copy=False)
|
|
41
|
+
if not len(indices):
|
|
42
|
+
raise ValueError("feature_mask must select at least one feature")
|
|
43
|
+
return indices, tuple(available[int(index)] for index in indices)
|
|
@@ -47,6 +47,10 @@ _KMEANS_METADATA = {
|
|
|
47
47
|
"n_states",
|
|
48
48
|
"reassignment_ratio",
|
|
49
49
|
"sample_interval_seconds",
|
|
50
|
+
"sample_weight_effective_row_count",
|
|
51
|
+
"sample_weight_mode",
|
|
52
|
+
"sample_weight_sum",
|
|
53
|
+
"sample_weight_sum_squares",
|
|
50
54
|
"seed",
|
|
51
55
|
"source_contract",
|
|
52
56
|
"standardize_features",
|
|
@@ -249,6 +253,10 @@ def _array_payload(model: FittedModel) -> tuple[str, dict[str, object], dict[str
|
|
|
249
253
|
),
|
|
250
254
|
"iterations": int(model.iterations),
|
|
251
255
|
"inertia": float(model.inertia),
|
|
256
|
+
"sample_weight_mode": str(model.sample_weight_mode),
|
|
257
|
+
"sample_weight_sum": model.sample_weight_sum,
|
|
258
|
+
"sample_weight_sum_squares": model.sample_weight_sum_squares,
|
|
259
|
+
"sample_weight_effective_row_count": model.sample_weight_effective_row_count,
|
|
252
260
|
}
|
|
253
261
|
arrays = {name: np.asarray(getattr(model, name)) for name in _KMEANS_ARRAYS}
|
|
254
262
|
return "kmeans-state", metadata, arrays
|
|
@@ -425,6 +433,12 @@ def _load_kmeans(
|
|
|
425
433
|
fit_subjects=_string_tuple(metadata, "fit_subjects"),
|
|
426
434
|
fit_sample_count=fit_sample_count,
|
|
427
435
|
implementation=_string(metadata, "implementation"),
|
|
436
|
+
sample_weight_mode=_string(metadata, "sample_weight_mode"),
|
|
437
|
+
sample_weight_sum=_finite_float(metadata, "sample_weight_sum"),
|
|
438
|
+
sample_weight_sum_squares=_finite_float(metadata, "sample_weight_sum_squares"),
|
|
439
|
+
sample_weight_effective_row_count=_finite_float(
|
|
440
|
+
metadata, "sample_weight_effective_row_count"
|
|
441
|
+
),
|
|
428
442
|
)
|
|
429
443
|
|
|
430
444
|
|
|
@@ -174,6 +174,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
174
174
|
)
|
|
175
175
|
fit.add_argument("store", type=Path, help="existing FeatureStore directory")
|
|
176
176
|
fit.add_argument("output", type=Path, help="new fitted-model artifact directory")
|
|
177
|
+
fit.add_argument(
|
|
178
|
+
"--feature-keys",
|
|
179
|
+
type=Path,
|
|
180
|
+
help="JSON array of exact ordered feature keys to fit",
|
|
181
|
+
)
|
|
177
182
|
fit.add_argument("--method", choices=("kmeans", "hmm"), required=True)
|
|
178
183
|
fit.add_argument("--n-states", type=int, required=True)
|
|
179
184
|
fit.add_argument("--seed", type=int, required=True)
|
|
@@ -228,6 +233,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
228
233
|
fit.add_argument("--tol", type=float, default=1e-3)
|
|
229
234
|
fit.add_argument("--pca-batch-size", type=int, default=4096)
|
|
230
235
|
fit.add_argument("--minimum-sequence-length", type=int, default=2)
|
|
236
|
+
fit.add_argument(
|
|
237
|
+
"--sample-weight-mode",
|
|
238
|
+
choices=("uniform", "subject_session_balanced"),
|
|
239
|
+
default="uniform",
|
|
240
|
+
help="row weighting for materialized Lloyd KMeans",
|
|
241
|
+
)
|
|
231
242
|
|
|
232
243
|
predict = subparsers.add_parser(
|
|
233
244
|
"predict-states",
|
|
@@ -236,6 +247,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
236
247
|
predict.add_argument("store", type=Path, help="FeatureStore to decode")
|
|
237
248
|
predict.add_argument("model", type=Path, help="saved fitted-model artifact")
|
|
238
249
|
predict.add_argument("output", type=Path, help="new state-prediction artifact directory")
|
|
250
|
+
predict.add_argument(
|
|
251
|
+
"--feature-keys",
|
|
252
|
+
type=Path,
|
|
253
|
+
help="JSON array of exact ordered feature keys expected by the model",
|
|
254
|
+
)
|
|
239
255
|
predict.add_argument(
|
|
240
256
|
"--subject",
|
|
241
257
|
action="append",
|
|
@@ -256,10 +272,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
256
272
|
|
|
257
273
|
summarize_features = subparsers.add_parser(
|
|
258
274
|
"summarize-store",
|
|
259
|
-
help="write acquisition-level statistics for
|
|
275
|
+
help="write acquisition-level statistics for features or their equal-weight mean",
|
|
260
276
|
)
|
|
261
277
|
summarize_features.add_argument("store", type=Path, help="FeatureStore to summarize")
|
|
262
278
|
summarize_features.add_argument("output", type=Path, help="new endpoint-summary JSON file")
|
|
279
|
+
summarize_features.add_argument(
|
|
280
|
+
"--feature-keys",
|
|
281
|
+
type=Path,
|
|
282
|
+
help="JSON array of exact feature keys to select, such as an NBS component's edges",
|
|
283
|
+
)
|
|
284
|
+
summarize_features.add_argument(
|
|
285
|
+
"--feature-mean",
|
|
286
|
+
metavar="NAME",
|
|
287
|
+
help="average selected features within each sample before summarizing under NAME",
|
|
288
|
+
)
|
|
263
289
|
summarize_features.add_argument(
|
|
264
290
|
"--statistic",
|
|
265
291
|
action="append",
|
|
@@ -282,6 +308,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
282
308
|
describe.add_argument("store", type=Path, help="FeatureStore used to fit the model")
|
|
283
309
|
describe.add_argument("model", type=Path, help="saved KMeans or Gaussian HMM artifact")
|
|
284
310
|
describe.add_argument("output", type=Path, help="new state-description JSON file")
|
|
311
|
+
describe.add_argument(
|
|
312
|
+
"--feature-keys",
|
|
313
|
+
type=Path,
|
|
314
|
+
help="JSON array of exact ordered feature keys used to fit the model",
|
|
315
|
+
)
|
|
285
316
|
describe.add_argument("--top-features", type=int, default=10)
|
|
286
317
|
describe.add_argument(
|
|
287
318
|
"--network-map",
|
|
@@ -440,6 +471,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
440
471
|
score.add_argument("store", type=Path, help="FeatureStore to score")
|
|
441
472
|
score.add_argument("model", type=Path, help="saved fitted-model artifact")
|
|
442
473
|
score.add_argument("output", type=Path, help="new held-out score JSON file")
|
|
474
|
+
score.add_argument(
|
|
475
|
+
"--feature-keys",
|
|
476
|
+
type=Path,
|
|
477
|
+
help="JSON array of exact ordered feature keys expected by the model",
|
|
478
|
+
)
|
|
443
479
|
score.add_argument(
|
|
444
480
|
"--subject",
|
|
445
481
|
action="append",
|
|
@@ -29,7 +29,9 @@ from ..states.interpretation import (
|
|
|
29
29
|
write_state_description,
|
|
30
30
|
)
|
|
31
31
|
from ..states.selection import _write_state_count_comparison
|
|
32
|
-
from ..storage
|
|
32
|
+
from ..storage import FeatureStore, summarize_store_statistics
|
|
33
|
+
from ..storage.summary import _write_store_summary
|
|
34
|
+
from .states import _load_feature_keys, _selected_feature_store
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def describe_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
@@ -39,6 +41,7 @@ def describe_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
|
39
41
|
namespace.model,
|
|
40
42
|
top_features=namespace.top_features,
|
|
41
43
|
network_map_path=namespace.network_map,
|
|
44
|
+
feature_keys=_load_feature_keys(namespace.feature_keys),
|
|
42
45
|
)
|
|
43
46
|
output = write_state_description(payload, namespace.output)
|
|
44
47
|
return {
|
|
@@ -84,7 +87,8 @@ def infer_state_metrics(namespace: argparse.Namespace) -> dict[str, object]:
|
|
|
84
87
|
def summarize_store(namespace: argparse.Namespace) -> dict[str, object]:
|
|
85
88
|
"""Write acquisition-level feature statistics from a FeatureStore."""
|
|
86
89
|
statistics = tuple(namespace.statistic or ("mean",))
|
|
87
|
-
|
|
90
|
+
store = _selected_feature_store(FeatureStore.open(namespace.store), namespace.feature_keys)
|
|
91
|
+
payload = summarize_store_statistics(store, statistics, feature_mean=namespace.feature_mean)
|
|
88
92
|
output = _write_store_summary(payload, namespace.output)
|
|
89
93
|
return {
|
|
90
94
|
"output": str(output),
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import json
|
|
6
7
|
|
|
7
8
|
from ..artifacts import (
|
|
8
9
|
StatePredictions,
|
|
@@ -31,9 +32,29 @@ from ..states.streaming_hmm import (
|
|
|
31
32
|
from ..storage import FeatureStore
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def _load_feature_keys(path):
|
|
36
|
+
if path is None:
|
|
37
|
+
return None
|
|
38
|
+
if not path.is_file() or path.is_symlink():
|
|
39
|
+
raise ValueError(f"feature-keys file must be an existing non-symlink file: {path}")
|
|
40
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
41
|
+
if not isinstance(raw, list) or not raw:
|
|
42
|
+
raise ValueError("feature-keys JSON must be a non-empty array")
|
|
43
|
+
if any(not isinstance(key, list) or not key for key in raw):
|
|
44
|
+
raise ValueError("every feature key must be a non-empty JSON array")
|
|
45
|
+
return tuple(tuple(str(part) for part in key) for key in raw)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _selected_feature_store(store: FeatureStore, path):
|
|
49
|
+
keys = _load_feature_keys(path)
|
|
50
|
+
return store if keys is None else store.select_features(feature_keys=keys)
|
|
51
|
+
|
|
52
|
+
|
|
34
53
|
def fit_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
35
54
|
"""Fit one state model from a disk-backed feature store."""
|
|
36
|
-
store =
|
|
55
|
+
store = _selected_feature_store(
|
|
56
|
+
FeatureStore.open(namespace.store), namespace.feature_keys
|
|
57
|
+
)
|
|
37
58
|
if namespace.output.exists() or namespace.output.is_symlink():
|
|
38
59
|
raise FileExistsError(f"model artifact path already exists: {namespace.output}")
|
|
39
60
|
subjects = None if namespace.subject is None else tuple(namespace.subject)
|
|
@@ -43,6 +64,14 @@ def fit_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
|
43
64
|
raise ValueError(f"requested fit subjects are absent from the store: {missing}")
|
|
44
65
|
|
|
45
66
|
if namespace.method == "kmeans":
|
|
67
|
+
if namespace.sample_weight_mode != "uniform" and (
|
|
68
|
+
namespace.fitting_mode != "materialized"
|
|
69
|
+
or namespace.algorithm != "lloyd"
|
|
70
|
+
or namespace.n_pca_components is not None
|
|
71
|
+
):
|
|
72
|
+
raise ValueError(
|
|
73
|
+
"subject_session_balanced weights require materialized Lloyd KMeans without PCA"
|
|
74
|
+
)
|
|
46
75
|
if namespace.fitting_mode == "streaming":
|
|
47
76
|
if namespace.algorithm != "minibatch":
|
|
48
77
|
raise ValueError("streaming KMeans requires --algorithm minibatch")
|
|
@@ -78,6 +107,7 @@ def fit_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
|
78
107
|
reassignment_ratio=namespace.reassignment_ratio,
|
|
79
108
|
n_pca_components=namespace.n_pca_components,
|
|
80
109
|
subjects=subjects,
|
|
110
|
+
sample_weight_mode=namespace.sample_weight_mode,
|
|
81
111
|
)
|
|
82
112
|
model = fit.model
|
|
83
113
|
fit_sequence_count = len(fit.assignments.sequences)
|
|
@@ -97,6 +127,7 @@ def fit_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
|
97
127
|
"initialization_passes": list(fit.initialization_passes),
|
|
98
128
|
"log_likelihood": None,
|
|
99
129
|
"inertia": model.inertia,
|
|
130
|
+
"sample_weight_mode": model.sample_weight_mode,
|
|
100
131
|
"init_sample_size": model.init_sample_size,
|
|
101
132
|
"n_pca_components": model.n_pca_components,
|
|
102
133
|
"pca_batch_size": model.pca_batch_size,
|
|
@@ -151,7 +182,9 @@ def selected_store_subjects(
|
|
|
151
182
|
|
|
152
183
|
def predict_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
153
184
|
"""Decode a feature store with a fitted state model."""
|
|
154
|
-
store =
|
|
185
|
+
store = _selected_feature_store(
|
|
186
|
+
FeatureStore.open(namespace.store), namespace.feature_keys
|
|
187
|
+
)
|
|
155
188
|
model = load_fitted_model(namespace.model)
|
|
156
189
|
subjects = selected_store_subjects(store, namespace.subject)
|
|
157
190
|
if isinstance(model, KMeansStateModel):
|
|
@@ -214,7 +247,9 @@ def score_states(namespace: argparse.Namespace) -> dict[str, object]:
|
|
|
214
247
|
"""Score a fitted state model on selected acquisitions."""
|
|
215
248
|
if namespace.output.exists() or namespace.output.is_symlink():
|
|
216
249
|
raise FileExistsError(f"state-model score output path already exists: {namespace.output}")
|
|
217
|
-
store =
|
|
250
|
+
store = _selected_feature_store(
|
|
251
|
+
FeatureStore.open(namespace.store), namespace.feature_keys
|
|
252
|
+
)
|
|
218
253
|
model = load_fitted_model(namespace.model)
|
|
219
254
|
subjects = selected_store_subjects(store, namespace.subject)
|
|
220
255
|
if isinstance(model, KMeansStateModel):
|