Skip to content

Commit c7455c0

Browse files
Merge pull request #4 from HiDiHlabs/dev
Dev
2 parents bf6651b + 423131b commit c7455c0

File tree

6 files changed

+70
-66
lines changed

6 files changed

+70
-66
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
88
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
99

10-
`multispaeti` is an implementation of
10+
`multispaeti` is a Python implementation of
1111
[MULTISPATI-PCA](https://doi.org/10.3170/2007-8-18312) /
1212
[spatialPCA](https://doi.org/10.1038/hdy.2008.34).
1313

docs/source/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
autodoc_typehints = "none"
3636
autodoc_typehints_format = "short"
37-
autoclass_content = "both"
37+
autoclass_content = "class"
3838
autodoc_member_order = "groupwise"
3939

4040
python_use_unqualified_type_names = True # still experimental

docs/source/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
What is ``multiSPAETI``?
22
========================
33

4-
``multispaeti`` is an implementation of
4+
``multispaeti`` is a Python implementation of
55
`MULTISPATI-PCA <https://doi.org/10.3170/2007-8-18312>`_ /
66
`spatialPCA <https://doi.org/10.1038/hdy.2008.34>`_.
77

docs/source/usage.rst

-7
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,3 @@ Alternatively, this can be achieved in one step by
5151
.. .. code-block:: python
5252
5353
.. X_transformed = msPCA.moransI_bounds()
54-
55-
.. and :py:meth:`multispaeti.MultispatiPCA.variance_moransI_decomposition` which allows
56-
.. to decompose the extracted eigenvalues into a variance and Moran's `I` contribution.
57-
58-
.. .. code-block:: python
59-
60-
.. var, moranI = msPCA.variance_moransI_decomposition(X)

multispaeti/_multispati_pca.py

+59-51
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,53 @@ class MultispatiPCA:
3131
:math:`H=1/(2n)*X^t(W+W^t)X` where `X` is matrix of `n` observations :math:`\\times`
3232
`d` features, and `W` is a matrix of the connectivity between observations.
3333
34+
Parameters
35+
----------
36+
n_components : int or tuple[int, int], optional
37+
Number of components to keep.
38+
If None, will keep all components (only supported for non-sparse `X`).
39+
If an int, it will keep the top `n_components`.
40+
If a tuple, it will keep the top and bottom `n_components` respectively.
41+
connectivity : scipy.sparse.sparray or scipy.sparse.spmatrix
42+
Matrix of row-wise neighbor definitions i.e. c\ :sub:`ij` is the connectivity of
43+
i :math:`\\to` j. The matrix does not have to be symmetric. It can be a
44+
binary adjacency matrix or a matrix of connectivities in which case
45+
c\ :sub:`ij` should be larger if i and j are close.
46+
A distance matrix should be transformed to connectivities by e.g.
47+
calculating :math:`1-d/d_{max}` beforehand.
48+
49+
Raises
50+
------
51+
ValueError
52+
If connectivity is not a square matrix.
53+
ZeroDivisionError
54+
If one of the observations has no neighbors.
55+
56+
Attributes
57+
----------
58+
components_ : numpy.ndarray
59+
The estimated components: Array of shape `(n_components, n_features)`.
60+
61+
variance_ : numpy.ndarray
62+
The estimated variance part of the eigenvalues. Array of shape `(n_components,)`.
63+
64+
moransI_ : numpy.ndarray
65+
The estimated Moran's I part of the eigenvalues. Array of shape `(n_components,)`.
66+
67+
eigenvalues_ : numpy.ndarray
68+
The eigenvalues corresponding to each of the selected components. Array of shape
69+
`(n_components,)`.
70+
71+
n_components_ : int
72+
The estimated number of components.
73+
74+
n_samples_ : int
75+
Number of samples in the training data.
76+
77+
n_features_in_ : int
78+
Number of features seen during :term:`fit`.
79+
80+
3481
References
3582
----------
3683
`Dray, Stéphane, Sonia Saïd, and Françis Débias. "Spatial ordination of vegetation
@@ -47,29 +94,6 @@ def __init__(
4794
*,
4895
connectivity: sparray | spmatrix,
4996
):
50-
"""
51-
Parameters
52-
----------
53-
54-
n_components : int or tuple[int, int], optional
55-
Number of components to keep.
56-
If None, will keep all components (only supported for non-sparse `X`).
57-
If an int, it will keep the top `n_components`.
58-
If a tuple, it will keep the top and bottom `n_components` respectively.
59-
connectivity : scipy.sparse.sparray or scipy.sparse.spmatrix
60-
Matrix of row-wise neighbor definitions i.e. c\ :sub:`ij` is the connectivity of
61-
i :math:`\\to` j. The matrix does not have to be symmetric. It can be a
62-
binary adjacency matrix or a matrix of connectivities in which case
63-
c\ :sub:`ij` should be larger if i and j are close.
64-
A distance matrix should be transformed to connectivities by e.g.
65-
calculating :math:`1-d/d_{max}` beforehand.
66-
Raises
67-
------
68-
ValueError
69-
If connectivity is not a square matrix.
70-
ZeroDivisionError
71-
If one of the observations has no neighbors.
72-
"""
7397
self._fitted = False
7498
W = csr_array(connectivity)
7599
if W.shape[0] != W.shape[1]:
@@ -247,7 +271,10 @@ def transform(self, X: _X) -> np.ndarray:
247271
self._not_fitted()
248272

249273
X = scale(X, with_mean=not issparse(X))
250-
return X @ self.components_
274+
X_t = X @ self.components_
275+
self.variance_, self.moransI_ = self._variance_moransI_decomposition(X_t)
276+
277+
return X_t
251278

252279
def fit_transform(self, X: _X) -> np.ndarray:
253280
"""
@@ -291,36 +318,17 @@ def transform_spatial_lag(self, X: _X) -> np.ndarray:
291318
def _spatial_lag(self, X: np.ndarray) -> np.ndarray:
292319
return self.W @ X
293320

294-
def variance_moransI_decomposition(self, X: _X) -> tuple[np.ndarray, np.ndarray]:
295-
"""
296-
Calculate the decomposition of the variance and Moran's I for `n_components`.
297-
298-
Parameters
299-
----------
300-
X : numpy.ndarray or scipy.sparse.csr_array or scipy.sparse.csc_array
301-
Array of observations x features.
302-
303-
Returns
304-
-------
305-
tuple[numpy.ndarray, numpy.ndarray]
306-
Variance and Moran's I.
307-
308-
Raises
309-
------
310-
ValueError
311-
If instance has not been fitted.
312-
"""
313-
if not self._fitted:
314-
self._not_fitted()
315-
transformed = self.transform(X)
316-
lag = self._spatial_lag(transformed)
321+
def _variance_moransI_decomposition(
322+
self, X_t: np.ndarray
323+
) -> tuple[np.ndarray, np.ndarray]:
324+
lag = self._spatial_lag(X_t)
317325

318326
# vector of row_Weights from dudi.PCA
319-
# (we only use default row_weights i.e. 1/n anyways)
320-
w = 1 / X.shape[0]
327+
# (we only use default row_weights i.e. 1/n)
328+
w = 1 / X_t.shape[0]
321329

322-
variance = np.sum(transformed * transformed * w, axis=0)
323-
moran = np.sum(transformed * lag * w, axis=0) / variance
330+
variance = np.sum(X_t * X_t * w, axis=0)
331+
moran = np.sum(X_t * lag * w, axis=0) / variance
324332

325333
return variance, moran
326334

multispaeti/_plotting.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ def plot_eigenvalues(msPCA: MultispatiPCA, *, n_top: int | None = None) -> Figur
1313
Parameters
1414
----------
1515
msPCA : MultispatiPCA
16+
An instance of MultispatiPCA that has already been used for
17+
:py:meth:`multispaeti.MultispatiPCA.fit` so that eigenvalues have already been
18+
calculated.
1619
n_top : int, optional
1720
Plot the `n_top` highest and `n_top` lowest eigenvalues in a zoomed in view.
1821
@@ -52,7 +55,7 @@ def plot_eigenvalues(msPCA: MultispatiPCA, *, n_top: int | None = None) -> Figur
5255

5356

5457
def plot_variance_moransI_decomposition(
55-
msPCA: MultispatiPCA, X, *, sparse_approx: bool = True, **kwargs
58+
msPCA: MultispatiPCA, *, sparse_approx: bool = True, **kwargs
5659
) -> Figure:
5760
"""
5861
Plot the decomposition of variance and Moran's I of the MULTISPATI-PCA eigenvalues.
@@ -63,8 +66,9 @@ def plot_variance_moransI_decomposition(
6366
Parameters
6467
----------
6568
msPCA : multispaeti.MultispatiPCA
66-
X : numpy.ndarray or scipy.sparse.csr_array or scipy.sparse.csc_array
67-
TODO Data to calculate the decomposition for.
69+
An instance of MultispatiPCA that has already been used for
70+
:py:meth:`multispaeti.MultispatiPCA.transform` so that variance and Moran's I
71+
contributions to the eigenvalues have already been calculated.
6872
sparse_approx : bool
6973
Whether to use a sparse approximation to calculate the decomposition.
7074
@@ -73,11 +77,10 @@ def plot_variance_moransI_decomposition(
7377
matplotlib.figure.Figure
7478
"""
7579

76-
variance, moranI = msPCA.variance_moransI_decomposition(X)
7780
I_min, I_max, I_0 = msPCA.moransI_bounds(sparse_approx=sparse_approx)
7881

7982
fig, ax = plt.subplots(1)
80-
_ = ax.scatter(x=variance, y=moranI, **kwargs)
83+
_ = ax.scatter(x=msPCA.variance_, y=msPCA.moransI_, **kwargs)
8184

8285
plt.axhline(y=I_0, ls="--")
8386
plt.axhline(y=I_min, ls="--")

0 commit comments

Comments
 (0)