diff --git a/docs/source/hotspot.rst b/docs/source/hotspot.rst index 26e7557..e926387 100644 --- a/docs/source/hotspot.rst +++ b/docs/source/hotspot.rst @@ -1,8 +1,5 @@ Function Reference ****************** - -Where does this stuff get included? - .. autoclass:: hotspot.Hotspot :members: diff --git a/hotspot/hotspot.py b/hotspot/hotspot.py index a31b9df..88c5256 100644 --- a/hotspot/hotspot.py +++ b/hotspot/hotspot.py @@ -90,12 +90,6 @@ def __init__( "Both `distances_obsp_key` and `tree` provided - only one of these should be provided." ) - if distances is not None: - assert not issparse(distances) - distances = pd.DataFrame( - distances, index=adata.obs_names, columns=adata.obs_names - ) - if latent is not None: latent = pd.DataFrame(latent, index=adata.obs_names) @@ -260,7 +254,7 @@ def legacy_init( input_adata.obs[tc_key] = umi_counts.values dkey = "distances" if distances is not None: - input_adata.obsp[dkey] = np.asarray(distances) + input_adata.obsp[dkey] = distances dist_input = True else: dist_input = False diff --git a/hotspot/knn.py b/hotspot/knn.py index 0a84a12..82d7885 100644 --- a/hotspot/knn.py +++ b/hotspot/knn.py @@ -5,6 +5,7 @@ from numba import jit from tqdm import tqdm from pynndescent import NNDescent +import warnings def neighbors_and_weights(data, n_neighbors=30, neighborhood_factor=3, approx_neighbors=True): @@ -26,8 +27,10 @@ def neighbors_and_weights(data, n_neighbors=30, neighborhood_factor=3, approx_ne coords = data.values if approx_neighbors: - index = NNDescent(coords, n_neighbors=n_neighbors) + # pynndescent first neighbor is self, unlike sklearn + index = NNDescent(coords, n_neighbors=n_neighbors + 1) ind, dist = index.neighbor_graph + ind, dist = ind[:, 1:], dist[:, 1:] else: nbrs = NearestNeighbors(n_neighbors=n_neighbors, algorithm="ball_tree").fit(coords) @@ -45,7 +48,7 @@ def neighbors_and_weights(data, n_neighbors=30, neighborhood_factor=3, approx_ne def neighbors_and_weights_from_distances( - distances, n_neighbors=30, neighborhood_factor=3 + distances, cell_index, n_neighbors=30, neighborhood_factor=3 ): """ Computes nearest neighbors and associated weights using @@ -61,15 +64,23 @@ def neighbors_and_weights_from_distances( weights: pandas.Dataframe num_cells x n_neighbors """ + if isinstance(distances, pd.DataFrame): + distances = distances.values nbrs = NearestNeighbors( n_neighbors=n_neighbors, algorithm="brute", metric="precomputed" - ).fit(distances.values) - dist, ind = nbrs.kneighbors() + ).fit(distances) + try: + dist, ind = nbrs.kneighbors() + # already is a neighbors graph + except ValueError: + nn = np.asarray((distances[0] > 0).sum()) + warnings.warn(f"Provided cell-cell distance graph is likely a {nn}-neighbors graph. Using {nn} precomputed neighbors.") + dist, ind = nbrs.kneighbors(n_neighbors=nn-1) weights = compute_weights(dist, neighborhood_factor=neighborhood_factor) - ind = pd.DataFrame(ind, index=distances.index) + ind = pd.DataFrame(ind, index=cell_index) neighbors = ind weights = pd.DataFrame( weights, index=neighbors.index, columns=neighbors.columns diff --git a/pyproject.toml b/pyproject.toml index 30c30d5..f3818f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ packages = [ {include = "hotspot"}, ] readme = "README.md" -version = "1.1" +version = "1.1.1" [tool.poetry.dependencies] pytest = {version = ">=5.0", optional = true} @@ -50,10 +50,11 @@ sphinx-book-theme = {version= ">=0.2.0", optional = true} nbsphinx = {version = "*", optional = true} sphinx = {version = ">=4.1", optional = true} ipython = {version = "*", optional = true} +scanpy = {version = "*", optional = true} [tool.poetry.extras] -test = ["pytest"] +test = ["pytest", "scanpy"] docs=["sphinx-book-theme", "nbsphinx", "sphinx>", "ipython"] [tool.poetry.dev-dependencies] diff --git a/tests/test_validations.py b/tests/test_validations.py index e4da293..d62b507 100644 --- a/tests/test_validations.py +++ b/tests/test_validations.py @@ -4,6 +4,7 @@ from hotspot import Hotspot import anndata import pytest +import scanpy as sc from scipy.sparse import csc_matrix @@ -73,6 +74,18 @@ def test_models(): assert isinstance(hs.module_scores, pd.DataFrame) assert (hs.module_scores.index == gene_exp.columns).all() + # test precomputed distance matrix + sc.pp.neighbors(adata, use_rep="latent", n_neighbors=30) + hs = Hotspot( + adata, + model="normal", + umi_counts_obs_key="umi_counts", + layer_key="sparse", + distances_obsp_key="distances" + ) + hs.create_knn_graph(False, n_neighbors=30) + hs.compute_autocorrelations() + def test_filter_genes(): """