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.
Files changed (158) hide show
  1. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/.gitignore +9 -4
  2. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/PKG-INFO +1 -1
  3. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/__init__.py +6 -4
  4. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/__init__.py +83 -3
  5. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/io.py +6 -1
  6. nltools-0.6.0.dev1/nltools/data/adjacency/plotting.py +390 -0
  7. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/state.py +16 -6
  8. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/stats.py +5 -5
  9. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/__init__.py +41 -17
  10. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/bootstrap.py +20 -15
  11. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/io.py +6 -0
  12. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/modeling.py +158 -64
  13. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/plotting.py +6 -1
  14. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/prediction.py +40 -38
  15. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/utils.py +30 -17
  16. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/diagnostics.py +1 -1
  17. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/io.py +84 -41
  18. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/plotting.py +2 -4
  19. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/results.py +97 -6
  20. nltools-0.6.0.dev1/nltools/data/results_io.py +210 -0
  21. nltools-0.6.0.dev1/nltools/data/simulator/haxby.py +283 -0
  22. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/io/h5.py +105 -0
  23. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/results.py +36 -1
  24. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/__init__.py +1 -2
  25. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/adjacency.py +1 -64
  26. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/prediction.py +1 -1
  27. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_corrections.py +1 -2
  28. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_procrustes.py +3 -4
  29. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_mask.py +1 -2
  30. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/datasets/test_datasets.py +89 -4
  31. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/test_adjacency.py +139 -41
  32. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/pyproject.toml +10 -9
  33. nltools-0.6.0.dev0/nltools/data/adjacency/plotting.py +0 -174
  34. nltools-0.6.0.dev0/nltools/data/simulator/haxby.py +0 -124
  35. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/LICENSE +0 -0
  36. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/README.md +0 -0
  37. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/__init__.py +0 -0
  38. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/__init__.py +0 -0
  39. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/alignment/__init__.py +0 -0
  40. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/alignment/procrustes.py +0 -0
  41. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/alignment/srm.py +0 -0
  42. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/backends.py +0 -0
  43. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/corrections.py +0 -0
  44. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/decoding.py +0 -0
  45. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/__init__.py +0 -0
  46. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/bootstrap.py +0 -0
  47. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/correlation.py +0 -0
  48. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/intersubject.py +0 -0
  49. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/isc.py +0 -0
  50. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/matrix.py +0 -0
  51. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/one_sample.py +0 -0
  52. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/random.py +0 -0
  53. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/timeseries.py +0 -0
  54. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/two_sample.py +0 -0
  55. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/utils.py +0 -0
  56. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/inference/validation.py +0 -0
  57. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/neighborhoods.py +0 -0
  58. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/outliers.py +0 -0
  59. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/regression.py +0 -0
  60. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/signal.py +0 -0
  61. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/similarity.py +0 -0
  62. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/algorithms/validation.py +0 -0
  63. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/cross_validation.py +0 -0
  64. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/modeling.py +0 -0
  65. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/adjacency/utils.py +0 -0
  66. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/__init__.py +0 -0
  67. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/labeling.py +0 -0
  68. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/loading.py +0 -0
  69. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/registry.py +0 -0
  70. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/atlases/reporting.py +0 -0
  71. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/analysis.py +0 -0
  72. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/validation.py +0 -0
  73. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/viewer.js +0 -0
  74. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/braindata/viewer.py +0 -0
  75. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/combine.py +0 -0
  76. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/__init__.py +0 -0
  77. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/append.py +0 -0
  78. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/regressors.py +0 -0
  79. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/transforms.py +0 -0
  80. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/designmatrix/utils.py +0 -0
  81. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/ownership.py +0 -0
  82. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/roc/__init__.py +0 -0
  83. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/simulator/__init__.py +0 -0
  84. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/data/validation.py +0 -0
  85. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/datasets.py +0 -0
  86. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/io/__init__.py +0 -0
  87. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/io/events.py +0 -0
  88. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/mask.py +0 -0
  89. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/__init__.py +0 -0
  90. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/glm.py +0 -0
  91. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/ridge.py +0 -0
  92. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/models/validation.py +0 -0
  93. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/brain.py +0 -0
  94. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/plotting/decomposition.py +0 -0
  95. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/resources/covariates_example.csv +0 -0
  96. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/resources/onsets_example.csv +0 -0
  97. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/__init__.py +0 -0
  98. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/config.py +0 -0
  99. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/fetch.py +0 -0
  100. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/matching.py +0 -0
  101. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/paths.py +0 -0
  102. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/templates/registry.py +0 -0
  103. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/conftest.py +0 -0
  104. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/__init__.py +0 -0
  105. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/__init__.py +0 -0
  106. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/conftest.py +0 -0
  107. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_decoding.py +0 -0
  108. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_intersubject.py +0 -0
  109. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_neighborhoods.py +0 -0
  110. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_outliers.py +0 -0
  111. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_regression.py +0 -0
  112. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_signal.py +0 -0
  113. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_algorithms/test_similarity.py +0 -0
  114. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_backends.py +0 -0
  115. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_bootstrap.py +0 -0
  116. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_cross_validation.py +0 -0
  117. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_gpu_policy.py +0 -0
  118. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_hyperalignment.py +0 -0
  119. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/__init__.py +0 -0
  120. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_api_conventions.py +0 -0
  121. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_correlation.py +0 -0
  122. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_cpu_parallelization.py +0 -0
  123. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_isc_group.py +0 -0
  124. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_isc_vocabulary.py +0 -0
  125. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_matrix.py +0 -0
  126. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_one_sample.py +0 -0
  127. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_progress_bar.py +0 -0
  128. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_tail_vocabulary.py +0 -0
  129. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_timeseries.py +0 -0
  130. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_two_sample.py +0 -0
  131. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_inference/test_utils.py +0 -0
  132. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_isc.py +0 -0
  133. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_srm.py +0 -0
  134. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/core/test_utils.py +0 -0
  135. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/datasets/__init__.py +0 -0
  136. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/io_tests/__init__.py +0 -0
  137. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/io_tests/test_file_reader.py +0 -0
  138. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/io_tests/test_h5.py +0 -0
  139. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/__init__.py +0 -0
  140. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/conftest.py +0 -0
  141. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_glm.py +0 -0
  142. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_glm_warnings.py +0 -0
  143. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_results.py +0 -0
  144. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/models/test_ridge.py +0 -0
  145. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/__init__.py +0 -0
  146. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/test_f123_prediction.py +0 -0
  147. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/plotting/test_surface.py +0 -0
  148. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/pyodide/.gitignore +0 -0
  149. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/pyodide/test_runner.mjs +0 -0
  150. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/support/__init__.py +0 -0
  151. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/support/test_designation.py +0 -0
  152. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/templates/__init__.py +0 -0
  153. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/templates/test_brainspace.py +0 -0
  154. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/templates/test_fetch_pyodide.py +0 -0
  155. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/utils/__init__.py +0 -0
  156. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/tests/utils/test_utils.py +0 -0
  157. {nltools-0.6.0.dev0 → nltools-0.6.0.dev1}/nltools/utils.py +0 -0
  158. {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
- # Tutorial pages: build artifacts generated from the marimo notebooks beside
82
- # them by scripts/marimo_to_zensical.py (`uv run poe docs-generate`). The
83
- # patterns match the notebooks (scripts/marimo_to_zensical.py TUTORIAL_GLOBS),
84
- # so a hand-written page in either directory is still tracked.
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.dev0
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 (`Predict`, `BootstrapResult`, `ContrastResult`) plus the
7
- brain-space configuration record (`BrainSpaceConfig`).
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, Predict
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
- "Predict",
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 always has a
23
- zero diagonal; input diagonals are discarded. Flat rectangular stacks require
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. Symmetric diagonals are zero.
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
- G = nx.Graph(adj.squareform())
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 a zero symmetric diagonal."""
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
- return (
326
- squareform(row)
327
- if adj.issymmetric
328
- else row.reshape(adj.n_nodes, adj.n_nodes).copy()
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 nltools.plotting import _plot_stacked_adjacency
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
- _plot_stacked_adjacency(adj, data)
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
- _, a = plt.subplots(len(adj))
158
- for i in a:
159
- _plot_stacked_adjacency(adj, data, ax=i)
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: