From 13a6d12c3d792aeea5c69d17e35ded6cf14564cf Mon Sep 17 00:00:00 2001 From: xingyan <544568643@qq.com> Date: Sat, 16 Oct 2021 19:41:34 +0800 Subject: [PATCH] refine --- .gitignore | 4 + MANIFEST.in | 4 + build/lib/swnn/__init__.py | 17 -- build/lib/swnn/builder.py | 248 ------------------ build/lib/swnn/graph2tree.py | 362 --------------------------- build/lib/swnn/multipartite_graph.py | 266 -------------------- build/lib/swnn/utils/__init__.py | 13 - build/lib/swnn/utils/_scale.py | 152 ----------- build/lib/swnn/utils/plot.py | 33 --- build/lib/swnn/utils/process.py | 355 -------------------------- dist/swnn-0.1.0-py3-none-any.whl | Bin 19005 -> 0 bytes dist/swnn-0.1.0-py3.8.egg | Bin 38586 -> 0 bytes dist/swnn-0.1.0.tar.gz | Bin 16188 -> 0 bytes swnn.egg-info/PKG-INFO | 87 ------- swnn.egg-info/SOURCES.txt | 16 -- swnn.egg-info/dependency_links.txt | 1 - swnn.egg-info/requires.txt | 6 - swnn.egg-info/top_level.txt | 1 - 18 files changed, 8 insertions(+), 1557 deletions(-) create mode 100644 MANIFEST.in delete mode 100644 build/lib/swnn/__init__.py delete mode 100644 build/lib/swnn/builder.py delete mode 100644 build/lib/swnn/graph2tree.py delete mode 100644 build/lib/swnn/multipartite_graph.py delete mode 100644 build/lib/swnn/utils/__init__.py delete mode 100644 build/lib/swnn/utils/_scale.py delete mode 100644 build/lib/swnn/utils/plot.py delete mode 100644 build/lib/swnn/utils/process.py delete mode 100644 dist/swnn-0.1.0-py3-none-any.whl delete mode 100644 dist/swnn-0.1.0-py3.8.egg delete mode 100644 dist/swnn-0.1.0.tar.gz delete mode 100644 swnn.egg-info/PKG-INFO delete mode 100644 swnn.egg-info/SOURCES.txt delete mode 100644 swnn.egg-info/dependency_links.txt delete mode 100644 swnn.egg-info/requires.txt delete mode 100644 swnn.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore index 77a35eb..9b2db68 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ __pycache__ /notebooks/.ipynb_checkpoints _tmp* + +#dist/ +#build/ +#*.egg-info diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..66b6e30 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include ./sample_data/subsampled_B-L0-0.2.h5ad.rar +include ./sample_data/hvg_frequencies.csv +include ./sample_data/lineage_colors.csv +include README.md \ No newline at end of file diff --git a/build/lib/swnn/__init__.py b/build/lib/swnn/__init__.py deleted file mode 100644 index 2106ad2..0000000 --- a/build/lib/swnn/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: UTF-8 -*- -""" -Construction of developmental tree from single-cell RNA-seq data using -StagewiseNN -""" - -from .utils import * -from .multipartite_graph import * -from .graph2tree import ( - adaptive_tree, - max_connection, - make_children_dict, - find_children, -) -from .builder import Builder - - diff --git a/build/lib/swnn/builder.py b/build/lib/swnn/builder.py deleted file mode 100644 index af1ca55..0000000 --- a/build/lib/swnn/builder.py +++ /dev/null @@ -1,248 +0,0 @@ -# -*- coding: UTF-8 -*- -""" -@CreateDate: 2021/07/25 -@Author: Xingyan Liu -@File: builder.py -@Project: stagewiseNN -""" -import os -import sys -from pathlib import Path -from typing import Sequence, Mapping, Optional, Union, Callable -import logging -import pandas as pd -import numpy as np -from scipy import sparse -import scanpy as sc - -from .utils import quick_preprocess_raw, make_binary -from .multipartite_graph import stagewise_knn -from .graph2tree import max_connection, adaptive_tree - - -class BuilderParams: - - def __init__(self, **kwargs): - - self._dict = {} - self.update(**kwargs) - - def update(self, **kwargs): - self._dict.update(**kwargs) - return self - - @property - def keys(self): - return self._dict.keys() - - def __getattr__(self, key): - return self._dict[key] - - -class Builder(object): - - def __init__( - self, - stage_order: Sequence, - **build_params - ): - """ - Parameters - ---------- - stage_order: Sequence - the order of stages - """ - self.stage_order = stage_order - self._params = BuilderParams(**build_params) - self._distmat = None - self._connect = None - self._stage_lbs = None - self._group_lbs = None - self._edgedf = None - self._refined_group_lbs = None - - @property - def stage_lbs(self): - return self._stage_lbs - - @property - def group_lbs(self): - return self._group_lbs - - # @group_lbs.setter - # def group_lbs(self, group_lbs): - # pass - - @property - def distmat(self): - return self._distmat - - @property - def connect(self): - return self._connect - - @property - def connect_bin(self): - """ binarized edges """ - if self._connect is not None: - return make_binary(self._connect) - return None - - @property - def edgedf(self): - return self._edgedf - - @property - def refined_group_lbs(self): - return self._refined_group_lbs - - def build_graph( - self, - X, stage_lbs, - binary_edge: bool = True, - ks: Union[Sequence[int], int] = 10, - n_pcs: Union[Sequence[int], int] = 10, - pca_base_on: Optional[str] = 'stacked', - leaf_size: int = 5, - **kwargs - ): - """ - Build multipartite KNN-graph stage-by-stage. - - Parameters - ---------- - X: np.ndarray or sparse matrix - data matrix, of shape (n_samples, n_features) - stage_lbs: Sequence - stage labels for each sample (nodes in `build_graph`) - binary_edge: bool (default=True) - whether to use the binarized edges. Set as True may cause some - information loss but a more robust result. - ks: - the number of nearest neighbors to be calculated. - n_pcs: - The number of principal components after PCA reduction. - If `pca_base_on` is None, this will be ignored. - pca_base_on: str {'x1', 'x2', 'stacked', None} (default='stacked') - if None, perform KNN on the original data space. - leaf_size: int (default=5) - Leaf size passed to BallTree or KDTree, for adjusting the - approximation level. The higher the faster, while of - less promises to find the exact nearest neighbors. - Setting as 1 for brute-force (exact) KNN. - kwargs: - other parameters for `stagewise_knn` - - Returns - ------- - distmat: sparse.csr_matrix - the distance matrix, of shape (n_samples, n_samples) - connect: sparse.csr_matrix - the connectivities matrix, of shape (n_samples, n_samples) - """ - self._stage_lbs = stage_lbs - distmat, connect = stagewise_knn( - X, self.stage_lbs, - stage_order=self.stage_order, - k=ks, - leaf_size=leaf_size, # 1 for brute-force KNN - pca_base_on=pca_base_on, - n_pcs=n_pcs, - binary_edge=False, - **kwargs - ) - self._distmat = distmat - self._connect = connect - if binary_edge: - connect = self.connect_bin - - # record parameters - self._params.update( - binary_edge=binary_edge, - ks=ks, - n_pcs=n_pcs, - pca_base_on=pca_base_on, - leaf_size=leaf_size, - ) - - return distmat, connect - - def build_tree( - self, - group_lbs: Sequence, - stage_lbs: Optional[Sequence] = None, - ignore_pa=(), - ext_sep: str = '_', - ): - """ - Adaptatively build the developmental tree from the stagewise-KNN graph. - - Parameters - ---------- - group_lbs: Sequence - group labels for each sample (nodes in `build_graph`) - stage_lbs: Sequence - stage labels for each sample (nodes in `build_graph`) - ignore_pa: list or set - parent nodes to be ignored; empty tuple by default. - ext_sep: str - parse string for automatically extract the stage-labels from - `group_lbs` - - Returns - ------- - edgedf: pd.DataFrame - pd.DataFrame of columns {'node', 'parent', 'prop'}, - and of the same number of rows as number of total stage-clusters. - the column 'prop' is the proportion of nodes that have votes for - the current parent. - refined_group_lbs: - refined group labels for each sample (e.g. single-cell) - """ - # connect-matrix NOT calculated by StagewiseNN may cause un-expected - # result by using `sparse.triu()`. - # TODO: define `take_cross_stage_edges(spmatrix)` - conn_upper = sparse.triu(self.connect) - adj_max = max_connection(conn_upper) - - self._group_lbs = group_lbs - if self.stage_lbs is None: - self._stage_lbs = stage_lbs - - edgedf, refined_group_lbs = adaptive_tree( - adj_max, self.group_lbs, - stage_lbs=self.stage_lbs, - stage_ord=self.stage_order, - ignore_pa=ignore_pa, - ext_sep=ext_sep, - ) - - self._edgedf = edgedf - self._refined_group_lbs = refined_group_lbs - - # record parameters - self._params.update( - ignore_pa=ignore_pa, - ext_sep=ext_sep, - ) - - return edgedf, refined_group_lbs - - -def __test__(): - pass - - -if __name__ == '__main__': - import time - - logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s %(filename)s-%(lineno)d-%(funcName)s(): ' - '%(levelname)s\n%(message)s' - ) - t = time.time() - __test__() - print('Done running file: {}\nTime: {}'.format( - os.path.abspath(__file__), time.time() - t, - )) diff --git a/build/lib/swnn/graph2tree.py b/build/lib/swnn/graph2tree.py deleted file mode 100644 index 6340c81..0000000 --- a/build/lib/swnn/graph2tree.py +++ /dev/null @@ -1,362 +0,0 @@ -# -*- coding: UTF-8 -*- -# @CreateDate: 2020/07/18 -# @Author: Xingyan Liu -# @File: graph2tree.py -# @Project: stagewiseNN - -import os -import sys -from pathlib import Path -from typing import Sequence, List, Mapping, Optional, Union, Callable, Any -import logging -import pandas as pd -import numpy as np -from scipy import sparse -from sklearn.preprocessing import label_binarize -from .utils import label_binarize_each - - -def adaptive_tree(adj_max: sparse.spmatrix, - group_lbs: Sequence, - stage_lbs: Optional[Sequence] = None, - stage_ord: Optional[Sequence] = None, - ignore_pa: Union[List, set] = (), - ext_sep: str = '_', - ): - """ - Adaptatively build the developmental tree from the stagewise-KNN graph. - - Parameters - ---------- - adj_max: - sparse.csc_matrix, shape = (n_points, n_points) - adjacent matrix of single points (cells) - group_lbs: - np.array, shape = (n_points,) - group labels specifying each cluster in each stage. - e.g. 'stage1_1' specifies the cluster 1 in stage1. - stage_lbs: - np.array, shape = (n_points,) - stage labels, better be explicitly assigned. If None, this will be - extracted from `group_lbs`, and may cause unexpected results. - stage_ord: - np.array, shape = (n_stages,) - order of stages, better provided by user; if None, it will be decided - automatically. - ignore_pa: list or set - parent nodes to be ignored; empty tuple by default. - ext_sep: str - parse string for automatically extract the stage-labels from `group_lbs` - - Returns - ------- - edgedf: pd.DataFrame - a DataFrame with each row representing an edge, columns are - ['node', 'parent', 'prop'], where 'prop' is the proportion of nodes - that have voted for the current parent. - group_lbs: - refined group-labels for each sample (e.g. single-cell) - - Examples - -------- - >>> edgedf, group_lbs = adaptive_tree(adj_max, group_lbs, stage_ord=stages) - - """ - group_lbs = np.asarray(group_lbs).copy() - if stage_lbs is None: - stage_lbs = _extract_field(group_lbs, sep=ext_sep, i=0) - if stage_ord is None: - print('stage orders are decided automatically:') - stage_ord = pd.unique(stage_lbs) - print(stage_ord) - - stg_grp_dict = make_stage_group_dict(group_lbs, stage_lbs=stage_lbs) - - adj_max = sparse.csc_matrix(adj_max) - edgedfs = [] - for i, stg0 in enumerate(stage_ord[: -1]): - - groups0 = stg_grp_dict[stg0] - if len(groups0) < 2: - continue - - stg1 = stage_ord[i + 1] - inds0 = np.flatnonzero(stage_lbs == stg0) - inds1 = np.flatnonzero(stage_lbs == stg1) - adj_sub = adj_max[:, inds1][inds0, :] - group_lbs0 = group_lbs[inds0] - group_lbs1 = group_lbs[inds1] - # groups1 = stg_grp_dict[stg1] - print(f'connecting stage {stg0} and {stg1}') - - edgedf_sub, new_group_lbs1, new_groups1 = connect_near_stages( - adj_sub, - group_lbs0, group_lbs1, - groups0=groups0, groups1=stg_grp_dict[stg1], - # stg0=stg0, stg1=stg1, - df2edges=True, - ignore_pa=ignore_pa, - ) - group_lbs[inds1] = new_group_lbs1 - stg_grp_dict[stg1] = new_groups1 - edgedfs.append(edgedf_sub) - print() - - edgedf = pd.concat(edgedfs, axis=0, ignore_index=True) - - return edgedf, group_lbs # , stg_grp_dict - - -def connect_near_stages(adj_max, - group_lbs0, group_lbs1, - groups0=None, groups1=None, - # stg0=None, stg1=None, - ignore_pa: Sequence = (), - df2edges: bool = True, - sep: str = '_', - ): - """ - Parameters - ---------- - adj_max: - csc_sparse matrix, adjacent matrix of samples (cells), - shape (n_samples, n_samples) - group_lbs0: - group labels of the parent stage - group_lbs1: - group labels of the descendent stage - - Returns - ------- - edgedf: - a DataFrame with each row representing an edge, columns are - ['node', 'parent', 'prop'], where 'prop' is the proportion of nodes - that vote for the current parent. - new_group_lbs1: - np.array, refined group labels - new_groups1: - unique group names from `new_group_lbs1` - - """ - adj_max = sparse.csc_matrix(adj_max) - groups0 = pd.unique(group_lbs0) if groups0 is None else groups0 - groups1 = pd.unique(group_lbs1) if groups1 is None else groups1 - # each column normalized to unit sum - # group_conn = agg_group_edges(adj_max, group_lbs0, group_lbs1, - # groups0=groups0, groups1=groups1,) - # print(group_conn) - voting_props = agg_group_edge_props(adj_max, group_lbs0, group_lbs1, - groups0=groups0, groups1=groups1, - axis=0) - - winner_pas = voting_props.idxmax(axis=0) # a series - single_pas = [x for x in groups0 if x not in winner_pas.values] - print('parent nodes that had no descendent:', single_pas) - for p in ignore_pa: - if p in single_pas: - print(f'ignore single parent node {p}') - single_pas.remove(p) - - # modify the groups labels in group1 - is_strlbs = isinstance(group_lbs1[0], str) - if is_strlbs: - stg1 = groups1[0].split(sep)[0] - new_group_lbs1 = _extract_field(group_lbs1, sep=sep, i=-1).astype(int) - else: # integer labels - new_group_lbs1 = group_lbs1.copy() - max_num = new_group_lbs1.max() - - print('Taking descendant-points from other nodes (groups)') - # adj_max = max_connection(adj_max, axis=0) - for i, pa in enumerate(single_pas): - parent_ids = group_lbs0 == pa - sub_adj = adj_max[parent_ids, :] # sns.heatmap(sub_adj.toarray()) - rows, cols = sub_adj.nonzero() # `cols` is the indices of child-nodes - new_name = max_num + 1 + i - new_group_lbs1[cols] = new_name - - new_groups1 = np.unique(new_group_lbs1) - if is_strlbs: - print('pasting stage labels') - new_groups1 = [f'{stg1}{sep}{x}' for x in new_groups1] - new_group_lbs1 = np.array( - list(map(lambda x: f'{stg1}{sep}{x}', new_group_lbs1))) - - # ========= new voting proportions ============ - voting_props = agg_group_edge_props(adj_max, group_lbs0, new_group_lbs1, - groups0=groups0, groups1=new_groups1, - axis=0, verbose=True) - - edgedf = max_connection(voting_props, axis=0, df2edges=df2edges) - return edgedf, new_group_lbs1, new_groups1 - - -def agg_group_edge_props(adj, group_lbs0, group_lbs1=None, - groups0=None, groups1=None, - # asdf = True, - axis=0, verbose=True): - group_conn = agg_group_edges(adj, group_lbs0, group_lbs1=group_lbs1, - groups0=groups0, groups1=groups1, asdf=True, - verbose=verbose) - # normalize each column to unit sum - voting_props = group_conn.apply(lambda x: x / x.sum(), axis=axis) - return voting_props - - -def agg_group_edges(adj, group_lbs0, group_lbs1=None, - groups0=None, groups1=None, asdf=True, verbose=True): - """ - Parameters - ---------- - adj: - adjacent matrix of shape (N0, N1), if `group_lbs1` is None, then set N0=N1. - group_lbs0: - a list or a np.array of shape (N0,) - group_lbs1: - a list or a np.array of shape (N1,) - - Returns - ------- - group_conn: summation of connected edges between given groups - """ - # if sparse.issparse(adj): - adj = sparse.csc_matrix(adj) - - groups0 = pd.unique(group_lbs0) if groups0 is None else groups0 - lb1hot0 = label_binarize_each(group_lbs0, classes=groups0, sparse_out=True) - if group_lbs1 is None: - lb1hot1 = lb1hot0 - groups1 = groups0 - else: - groups1 = pd.unique(group_lbs1) if groups1 is not None else groups1 - lb1hot1 = label_binarize_each(group_lbs1, classes=groups1, - sparse_out=True) - if verbose: - print('---> aggregating edges...') - print('unique labels of rows:', groups0) - print('unique labels of columns:', groups1) - print('grouping elements (edges)') - print('shape of the one-hot-labels:', lb1hot0.shape, lb1hot1.shape) - group_conn = lb1hot0.T.dot(adj).dot(lb1hot1) - # print(group_conn.shape) - if asdf: - group_conn = pd.DataFrame(group_conn.toarray(), - index=groups0, columns=groups1) - - return group_conn - - -def max_connection(adj, axis=0, df2edges=False): - """ - keep only max element (connection) for each column (axis=0), and remove - the other elements (connections) - """ - if isinstance(adj, pd.DataFrame): - def keep_max(x): - cut = x.max() - x[x < cut] = 0 - return x - - adj_max = adj.apply(keep_max, axis=axis) - if df2edges: - adj_max = _make_edgedf(adj_max, col_key='node', row_key='parent', - data_key='prop', ) - else: - adj = sparse.csc_matrix(adj) - shape = adj.shape - vmaxs = adj.max(axis=0).A[0] # .A[0] for csc matrix - idxmax = adj.argmax(axis=axis).A[0] # .A[0] for csc matrix - adj_max = sparse.coo_matrix( - (vmaxs, - (idxmax, np.arange(shape[1]))), - shape=shape) - return adj_max - - -def _make_edgedf(df, col_key='node', row_key='parent', - data_key='prop', ): - coo_data = sparse.coo_matrix(df.values) - edgedf = pd.DataFrame({ - col_key: df.columns.take(coo_data.col), - row_key: df.index.take(coo_data.row), - data_key: coo_data.data, - }) - - return edgedf - - -def make_stage_group_dict(group_lbs, stage_lbs=None): - if stage_lbs is None: - stage_lbs = _extract_field(group_lbs, sep='_', i=0) - - stages = pd.unique(stage_lbs) - group_lbs = np.asarray(group_lbs) - dct = {} - for stg in stages: - dct[stg] = pd.unique(group_lbs[stage_lbs == stg]) - return dct - - -def find_children( - nodes: Sequence, - children_dict: Mapping[Any, Sequence], - n: int = 100): - """ - Parameters - ---------- - nodes: - better a list of node(s) to be looked up - children_dict: dict - parent (key) -> children (value) - n: - max number of iterations - """ - if isinstance(nodes, (int, str)): - nodes = [nodes] - has_children = [nd in children_dict.keys() for nd in nodes] - has_any_children = any(has_children) - if not has_any_children: - return [] - if n < 1: - return [] - - children = [] - for i, nd in enumerate(nodes): - if has_children[i]: - children += children_dict[nd] - - n -= 1 - children = children + find_children(children, children_dict, n=n) - # if None in children: - # children.remove(None) - return children - - -def make_children_dict(df_tree, column_pa='parent', column_ch='node'): - """ making a dict for looking up descendants - """ - children_dict = df_tree.groupby(column_pa)[column_ch].apply(lambda x: list(x)) - return children_dict.to_dict() - - -def _extract_field(labels, sep='_', i=0): - return np.array(list(map(lambda x: x.split(sep)[i], labels))) - - -def __test__(): - pass - - -if __name__ == '__main__': - import time - - logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s %(filename)s-%(lineno)d-%(funcName)s(): ' - '%(levelname)s\n%(message)s' - ) - t = time.time() - __test__() - print('Done running file: {}\nTime: {}'.format( - os.path.abspath(__file__), time.time() - t, - )) diff --git a/build/lib/swnn/multipartite_graph.py b/build/lib/swnn/multipartite_graph.py deleted file mode 100644 index 384de67..0000000 --- a/build/lib/swnn/multipartite_graph.py +++ /dev/null @@ -1,266 +0,0 @@ -# -*- coding: UTF-8 -*- -# @CreateDate: 2020/07/18 -# @Author: Xingyan Liu -# @File: multipartite_graph.py -# @Project: stagewiseNN - -import os -import sys -from pathlib import Path -from typing import Sequence, Mapping, Optional, Union, Callable -import logging -import pandas as pd -import numpy as np -from scipy import sparse -from sklearn.preprocessing import normalize -from sklearn.decomposition import PCA -from sklearn.neighbors import NearestNeighbors, KDTree - - -def stagewise_knn(X: np.ndarray, - stage_lbs: Sequence, - stage_order: Sequence, - leaf_size: Optional[int] = 5, - n_pcs: Union[Sequence[int], int] = 30, - k: Union[Sequence[int], int] = 30, - pca_base_on: Optional[str] = 'stack', - binary_edge: bool = True, - norm_dists=False, - **kwargs - ): - """ - Build multipartite KNN-graph stage-by-stage. - - Parameters - ---------- - X: np.ndarray or sparse matrix - data matrix, of shape (n_samples, n_features) - stage_lbs: Sequence - stage labels for each sample (nodes in `build_graph`) - stage_order: Sequence - stage order - binary_edge: bool (default=True) - whether to use the binarized edges. Set as True may cause some - information loss but a more robust result. - k: - the number of nearest neighbors to be calculated. - n_pcs: - The number of principal components after PCA reduction. - If `pca_base_on` is None, this will be ignored. - pca_base_on: str {'x1', 'x2', 'stacked', None} (default='stacked') - if None, perform KNN on the original data space. - leaf_size: int (default=5) - Leaf size passed to BallTree or KDTree, for adjusting the - approximation level. The higher the faster, while of - less promises to find the exact nearest neighbors. - Setting as 1 for brute-force (exact) KNN. - norm_dists: bool - whether to normalize the distance for each pair of adjacent-stages. - - Returns - ------- - distmat: sparse.csr_matrix - the distance matrix, of shape (n_samples, n_samples) - connect: sparse.csr_matrix - the connectivities matrix, of shape (n_samples, n_samples) - - """ - # setting parameters - n_pcs = [n_pcs] * len(stage_order) if isinstance(n_pcs, int) else n_pcs - k = [k] * len(stage_order) if isinstance(k, int) else k - - stage_lbs = np.asarray(stage_lbs) - N = X.shape[0] - Inds = np.arange(N) - - iis = [] - dds = [] - jjs = [] - conns = [] - for i, stg1 in enumerate(stage_order): - if i == 0: - # stg0 = stg1 - inds_earlier = inds_later = stage_lbs == stg1 - X_earlier = X[inds_earlier, :] - X_later = None - else: - stg0 = stage_order[i - 1] - inds_earlier = stage_lbs == stg0 - inds_later = stage_lbs == stg1 - X_earlier = X[inds_earlier, :] - X_later = X[inds_later, :] - - if pca_base_on is not None: - X_earlier, X_later = pca_transform( - X_earlier, X_later, - n_pcs=n_pcs[i], base_on=pca_base_on - ) - - dist, inds0 = approx_knn(X_earlier, X_later, k=k[i], - leaf_size=leaf_size, **kwargs) - inds = np.take(Inds[inds_earlier], inds0) - conn = _dist_to_connection(dist, norm=norm_dists, binarize=binary_edge) - - iis.append(np.repeat(Inds[inds_later], k[i])) - jjs.append(inds.flatten()) - dds.append(dist.flatten()) - conns.append(conn.flatten()) - - logging.info('Done on KNN searching, constructing the results distances ' - 'and connections') - iis, jjs, dds, conns = tuple(map( - lambda x: np.concatenate(x), (iis, jjs, dds, conns) - )) - - distmat = sparse.coo_matrix((dds, (iis, jjs)), shape=(N, N)) - distmat += distmat.T - connect = sparse.coo_matrix((conns, (iis, jjs)), shape=(N, N)) - connect += connect.T - logging.info(f'distance matrix of shape {distmat.shape} and ' - f'{distmat.nnz} non-zeros') - logging.info(f'connection matrix of shape {connect.shape} and ' - f'{connect.nnz} non-zeros') - return distmat, connect - - -def _dist_to_connection( - dist: np.ndarray, - norm: bool = True, - binarize: bool = False, - sigma: float = 1., -): - """ - dist: shape=(n_samples, n_neighbors) - """ - if binarize: - return np.ones_like(dist, dtype=float) - else: - if norm: - conn = normalize( - _connect_heat_kernel(_normalize_dists(dist), sigma), - 'l1', axis=1, - ) * np.sqrt(dist.shape[1]) - else: - conn = normalize( - _connect_heat_kernel(dist, sigma), 'l1', axis=1 - ) * np.sqrt(dist.shape[1]) - return conn - - -def _normalize_dists(d): - """ - d: 2-D np.array, normalization will perform on each row - """ - return np.array(list(map(_normalize_dists_single, d))) - - -def _normalize_dists_single(d): - """ - d: 1-D np.array - """ - d1 = d[d.nonzero()] - vmin = d1.min() * 0.99 - # vmax = d1.max() * 0.99 - med = np.median(d1) - d2 = (d - vmin) / (med - vmin) - d2[d2 < 0] = 0 - return d2 - - -def _connect_heat_kernel(d, sigma): - return np.exp(-np.square(d / sigma)) - - -def pca_transform( - X1: np.ndarray, - X2: Optional[np.ndarray] = None, - n_pcs: int = 50, - base_on: str = 'stacked', - **kwargs -): - """ - base_on: {'x1', 'x2', 'stacked'} - """ - pca = PCA(n_components=n_pcs, **kwargs) - if X2 is None: - logging.debug(f'base_on=X1, and X2=None') - return pca.fit_transform(X1), None - if base_on.lower() == 'x1': - logging.debug(f'PCA base_on: {base_on}') - X1 = pca.fit_transform(X1) - X2 = pca.transform(X2) - elif base_on.lower() == 'x2': - logging.debug(f'PCA base_on: {base_on}') - X2 = pca.fit_transform(X2) - X1 = pca.transform(X1) - else: - logging.debug(f'PCA on the stacked data matrix (base_on={base_on})') - n1 = X1.shape[0] - X_pca = pca.fit_transform(np.vstack([X1, X2])) - X1 = X_pca[: n1, :] - X2 = X_pca[n1:, :] - return X1, X2 - - -def approx_knn( - X_ref: np.ndarray, - X_que: Optional[np.ndarray] = None, - metric='cosine', - k: int = 5, - precis: float = 0.1, - leaf_size: Optional[int] = 5, - leaf_size_max: int = 30, - algorithm='kd_tree', - metric_params=None, - **kwargs -): - """ - Parameters - ---------- - algorithm : {'auto', 'ball_tree', 'kd_tree', 'brute'} - default='auto' - leaf_size : int, default=30 - Leaf size passed to BallTree or KDTree. - """ - if leaf_size is None: - n = X_ref.shape[0] - leaf_size = min([int(np.sqrt(n * n) * precis), leaf_size_max]) - leaf_size = max([leaf_size, 1]) - elif leaf_size <= 1: - algorithm = 'brute' - - if metric == 'cosine': # pretend cosine matric - X_ref = normalize(X_ref, axis=1) - if X_que is not None: - X_que = normalize(X_que, axis=1) - metric = 'minkowski' - indexer = NearestNeighbors( - n_neighbors=k, - algorithm=algorithm, - leaf_size=leaf_size, - metric=metric, metric_params=metric_params, - **kwargs - ).fit(X_ref) - dist, inds = indexer.kneighbors(X_que, return_distance=True) - # indexer = KDTree(X_ref, leaf_size=leaf_size, metric=metric) - # dist, inds = indexer.query(X_que, k=k) - return dist, inds - - -def __test__(): - pass - - -if __name__ == '__main__': - import time - - logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s %(filename)s-%(lineno)d-%(funcName)s(): ' - '%(levelname)s\n%(message)s' - ) - t = time.time() - __test__() - print('Done running file: {}\nTime: {}'.format( - os.path.abspath(__file__), time.time() - t, - )) diff --git a/build/lib/swnn/utils/__init__.py b/build/lib/swnn/utils/__init__.py deleted file mode 100644 index b876a4f..0000000 --- a/build/lib/swnn/utils/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: UTF-8 -*- -""" -@CreateDate: 2021/07/18 -@Author: Xingyan Liu -@File: __init__.py -@Project: stagewiseNN -""" - -from .plot import * -from .process import * - - - diff --git a/build/lib/swnn/utils/_scale.py b/build/lib/swnn/utils/_scale.py deleted file mode 100644 index e870e83..0000000 --- a/build/lib/swnn/utils/_scale.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: UTF-8 -*- -""" -@CreateDate: 2021/07/18 -@Author: Xingyan Liu -@File: _scale.py -@Project: stagewiseNN -""" -import os -import sys -from pathlib import Path -from typing import Sequence, Mapping, Optional, Union, Callable -import logging -import pandas as pd -import numpy as np -from scipy import sparse -import scanpy as sc -from sklearn.preprocessing import StandardScaler, label_binarize - - -def zscore(X, with_mean=True, scale=True, ): - """ For each column of X, do centering (z-scoring) - ==== - code borrowed from `scanpy.pp._simple` - """ - scaler = StandardScaler(with_mean=with_mean, copy=True).partial_fit(X) - if scale: - # user R convention (unbiased estimator) - e_adjust = np.sqrt(X.shape[0] / (X.shape[0] - 1)) - scaler.scale_ *= e_adjust - else: - scaler.scale_ = np.array([1] * X.shape[1]) - X_new = scaler.transform(X) - if isinstance(X, pd.DataFrame): - X_new = pd.DataFrame(X_new, index=X.index, columns=X.columns) - return X_new - - -def group_zscore(X, labels, with_mean=True, scale=True, max_value=None): - """ - For each column of X, do within-group centering (z-scoring) - ====== - X: np.array, shape (n_samples, n_features) - i.e. each row of X is an observation, wile each column is a feature - - with_mean: boolean, True by default - If True, center the data before scaling, and X shoud be a dense matrix. - """ - isdf = False - if isinstance(X, pd.DataFrame): - isdf = True - index, columns, X = X.index, X.columns, X.values - X = X.astype(np.float).copy() - labels = np.asarray(labels) - unique_labels = np.unique(labels) - for lb in unique_labels: - ind = labels == lb - if sum(ind) == 1: - logging.warning(f'ignoring class {lb} with only one sample.') - continue - X[ind, :] = zscore(X[ind, :], with_mean=with_mean, scale=scale) - - if max_value is not None: - X[X > max_value] = max_value - logging.info('... clipping at max_value', max_value) - - if isdf: - X = pd.DataFrame(X, index=index, columns=columns) - return X - - -def group_zscore_adata(adt, key='counts', groupby='batch', key_new=None, - max_value=None, - with_mean=True, - cover=True, **kwds): - """ - adt: AnnData - key: str in ['X_pca', 'count'] - can be a key from adt.obsm, e.g. `key='X_pca'` - If key == 'counts', then do scaling on `adt.X` - and cover the old count matrix, ignoring the `cover` parameter - groupby: str; - A key from adt.obs, from which the labels are take - cover: bool - whether to cover the old X with the scored X - """ - labels = adt.obs[groupby] - if key == 'counts': - logging.info('doing z-score scaling on count matrix, ' - 'transformed into a dense array') - if sparse.issparse(adt.X): - X = adt.X.toarray() - else: - X = adt.X - if not cover: - adt = adt.copy() - X_scaled = group_zscore( - X, labels, with_mean=with_mean, - max_value=max_value, **kwds) - # logging.debug('X_scaled = %s', X_scaled) - adt.X = X_scaled - else: - if cover: - key_new = key - else: - key_new = key + '_new' if key_new is None else key_new - adt.obsm[key_new] = group_zscore( - adt.obsm[key], labels, with_mean=with_mean, max_value=None, **kwds) - - return adt - - -def wrapper_scale(adata, zero_center=True, max_value=None, - groupby=None, copy=False, **kwds): - """ - Wrapper function for centering and scaling data matrix `X` in sc.AnnData, - extended for within-batch cprocessing. - - Example - ======= - wrapper_scale(adata, groupby='batch') - """ - if groupby is not None: - logging.info(f'doing within-group scaling, group by [ {groupby} ]') - return group_zscore_adata(adata, key='counts', - max_value=max_value, - groupby=groupby, - with_mean=zero_center, - cover=not copy, - **kwds) - else: - logging.info('using the build-in function `sc.pp.scale(..)`') - return sc.pp.scale(adata, zero_center=zero_center, - max_value=max_value, copy=copy) - - -def __test__(): - pass - - -if __name__ == '__main__': - import time - - logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s %(filename)s-%(lineno)d-%(funcName)s(): ' - '%(levelname)s\n%(message)s' - ) - t = time.time() - __test__() - print('Done running file: {}\nTime: {}'.format( - os.path.abspath(__file__), time.time() - t, - )) diff --git a/build/lib/swnn/utils/plot.py b/build/lib/swnn/utils/plot.py deleted file mode 100644 index f2a4b31..0000000 --- a/build/lib/swnn/utils/plot.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: UTF-8 -*- -""" -@CreateDate: 2020/07/18 -@Author: Xingyan Liu -@File: plot.py -@Project: stagewiseNN -""" -import os -import sys -from pathlib import Path -from typing import Sequence, Mapping, Optional, Union, Callable -import logging -import pandas as pd -import numpy as np - - -def __test__(): - pass - - -if __name__ == '__main__': - import time - - logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s %(filename)s-%(lineno)d-%(funcName)s(): ' - '%(levelname)s\n%(message)s' - ) - t = time.time() - __test__() - print('Done running file: {}\nTime: {}'.format( - os.path.abspath(__file__), time.time() - t, - )) diff --git a/build/lib/swnn/utils/process.py b/build/lib/swnn/utils/process.py deleted file mode 100644 index 4cbf1da..0000000 --- a/build/lib/swnn/utils/process.py +++ /dev/null @@ -1,355 +0,0 @@ -# -*- coding: UTF-8 -*- -""" -@CreateDate: 2020/07/18 -@Author: Xingyan Liu -@File: process.py -@Project: stagewiseNN -""" -import os -import sys -from pathlib import Path -from typing import Sequence, Mapping, Optional, Union, Callable -import logging -import pandas as pd -import numpy as np -import scanpy as sc -from scipy import sparse -from sklearn.preprocessing import label_binarize -from ._scale import wrapper_scale - - -def check_dirs(path): - if os.path.exists(path): - print('already exists:\n\t%s' % path) - else: - os.makedirs(path) - print('a new directory made:\n\t%s' % path) - - -def reverse_dict(d: dict, ): - """ - the values of the dict must be list-like type - """ - d_rev = {} - for k in d.keys(): - vals = d[k] - _d = dict.fromkeys(vals, k) - d_rev.update(_d) - return d_rev - - -def describe_dataframe(df: pd.DataFrame, **kwargs): - for c in df.columns: - print(c.center(40, '-'), **kwargs) - print(describe_series(df[c], asstr=True), **kwargs) - - -def describe_series( - srs: Sequence, max_cats: int = 100, - asstr: bool = False, -): - """ inspect data-structure """ - srs = pd.Series(srs) - if len(srs.unique()) <= max_cats: - desc_type = 'Value counts' - result = srs.value_counts(dropna=False) - elif isinstance(srs[0], (int, float)): - desc_type = 'Numerical summary' - result = srs.describe() - else: - desc_type = 'Header lines' - result = srs.head() - if asstr: - return f'{desc_type}:\n{result}' - else: - return result, desc_type - - -def make_binary(mat): - mat_bin = mat.copy() - mat_bin[mat_bin > 0] = 1. - return mat_bin - - -def set_adata_hvgs( - adata: sc.AnnData, - gene_list: Optional[Sequence] = None, - indicator: Optional[Sequence[bool]] = None, - slim: bool = True, - copy: bool = False, -): - """ - Setting the given (may be pre-computed) set of genes as highly variable, - if `copy` is False, changes will be made to the input adata. - if slim is True and adata.raw is None, raw data will be backup. - """ - if copy: - adata = adata.copy() - logging.info( - 'Setting the given set of %d genes as highly variable' % len(gene_list)) - if (indicator is None) and (gene_list is not None): - indicator = [g in gene_list for g in adata.var_names] - adata.var['highly_variable'] = indicator - if slim: - if adata.raw is None: - adata.raw = adata - logging.info('slimming adata to contain only HVGs') - adata = adata[:, adata.var['highly_variable']].copy() - return adata - - -def change_names( - seq: Sequence, - mapping: Optional[Mapping] = None, - **kwmaps -) -> list: - mapping = {} if mapping is None else mapping - mapping.update(kwmaps) - func = lambda x: mapping.get(x, x) - return list(map(func, seq)) - - -def normalize_default( - adata, target_sum=None, - copy=False, log_only=False, -): - if copy: - adata = adata.copy() - logging.info('A copy of AnnData made!') - else: - logging.info('No copy was made, the input AnnData will be changed!') - logging.info('normalizing datasets with default settings.') - if not log_only: - logging.info( - f'performing total-sum normalization, target_sum={target_sum}...') - sc.pp.normalize_total(adata, target_sum=target_sum) - else: - logging.info('skipping total-sum normalization') - sc.pp.log1p(adata) - return adata - - -def normalize_log_then_total( - adata, target_sum=None, - copy=False, -): - """ For SplitSeq data, performing log(x+1) BEFORE total-sum normalization - will results a better UMAP visualization (e.g. clusters would be less - confounded by different total-counts ). - """ - if copy: - adata = adata.copy() - logging.info('A copy of AnnData made!') - sc.pp.log1p(adata, ) - sc.pp.normalize_total(adata, target_sum=target_sum, ) - return adata - - -def set_precomputed_neighbors( - adata, - distances, - connectivities=None, - n_neighbors=15, - metric='cosine', # pretended parameter - method='umap', # pretended parameter - metric_kwds=None, # pretended parameter - use_rep=None, # pretended parameter - n_pcs=None, # pretended parameter - key_added=None, # -): - if key_added is None: - key_added = 'neighbors' - conns_key = 'connectivities' - dists_key = 'distances' - else: - conns_key = key_added + '_connectivities' - dists_key = key_added + '_distances' - - if connectivities is None: - connectivities = distances.copy().tocsr() - connectivities[connectivities > 0] = 1 - - adata.obsp[dists_key] = distances - adata.obsp[conns_key] = connectivities - - adata.uns[key_added] = {} - neighbors_dict = adata.uns[key_added] - neighbors_dict['connectivities_key'] = conns_key - neighbors_dict['distances_key'] = dists_key - - neighbors_dict['params'] = {'n_neighbors': n_neighbors, 'method': method} - neighbors_dict['params']['metric'] = metric - if metric_kwds is not None: - neighbors_dict['params']['metric_kwds'] = metric_kwds - if use_rep is not None: - neighbors_dict['params']['use_rep'] = use_rep - if n_pcs is not None: - neighbors_dict['params']['n_pcs'] = n_pcs - - return adata - - -def quick_preprocess_raw( - adata: sc.AnnData, - target_sum: Optional[int] = None, - hvgs: Optional[Sequence] = None, - batch_key=None, - copy=True, - log_first: bool = False, - **hvg_kwds -) -> sc.AnnData: - if copy: - _adata = adata.copy() - logging.info('A copy of AnnData made!') - else: - _adata = adata - logging.info('No copy was made, the input AnnData will be changed!') - # 1: normalization - if log_first: - normalize_log_then_total(_adata, target_sum=target_sum) - else: - normalize_default(_adata, target_sum=target_sum) - # 2: HVG selection (skipped if `hvgs` is given) - if hvgs is None: - sc.pp.highly_variable_genes( - _adata, batch_key=batch_key, **hvg_kwds) - indicator = _adata.var['highly_variable'] - # _adata = _adata[:, _adata.var['highly_variable']] - else: - indicator = None - _adata = set_adata_hvgs(_adata, gene_list=hvgs, indicator=indicator, ) - # 3: z-score - wrapper_scale(_adata, groupby=batch_key) - return _adata - - -def label_binarize_each(labels, classes, sparse_out=True): - lb1hot = label_binarize(labels, classes=classes, sparse_output=sparse_out) - if len(classes) == 2: - lb1hot = lb1hot.toarray() - lb1hot = np.c_[1 - lb1hot, lb1hot] - if sparse_out: - lb1hot = sparse.csc_matrix(lb1hot) - return lb1hot - - -def group_mean(X, labels, - binary=False, classes=None, features=None, - print_groups=True): - """ - This function may work with more efficiency than `df.groupby().mean()` - when handling sparse matrix. - - Parameters - ---------- - X: shape (n_samples, n_features) - labels: shape (n_samples, ) - classes: optional - names of groups - features: optional - names of features - print_groups: bool - whether to inspect the groups - """ - classes = np.unique(labels, ) if classes is None else classes - if binary: - X = (X > 0) # .astype('float') - print('Binarized...the results will be the expression proportions.') - - if len(classes) == 1: - grp_mean = X.mean(axis=0).T - else: - lb1hot = label_binarize_each(labels, classes=classes, sparse_out=True) - print(f'Calculating feature averages for {len(classes)} groups') - if print_groups: - print(classes) - grp_mean = X.T.dot(lb1hot) / lb1hot.sum(axis=0) - grp_mean = pd.DataFrame(grp_mean, columns=classes, index=features) - return grp_mean - - -def group_mean_dense( - X, labels, binary=False, - index_name='group', - classes=None, -): - classes = np.unique(labels, ) if classes is None else classes - if binary: - X = (X > 0) # .astype('float') - logging.info('Binarized...the results will be the expression ' - 'proportions.') - tmp = pd.DataFrame(X) - tmp[index_name] = list(labels) - avgs = tmp.groupby(index_name).mean() - # print(avgs.shape) - return avgs.T # each column as a group - - -def group_median_dense( - X, labels, binary=False, - index_name='group', - classes=None, -): - classes = np.unique(labels, ) if classes is None else classes - if binary: - X = (X > 0) # .astype('float') - print('Binarized...the results will be the expression proportions.') - tmp = pd.DataFrame(X) - tmp[index_name] = list(labels) - avgs = tmp.groupby(index_name).median() - print(avgs.shape) - return avgs.T # each column as a group - - -def group_mean_adata(adata: sc.AnnData, - groupby: str, - features=None, binary=False, use_raw=False): - """ - Parameters - ---------- - groupby: a column name in adata.obs - features: a subset of names in adata.var_names (or adata.raw.var_names) - - Returns - ------- - a pd.DataFrame with features as index and groups as columns - """ - labels = adata.obs[groupby] - logging.debug(f'Computing averages grouped by {groupby}') - if use_raw and adata.raw is not None: - if features is not None: - features = [f for f in features if f in adata.raw.var_names] - X = adata.raw[:, features].X - else: - features = adata.raw.var_names - X = adata.raw.X - else: - if features is not None: - features = [f for f in features if f in adata.var_names] - X = adata[:, features].X - else: - features = adata.var_names - X = adata.X - if sparse.issparse(X): - return group_mean(X, labels, binary=binary, features=features) - else: - return group_mean_dense(X, labels, binary=binary, ) - - -def __test__(): - pass - - -if __name__ == '__main__': - import time - - logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s %(filename)s-%(lineno)d-%(funcName)s(): ' - '%(levelname)s\n%(message)s' - ) - t = time.time() - __test__() - print('Done running file: {}\nTime: {}'.format( - os.path.abspath(__file__), time.time() - t, - )) diff --git a/dist/swnn-0.1.0-py3-none-any.whl b/dist/swnn-0.1.0-py3-none-any.whl deleted file mode 100644 index 65bdc341d05424cebe3d276d9390ee1f7651fd14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19005 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 Rn}7NMFxbYa03`qe{Xada2WtQT diff --git a/swnn.egg-info/PKG-INFO b/swnn.egg-info/PKG-INFO deleted file mode 100644 index a547e61..0000000 --- a/swnn.egg-info/PKG-INFO +++ /dev/null @@ -1,87 +0,0 @@ -Metadata-Version: 2.1 -Name: swnn -Version: 0.1.0 -Summary: A tool for building developmental tree from scRNA-seq -Home-page: https://github.com/XingyanLiu/stagewiseNN -Author: Xingyan Liu -Author-email: 544568643@qq.com -License: MIT -Platform: UNKNOWN -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Requires-Python: >=3.6.0 -Description-Content-Type: text/markdown -Provides-Extra: downstream analysis -License-File: LICENSE - - -stagewiseNN -=========== - -To be completed! - -**stagewiseNN** is a computational tool for constructing -developmental tree from Multi-staged single-cell RNA-seq data. - -(see [documentation](https://xingyanliu.github.io/stagewiseNN/index.html) for detailed guides) - -It is easy to use: - -```python -import swnn - -# ====== Inputs ====== -# data_matrix = .. -# stage_labels = .. -# group_labels = .. -# stage_order = [f'stage_{i}' for i in range(5)] - -builder = swnn.Builder(stage_order=stage_order) -# step 1: -# building (stage-wise) single-cell graph -distmat, connect = builder.build_graph( - X=data_matrix, stage_lbs=stage_labels, - ) -# step 2: -# build developmental tree from single-cell graph -builder.build_tree(group_labels, stage_labels,) -``` - - -Installation ------------- - -Install stagewiseNN by running (in the command line): - -```shell -pip install swnn -``` - -or install from source code: - -```shell -git clone https://github.com/XingyanLiu/stagewiseNN.git -cd stagewiseNN -python setup.py install -``` - -Contribute ----------- - -- Issue Tracker: https://github.com/XingyanLiu/stagewiseNN/issues -- Source Code: https://github.com/XingyanLiu/stagewiseNN - -Support -------- - -If you are having issues, please let us know. -We have a mailing list located at: - -* xingyan@amss.ac.cn -* 544568643@qq.com - - diff --git a/swnn.egg-info/SOURCES.txt b/swnn.egg-info/SOURCES.txt deleted file mode 100644 index 4a045af..0000000 --- a/swnn.egg-info/SOURCES.txt +++ /dev/null @@ -1,16 +0,0 @@ -LICENSE -README.md -setup.py -swnn/__init__.py -swnn/builder.py -swnn/graph2tree.py -swnn/multipartite_graph.py -swnn.egg-info/PKG-INFO -swnn.egg-info/SOURCES.txt -swnn.egg-info/dependency_links.txt -swnn.egg-info/requires.txt -swnn.egg-info/top_level.txt -swnn/utils/__init__.py -swnn/utils/_scale.py -swnn/utils/plot.py -swnn/utils/process.py \ No newline at end of file diff --git a/swnn.egg-info/dependency_links.txt b/swnn.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/swnn.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/swnn.egg-info/requires.txt b/swnn.egg-info/requires.txt deleted file mode 100644 index 3759197..0000000 --- a/swnn.egg-info/requires.txt +++ /dev/null @@ -1,6 +0,0 @@ -scanpy -scikit_learn -scipy - -[downstream analysis] -scanpy diff --git a/swnn.egg-info/top_level.txt b/swnn.egg-info/top_level.txt deleted file mode 100644 index cf92c39..0000000 --- a/swnn.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -swnn