nltools 0.6.0.dev0__tar.gz → 0.6.0.dev1__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.
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/.gitignore +9 -4
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/PKG-INFO +1 -1
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/__init__.py +6 -4
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/__init__.py +83 -3
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/io.py +6 -1
- nltools-0.6.0.dev1/nltools/data/adjacency/plotting.py +390 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/state.py +16 -6
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/stats.py +5 -5
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/__init__.py +41 -17
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/bootstrap.py +20 -15
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/io.py +6 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/modeling.py +158 -64
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/plotting.py +6 -1
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/prediction.py +40 -38
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/utils.py +30 -17
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/diagnostics.py +1 -1
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/io.py +84 -41
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/plotting.py +2 -4
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/results.py +97 -6
- nltools-0.6.0.dev1/nltools/data/results_io.py +210 -0
- nltools-0.6.0.dev1/nltools/data/simulator/haxby.py +283 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/io/h5.py +105 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/results.py +36 -1
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/__init__.py +1 -2
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/adjacency.py +1 -64
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/prediction.py +1 -1
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_corrections.py +1 -2
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_procrustes.py +3 -4
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_mask.py +1 -2
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/datasets/test_datasets.py +89 -4
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/test_adjacency.py +139 -41
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/pyproject.toml +10 -9
- nltools-0.6.0.dev0/nltools/data/adjacency/plotting.py +0 -174
- nltools-0.6.0.dev0/nltools/data/simulator/haxby.py +0 -124
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/LICENSE +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/README.md +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/alignment/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/alignment/procrustes.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/alignment/srm.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/backends.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/corrections.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/decoding.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/bootstrap.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/correlation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/intersubject.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/isc.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/matrix.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/one_sample.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/random.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/timeseries.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/two_sample.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/utils.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/validation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/neighborhoods.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/outliers.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/regression.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/signal.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/similarity.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/validation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/cross_validation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/modeling.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/utils.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/labeling.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/loading.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/registry.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/reporting.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/analysis.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/validation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/viewer.js +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/viewer.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/combine.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/append.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/regressors.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/transforms.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/utils.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/ownership.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/roc/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/simulator/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/validation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/datasets.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/io/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/io/events.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/mask.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/glm.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/ridge.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/validation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/brain.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/decomposition.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/resources/covariates_example.csv +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/resources/onsets_example.csv +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/config.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/fetch.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/matching.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/paths.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/registry.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/conftest.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/conftest.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_decoding.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_intersubject.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_neighborhoods.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_outliers.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_regression.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_signal.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_similarity.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_backends.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_bootstrap.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_cross_validation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_gpu_policy.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_hyperalignment.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_api_conventions.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_correlation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_cpu_parallelization.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_isc_group.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_isc_vocabulary.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_matrix.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_one_sample.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_progress_bar.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_tail_vocabulary.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_timeseries.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_two_sample.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_utils.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_isc.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_srm.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_utils.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/datasets/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/io_tests/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/io_tests/test_file_reader.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/io_tests/test_h5.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/conftest.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_glm.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_glm_warnings.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_results.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_ridge.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/test_f123_prediction.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/test_surface.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/pyodide/.gitignore +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/pyodide/test_runner.mjs +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/support/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/support/test_designation.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/templates/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/templates/test_brainspace.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/templates/test_fetch_pyodide.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/utils/__init__.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/utils/test_utils.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/utils.py +0 -0
- {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/version.py +0 -0
|
@@ -78,10 +78,15 @@ scratch/
|
|
|
78
78
|
# Zensical site output
|
|
79
79
|
/site/
|
|
80
80
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
# A locally built wheel staged for the quickstart page's ```pyodide cells to
|
|
82
|
+
# install from, when testing a version that is not on PyPI yet.
|
|
83
|
+
docs/_static/wheels/
|
|
84
|
+
|
|
85
|
+
# Generated pages: build artifacts written from the marimo notebooks beside them
|
|
86
|
+
# by scripts/marimo_to_zensical.py (`uv run poe docs-generate`). The patterns
|
|
87
|
+
# match the notebooks (scripts/marimo_to_zensical.py NOTEBOOK_GLOBS), so a
|
|
88
|
+
# hand-written page in any of these directories is still tracked.
|
|
89
|
+
docs/quickstart.md
|
|
85
90
|
docs/tutorials/basics/[0-9]*_*.md
|
|
86
91
|
docs/tutorials/data-operations/[0-9]*_*.md
|
|
87
92
|
docs/tutorials/analysis/[0-9]*_*.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nltools
|
|
3
|
-
Version: 0.6.0.
|
|
3
|
+
Version: 0.6.0.dev1
|
|
4
4
|
Summary: A Python package to analyze neuroimaging data
|
|
5
5
|
Project-URL: Homepage, https://nltools.org
|
|
6
6
|
Author-email: "Luke J. Chang" <luke.j.chang@dartmouth.edu>, Eshin Jolly <eshin.jolly@gmail.com>
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
`BrainData` (masked voxel data), `Adjacency` (similarity/distance matrices),
|
|
4
4
|
`DesignMatrix` (regressors), the `Roc` analysis class, the
|
|
5
5
|
`Simulator`/`SimulateGrid` data simulators, and the frozen result records those
|
|
6
|
-
classes return (`
|
|
7
|
-
brain-space configuration record
|
|
6
|
+
classes return (`FitResult`, `PredictResult`, `BootstrapResult`,
|
|
7
|
+
`ContrastResult`) plus the brain-space configuration record
|
|
8
|
+
(`BrainSpaceConfig`).
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from .braindata import BrainData
|
|
11
12
|
from .adjacency import Adjacency
|
|
12
13
|
from .designmatrix import DesignMatrix
|
|
13
|
-
from .results import BootstrapResult,
|
|
14
|
+
from .results import BootstrapResult, FitResult, PredictResult
|
|
14
15
|
from .simulator import Simulator, SimulateGrid
|
|
15
16
|
from .roc import Roc
|
|
16
17
|
from nltools.models.results import ContrastResult
|
|
@@ -23,7 +24,8 @@ __all__ = [
|
|
|
23
24
|
"BrainSpaceConfig",
|
|
24
25
|
"ContrastResult",
|
|
25
26
|
"DesignMatrix",
|
|
26
|
-
"
|
|
27
|
+
"FitResult",
|
|
28
|
+
"PredictResult",
|
|
27
29
|
"Roc",
|
|
28
30
|
"SimulateGrid",
|
|
29
31
|
"Simulator",
|
|
@@ -19,8 +19,9 @@ class Adjacency:
|
|
|
19
19
|
"""Represent adjacency matrices in vectorized form.
|
|
20
20
|
|
|
21
21
|
Store distance/similarity matrices as strict upper triangles and directed
|
|
22
|
-
matrices as full row-major vectors. Symmetric reconstruction
|
|
23
|
-
|
|
22
|
+
matrices as full row-major vectors. Symmetric reconstruction fills the
|
|
23
|
+
diagonal from the matrix type — 0 for a distance, 1 for a similarity — and
|
|
24
|
+
input diagonals are discarded. Flat rectangular stacks require
|
|
24
25
|
an explicit `*_flat` matrix type. A list or 2-D flat array retains stack rank,
|
|
25
26
|
including one matrix. A zero-length symmetric vector represents one node.
|
|
26
27
|
Construction and result methods return independently owned mutable state.
|
|
@@ -376,6 +377,10 @@ class Adjacency:
|
|
|
376
377
|
):
|
|
377
378
|
"""Create a heatmap of an Adjacency matrix.
|
|
378
379
|
|
|
380
|
+
Matrices whose off-diagonal values cross zero are drawn on `"RdBu_r"`,
|
|
381
|
+
centered at 0 with symmetric limits; one-signed matrices keep seaborn's
|
|
382
|
+
sequential default. `cmap`, `center`, `vmin` and `vmax` override this.
|
|
383
|
+
|
|
379
384
|
Args:
|
|
380
385
|
limit (int): Number of heatmaps to plot if the object contains multiple
|
|
381
386
|
matrices. Default 3.
|
|
@@ -535,6 +540,80 @@ class Adjacency:
|
|
|
535
540
|
figsize=figsize,
|
|
536
541
|
)
|
|
537
542
|
|
|
543
|
+
def plot_stacked( # nosemgrep: kwargs-internal-forwarding # forwards to seaborn via plotting._plot_stacked
|
|
544
|
+
self,
|
|
545
|
+
data,
|
|
546
|
+
*,
|
|
547
|
+
labels=None,
|
|
548
|
+
upper_title=None,
|
|
549
|
+
lower_title=None,
|
|
550
|
+
cmap=None,
|
|
551
|
+
vmin=None,
|
|
552
|
+
vmax=None,
|
|
553
|
+
colorbar=True,
|
|
554
|
+
ax=None,
|
|
555
|
+
**kwargs,
|
|
556
|
+
):
|
|
557
|
+
"""Draw this matrix and another as complementary triangles of one square.
|
|
558
|
+
|
|
559
|
+
This matrix fills the upper-right triangle and `data` the lower-left,
|
|
560
|
+
with the diagonal hidden in both so a one-cell white gap runs between
|
|
561
|
+
them — the intersubject RSA figure that compares a neural similarity
|
|
562
|
+
structure against a behavioural one. Each triangle keeps its own
|
|
563
|
+
colormap and limits, so the two matrices need not share units: signed
|
|
564
|
+
matrices are anchored at zero on `"RdBu_r"` like `plot`, one-signed ones
|
|
565
|
+
keep seaborn's sequential default.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
data (Adjacency): Single matrix over the same nodes, drawn in the
|
|
569
|
+
lower triangle.
|
|
570
|
+
labels (list, optional): Node tick labels. Defaults to the stored
|
|
571
|
+
labels, or no ticks when there are none; `False` suppresses them.
|
|
572
|
+
upper_title (str, optional): Title drawn above the square.
|
|
573
|
+
lower_title (str, optional): Title drawn below the square.
|
|
574
|
+
cmap (str | matplotlib.colors.Colormap | tuple, optional): One
|
|
575
|
+
colormap for both triangles, or an `(upper, lower)` tuple.
|
|
576
|
+
vmin (float | tuple, optional): One lower limit for both triangles,
|
|
577
|
+
or an `(upper, lower)` tuple.
|
|
578
|
+
vmax (float | tuple, optional): One upper limit for both triangles,
|
|
579
|
+
or an `(upper, lower)` tuple.
|
|
580
|
+
colorbar (bool): Draw colorbars. One bar when both triangles share a
|
|
581
|
+
colormap and limits, two when they do not. Default True.
|
|
582
|
+
ax (matplotlib.axes.Axes, optional): Axis to draw on.
|
|
583
|
+
**kwargs (dict): Forwarded to `seaborn.heatmap` for both triangles.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
matplotlib.axes.Axes: The axis holding both triangles.
|
|
587
|
+
|
|
588
|
+
Raises:
|
|
589
|
+
ValueError: If `data` is not an `Adjacency`, either object holds
|
|
590
|
+
more than one matrix, or the two cover different numbers of nodes.
|
|
591
|
+
|
|
592
|
+
Examples:
|
|
593
|
+
```python
|
|
594
|
+
brain_rdm.plot_stacked(
|
|
595
|
+
behavior_rdm,
|
|
596
|
+
upper_title="PCC ROI similarity",
|
|
597
|
+
lower_title="Self-control similarity",
|
|
598
|
+
)
|
|
599
|
+
```
|
|
600
|
+
"""
|
|
601
|
+
from .plotting import _plot_stacked
|
|
602
|
+
|
|
603
|
+
return _plot_stacked(
|
|
604
|
+
self,
|
|
605
|
+
data,
|
|
606
|
+
labels=labels,
|
|
607
|
+
upper_title=upper_title,
|
|
608
|
+
lower_title=lower_title,
|
|
609
|
+
cmap=cmap,
|
|
610
|
+
vmin=vmin,
|
|
611
|
+
vmax=vmax,
|
|
612
|
+
colorbar=colorbar,
|
|
613
|
+
ax=ax,
|
|
614
|
+
**kwargs,
|
|
615
|
+
)
|
|
616
|
+
|
|
538
617
|
def r_to_z(self):
|
|
539
618
|
"""Apply Fisher's r-to-z transformation to each data element."""
|
|
540
619
|
from .stats import _r_to_z
|
|
@@ -679,7 +758,8 @@ class Adjacency:
|
|
|
679
758
|
|
|
680
759
|
Returns:
|
|
681
760
|
np.ndarray | list[np.ndarray]: Detached square matrix, or a list of
|
|
682
|
-
detached matrices for a stack.
|
|
761
|
+
detached matrices for a stack. A symmetric diagonal is filled
|
|
762
|
+
from the matrix type: 0 for a distance, 1 for a similarity.
|
|
683
763
|
"""
|
|
684
764
|
from .state import _to_square
|
|
685
765
|
|
|
@@ -78,7 +78,12 @@ def _to_graph(adj):
|
|
|
78
78
|
if adj.matrix_type == "directed":
|
|
79
79
|
G = nx.DiGraph(adj.squareform())
|
|
80
80
|
else:
|
|
81
|
-
|
|
81
|
+
# The diagonal of a symmetric square is a constant of the matrix
|
|
82
|
+
# type (1 for a similarity), not an edge; drop it so no node picks
|
|
83
|
+
# up a self-loop.
|
|
84
|
+
square = np.asarray(adj.squareform()).copy()
|
|
85
|
+
np.fill_diagonal(square, 0)
|
|
86
|
+
G = nx.Graph(square)
|
|
82
87
|
if adj.labels:
|
|
83
88
|
labels = dict(zip(G.nodes, adj.labels))
|
|
84
89
|
nx.relabel_nodes(G, labels, copy=False)
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""Plotting functions for Adjacency matrices."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _divergent_heatmap_defaults(square):
|
|
7
|
+
"""Heatmap keywords that anchor a signed matrix at zero, or none if one-signed.
|
|
8
|
+
|
|
9
|
+
Seaborn picks a sequential ramp and data-range limits whenever `center` is
|
|
10
|
+
unset, which leaves a matrix straddling zero with no visible anchor there.
|
|
11
|
+
The diagonal is excluded because it is a stored constant (0 for a distance,
|
|
12
|
+
1 for a similarity), not data, and it would otherwise set the limits.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
square (np.ndarray): Square matrix about to be drawn.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
dict: `cmap`, `center`, `vmin` and `vmax` for a signed matrix; empty
|
|
19
|
+
for a one-signed, empty or all-NaN one.
|
|
20
|
+
"""
|
|
21
|
+
off_diagonal = square[~np.eye(square.shape[0], dtype=bool)]
|
|
22
|
+
if off_diagonal.size == 0 or np.all(np.isnan(off_diagonal)):
|
|
23
|
+
return {}
|
|
24
|
+
low, high = np.nanmin(off_diagonal), np.nanmax(off_diagonal)
|
|
25
|
+
if not (low < 0 < high):
|
|
26
|
+
return {}
|
|
27
|
+
limit = float(max(abs(low), abs(high)))
|
|
28
|
+
return {"cmap": "RdBu_r", "center": 0, "vmin": -limit, "vmax": limit}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _heatmap_kwargs(square, kwargs):
|
|
32
|
+
"""Merge the divergent defaults under the caller's own heatmap keywords."""
|
|
33
|
+
merged = dict(kwargs)
|
|
34
|
+
for name, value in _divergent_heatmap_defaults(square).items():
|
|
35
|
+
merged.setdefault(name, value)
|
|
36
|
+
return merged
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _triangle_pair(value):
|
|
40
|
+
"""Split a per-triangle argument into its `(upper, lower)` halves.
|
|
41
|
+
|
|
42
|
+
A 2-tuple gives each triangle its own value; anything else, `None`
|
|
43
|
+
included, is one value both triangles share.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
value: Scalar for both triangles, or a `(upper, lower)` tuple.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
tuple: The upper and lower value.
|
|
50
|
+
"""
|
|
51
|
+
if isinstance(value, tuple) and len(value) == 2:
|
|
52
|
+
return value[0], value[1]
|
|
53
|
+
return value, value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _stacked_triangle_kwargs(square, mask, cmap, vmin, vmax, kwargs):
|
|
57
|
+
"""Resolve one triangle's heatmap keywords, caller's values over the divergent defaults.
|
|
58
|
+
|
|
59
|
+
Limits always end up explicit, even when the caller gave none, so the two
|
|
60
|
+
triangles can be compared for a shared colorbar instead of each being
|
|
61
|
+
scaled by seaborn behind our back.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
square (np.ndarray): The triangle's source matrix.
|
|
65
|
+
mask (np.ndarray): Boolean mask of the cells this triangle hides.
|
|
66
|
+
cmap: Colormap for this triangle, or None for the default.
|
|
67
|
+
vmin: Lower limit for this triangle, or None to take it from the data.
|
|
68
|
+
vmax: Upper limit for this triangle, or None to take it from the data.
|
|
69
|
+
kwargs (dict): The caller's remaining `seaborn.heatmap` keywords.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
dict: Heatmap keywords for this triangle.
|
|
73
|
+
"""
|
|
74
|
+
resolved = dict(kwargs)
|
|
75
|
+
if cmap is not None:
|
|
76
|
+
resolved["cmap"] = cmap
|
|
77
|
+
if vmin is not None:
|
|
78
|
+
resolved["vmin"] = vmin
|
|
79
|
+
if vmax is not None:
|
|
80
|
+
resolved["vmax"] = vmax
|
|
81
|
+
for name, value in _divergent_heatmap_defaults(square).items():
|
|
82
|
+
resolved.setdefault(name, value)
|
|
83
|
+
values = square[~mask]
|
|
84
|
+
if values.size and not np.all(np.isnan(values)):
|
|
85
|
+
resolved.setdefault("vmin", float(np.nanmin(values)))
|
|
86
|
+
resolved.setdefault("vmax", float(np.nanmax(values)))
|
|
87
|
+
return resolved
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _color_scale(heatmap_kwargs):
|
|
91
|
+
"""The part of a triangle's keywords a colorbar speaks for."""
|
|
92
|
+
return tuple(
|
|
93
|
+
str(heatmap_kwargs.get(name)) for name in ("cmap", "center", "vmin", "vmax")
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _plot_stacked(
|
|
98
|
+
adj,
|
|
99
|
+
data,
|
|
100
|
+
*,
|
|
101
|
+
labels=None,
|
|
102
|
+
upper_title=None,
|
|
103
|
+
lower_title=None,
|
|
104
|
+
cmap=None,
|
|
105
|
+
vmin=None,
|
|
106
|
+
vmax=None,
|
|
107
|
+
colorbar=True,
|
|
108
|
+
ax=None,
|
|
109
|
+
**kwargs,
|
|
110
|
+
):
|
|
111
|
+
"""Draw two matrices as the complementary triangles of one square.
|
|
112
|
+
|
|
113
|
+
`adj` fills the upper-right triangle and `data` the lower-left, with the
|
|
114
|
+
diagonal hidden in both so a one-cell white gap separates them. Each
|
|
115
|
+
triangle carries its own colormap and limits, resolved from
|
|
116
|
+
`_divergent_heatmap_defaults` unless the caller names them, so the two
|
|
117
|
+
matrices need not share units.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
adj (Adjacency): Single matrix drawn in the upper triangle.
|
|
121
|
+
data (Adjacency): Single matrix over the same nodes, drawn in the lower
|
|
122
|
+
triangle.
|
|
123
|
+
labels (list, optional): Node tick labels. Defaults to `adj.labels`, or
|
|
124
|
+
no ticks when it has none; `False` suppresses them.
|
|
125
|
+
upper_title (str, optional): Title drawn above the square.
|
|
126
|
+
lower_title (str, optional): Title drawn below the square.
|
|
127
|
+
cmap (str | matplotlib.colors.Colormap | tuple, optional): One
|
|
128
|
+
colormap for both triangles, or an `(upper, lower)` tuple.
|
|
129
|
+
vmin (float | tuple, optional): One lower limit for both triangles,
|
|
130
|
+
or an `(upper, lower)` tuple.
|
|
131
|
+
vmax (float | tuple, optional): One upper limit for both triangles,
|
|
132
|
+
or an `(upper, lower)` tuple.
|
|
133
|
+
colorbar (bool): Draw colorbars. One bar when the triangles share a
|
|
134
|
+
colormap and limits, two when they do not. Default True.
|
|
135
|
+
ax (matplotlib.axes.Axes, optional): Axis to draw on.
|
|
136
|
+
**kwargs (dict): Forwarded to `seaborn.heatmap` for both triangles;
|
|
137
|
+
`cbar`, `cbar_ax` and `mask` are controlled here.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
matplotlib.axes.Axes: The axis holding both triangles.
|
|
141
|
+
"""
|
|
142
|
+
import matplotlib.pyplot as plt
|
|
143
|
+
import seaborn as sns
|
|
144
|
+
|
|
145
|
+
from nltools.data import Adjacency
|
|
146
|
+
|
|
147
|
+
if not isinstance(data, Adjacency):
|
|
148
|
+
raise ValueError("data must be an Adjacency instance.")
|
|
149
|
+
if not adj.is_single_matrix or not data.is_single_matrix:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
"plot_stacked draws one matrix per triangle; index a stack first."
|
|
152
|
+
)
|
|
153
|
+
if adj.n_nodes != data.n_nodes:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
"Both matrices must describe the same nodes; got "
|
|
156
|
+
f"{adj.n_nodes} and {data.n_nodes}."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
upper_square = adj.squareform()
|
|
160
|
+
lower_square = data.squareform()
|
|
161
|
+
ones = np.ones((adj.n_nodes, adj.n_nodes), dtype=bool)
|
|
162
|
+
upper_mask = np.tril(ones)
|
|
163
|
+
lower_mask = np.triu(ones)
|
|
164
|
+
|
|
165
|
+
upper_cmap, lower_cmap = _triangle_pair(cmap)
|
|
166
|
+
upper_vmin, lower_vmin = _triangle_pair(vmin)
|
|
167
|
+
upper_vmax, lower_vmax = _triangle_pair(vmax)
|
|
168
|
+
upper_kwargs = _stacked_triangle_kwargs(
|
|
169
|
+
upper_square, upper_mask, upper_cmap, upper_vmin, upper_vmax, kwargs
|
|
170
|
+
)
|
|
171
|
+
lower_kwargs = _stacked_triangle_kwargs(
|
|
172
|
+
lower_square, lower_mask, lower_cmap, lower_vmin, lower_vmax, kwargs
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if labels is None:
|
|
176
|
+
labels = adj.labels if adj.labels else False
|
|
177
|
+
if labels is not False and len(labels) != adj.n_nodes:
|
|
178
|
+
raise ValueError("labels must have one entry per node.")
|
|
179
|
+
|
|
180
|
+
if ax is None:
|
|
181
|
+
_, ax = plt.subplots(1, figsize=(7, 6))
|
|
182
|
+
ax.set_facecolor("white")
|
|
183
|
+
|
|
184
|
+
if not colorbar:
|
|
185
|
+
upper_cbar_ax, lower_cbar_ax = None, None
|
|
186
|
+
elif _color_scale(upper_kwargs) == _color_scale(lower_kwargs):
|
|
187
|
+
upper_cbar_ax, lower_cbar_ax = ax.inset_axes([1.03, 0.15, 0.03, 0.7]), None
|
|
188
|
+
else:
|
|
189
|
+
upper_cbar_ax = ax.inset_axes([1.03, 0.55, 0.03, 0.42])
|
|
190
|
+
lower_cbar_ax = ax.inset_axes([1.03, 0.03, 0.03, 0.42])
|
|
191
|
+
|
|
192
|
+
for square, mask, triangle_kwargs, cbar_ax in (
|
|
193
|
+
(upper_square, upper_mask, upper_kwargs, upper_cbar_ax),
|
|
194
|
+
(lower_square, lower_mask, lower_kwargs, lower_cbar_ax),
|
|
195
|
+
):
|
|
196
|
+
triangle_kwargs["mask"] = mask
|
|
197
|
+
triangle_kwargs["ax"] = ax
|
|
198
|
+
triangle_kwargs["cbar"] = cbar_ax is not None
|
|
199
|
+
if cbar_ax is not None:
|
|
200
|
+
triangle_kwargs["cbar_ax"] = cbar_ax
|
|
201
|
+
triangle_kwargs.setdefault("square", True)
|
|
202
|
+
triangle_kwargs.setdefault("linewidths", 0.5)
|
|
203
|
+
triangle_kwargs.setdefault("linecolor", "white")
|
|
204
|
+
triangle_kwargs.setdefault("xticklabels", labels)
|
|
205
|
+
triangle_kwargs.setdefault("yticklabels", labels)
|
|
206
|
+
sns.heatmap(square, **triangle_kwargs)
|
|
207
|
+
|
|
208
|
+
if upper_title is not None:
|
|
209
|
+
ax.set_title(upper_title)
|
|
210
|
+
if lower_title is not None:
|
|
211
|
+
ax.set_xlabel(lower_title)
|
|
212
|
+
return ax
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _plot_adjacency(adj, *, limit=3, ax=None, **kwargs):
|
|
216
|
+
"""Create a heatmap of an Adjacency matrix.
|
|
217
|
+
|
|
218
|
+
Signed matrices are anchored at zero (see `_divergent_heatmap_defaults`);
|
|
219
|
+
caller keywords always win.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
adj (Adjacency): Adjacency object to plot.
|
|
223
|
+
limit (int): Number of heatmaps to plot if the object contains multiple
|
|
224
|
+
matrices. Default 3.
|
|
225
|
+
ax (matplotlib.axes.Axes, optional): Axis to draw on (single matrix only).
|
|
226
|
+
**kwargs (dict): Forwarded to `seaborn.heatmap`.
|
|
227
|
+
"""
|
|
228
|
+
import matplotlib.pyplot as plt
|
|
229
|
+
import seaborn as sns
|
|
230
|
+
|
|
231
|
+
if adj.is_single_matrix:
|
|
232
|
+
if ax is None:
|
|
233
|
+
_, ax = plt.subplots(nrows=1, figsize=(7, 5))
|
|
234
|
+
square = adj.squareform()
|
|
235
|
+
heatmap_kwargs = _heatmap_kwargs(square, kwargs)
|
|
236
|
+
if adj.labels:
|
|
237
|
+
sns.heatmap(
|
|
238
|
+
square,
|
|
239
|
+
square=True,
|
|
240
|
+
ax=ax,
|
|
241
|
+
xticklabels=adj.labels,
|
|
242
|
+
yticklabels=adj.labels,
|
|
243
|
+
**heatmap_kwargs,
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
sns.heatmap(square, square=True, ax=ax, **heatmap_kwargs)
|
|
247
|
+
else:
|
|
248
|
+
if ax is not None:
|
|
249
|
+
print("ax is ignored when plotting multiple images")
|
|
250
|
+
n_subs = np.minimum(len(adj), limit)
|
|
251
|
+
_, a = plt.subplots(nrows=n_subs, figsize=(7, len(adj) * 5))
|
|
252
|
+
for i in range(n_subs):
|
|
253
|
+
square = adj[i].squareform()
|
|
254
|
+
heatmap_kwargs = _heatmap_kwargs(square, kwargs)
|
|
255
|
+
if adj.labels:
|
|
256
|
+
sns.heatmap(
|
|
257
|
+
square,
|
|
258
|
+
square=True,
|
|
259
|
+
xticklabels=adj.labels[i],
|
|
260
|
+
yticklabels=adj.labels[i],
|
|
261
|
+
ax=a[i],
|
|
262
|
+
**heatmap_kwargs,
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
sns.heatmap(square, square=True, ax=a[i], **heatmap_kwargs)
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _plot_mds(
|
|
270
|
+
adj,
|
|
271
|
+
*,
|
|
272
|
+
n_components=2,
|
|
273
|
+
metric_mds=True,
|
|
274
|
+
labels=None,
|
|
275
|
+
labels_color=None,
|
|
276
|
+
cmap=None,
|
|
277
|
+
view=(30, 20),
|
|
278
|
+
figsize=None,
|
|
279
|
+
ax=None,
|
|
280
|
+
n_jobs=-1,
|
|
281
|
+
**kwargs,
|
|
282
|
+
):
|
|
283
|
+
"""Plot multidimensional scaling.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
adj (Adjacency): Adjacency object to plot (must be a single distance matrix).
|
|
287
|
+
n_components (int): Number of dimensions to project (2 or 3).
|
|
288
|
+
metric_mds (bool): Perform metric (True) or non-metric (False) scaling.
|
|
289
|
+
Default True.
|
|
290
|
+
labels (list, optional): Overrides the labels stored on `adj`.
|
|
291
|
+
labels_color (list, optional): One color per label.
|
|
292
|
+
cmap (matplotlib.colors.Colormap, optional): Colormap. Default `plt.cm.hot_r`.
|
|
293
|
+
view (tuple): Elevation/azimuth for a 3-D plot. Default (30, 20).
|
|
294
|
+
figsize (list): Figure size. Default [12, 8].
|
|
295
|
+
ax (matplotlib.axes.Axes, optional): Axis to draw on.
|
|
296
|
+
n_jobs (int): Number of parallel jobs.
|
|
297
|
+
**kwargs (dict): Forwarded to `sklearn.manifold.MDS`.
|
|
298
|
+
"""
|
|
299
|
+
import matplotlib.pyplot as plt
|
|
300
|
+
from sklearn.manifold import MDS, ClassicalMDS
|
|
301
|
+
|
|
302
|
+
if cmap is None:
|
|
303
|
+
cmap = plt.cm.hot_r
|
|
304
|
+
if figsize is None:
|
|
305
|
+
figsize = [12, 8]
|
|
306
|
+
|
|
307
|
+
if adj.matrix_type != "distance":
|
|
308
|
+
raise ValueError("MDS only works on distance matrices.")
|
|
309
|
+
if not adj.is_single_matrix:
|
|
310
|
+
raise ValueError("MDS only works on single matrices.")
|
|
311
|
+
if n_components not in [2, 3]:
|
|
312
|
+
raise ValueError(f"Cannot plot {n_components}-d image")
|
|
313
|
+
if labels is not None:
|
|
314
|
+
if len(labels) != adj.n_nodes:
|
|
315
|
+
raise ValueError(
|
|
316
|
+
"Make sure labels matches the same shape as Adjacency data"
|
|
317
|
+
)
|
|
318
|
+
else:
|
|
319
|
+
labels = adj.labels
|
|
320
|
+
if labels_color is not None:
|
|
321
|
+
if len(labels) == 0:
|
|
322
|
+
raise ValueError("Make sure that Adjacency object has labels specified.")
|
|
323
|
+
if len(labels) != len(labels_color):
|
|
324
|
+
raise ValueError("Length of labels_color must match self.labels.")
|
|
325
|
+
|
|
326
|
+
# Run MDS (sklearn >= 1.8 API). The classical-MDS starting configuration is
|
|
327
|
+
# built here, at the requested width, and passed to `fit_transform`, which
|
|
328
|
+
# takes precedence over the constructor's `init` — sklearn skips building
|
|
329
|
+
# its own, so this is computed once. Asking the constructor for it instead
|
|
330
|
+
# gives a 2-D start whatever `n_components` says, because it builds its
|
|
331
|
+
# `ClassicalMDS` with that class's own default, and `smacof` then adopts the
|
|
332
|
+
# start's width: a 3-D request would silently come back 2-D. `init` and
|
|
333
|
+
# `n_init` are still named because omitting either warns until they become
|
|
334
|
+
# sklearn's defaults in 1.9/1.10; classical MDS is deterministic, so one run
|
|
335
|
+
# suffices.
|
|
336
|
+
square = adj.squareform()
|
|
337
|
+
init = ClassicalMDS(n_components=n_components, metric="precomputed").fit_transform(
|
|
338
|
+
square
|
|
339
|
+
)
|
|
340
|
+
mds = MDS(
|
|
341
|
+
n_components=n_components,
|
|
342
|
+
metric_mds=metric_mds,
|
|
343
|
+
n_jobs=n_jobs,
|
|
344
|
+
metric="precomputed",
|
|
345
|
+
init="classical_mds",
|
|
346
|
+
n_init=1,
|
|
347
|
+
**kwargs,
|
|
348
|
+
)
|
|
349
|
+
proj = mds.fit_transform(square, init=init)
|
|
350
|
+
|
|
351
|
+
# Create Plot
|
|
352
|
+
if ax is None: # Create axis
|
|
353
|
+
fig = plt.figure(figsize=figsize)
|
|
354
|
+
if n_components == 3:
|
|
355
|
+
ax = fig.add_subplot(111, projection="3d")
|
|
356
|
+
ax.view_init(*view)
|
|
357
|
+
elif n_components == 2:
|
|
358
|
+
ax = fig.add_subplot(111)
|
|
359
|
+
|
|
360
|
+
# Plot dots
|
|
361
|
+
if n_components == 3:
|
|
362
|
+
ax.scatter(proj[:, 0], proj[:, 1], proj[:, 2], s=1, c="k")
|
|
363
|
+
elif n_components == 2:
|
|
364
|
+
ax.scatter(proj[:, 0], proj[:, 1], s=1, c="k")
|
|
365
|
+
|
|
366
|
+
# Plot labels
|
|
367
|
+
if labels_color is None:
|
|
368
|
+
labels_color = ["black"] * len(labels)
|
|
369
|
+
if n_components == 3:
|
|
370
|
+
for (x, y, z), label, color in zip(proj, labels, labels_color):
|
|
371
|
+
ax.text(
|
|
372
|
+
x,
|
|
373
|
+
y,
|
|
374
|
+
z,
|
|
375
|
+
label,
|
|
376
|
+
color="white",
|
|
377
|
+
bbox={"facecolor": color, "alpha": 1, "boxstyle": "round,pad=0.3"},
|
|
378
|
+
)
|
|
379
|
+
else:
|
|
380
|
+
for (x, y), label, color in zip(proj, labels, labels_color):
|
|
381
|
+
ax.text(
|
|
382
|
+
x,
|
|
383
|
+
y,
|
|
384
|
+
label,
|
|
385
|
+
color="white", # color,
|
|
386
|
+
bbox={"facecolor": color, "alpha": 1, "boxstyle": "round,pad=0.3"},
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
ax.xaxis.set_visible(False)
|
|
390
|
+
ax.yaxis.set_visible(False)
|
|
@@ -317,16 +317,26 @@ def _append(left, right):
|
|
|
317
317
|
|
|
318
318
|
|
|
319
319
|
def _to_square(adj):
|
|
320
|
-
"""Export detached square matrices with
|
|
320
|
+
"""Export detached square matrices with the diagonal the matrix type implies.
|
|
321
|
+
|
|
322
|
+
Symmetric matrices are stored without their diagonal, and scipy's
|
|
323
|
+
`squareform` writes zeros there. Zero is the right value for a distance but
|
|
324
|
+
not for a similarity, where every node is maximally similar to itself: a
|
|
325
|
+
zero diagonal distorts plots and colour limits, and re-reading the square
|
|
326
|
+
infers the wrong matrix type. Directed matrices are stored whole and keep
|
|
327
|
+
the diagonal they were given.
|
|
328
|
+
"""
|
|
321
329
|
if adj.matrix_type == "empty":
|
|
322
330
|
return np.empty((0, 0))
|
|
323
331
|
|
|
332
|
+
diagonal = 1 if adj.matrix_type == "similarity" else 0
|
|
333
|
+
|
|
324
334
|
def expand(row):
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
335
|
+
if not adj.issymmetric:
|
|
336
|
+
return row.reshape(adj.n_nodes, adj.n_nodes).copy()
|
|
337
|
+
square = squareform(row)
|
|
338
|
+
np.fill_diagonal(square, diagonal)
|
|
339
|
+
return square
|
|
330
340
|
|
|
331
341
|
return (
|
|
332
342
|
expand(adj.data) if adj.is_single_matrix else [expand(row) for row in adj.data]
|
|
@@ -59,7 +59,7 @@ def _similarity(
|
|
|
59
59
|
correlation_permutation_test,
|
|
60
60
|
matrix_permutation_test,
|
|
61
61
|
)
|
|
62
|
-
from
|
|
62
|
+
from .plotting import _plot_stacked
|
|
63
63
|
|
|
64
64
|
if nan_policy not in ("omit", "propagate", "raise"):
|
|
65
65
|
raise ValueError(
|
|
@@ -136,7 +136,7 @@ def _similarity(
|
|
|
136
136
|
|
|
137
137
|
if adj.is_single_matrix:
|
|
138
138
|
if plot:
|
|
139
|
-
|
|
139
|
+
_plot_stacked(adj, data2)
|
|
140
140
|
arr1 = _convert_data_similarity(data1, permutation_method=method)
|
|
141
141
|
arr2 = _convert_data_similarity(data2, permutation_method=method)
|
|
142
142
|
arr1, arr2 = _handle_nans(arr1, arr2, nan_policy)
|
|
@@ -154,9 +154,9 @@ def _similarity(
|
|
|
154
154
|
if plot:
|
|
155
155
|
import matplotlib.pyplot as plt
|
|
156
156
|
|
|
157
|
-
_,
|
|
158
|
-
for
|
|
159
|
-
|
|
157
|
+
_, axes = plt.subplots(len(adj))
|
|
158
|
+
for matrix, matrix_ax in zip(adj, axes):
|
|
159
|
+
_plot_stacked(matrix, data2, ax=matrix_ax)
|
|
160
160
|
results = []
|
|
161
161
|
arr2_base = _convert_data_similarity(data2, permutation_method=method)
|
|
162
162
|
for x in adj:
|