├── .DS_Store ├── .readthedocs.yaml ├── LICENSE.md ├── README.md ├── build ├── .DS_Store └── lib │ └── flowsig │ ├── __init__.py │ ├── data │ ├── allTFs_human.txt │ ├── allTFs_mouse.txt │ ├── allTFs_zebrafish.txt │ ├── cellchat_interactions_and_tfs_human.csv │ └── cellchat_interactions_and_tfs_mouse.csv │ ├── plotting │ ├── __init__.py │ └── _plotting.py │ ├── preprocessing │ ├── __init__.py │ ├── _flow_expressions.py │ ├── _flow_preprocessing.py │ ├── _gem_construction.py │ ├── _spatial_blocking.py │ └── _townes_nsf_utils.py │ ├── tools │ ├── __init__.py │ ├── _network.py │ ├── _spatial.py │ └── _validate_network.py │ └── utilities │ ├── __init__.py │ └── _utils.py ├── dist ├── flowsig-0.1.0-py2.py3-none-any.whl └── flowsig-0.1.0.tar.gz ├── docs ├── .DS_Store ├── Makefile ├── build │ ├── .DS_Store │ ├── doctrees │ │ ├── environment.pickle │ │ └── index.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _sources │ │ └── index.rst.txt │ │ ├── _static │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── custom.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── sphinx_highlight.js │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── search.html │ │ └── searchindex.js ├── make.bat └── source │ ├── .DS_Store │ ├── conf.py │ └── index.rst ├── flowsig.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── requires.txt └── top_level.txt ├── flowsig ├── .DS_Store ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── __init__.cpython-38.pyc │ └── __init__.cpython-39.pyc ├── data │ ├── allTFs_human.txt │ ├── allTFs_mouse.txt │ ├── allTFs_zebrafish.txt │ ├── cellchat_interactions_and_tfs_human.csv.gz │ ├── cellchat_interactions_and_tfs_mouse.csv.gz │ ├── cellchat_interactions_tfs_human.csv.gz │ └── cellchat_interactions_tfs_mouse.csv.gz ├── plotting │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── _plotting.cpython-310.pyc │ │ └── _plotting.cpython-38.pyc │ └── _plotting.py ├── preprocessing │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── __init__.cpython-39.pyc │ │ ├── _flow_expressions.cpython-310.pyc │ │ ├── _flow_expressions.cpython-38.pyc │ │ ├── _flow_expressions.cpython-39.pyc │ │ ├── _flow_preprocessing.cpython-310.pyc │ │ ├── _flow_preprocessing.cpython-38.pyc │ │ ├── _flow_preprocessing.cpython-39.pyc │ │ ├── _gem_construction.cpython-310.pyc │ │ ├── _gem_construction.cpython-38.pyc │ │ ├── _gem_construction.cpython-39.pyc │ │ ├── _spatial_blocking.cpython-310.pyc │ │ ├── _spatial_blocking.cpython-38.pyc │ │ ├── _townes_nsf_utils.cpython-310.pyc │ │ └── _townes_nsf_utils.cpython-38.pyc │ ├── _flow_expressions.py │ ├── _flow_preprocessing.py │ ├── _gem_construction.py │ ├── _spatial_blocking.py │ └── _townes_nsf_utils.py ├── tools │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── _network.cpython-310.pyc │ │ ├── _network.cpython-38.pyc │ │ ├── _validate_network.cpython-310.pyc │ │ └── _validate_network.cpython-38.pyc │ ├── _network.py │ ├── _spatial.py │ └── _validate_network.py ├── tutorials │ ├── .ipynb_checkpoints │ │ ├── mouse_embryo_stereoseq_example-checkpoint.ipynb │ │ ├── mouse_embryo_stereoseq_example_script-checkpoint.py │ │ ├── pancreatic_islets_example_script-checkpoint.py │ │ └── pancreatic_islets_scrnaseq_example-checkpoint.ipynb │ ├── burkhardt21_communications_Ctrl.csv │ ├── burkhardt21_communications_IFNg.csv │ ├── mouse_embryo_stereoseq_example.ipynb │ ├── mouse_embryo_stereoseq_example_script.py │ ├── pancreatic_islets_example_script.py │ └── pancreatic_islets_scrnaseq_example.ipynb └── utilities │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── __init__.cpython-38.pyc │ ├── _utils.cpython-310.pyc │ └── _utils.cpython-38.pyc │ └── _utils.py ├── poetry.lock └── pyproject.toml /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/.DS_Store -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Read the Docs configuration file for Sphinx projects 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.10" 13 | # You can also specify other tool versions: 14 | # nodejs: "20" 15 | # rust: "1.70" 16 | # golang: "1.20" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 22 | # builder: "dirhtml" 23 | # Fail on all warnings to avoid broken references 24 | # fail_on_warning: true 25 | 26 | # Optionally build your docs in additional formats such as PDF and ePub 27 | # formats: 28 | # - pdf 29 | # - epub 30 | 31 | # Optional but recommended, declare the Python requirements required 32 | # to build your documentation 33 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 34 | # python: 35 | # install: 36 | # - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Axel Allen Almet] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/build/.DS_Store -------------------------------------------------------------------------------- /build/lib/flowsig/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__ file 2 | import warnings 3 | warnings.filterwarnings('ignore') 4 | 5 | from . import preprocessing as pp 6 | from . import tools as tl 7 | from . import plotting as pl 8 | from . import utilities as ul 9 | 10 | import sys 11 | sys.modules.update({f'{__name__}.{m}': globals()[m] for m in ['pp','tl','pl', 'ul']}) -------------------------------------------------------------------------------- /build/lib/flowsig/plotting/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plotting import plot_differentially_flowing_signals, plot_intercellular_flows -------------------------------------------------------------------------------- /build/lib/flowsig/preprocessing/__init__.py: -------------------------------------------------------------------------------- 1 | from ._flow_expressions import construct_flows_from_cellchat, construct_flows_from_commot, construct_flows_from_cellphonedb, construct_flows_from_liana 2 | from ._flow_preprocessing import subset_for_flow_type, filter_flow_vars, determine_differentially_flowing_vars, determine_spatially_flowing_vars, determine_informative_variables 3 | from ._gem_construction import construct_gems_using_pyliger, construct_gems_using_nsf, construct_gems_using_nmf 4 | from ._spatial_blocking import construct_spatial_blocks 5 | -------------------------------------------------------------------------------- /build/lib/flowsig/preprocessing/_flow_preprocessing.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple, Optional 2 | import numpy as np 3 | import scanpy as sc 4 | import squidpy as sq 5 | import pandas as pd 6 | 7 | def subset_for_flow_type(adata: sc.AnnData, 8 | var_type: str = 'all', 9 | flowsig_expr_key: str = 'X_flow', 10 | flowsig_network_key: str = 'flowsig_network'): 11 | 12 | var_types = ['all', 'inflow', 'module', 'outflow'] 13 | 14 | if var_type not in var_types: 15 | ValueError("Need to specify var_type as one of the following: %s" % var_types) 16 | 17 | X_flow = adata.obsm[flowsig_expr_key] 18 | adata_subset = sc.AnnData(X=X_flow) 19 | adata_subset.obs = adata.obs 20 | adata_subset.var = pd.DataFrame(adata.uns[flowsig_network_key]['flow_var_info']) 21 | 22 | if var_type != 'all': 23 | 24 | adata_subset = adata_subset[:, adata_subset.var['Type'] == var_type] 25 | 26 | return adata_subset 27 | 28 | 29 | def filter_flow_vars(adata: sc.AnnData, 30 | vars_subset: List[str], 31 | flowsig_expr_key: str = 'X_flow', 32 | flowsig_network_key: str = 'flowsig_network'): 33 | 34 | X_flow_orig = adata.obsm[flowsig_expr_key] 35 | flow_var_info_orig = adata.uns[flowsig_network_key]['flow_var_info'] 36 | flowsig_vars_orig = flow_var_info_orig.index.tolist() 37 | 38 | # We define the list in this weird way to preserve all the var types etc 39 | subset_indices = [flowsig_vars_orig.index(flow_var) for flow_var in vars_subset] 40 | 41 | X_flow = X_flow_orig[:, subset_indices] 42 | 43 | # Subset the flowsig network info as well 44 | flow_var_info = flow_var_info_orig[flow_var_info_orig.index.isin(vars_subset)] 45 | 46 | # Store the new FlowSig variable information 47 | flowsig_info = {'flow_var_info': flow_var_info} 48 | flowsig_orig_info = {'flow_var_info': flow_var_info_orig} 49 | 50 | adata.obsm[flowsig_expr_key + '_orig'] = X_flow_orig 51 | adata.obsm[flowsig_expr_key] = X_flow 52 | adata.uns[flowsig_network_key + '_orig'] = flowsig_orig_info 53 | adata.uns[flowsig_network_key] = flowsig_info 54 | 55 | 56 | def determine_differentially_flowing_vars(adata: sc.AnnData, 57 | condition_key: str, 58 | control_key: str, 59 | flowsig_expr_key: str = 'X_flow', 60 | flowsig_network_key: str = 'flowsig_network', 61 | qval_threshold: float = 0.05, 62 | logfc_threshold: float = 0.5): 63 | 64 | # Construct AnnData for flow expression 65 | perturbed_conditions = [cond for cond in adata.obs[condition_key].unique().tolist() if cond != control_key] 66 | flow_var_info = adata.uns[flowsig_network_key]['flow_var_info'] 67 | 68 | # Construct inflow and outflow adata objects 69 | adata_inflow = subset_for_flow_type(adata, 70 | var_type = 'inflow', 71 | flowsig_expr_key = flowsig_expr_key, 72 | flowsig_network_key = flowsig_network_key) 73 | 74 | 75 | adata_outflow = subset_for_flow_type(adata, 76 | var_type = 'outflow', 77 | flowsig_expr_key = flowsig_expr_key, 78 | flowsig_network_key = flowsig_network_key) 79 | 80 | # Calculate differentially inflowing vars 81 | adata_inflow.uns['log1p'] = {'base': None} # Just in case 82 | sc.tl.rank_genes_groups(adata_inflow, key_added=condition_key, groupby=condition_key, method='wilcoxon') 83 | 84 | # Determine the differentially flowing vars 85 | diff_inflow_vars = [] 86 | 87 | lowqval_des_inflow = {} 88 | for cond in perturbed_conditions: 89 | 90 | # Get the DEs with respect to this contrast 91 | result = sc.get.rank_genes_groups_df(adata_inflow, group=cond, key=condition_key).copy() 92 | result["-logQ"] = -np.log(result["pvals"].astype("float")) 93 | lowqval_de = result.loc[(np.abs(result["logfoldchanges"]) > logfc_threshold)&(result["pvals_adj"] < qval_threshold)] 94 | 95 | lowqval_des_inflow[cond] = lowqval_de['names'].tolist() 96 | 97 | diff_inflow_vars = list(set.union(*map(set, [lowqval_des_inflow[cond] for cond in lowqval_des_inflow]))) 98 | 99 | # Calculate differentially inflowing vars 100 | adata_outflow.uns['log1p'] = {'base':None} # Just in case 101 | sc.tl.rank_genes_groups(adata_outflow, key_added=condition_key, groupby=condition_key, method='wilcoxon') 102 | 103 | # Determine the differentially flowing vars 104 | diff_outflow_vars = [] 105 | 106 | lowqval_des_outflow = {} 107 | for cond in perturbed_conditions: 108 | 109 | # Get the DEs with respect to this contrast 110 | result = sc.get.rank_genes_groups_df(adata_outflow, group=cond, key=condition_key).copy() 111 | result["-logQ"] = -np.log(result["pvals"].astype("float")) 112 | lowqval_de = result.loc[(np.abs(result["logfoldchanges"]) > logfc_threshold)&(abs(result["pvals_adj"]) < qval_threshold)] 113 | 114 | lowqval_des_outflow[cond] = lowqval_de['names'].tolist() 115 | 116 | diff_outflow_vars = list(set.union(*map(set, [lowqval_des_outflow[cond] for cond in lowqval_des_outflow]))) 117 | 118 | # We don't change GEM vars because from experience, they typically incorporate condition-specific changes as is 119 | gem_vars = flow_var_info[flow_var_info['Type'] == 'module'].index.tolist() 120 | vars_to_subset = diff_inflow_vars + diff_outflow_vars + gem_vars 121 | 122 | filter_flow_vars(adata, 123 | vars_to_subset, 124 | flowsig_expr_key, 125 | flowsig_network_key) 126 | 127 | def determine_spatially_flowing_vars(adata: sc.AnnData, 128 | flowsig_expr_key: str = 'X_flow', 129 | flowsig_network_key: str = 'flowsig_network', 130 | moran_threshold: float = 0.1, 131 | coord_type: str = 'grid', 132 | n_neighbours: int = 6, 133 | library_key: str = None, 134 | n_perms: int = None, 135 | n_jobs: int = None): 136 | 137 | # Get the flow info 138 | flow_var_info = adata.uns[flowsig_network_key]['flow_var_info'] 139 | 140 | # Construct inflow and outflow adata objects 141 | adata_inflow = subset_for_flow_type(adata, 142 | var_type = 'inflow', 143 | flowsig_expr_key = flowsig_expr_key, 144 | flowsig_network_key = flowsig_network_key) 145 | 146 | adata_outflow = subset_for_flow_type(adata, 147 | var_type = 'outflow', 148 | flowsig_expr_key = flowsig_expr_key, 149 | flowsig_network_key = flowsig_network_key) 150 | 151 | if 'spatial' not in adata.obsm: 152 | ValueError("Need to specify spatial coordinates in adata.obsm['spatial'].") 153 | else: 154 | adata_outflow.obsm['spatial'] = adata.obsm['spatial'] 155 | adata_inflow.obsm['spatial'] = adata.obsm['spatial'] 156 | 157 | # Can't have spatial connectivities without spatial coordinates, lol 158 | if 'spatial_connectivities' not in adata.obsp: 159 | 160 | coord_types = ['grid', 'generic'] 161 | 162 | if coord_type not in coord_types: 163 | ValueError("Please specify coord_type to be one of %s" % coord_types) 164 | 165 | sq.gr.spatial_neighbors(adata_outflow, coord_type=coord_type, n_neighs=n_neighbours, library_key=library_key) 166 | sq.gr.spatial_neighbors(adata_inflow, coord_type=coord_type, n_neighs=n_neighbours, library_key=library_key) 167 | 168 | sq.gr.spatial_autocorr(adata_outflow, genes=adata_outflow.var_names.tolist(), n_perms=n_perms, n_jobs=n_jobs) 169 | sq.gr.spatial_autocorr(adata_inflow, genes=adata_inflow.var_names.tolist(), n_perms=n_perms, n_jobs=n_jobs) 170 | 171 | # Filter genes based on moran_threshold 172 | svg_outflows = adata_outflow.uns['moranI'][adata_outflow.uns['moranI']['I'] > moran_threshold].index.tolist() 173 | svg_inflows = adata_inflow.uns['moranI'][adata_inflow.uns['moranI']['I'] > moran_threshold].index.tolist() 174 | gem_vars = flow_var_info[flow_var_info['Type'] == 'module'].index.tolist() 175 | 176 | spatially_flowing_vars = svg_outflows + svg_inflows + gem_vars 177 | 178 | # Re-adjust the flow variables 179 | filter_flow_vars(adata, 180 | vars_subset = spatially_flowing_vars, 181 | flowsig_expr_key = flowsig_expr_key, 182 | flowsig_network_key = flowsig_network_key) 183 | 184 | def determine_informative_variables(adata: sc.AnnData, 185 | flowsig_expr_key: str = 'X_flow', 186 | flowsig_network_key: str = 'flowsig_network', 187 | spatial: bool = False, 188 | condition_key: str = None, 189 | control_key: str = None, 190 | moran_threshold: float = 0.1, 191 | qval_threshold: float = 0.05, 192 | logfc_threshold: float = 0.5, 193 | coord_type: str = 'grid', 194 | n_neighbours: int = 6, 195 | library_key: str = None): 196 | 197 | 198 | if spatial: # We calculate the spatial autocorrelation (using Moran's I) and cut off genes below a defined threshold 199 | 200 | determine_spatially_flowing_vars(adata, 201 | flowsig_expr_key=flowsig_expr_key, 202 | flowsig_network_key=flowsig_network_key, 203 | moran_threshold=moran_threshold, 204 | coord_type=coord_type, 205 | n_neighbours=n_neighbours, 206 | library_key=library_key) 207 | 208 | else: 209 | 210 | determine_differentially_flowing_vars(adata, 211 | condition_key=condition_key, 212 | control_key=control_key, 213 | flowsig_expr_key=flowsig_expr_key, 214 | flowsig_network_key=flowsig_network_key, 215 | qval_threshold=qval_threshold, 216 | logfc_threshold=logfc_threshold) 217 | -------------------------------------------------------------------------------- /build/lib/flowsig/preprocessing/_gem_construction.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | import numpy as np 3 | import pyliger 4 | from tensorflow_probability import math as tm 5 | tfk = tm.psd_kernels 6 | import spatial_factorization as sf 7 | from typing import Optional 8 | from scipy.sparse import csr_matrix 9 | from ._townes_nsf_utils import * 10 | from sklearn.decomposition import NMF 11 | 12 | def construct_gems_using_pyliger(adata: sc.AnnData, 13 | n_gems: int, 14 | layer_key: str, 15 | condition_key: str): 16 | 17 | conditions = adata.obs[condition_key].unique().tolist() 18 | ad = adata.copy() 19 | 20 | ad.X = csr_matrix(ad.layers[layer_key].copy()) 21 | ad.obs.index.name = 'index' 22 | ad.var.index.name = 'index' 23 | 24 | # Create LIGER object 25 | adata_list = [] 26 | for cond in conditions: 27 | adata_cond = ad[ad.obs[condition_key] == cond].copy() 28 | adata_cond.uns['sample_name'] = cond 29 | adata_list.append(adata_cond) 30 | 31 | adata_liger = pyliger.create_liger(adata_list, make_sparse=True) 32 | 33 | pyliger.normalize(adata_liger) 34 | pyliger.select_genes(adata_liger) 35 | pyliger.scale_not_center(adata_liger) 36 | 37 | # Save the var_names that were used for the NMF 38 | # adata_burkhardt.uns['pyliger_vars'] = burkhardt_liger.adata_list[0].var_names.tolist() 39 | 40 | pyliger.optimize_ALS(adata_liger, k = n_gems) 41 | 42 | # # Save results to adata 43 | X_gem = np.zeros((adata.n_obs, n_gems)) 44 | pyliger_info = {} 45 | 46 | for i, cond in enumerate(conditions): 47 | cond_indices = np.where(adata.obs[condition_key] == cond)[0] 48 | X_gem[cond_indices] = adata_liger.adata_list[i].obsm['H'] 49 | 50 | pyliger_info[cond] = {'H': adata_liger.adata_list[i].obsm['H'], 51 | 'W': adata_liger.adata_list[i].varm['W'], 52 | 'V': adata_liger.adata_list[i].varm['V']} 53 | 54 | adata.uns['pyliger_info'] = pyliger_info 55 | adata.uns['pyliger_info']['vars'] = adata_liger.adata_list[0].var_names.tolist() 56 | adata.uns['pyliger_info']['n_gems'] = n_gems 57 | adata.obsm['X_gem'] = X_gem 58 | 59 | def construct_gems_using_nsf(adata: sc.AnnData, 60 | n_gems: int, 61 | layer_key: str, 62 | spatial_key: str = "spatial", 63 | n_inducing_pts: int = 500, 64 | length_scale: float = 10.0): 65 | 66 | ad = adata.copy() 67 | 68 | X = ad.obsm[spatial_key] 69 | # Take raw count data for NSF 70 | training_fraction = 1.0 71 | D,Dval = anndata_to_train_val(ad, 72 | layer=layer_key, 73 | train_frac=training_fraction, 74 | flip_yaxis=True) 75 | Ntr,J = D["Y"].shape 76 | Xtr = D["X"] 77 | ad = adata[:Ntr,:] 78 | #convert to tensorflow objects 79 | Dtf = prepare_datasets_tf(D,Dval=Dval) 80 | 81 | Z = kmeans_inducing_pts(Xtr, n_inducing_pts) 82 | M = Z.shape[0] #number of inducing points 83 | ker = tfk.MaternThreeHalves 84 | 85 | fit = sf.SpatialFactorization(J, n_gems, Z, psd_kernel=ker, length_scale=length_scale, nonneg=True, lik="poi") 86 | fit.init_loadings(D["Y"], X=Xtr, sz=D["sz"], shrinkage=0.3) 87 | tro = sf.ModelTrainer(fit) 88 | tro.train_model(*Dtf, status_freq=50) #about 3 mins 89 | 90 | insf = interpret_nsf(fit,Xtr,S=100,lda_mode=False) 91 | 92 | adata.uns['nsf_info'] = insf 93 | adata.uns['nsf_info']['vars'] = adata.var_names.tolist() 94 | adata.uns['nsf_info']['n_gems'] = n_gems 95 | adata.obsm['X_gem'] = insf['factors'] 96 | 97 | def construct_gems_using_nmf(adata: sc.AnnData, 98 | n_gems: int, 99 | layer_key: str, 100 | random_state: int = 0, 101 | max_iter: int = 1000): 102 | 103 | X_expr = adata.layers[layer_key].copy() 104 | 105 | model = NMF(n_components=n_gems, init='random', random_state=random_state, max_iter=max_iter) 106 | 107 | W = model.fit_transform(X_expr) 108 | H = model.components_ 109 | 110 | W_sum = W.sum(axis=0) 111 | W_lda = W / W_sum 112 | 113 | H_scaled = H.T * W_sum 114 | H_sum = H_scaled.sum(axis=1) 115 | H_lda = (H_scaled.T / H_sum).T 116 | 117 | fact_orders = np.argsort(-H_lda.sum(axis=0)) 118 | 119 | W_lda = W_lda[:, fact_orders] 120 | H_lda = H_lda[:, fact_orders].T 121 | 122 | adata.uns['nmf_info'] = {'n_gems': n_gems, 123 | 'vars': adata.var_names.tolist(), 124 | 'factors':W_lda, 125 | 'loadings':H_lda, 126 | 'totals':W_sum} 127 | 128 | adata.obsm['X_gem'] = W_lda 129 | -------------------------------------------------------------------------------- /build/lib/flowsig/preprocessing/_spatial_blocking.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import scanpy as sc 3 | from sklearn.cluster import KMeans 4 | 5 | def construct_spatial_blocks(adata: sc.AnnData, 6 | n_blocks: int, 7 | use_graph: bool = False, 8 | graph_adjacency: str = 'spatial_connectivities', 9 | resolution: float = None, 10 | spatial_block_key: str = "spatial_block", 11 | spatial_key: str = "spatial"): 12 | 13 | # If we want to construct spatial clusters from the graph directly, we use leiden clustering 14 | if use_graph: 15 | 16 | if resolution is None: 17 | ValueError('Need to specify a clustering resolution to construct blocks from spatial graph.') 18 | 19 | else: 20 | sc.tl.leiden(adata, resolution=resolution, key_added=spatial_block_key, adjacency=graph_adjacency) 21 | 22 | else: # Run k-means clustering on the spatial coordinates (produces more "even" blocks) 23 | 24 | kmeans = KMeans(n_clusters=n_blocks).fit(adata.obsm[spatial_key]) 25 | adata.obs[spatial_block_key] = pd.Series(kmeans.labels_, dtype='category').values -------------------------------------------------------------------------------- /build/lib/flowsig/preprocessing/_townes_nsf_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from contextlib import suppress 3 | from math import ceil 4 | from tensorflow import constant 5 | from tensorflow.data import Dataset 6 | from sklearn.cluster import KMeans 7 | from copy import deepcopy 8 | 9 | # All of this functionaliy has been taken from https://github.com/willtownes/nsf-paper/blob/main/utils/preprocess.py 10 | # and https://github.com/willtownes/nsf-paper/blob/main/utils/postprocess.py 11 | def rescale_spatial_coords(X,box_side=4): 12 | """ 13 | X is an NxD matrix of spatial coordinates 14 | Returns a rescaled version of X such that aspect ratio is preserved 15 | But data are centered at zero and area of equivalent bounding box set to 16 | box_side^D 17 | Goal is to rescale X to be similar to a N(0,1) distribution in all axes 18 | box_side=4 makes the data fit in range (-2,2) 19 | """ 20 | xmin = X.min(axis=0) 21 | X -= xmin 22 | x_gmean = np.exp(np.mean(np.log(X.max(axis=0)))) 23 | X *= box_side/x_gmean 24 | return X - X.mean(axis=0) 25 | 26 | def scanpy_sizefactors(Y): 27 | sz = Y.sum(axis=1,keepdims=True) 28 | return sz/np.median(sz) 29 | 30 | def anndata_to_train_val(ad, layer=None, nfeat=None, train_frac=0.95, 31 | sz="constant", dtp="float32", flip_yaxis=True): 32 | """ 33 | Convert anndata object ad to a training data dictionary 34 | and a validation data dictionary 35 | Requirements: 36 | * rows of ad are pre-shuffled to ensure random split of train/test 37 | * spatial coordinates in ad.obsm['spatial'] 38 | * features (cols) of ad sorted in decreasing importance (eg with deviance) 39 | """ 40 | if nfeat is not None: ad = ad[:,:nfeat] 41 | N = ad.shape[0] 42 | Ntr = round(train_frac*N) 43 | X = ad.obsm["spatial"].copy().astype(dtp) 44 | if flip_yaxis: X[:,1] = -X[:,1] 45 | X = rescale_spatial_coords(X) 46 | if layer is None: Y = ad.X 47 | else: Y = ad.layers[layer] 48 | with suppress(AttributeError): 49 | Y = Y.toarray() #in case Y is a sparse matrix 50 | Y = Y.astype(dtp) 51 | Dtr = {"X":X[:Ntr,:], "Y":Y[:Ntr,:]} 52 | Dval = {"X":X[Ntr:,:], "Y":Y[Ntr:,:]} 53 | if sz=="constant": 54 | Dtr["sz"] = np.ones((Ntr,1),dtype=dtp) 55 | Dval["sz"] = np.ones((N-Ntr,1),dtype=dtp) 56 | elif sz=="mean": 57 | Dtr["sz"] = Dtr["Y"].mean(axis=1,keepdims=True) 58 | Dval["sz"] = Dval["Y"].mean(axis=1,keepdims=True) 59 | elif sz=="scanpy": 60 | Dtr["sz"] = scanpy_sizefactors(Dtr["Y"]) 61 | Dval["sz"] = scanpy_sizefactors(Dval["Y"]) 62 | else: 63 | raise ValueError("unrecognized size factors 'sz'") 64 | Dtr["idx"] = np.arange(Ntr) 65 | if Ntr>=N: Dval = None #avoid returning an empty array 66 | return Dtr,Dval 67 | 68 | def minibatch_size_adjust(num_obs,batch_size): 69 | """ 70 | Calculate adjusted minibatch size that divides 71 | num_obs as evenly as possible 72 | num_obs : number of observations in full data 73 | batch_size : maximum size of a minibatch 74 | """ 75 | nbatch = ceil(num_obs/float(batch_size)) 76 | return int(ceil(num_obs/nbatch)) 77 | 78 | def prepare_datasets_tf(Dtrain,Dval=None,shuffle=False,batch_size=None): 79 | """ 80 | Dtrain and Dval are dicts containing numpy np.arrays of data. 81 | Dtrain must contain the key "Y" 82 | Returns a from_tensor_slices conversion of Dtrain and a dict of tensors for Dval 83 | """ 84 | Ntr = Dtrain["Y"].shape[0] 85 | if batch_size is None: 86 | #ie one batch containing all observations by default 87 | batch_size = Ntr 88 | else: 89 | batch_size = minibatch_size_adjust(Ntr,batch_size) 90 | Dtrain = Dataset.from_tensor_slices(Dtrain) 91 | if shuffle: 92 | Dtrain = Dtrain.shuffle(Ntr) 93 | Dtrain = Dtrain.batch(batch_size) 94 | if Dval is not None: 95 | Dval = {i:constant(Dval[i]) for i in Dval} 96 | return Dtrain, Ntr, Dval 97 | 98 | def kmeans_inducing_pts(X,M): 99 | M = int(M) 100 | Z = np.unique(X, axis=0) 101 | unique_locs = Z.shape[0] 102 | if M gene expression module -> outflow signal. 17 | As the CPDAG contains directed arcs and undirected edges, we remove directed arcs 18 | that 19 | 20 | Parameters 21 | ---------- 22 | adata 23 | The annotated dataframe (typically from Scanpy) of the single-cell data. 24 | You need to have run FlowSig before running this step. 25 | 26 | edge_threshold 27 | The relative frequency of bootstrap edge frequency above which we keep edges. 28 | For directed arcs, we consider single edge frequencies. For undirected edges, 29 | we consider total edge weight. 30 | 31 | flowsig_network_key 32 | The label in adata.uns where all of the flowsig output is stored, including the learned 33 | adjacency corresponding to the CPDAG (markov equivalence class), the flow variables used 34 | for inference, as well as their "flow types", which can be either inflow (ing), module 35 | (TFs or factors), or outflow (ing) signals. 36 | 37 | adjacency_key 38 | String key that specifies the adjacency for the learned network is stored in adata.uns[flowsig_network_key]. 39 | 40 | adjacency_filtered_key 41 | String key that specifies where the validated network will be stored. 42 | 43 | Returns 44 | ------- 45 | adjacency_filtered 46 | Matrix that encodes the CPDAG containing high-confidence directed arcs and 47 | undirected arcs. 48 | 49 | """ 50 | 51 | # Get the adjacency 52 | adjacency = adata.uns[flowsig_network_key]['network'][adjacency_key] 53 | flow_vars = adata.uns[flowsig_network_key]['flow_var_info'].index.tolist() 54 | 55 | cpdag = gpm.PDAG.from_amat(adjacency) 56 | 57 | adjacency_filtered = np.zeros(adjacency.shape) 58 | 59 | # First, let us calculate the total edge weights 60 | total_edge_weights = {} 61 | 62 | nonzero_rows, nonzero_cols = adjacency.nonzero() 63 | 64 | for i in range(len(nonzero_rows)): 65 | 66 | row_ind = nonzero_rows[i] 67 | col_ind = nonzero_cols[i] 68 | 69 | node_1 = flow_vars[row_ind] 70 | node_2 = flow_vars[col_ind] 71 | 72 | edge = (node_1, node_2) 73 | 74 | # We either haven't recorded the edge, or we've taken the reverse edge previously 75 | if (edge[1], edge[0]) in total_edge_weights: 76 | total_edge_weights[(edge[1], edge[0])] += adjacency[row_ind, col_ind] 77 | else: 78 | total_edge_weights[edge] = adjacency[row_ind, col_ind] 79 | 80 | for arc in cpdag.arcs: 81 | 82 | node_1 = flow_vars[tuple(arc)[0]] 83 | node_2 = flow_vars[tuple(arc)[1]] 84 | 85 | row_ind = flow_vars.index(node_1) 86 | col_ind = flow_vars.index(node_2) 87 | 88 | edge_weight = adjacency[row_ind, col_ind] 89 | 90 | # Need to account for both (node1, node2) and (node1, node1) as 91 | # adjacency encodes directed network 92 | if edge_weight >= edge_threshold: 93 | adjacency_filtered[row_ind, col_ind] = edge_weight 94 | 95 | for edge in cpdag.edges: 96 | 97 | node_1 = flow_vars[tuple(edge)[0]] 98 | node_2 = flow_vars[tuple(edge)[1]] 99 | 100 | # For directed arcs, we simply consider the total edge weights 101 | total_edge_weight = 0.0 102 | 103 | if (node_1, node_2) in total_edge_weights: 104 | 105 | total_edge_weight = total_edge_weights[(node_1, node_2)] 106 | 107 | else: 108 | 109 | total_edge_weight = total_edge_weights[(node_2, node_1)] 110 | 111 | # Need to account for both (node1, node2) and (node2, node1) as 112 | # adjacency encodes directed network 113 | if total_edge_weight >= edge_threshold: 114 | 115 | adjacency_filtered[tuple(edge)[0], tuple(edge)[1]] = adjacency[tuple(edge)[0], tuple(edge)[1]] 116 | adjacency_filtered[tuple(edge)[1], tuple(edge)[0]] = adjacency[tuple(edge)[1], tuple(edge)[0]] 117 | 118 | print(adjacency_filtered) 119 | 120 | # Save the "validated" adjacency 121 | filtered_adjacency_key = adjacency_key + '_' + filtered_key 122 | adata.uns[flowsig_network_key]['network'][filtered_adjacency_key] = adjacency_filtered 123 | 124 | def apply_biological_flow(adata: sc.AnnData, 125 | flowsig_network_key: str = 'flowsig_network', 126 | adjacency_key: str = 'adjacency', 127 | validated_key: str = 'validated'): 128 | """ 129 | Validate the learned CPDAG from UT-IGSP by checking edges against the assumed 130 | biological flow model, inflow signal -> gene expression module -> outflow signal. 131 | As the CPDAG contains directed arcs and undirected edges, we remove directed arcs 132 | that do not follow these edge relations. For undirected edges that represent one of 133 | inflow -- gene expression module, gene expression module -- gene expression module, 134 | and gene expression module -- outflow, we orient them so that they make "biological 135 | sense". 136 | 137 | Parameters 138 | ---------- 139 | adata 140 | The annotated dataframe (typically from Scanpy) of the single-cell data. 141 | You need to have run FlowSig before running this step. 142 | 143 | flowsig_network_key 144 | The label in adata.uns where all of the flowsig output is stored, including the learned 145 | adjacency corresponding to the CPDAG (markov equivalence class), the flow variables used 146 | for inference, as well as their "flow types", which can be either inflow (ing), module (TFs or factors), 147 | or outflow (ing) signals. 148 | 149 | adjacency_key 150 | String key that specifies the adjacency for the learned network is stored in adata.uns[flowsig_network_key]. 151 | 152 | adjacency_validated_key 153 | String key that specifies where the validated network will be stored. 154 | 155 | Returns 156 | ------- 157 | adjacency_validated 158 | Matrix that encodes the CPDAG containing "biologically realistic" inferred flows, from 159 | inflow variables, to gene expression module variables, to outflow variables. 160 | 161 | """ 162 | 163 | # Get the adjacency 164 | adjacency = adata.uns[flowsig_network_key]['network'][adjacency_key] 165 | flow_vars = adata.uns[flowsig_network_key]['flow_var_info'].index.tolist() 166 | flow_var_info = adata.uns[flowsig_network_key]['flow_var_info'] 167 | 168 | cpdag = gpm.PDAG.from_amat(adjacency) 169 | adjacency_validated = np.zeros(adjacency.shape) 170 | 171 | for arc in cpdag.arcs: 172 | 173 | node_1 = flow_vars[tuple(arc)[0]] 174 | node_2 = flow_vars[tuple(arc)[1]] 175 | 176 | # Classify node types 177 | node_1_type = flow_var_info.loc[node_1]['Type'] 178 | node_2_type = flow_var_info.loc[node_2]['Type'] 179 | 180 | # Now we decide whether or not to add the edges 181 | add_edge = False 182 | 183 | # Define the edge because we may need to reverse it 184 | edge = (node_1, node_2) 185 | 186 | # If there's a link from received morphogen to a TF 187 | if ( (node_1_type == 'inflow')&(node_2_type == 'module') ): 188 | 189 | add_edge = True 190 | 191 | if ( (node_1_type == 'module')&(node_2_type == 'outflow') ): 192 | 193 | add_edge = True 194 | 195 | if ((node_1_type == 'module')&(node_2_type == 'module')): 196 | 197 | add_edge = True 198 | 199 | if add_edge: 200 | 201 | row_ind = tuple(arc)[0] 202 | col_ind = tuple(arc)[1] 203 | 204 | adjacency_validated[row_ind, col_ind] = adjacency[row_ind, col_ind] 205 | 206 | for edge in cpdag.edges: 207 | 208 | node_1 = flow_vars[tuple(edge)[0]] 209 | node_2 = flow_vars[tuple(edge)[1]] 210 | 211 | # Classify node types 212 | node_1_type = flow_var_info.loc[node_1]['Type'] 213 | node_2_type = flow_var_info.loc[node_2]['Type'] 214 | 215 | # Define the edge because we may need to reverse it 216 | add_edge = False 217 | 218 | # If there's a link from received morphogen to a TF 219 | if ( (node_1_type == 'inflow')&(node_2_type == 'module') ): 220 | 221 | add_edge = True 222 | 223 | # If there's a link from received morphogen to a TF 224 | if ( (node_1_type == 'module')&(node_2_type == 'inflow') ): 225 | 226 | add_edge = True 227 | 228 | if ( (node_1_type == 'module')&(node_2_type == 'outflow') ): 229 | 230 | add_edge = True 231 | 232 | if ( (node_1_type == 'outflow')&(node_2_type == 'module') ): 233 | 234 | add_edge = True 235 | 236 | if ((node_1_type == 'module')&(node_2_type == 'module')): 237 | 238 | add_edge = True 239 | 240 | if add_edge: 241 | 242 | row_ind = tuple(edge)[0] 243 | col_ind = tuple(edge)[1] 244 | 245 | adjacency_validated[row_ind, col_ind] = adjacency[row_ind, col_ind] 246 | 247 | adjacency_validated[col_ind, row_ind] = adjacency[col_ind, row_ind] 248 | 249 | # Save the "validated" adjacency 250 | validated_adjacency_key = adjacency_key + '_' + validated_key 251 | adata.uns[flowsig_network_key]['network'][validated_adjacency_key] = adjacency_validated 252 | 253 | def construct_intercellular_flow_network(adata: sc.AnnData, 254 | flowsig_network_key: str = 'flowsig_network', 255 | adjacency_key: str = 'adjacency'): 256 | 257 | flow_vars = adata.uns[flowsig_network_key]['flow_var_info'].index.tolist() 258 | flow_var_info = adata.uns[flowsig_network_key]['flow_var_info'] 259 | 260 | flow_adjacency = adata.uns[flowsig_network_key]['network'][adjacency_key] 261 | 262 | nonzero_rows, nonzero_cols = flow_adjacency.nonzero() 263 | 264 | total_edge_weights = {} 265 | 266 | for i in range(len(nonzero_rows)): 267 | row_ind = nonzero_rows[i] 268 | col_ind = nonzero_cols[i] 269 | 270 | node_1 = flow_vars[row_ind] 271 | node_2 = flow_vars[col_ind] 272 | 273 | edge = (node_1, node_2) 274 | 275 | if ( (edge not in total_edge_weights)&((edge[1], edge[0]) not in total_edge_weights) ): 276 | total_edge_weights[edge] = flow_adjacency[row_ind, col_ind] 277 | else: 278 | if (edge[1], edge[0]) in total_edge_weights: 279 | total_edge_weights[(edge[1], edge[0])] += flow_adjacency[row_ind, col_ind] 280 | 281 | flow_network = nx.DiGraph() 282 | 283 | # Now let's consturct the graph from the CPDAG 284 | cpdag = gpm.PDAG.from_amat(flow_adjacency) 285 | 286 | # Add the directed edges (arcs) first 287 | for arc in cpdag.arcs: 288 | 289 | node_1 = flow_vars[tuple(arc)[0]] 290 | node_2 = flow_vars[tuple(arc)[1]] 291 | 292 | # Classify node types 293 | node_1_type = flow_var_info.loc[node_1]['Type'] 294 | node_2_type = flow_var_info.loc[node_2]['Type'] 295 | 296 | # Now we decide whether or not to add the damn edges 297 | add_edge = False 298 | 299 | # Define the edge because we may need to reverse it 300 | edge = (node_1, node_2) 301 | 302 | # If there's a link from the expressed morphogen to the received morphogen FOR the same morphogen 303 | if ( (node_1_type == 'inflow')&(node_2_type == 'module') ): 304 | add_edge = True 305 | 306 | # If there's a link from received morphogen to a TF 307 | if ( (node_1_type == 'module')&(node_2_type == 'outflow') ): 308 | 309 | add_edge = True 310 | 311 | if ( (node_1_type == 'module')&(node_2_type == 'module') ): 312 | 313 | add_edge = True 314 | 315 | if add_edge: 316 | 317 | # Get the total edge weight 318 | total_edge_weight = 0.0 319 | 320 | if edge in total_edge_weights: 321 | 322 | total_edge_weight = total_edge_weights[edge] 323 | 324 | else: 325 | 326 | total_edge_weight = total_edge_weights[(edge[1], edge[0])] 327 | 328 | edge_weight = flow_adjacency[tuple(arc)[0], tuple(arc)[1]] 329 | 330 | flow_network.add_edge(*edge) 331 | flow_network.edges[edge[0], edge[1]]['weight'] = edge_weight / total_edge_weight 332 | flow_network.nodes[edge[0]]['type'] = node_1_type 333 | flow_network.nodes[edge[1]]['type'] = node_2_type 334 | 335 | for edge in cpdag.edges: 336 | 337 | node_1 = flow_vars[tuple(edge)[0]] 338 | node_2 = flow_vars[tuple(edge)[1]] 339 | 340 | # Classify node types 341 | node_1_type = flow_var_info.loc[node_1]['Type'] 342 | node_2_type = flow_var_info.loc[node_2]['Type'] 343 | 344 | # Define the edge because we may need to reverse it 345 | undirected_edge = (node_1, node_2) 346 | 347 | add_edge = False 348 | # If there's a link from the expressed morphogen to the received morphogen FOR the same morphogen 349 | if ( (node_1_type == 'inflow')&(node_2_type == 'module') ): 350 | 351 | add_edge = True 352 | 353 | if ( (node_1_type == 'module')&(node_2_type == 'inflow') ): 354 | 355 | add_edge = True 356 | undirected_edge = (node_2, node_1) 357 | 358 | if ( (node_1_type == 'module')&(node_2_type == 'outflow') ): 359 | 360 | add_edge = True 361 | 362 | if ( (node_1_type == 'outflow')&(node_2_type == 'module') ): 363 | 364 | add_edge = True 365 | undirected_edge = (node_2, node_1) 366 | 367 | if ((node_1_type == 'module')&(node_2_type == 'module')): 368 | 369 | add_edge = True 370 | 371 | if add_edge: 372 | 373 | # Get the total edge weight 374 | total_edge_weight = 0.0 375 | 376 | if undirected_edge in total_edge_weights: 377 | 378 | total_edge_weight = total_edge_weights[undirected_edge] 379 | 380 | else: 381 | 382 | total_edge_weight = total_edge_weights[(undirected_edge[1], undirected_edge[0])] 383 | 384 | flow_network.add_edge(*undirected_edge) 385 | flow_network.edges[undirected_edge[0], undirected_edge[1]]['weight'] = min(total_edge_weight, 1.0) 386 | flow_network.nodes[undirected_edge[0]]['type'] = node_1_type 387 | flow_network.nodes[undirected_edge[1]]['type'] = node_2_type 388 | 389 | # Add the other way if we have modules 390 | if ((node_1_type == 'module')&(node_2_type == 'module')): 391 | 392 | flow_network.add_edge(undirected_edge[1], undirected_edge[0]) 393 | flow_network.edges[undirected_edge[1], undirected_edge[0]]['weight'] = min(total_edge_weight, 1.0) 394 | flow_network.nodes[undirected_edge[0]]['type'] = node_2_type 395 | flow_network.nodes[undirected_edge[1]]['type'] = node_1_type 396 | 397 | return flow_network -------------------------------------------------------------------------------- /build/lib/flowsig/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | from ._utils import get_top_gem_genes -------------------------------------------------------------------------------- /build/lib/flowsig/utilities/_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import scanpy as sc 4 | from typing import Union, Sequence 5 | 6 | def get_top_nmf_genes(adata: sc.AnnData, 7 | gems: Union[str, Sequence[str]], 8 | n_genes: int, 9 | gene_type: str = 'all', 10 | model_organism: str = 'human'): 11 | 12 | gene_types = ['all', 'tf'] 13 | 14 | if gene_type not in gene_types: 15 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 16 | 17 | model_organisms = ['human', 'mouse'] 18 | 19 | if model_organism not in model_organisms: 20 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 21 | 22 | # Load the TFs if we need to 23 | if gene_type == 'tf': 24 | tfs_list = pd.read_csv('../data/allTFs_' + model_organism + '.txt', header=None)[0].tolist() 25 | 26 | n_gems = adata.uns['nmf_info']['n_gems'] 27 | nmf_vars = np.array(adata.uns['nmf_info']['vars'], dtype=object) 28 | nmf_loadings = adata.uns['nmf_info']['loadings'] 29 | 30 | all_gems = ['GEM-' + str(i + 1) for i in range(n_gems)] 31 | 32 | top_genes_in_gems = [] 33 | gem_labels = [] 34 | gem_weights = [] 35 | 36 | for gem in gems: 37 | 38 | gem_index = all_gems.index(all_gems) 39 | 40 | # Get the loadings corresponding tot his gem 41 | gem_loadings = nmf_loadings[gem_index, :] 42 | 43 | # Sort the genes in their order by loading 44 | sorted_gem_loadings = gem_loadings[np.argsort(-gem_loadings)] 45 | ordered_genes = nmf_vars[np.argsort(-gem_loadings)] 46 | 47 | if gene_type == 'all': 48 | 49 | for i in range(n_genes): 50 | 51 | top_genes_in_gems.append(ordered_genes[i]) 52 | gem_labels.append(gem) 53 | gem_weights.append(sorted_gem_loadings[i]) 54 | 55 | else: # We only take TFs from each GEM 56 | 57 | ordered_tfs = [gene for gene in ordered_genes if gene in tfs_list] 58 | sorted_tf_loadings = [sorted_gem_loadings[i] for i, gene in enumerate(ordered_genes) if gene in tfs_list] 59 | 60 | for i in range(n_genes): 61 | 62 | top_genes_in_gems.append(ordered_tfs[i]) 63 | gem_labels.append(gem) 64 | gem_weights.append(sorted_tf_loadings[i]) 65 | 66 | top_nmf_genes_df = pd.DataFrame(data={'Gene': top_genes_in_gems, 67 | 'GEM': gem_labels, 68 | 'Weight': gem_weights}) 69 | return top_nmf_genes_df 70 | 71 | def get_top_pyliger_genes(adata: sc.AnnData, 72 | gems: Union[str, Sequence[str]], 73 | n_genes: int, 74 | gene_type: str = 'all', 75 | model_organism: str = 'human'): 76 | 77 | gene_types = ['all', 'tf'] 78 | 79 | if gene_type not in gene_types: 80 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 81 | 82 | model_organisms = ['human', 'mouse'] 83 | 84 | if model_organism not in model_organisms: 85 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 86 | 87 | # Load the TFs if we need to 88 | if gene_type == 'tf': 89 | tfs_list = pd.read_csv('../data/allTFs_' + model_organism + '.txt', header=None)[0].tolist() 90 | 91 | pyliger_conds = [key for key in adata.uns['pyliger_info'].keys() if key not in ['n_vars', 'vars']] 92 | 93 | n_gems = adata.uns['pyliger_info']['n_gems'] 94 | pyliger_vars = np.array(adata.uns['pyliger_info']['vars'], dtype=object) 95 | 96 | all_gems = ['GEM-' + str(i + 1) for i in range(n_gems)] 97 | 98 | top_genes_in_gems = [] 99 | gem_labels = [] 100 | gem_weights = [] 101 | 102 | for gem in gems: 103 | 104 | gem_index = all_gems.index(all_gems) 105 | 106 | all_gem_loadings = {cond: adata.uns['pyliger_info'][cond]['W'][:, gem_index] \ 107 | + adata.uns['pyliger_info'][cond]['V'][:, gem_index] for cond in pyliger_conds} 108 | 109 | stacked_gem_loadings = np.hstack([all_gem_loadings[cond] for cond in all_gem_loadings]) 110 | stacked_gem_genes = np.hstack([pyliger_vars for cond in all_gem_loadings]) 111 | sorted_gem_loadings = stacked_gem_loadings[np.argsort(-stacked_gem_loadings)] 112 | init_top_gem_genes = stacked_gem_genes[np.argsort(-stacked_gem_loadings)] 113 | 114 | top_genes = [] 115 | 116 | for i, gene in enumerate(init_top_gem_genes): 117 | 118 | if gene_type == 'tf': 119 | 120 | if (gene in tfs_list)&(gene not in top_genes): 121 | 122 | top_genes.append(gene) # This tracks repeats, given that we're stacking the list of genes 123 | top_genes_in_gems.append(gene) 124 | gem_labels.append(gem) 125 | gem_weights.append(sorted_gem_loadings[i]) 126 | 127 | if len(top_genes) == n_genes: 128 | break 129 | else: 130 | 131 | if gene not in top_genes: 132 | 133 | top_genes.append(gene) # This tracks repeated names, given that we're stacking the list of genes 134 | top_genes_in_gems.append(gene) 135 | gem_labels.append(gem) 136 | gem_weights.append(sorted_gem_loadings[i]) 137 | 138 | if len(top_genes) == n_genes: 139 | break 140 | 141 | 142 | 143 | top_pyliger_genes_df = pd.DataFrame(data={'Gene': top_genes_in_gems, 144 | 'GEM': gem_labels, 145 | 'Weight': gem_weights}) 146 | return top_pyliger_genes_df 147 | 148 | def get_top_nsf_genes(adata: sc.AnnData, 149 | gems: Union[str, Sequence[str]], 150 | n_genes: int, 151 | gene_type: str = 'all', 152 | model_organism: str = 'human'): 153 | 154 | gene_types = ['all', 'tf'] 155 | 156 | if gene_type not in gene_types: 157 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 158 | 159 | model_organisms = ['human', 'mouse'] 160 | 161 | if model_organism not in model_organisms: 162 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 163 | 164 | # Load the TFs if we need to 165 | if gene_type == 'tf': 166 | tfs_list = pd.read_csv('../data/allTFs_' + model_organism + '.txt', header=None)[0].tolist() 167 | 168 | n_gems = adata.uns['nsf_info']['n_gems'] 169 | nsf_vars = np.array(adata.uns['nsf_info']['vars'], dtype=object) 170 | nsf_loadings = adata.uns['nsf_info']['loadings'].T 171 | 172 | all_gems = ['GEM-' + str(i + 1) for i in range(n_gems)] 173 | 174 | top_genes_in_gems = [] 175 | gem_labels = [] 176 | gem_weights = [] 177 | 178 | for gem in gems: 179 | 180 | gem_index = all_gems.index(all_gems) 181 | 182 | # Get the loadings corresponding tot his gem 183 | gem_loadings = nsf_loadings[gem_index, :] 184 | 185 | # Sort the genes in their order by loading 186 | sorted_gem_loadings = gem_loadings[np.argsort(-gem_loadings)] 187 | ordered_genes = nsf_vars[np.argsort(-gem_loadings)] 188 | 189 | if gene_type == 'all': 190 | 191 | for i in range(n_genes): 192 | 193 | top_genes_in_gems.append(ordered_genes[i]) 194 | gem_labels.append(gem) 195 | gem_weights.append(sorted_gem_loadings[i]) 196 | 197 | else: # We only take TFs from each GEM 198 | 199 | ordered_tfs = [gene for gene in ordered_genes if gene in tfs_list] 200 | sorted_tf_loadings = [sorted_gem_loadings[i] for i, gene in enumerate(ordered_genes) if gene in tfs_list] 201 | 202 | for i in range(n_genes): 203 | 204 | top_genes_in_gems.append(ordered_tfs[i]) 205 | gem_labels.append(gem) 206 | gem_weights.append(sorted_tf_loadings[i]) 207 | 208 | top_nsf_genes_df = pd.DataFrame(data={'Gene': top_genes_in_gems, 209 | 'GEM': gem_labels, 210 | 'Weight': gem_weights}) 211 | return top_nsf_genes_df 212 | 213 | def get_top_gem_genes(adata: sc.AnnData, 214 | gems: Union[str, Sequence[str]], 215 | n_genes: int, 216 | gem_key: str, 217 | method: str = 'pyliger', 218 | gene_type: str = 'all', 219 | model_organism: str = 'human'): 220 | 221 | # Perform a bunch of checks that we're specifying the right options 222 | methods = ['nmf', 'pyliger', 'nsf'] 223 | if method not in methods: 224 | raise ValueError ("Invalid method. Please select one of: %s" % methods) 225 | 226 | gene_types = ['all', 'tf'] 227 | 228 | if gene_type not in gene_types: 229 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 230 | 231 | model_organisms = ['human', 'mouse'] 232 | 233 | if model_organism not in model_organisms: 234 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 235 | 236 | if method == 'nmf': 237 | 238 | return get_top_nmf_genes(adata, gems, n_genes, gem_key, gene_type, model_organism) 239 | 240 | elif method == 'pyliger': 241 | 242 | return get_top_pyliger_genes(adata, gems, n_genes, gem_key, gene_type, model_organism) 243 | 244 | else: # Should be NSF 245 | 246 | return get_top_nsf_genes(adata, gems, n_genes, gem_key, gene_type, model_organism) 247 | -------------------------------------------------------------------------------- /dist/flowsig-0.1.0-py2.py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/dist/flowsig-0.1.0-py2.py3-none-any.whl -------------------------------------------------------------------------------- /dist/flowsig-0.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/dist/flowsig-0.1.0.tar.gz -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/.DS_Store -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/build/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/build/.DS_Store -------------------------------------------------------------------------------- /docs/build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: e8e1eb6f388c098601ff2c1b7ff1b5f5 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. flowsig documentation master file, created by 2 | sphinx-quickstart on Fri Jul 7 14:47:42 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to flowsig's documentation! 7 | =================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/build/html/_static/alabaster.css: -------------------------------------------------------------------------------- 1 | @import url("basic.css"); 2 | 3 | /* -- page layout ----------------------------------------------------------- */ 4 | 5 | body { 6 | font-family: Georgia, serif; 7 | font-size: 17px; 8 | background-color: #fff; 9 | color: #000; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | 15 | div.document { 16 | width: 940px; 17 | margin: 30px auto 0 auto; 18 | } 19 | 20 | div.documentwrapper { 21 | float: left; 22 | width: 100%; 23 | } 24 | 25 | div.bodywrapper { 26 | margin: 0 0 0 220px; 27 | } 28 | 29 | div.sphinxsidebar { 30 | width: 220px; 31 | font-size: 14px; 32 | line-height: 1.5; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #fff; 41 | color: #3E4349; 42 | padding: 0 30px 0 30px; 43 | } 44 | 45 | div.body > .section { 46 | text-align: left; 47 | } 48 | 49 | div.footer { 50 | width: 940px; 51 | margin: 20px auto 30px auto; 52 | font-size: 14px; 53 | color: #888; 54 | text-align: right; 55 | } 56 | 57 | div.footer a { 58 | color: #888; 59 | } 60 | 61 | p.caption { 62 | font-family: inherit; 63 | font-size: inherit; 64 | } 65 | 66 | 67 | div.relations { 68 | display: none; 69 | } 70 | 71 | 72 | div.sphinxsidebar a { 73 | color: #444; 74 | text-decoration: none; 75 | border-bottom: 1px dotted #999; 76 | } 77 | 78 | div.sphinxsidebar a:hover { 79 | border-bottom: 1px solid #999; 80 | } 81 | 82 | div.sphinxsidebarwrapper { 83 | padding: 18px 10px; 84 | } 85 | 86 | div.sphinxsidebarwrapper p.logo { 87 | padding: 0; 88 | margin: -10px 0 0 0px; 89 | text-align: center; 90 | } 91 | 92 | div.sphinxsidebarwrapper h1.logo { 93 | margin-top: -10px; 94 | text-align: center; 95 | margin-bottom: 5px; 96 | text-align: left; 97 | } 98 | 99 | div.sphinxsidebarwrapper h1.logo-name { 100 | margin-top: 0px; 101 | } 102 | 103 | div.sphinxsidebarwrapper p.blurb { 104 | margin-top: 0; 105 | font-style: normal; 106 | } 107 | 108 | div.sphinxsidebar h3, 109 | div.sphinxsidebar h4 { 110 | font-family: Georgia, serif; 111 | color: #444; 112 | font-size: 24px; 113 | font-weight: normal; 114 | margin: 0 0 5px 0; 115 | padding: 0; 116 | } 117 | 118 | div.sphinxsidebar h4 { 119 | font-size: 20px; 120 | } 121 | 122 | div.sphinxsidebar h3 a { 123 | color: #444; 124 | } 125 | 126 | div.sphinxsidebar p.logo a, 127 | div.sphinxsidebar h3 a, 128 | div.sphinxsidebar p.logo a:hover, 129 | div.sphinxsidebar h3 a:hover { 130 | border: none; 131 | } 132 | 133 | div.sphinxsidebar p { 134 | color: #555; 135 | margin: 10px 0; 136 | } 137 | 138 | div.sphinxsidebar ul { 139 | margin: 10px 0; 140 | padding: 0; 141 | color: #000; 142 | } 143 | 144 | div.sphinxsidebar ul li.toctree-l1 > a { 145 | font-size: 120%; 146 | } 147 | 148 | div.sphinxsidebar ul li.toctree-l2 > a { 149 | font-size: 110%; 150 | } 151 | 152 | div.sphinxsidebar input { 153 | border: 1px solid #CCC; 154 | font-family: Georgia, serif; 155 | font-size: 1em; 156 | } 157 | 158 | div.sphinxsidebar hr { 159 | border: none; 160 | height: 1px; 161 | color: #AAA; 162 | background: #AAA; 163 | 164 | text-align: left; 165 | margin-left: 0; 166 | width: 50%; 167 | } 168 | 169 | div.sphinxsidebar .badge { 170 | border-bottom: none; 171 | } 172 | 173 | div.sphinxsidebar .badge:hover { 174 | border-bottom: none; 175 | } 176 | 177 | /* To address an issue with donation coming after search */ 178 | div.sphinxsidebar h3.donation { 179 | margin-top: 10px; 180 | } 181 | 182 | /* -- body styles ----------------------------------------------------------- */ 183 | 184 | a { 185 | color: #004B6B; 186 | text-decoration: underline; 187 | } 188 | 189 | a:hover { 190 | color: #6D4100; 191 | text-decoration: underline; 192 | } 193 | 194 | div.body h1, 195 | div.body h2, 196 | div.body h3, 197 | div.body h4, 198 | div.body h5, 199 | div.body h6 { 200 | font-family: Georgia, serif; 201 | font-weight: normal; 202 | margin: 30px 0px 10px 0px; 203 | padding: 0; 204 | } 205 | 206 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 207 | div.body h2 { font-size: 180%; } 208 | div.body h3 { font-size: 150%; } 209 | div.body h4 { font-size: 130%; } 210 | div.body h5 { font-size: 100%; } 211 | div.body h6 { font-size: 100%; } 212 | 213 | a.headerlink { 214 | color: #DDD; 215 | padding: 0 4px; 216 | text-decoration: none; 217 | } 218 | 219 | a.headerlink:hover { 220 | color: #444; 221 | background: #EAEAEA; 222 | } 223 | 224 | div.body p, div.body dd, div.body li { 225 | line-height: 1.4em; 226 | } 227 | 228 | div.admonition { 229 | margin: 20px 0px; 230 | padding: 10px 30px; 231 | background-color: #EEE; 232 | border: 1px solid #CCC; 233 | } 234 | 235 | div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { 236 | background-color: #FBFBFB; 237 | border-bottom: 1px solid #fafafa; 238 | } 239 | 240 | div.admonition p.admonition-title { 241 | font-family: Georgia, serif; 242 | font-weight: normal; 243 | font-size: 24px; 244 | margin: 0 0 10px 0; 245 | padding: 0; 246 | line-height: 1; 247 | } 248 | 249 | div.admonition p.last { 250 | margin-bottom: 0; 251 | } 252 | 253 | div.highlight { 254 | background-color: #fff; 255 | } 256 | 257 | dt:target, .highlight { 258 | background: #FAF3E8; 259 | } 260 | 261 | div.warning { 262 | background-color: #FCC; 263 | border: 1px solid #FAA; 264 | } 265 | 266 | div.danger { 267 | background-color: #FCC; 268 | border: 1px solid #FAA; 269 | -moz-box-shadow: 2px 2px 4px #D52C2C; 270 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 271 | box-shadow: 2px 2px 4px #D52C2C; 272 | } 273 | 274 | div.error { 275 | background-color: #FCC; 276 | border: 1px solid #FAA; 277 | -moz-box-shadow: 2px 2px 4px #D52C2C; 278 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 279 | box-shadow: 2px 2px 4px #D52C2C; 280 | } 281 | 282 | div.caution { 283 | background-color: #FCC; 284 | border: 1px solid #FAA; 285 | } 286 | 287 | div.attention { 288 | background-color: #FCC; 289 | border: 1px solid #FAA; 290 | } 291 | 292 | div.important { 293 | background-color: #EEE; 294 | border: 1px solid #CCC; 295 | } 296 | 297 | div.note { 298 | background-color: #EEE; 299 | border: 1px solid #CCC; 300 | } 301 | 302 | div.tip { 303 | background-color: #EEE; 304 | border: 1px solid #CCC; 305 | } 306 | 307 | div.hint { 308 | background-color: #EEE; 309 | border: 1px solid #CCC; 310 | } 311 | 312 | div.seealso { 313 | background-color: #EEE; 314 | border: 1px solid #CCC; 315 | } 316 | 317 | div.topic { 318 | background-color: #EEE; 319 | } 320 | 321 | p.admonition-title { 322 | display: inline; 323 | } 324 | 325 | p.admonition-title:after { 326 | content: ":"; 327 | } 328 | 329 | pre, tt, code { 330 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 331 | font-size: 0.9em; 332 | } 333 | 334 | .hll { 335 | background-color: #FFC; 336 | margin: 0 -12px; 337 | padding: 0 12px; 338 | display: block; 339 | } 340 | 341 | img.screenshot { 342 | } 343 | 344 | tt.descname, tt.descclassname, code.descname, code.descclassname { 345 | font-size: 0.95em; 346 | } 347 | 348 | tt.descname, code.descname { 349 | padding-right: 0.08em; 350 | } 351 | 352 | img.screenshot { 353 | -moz-box-shadow: 2px 2px 4px #EEE; 354 | -webkit-box-shadow: 2px 2px 4px #EEE; 355 | box-shadow: 2px 2px 4px #EEE; 356 | } 357 | 358 | table.docutils { 359 | border: 1px solid #888; 360 | -moz-box-shadow: 2px 2px 4px #EEE; 361 | -webkit-box-shadow: 2px 2px 4px #EEE; 362 | box-shadow: 2px 2px 4px #EEE; 363 | } 364 | 365 | table.docutils td, table.docutils th { 366 | border: 1px solid #888; 367 | padding: 0.25em 0.7em; 368 | } 369 | 370 | table.field-list, table.footnote { 371 | border: none; 372 | -moz-box-shadow: none; 373 | -webkit-box-shadow: none; 374 | box-shadow: none; 375 | } 376 | 377 | table.footnote { 378 | margin: 15px 0; 379 | width: 100%; 380 | border: 1px solid #EEE; 381 | background: #FDFDFD; 382 | font-size: 0.9em; 383 | } 384 | 385 | table.footnote + table.footnote { 386 | margin-top: -15px; 387 | border-top: none; 388 | } 389 | 390 | table.field-list th { 391 | padding: 0 0.8em 0 0; 392 | } 393 | 394 | table.field-list td { 395 | padding: 0; 396 | } 397 | 398 | table.field-list p { 399 | margin-bottom: 0.8em; 400 | } 401 | 402 | /* Cloned from 403 | * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 404 | */ 405 | .field-name { 406 | -moz-hyphens: manual; 407 | -ms-hyphens: manual; 408 | -webkit-hyphens: manual; 409 | hyphens: manual; 410 | } 411 | 412 | table.footnote td.label { 413 | width: .1px; 414 | padding: 0.3em 0 0.3em 0.5em; 415 | } 416 | 417 | table.footnote td { 418 | padding: 0.3em 0.5em; 419 | } 420 | 421 | dl { 422 | margin: 0; 423 | padding: 0; 424 | } 425 | 426 | dl dd { 427 | margin-left: 30px; 428 | } 429 | 430 | blockquote { 431 | margin: 0 0 0 30px; 432 | padding: 0; 433 | } 434 | 435 | ul, ol { 436 | /* Matches the 30px from the narrow-screen "li > ul" selector below */ 437 | margin: 10px 0 10px 30px; 438 | padding: 0; 439 | } 440 | 441 | pre { 442 | background: #EEE; 443 | padding: 7px 30px; 444 | margin: 15px 0px; 445 | line-height: 1.3em; 446 | } 447 | 448 | div.viewcode-block:target { 449 | background: #ffd; 450 | } 451 | 452 | dl pre, blockquote pre, li pre { 453 | margin-left: 0; 454 | padding-left: 30px; 455 | } 456 | 457 | tt, code { 458 | background-color: #ecf0f3; 459 | color: #222; 460 | /* padding: 1px 2px; */ 461 | } 462 | 463 | tt.xref, code.xref, a tt { 464 | background-color: #FBFBFB; 465 | border-bottom: 1px solid #fff; 466 | } 467 | 468 | a.reference { 469 | text-decoration: none; 470 | border-bottom: 1px dotted #004B6B; 471 | } 472 | 473 | /* Don't put an underline on images */ 474 | a.image-reference, a.image-reference:hover { 475 | border-bottom: none; 476 | } 477 | 478 | a.reference:hover { 479 | border-bottom: 1px solid #6D4100; 480 | } 481 | 482 | a.footnote-reference { 483 | text-decoration: none; 484 | font-size: 0.7em; 485 | vertical-align: top; 486 | border-bottom: 1px dotted #004B6B; 487 | } 488 | 489 | a.footnote-reference:hover { 490 | border-bottom: 1px solid #6D4100; 491 | } 492 | 493 | a:hover tt, a:hover code { 494 | background: #EEE; 495 | } 496 | 497 | 498 | @media screen and (max-width: 870px) { 499 | 500 | div.sphinxsidebar { 501 | display: none; 502 | } 503 | 504 | div.document { 505 | width: 100%; 506 | 507 | } 508 | 509 | div.documentwrapper { 510 | margin-left: 0; 511 | margin-top: 0; 512 | margin-right: 0; 513 | margin-bottom: 0; 514 | } 515 | 516 | div.bodywrapper { 517 | margin-top: 0; 518 | margin-right: 0; 519 | margin-bottom: 0; 520 | margin-left: 0; 521 | } 522 | 523 | ul { 524 | margin-left: 0; 525 | } 526 | 527 | li > ul { 528 | /* Matches the 30px from the "ul, ol" selector above */ 529 | margin-left: 30px; 530 | } 531 | 532 | .document { 533 | width: auto; 534 | } 535 | 536 | .footer { 537 | width: auto; 538 | } 539 | 540 | .bodywrapper { 541 | margin: 0; 542 | } 543 | 544 | .footer { 545 | width: auto; 546 | } 547 | 548 | .github { 549 | display: none; 550 | } 551 | 552 | 553 | 554 | } 555 | 556 | 557 | 558 | @media screen and (max-width: 875px) { 559 | 560 | body { 561 | margin: 0; 562 | padding: 20px 30px; 563 | } 564 | 565 | div.documentwrapper { 566 | float: none; 567 | background: #fff; 568 | } 569 | 570 | div.sphinxsidebar { 571 | display: block; 572 | float: none; 573 | width: 102.5%; 574 | margin: 50px -30px -20px -30px; 575 | padding: 10px 20px; 576 | background: #333; 577 | color: #FFF; 578 | } 579 | 580 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 581 | div.sphinxsidebar h3 a { 582 | color: #fff; 583 | } 584 | 585 | div.sphinxsidebar a { 586 | color: #AAA; 587 | } 588 | 589 | div.sphinxsidebar p.logo { 590 | display: none; 591 | } 592 | 593 | div.document { 594 | width: 100%; 595 | margin: 0; 596 | } 597 | 598 | div.footer { 599 | display: none; 600 | } 601 | 602 | div.bodywrapper { 603 | margin: 0; 604 | } 605 | 606 | div.body { 607 | min-height: 0; 608 | padding: 0; 609 | } 610 | 611 | .rtd_doc_footer { 612 | display: none; 613 | } 614 | 615 | .document { 616 | width: auto; 617 | } 618 | 619 | .footer { 620 | width: auto; 621 | } 622 | 623 | .footer { 624 | width: auto; 625 | } 626 | 627 | .github { 628 | display: none; 629 | } 630 | } 631 | 632 | 633 | /* misc. */ 634 | 635 | .revsys-inline { 636 | display: none!important; 637 | } 638 | 639 | /* Make nested-list/multi-paragraph items look better in Releases changelog 640 | * pages. Without this, docutils' magical list fuckery causes inconsistent 641 | * formatting between different release sub-lists. 642 | */ 643 | div#changelog > div.section > ul > li > p:only-child { 644 | margin-bottom: 0; 645 | } 646 | 647 | /* Hide fugly table cell borders in ..bibliography:: directive output */ 648 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th { 649 | border: none; 650 | /* Below needed in some edge cases; if not applied, bottom shadows appear */ 651 | -moz-box-shadow: none; 652 | -webkit-box-shadow: none; 653 | box-shadow: none; 654 | } 655 | 656 | 657 | /* relbar */ 658 | 659 | .related { 660 | line-height: 30px; 661 | width: 100%; 662 | font-size: 0.9rem; 663 | } 664 | 665 | .related.top { 666 | border-bottom: 1px solid #EEE; 667 | margin-bottom: 20px; 668 | } 669 | 670 | .related.bottom { 671 | border-top: 1px solid #EEE; 672 | } 673 | 674 | .related ul { 675 | padding: 0; 676 | margin: 0; 677 | list-style: none; 678 | } 679 | 680 | .related li { 681 | display: inline; 682 | } 683 | 684 | nav#rellinks { 685 | float: right; 686 | } 687 | 688 | nav#rellinks li+li:before { 689 | content: "|"; 690 | } 691 | 692 | nav#breadcrumbs li+li:before { 693 | content: "\00BB"; 694 | } 695 | 696 | /* Hide certain items when printing */ 697 | @media print { 698 | div.related { 699 | display: none; 700 | } 701 | } -------------------------------------------------------------------------------- /docs/build/html/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 14 | "TEXTAREA", 15 | "INPUT", 16 | "SELECT", 17 | "BUTTON", 18 | ]); 19 | 20 | const _ready = (callback) => { 21 | if (document.readyState !== "loading") { 22 | callback(); 23 | } else { 24 | document.addEventListener("DOMContentLoaded", callback); 25 | } 26 | }; 27 | 28 | /** 29 | * Small JavaScript module for the documentation. 30 | */ 31 | const Documentation = { 32 | init: () => { 33 | Documentation.initDomainIndexTable(); 34 | Documentation.initOnKeyListeners(); 35 | }, 36 | 37 | /** 38 | * i18n support 39 | */ 40 | TRANSLATIONS: {}, 41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 42 | LOCALE: "unknown", 43 | 44 | // gettext and ngettext don't access this so that the functions 45 | // can safely bound to a different name (_ = Documentation.gettext) 46 | gettext: (string) => { 47 | const translated = Documentation.TRANSLATIONS[string]; 48 | switch (typeof translated) { 49 | case "undefined": 50 | return string; // no translation 51 | case "string": 52 | return translated; // translation exists 53 | default: 54 | return translated[0]; // (singular, plural) translation tuple exists 55 | } 56 | }, 57 | 58 | ngettext: (singular, plural, n) => { 59 | const translated = Documentation.TRANSLATIONS[singular]; 60 | if (typeof translated !== "undefined") 61 | return translated[Documentation.PLURAL_EXPR(n)]; 62 | return n === 1 ? singular : plural; 63 | }, 64 | 65 | addTranslations: (catalog) => { 66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 67 | Documentation.PLURAL_EXPR = new Function( 68 | "n", 69 | `return (${catalog.plural_expr})` 70 | ); 71 | Documentation.LOCALE = catalog.locale; 72 | }, 73 | 74 | /** 75 | * helper function to focus on search bar 76 | */ 77 | focusSearchBar: () => { 78 | document.querySelectorAll("input[name=q]")[0]?.focus(); 79 | }, 80 | 81 | /** 82 | * Initialise the domain index toggle buttons 83 | */ 84 | initDomainIndexTable: () => { 85 | const toggler = (el) => { 86 | const idNumber = el.id.substr(7); 87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 88 | if (el.src.substr(-9) === "minus.png") { 89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 90 | toggledRows.forEach((el) => (el.style.display = "none")); 91 | } else { 92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 93 | toggledRows.forEach((el) => (el.style.display = "")); 94 | } 95 | }; 96 | 97 | const togglerElements = document.querySelectorAll("img.toggler"); 98 | togglerElements.forEach((el) => 99 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 100 | ); 101 | togglerElements.forEach((el) => (el.style.display = "")); 102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 103 | }, 104 | 105 | initOnKeyListeners: () => { 106 | // only install a listener if it is really needed 107 | if ( 108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 110 | ) 111 | return; 112 | 113 | document.addEventListener("keydown", (event) => { 114 | // bail for input elements 115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 116 | // bail with special keys 117 | if (event.altKey || event.ctrlKey || event.metaKey) return; 118 | 119 | if (!event.shiftKey) { 120 | switch (event.key) { 121 | case "ArrowLeft": 122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 123 | 124 | const prevLink = document.querySelector('link[rel="prev"]'); 125 | if (prevLink && prevLink.href) { 126 | window.location.href = prevLink.href; 127 | event.preventDefault(); 128 | } 129 | break; 130 | case "ArrowRight": 131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 132 | 133 | const nextLink = document.querySelector('link[rel="next"]'); 134 | if (nextLink && nextLink.href) { 135 | window.location.href = nextLink.href; 136 | event.preventDefault(); 137 | } 138 | break; 139 | } 140 | } 141 | 142 | // some keyboard layouts may need Shift to get / 143 | switch (event.key) { 144 | case "/": 145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 146 | Documentation.focusSearchBar(); 147 | event.preventDefault(); 148 | } 149 | }); 150 | }, 151 | }; 152 | 153 | // quick alias for translations 154 | const _ = Documentation.gettext; 155 | 156 | _ready(Documentation.init); 157 | -------------------------------------------------------------------------------- /docs/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '0.1.0', 4 | LANGUAGE: 'en', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: true, 14 | }; -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/language_data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * language_data.js 3 | * ~~~~~~~~~~~~~~~~ 4 | * 5 | * This script contains the language-specific data used by searchtools.js, 6 | * namely the list of stopwords, stemmer, scorer and splitter. 7 | * 8 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. 9 | * :license: BSD, see LICENSE for details. 10 | * 11 | */ 12 | 13 | var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; 14 | 15 | 16 | /* Non-minified version is copied as a separate JS file, is available */ 17 | 18 | /** 19 | * Porter Stemmer 20 | */ 21 | var Stemmer = function() { 22 | 23 | var step2list = { 24 | ational: 'ate', 25 | tional: 'tion', 26 | enci: 'ence', 27 | anci: 'ance', 28 | izer: 'ize', 29 | bli: 'ble', 30 | alli: 'al', 31 | entli: 'ent', 32 | eli: 'e', 33 | ousli: 'ous', 34 | ization: 'ize', 35 | ation: 'ate', 36 | ator: 'ate', 37 | alism: 'al', 38 | iveness: 'ive', 39 | fulness: 'ful', 40 | ousness: 'ous', 41 | aliti: 'al', 42 | iviti: 'ive', 43 | biliti: 'ble', 44 | logi: 'log' 45 | }; 46 | 47 | var step3list = { 48 | icate: 'ic', 49 | ative: '', 50 | alize: 'al', 51 | iciti: 'ic', 52 | ical: 'ic', 53 | ful: '', 54 | ness: '' 55 | }; 56 | 57 | var c = "[^aeiou]"; // consonant 58 | var v = "[aeiouy]"; // vowel 59 | var C = c + "[^aeiouy]*"; // consonant sequence 60 | var V = v + "[aeiou]*"; // vowel sequence 61 | 62 | var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #8f5902; font-style: italic } /* Comment */ 9 | .highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ 10 | .highlight .g { color: #000000 } /* Generic */ 11 | .highlight .k { color: #004461; font-weight: bold } /* Keyword */ 12 | .highlight .l { color: #000000 } /* Literal */ 13 | .highlight .n { color: #000000 } /* Name */ 14 | .highlight .o { color: #582800 } /* Operator */ 15 | .highlight .x { color: #000000 } /* Other */ 16 | .highlight .p { color: #000000; font-weight: bold } /* Punctuation */ 17 | .highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ 18 | .highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ 19 | .highlight .cp { color: #8f5902 } /* Comment.Preproc */ 20 | .highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ 21 | .highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ 22 | .highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ 23 | .highlight .gd { color: #a40000 } /* Generic.Deleted */ 24 | .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ 25 | .highlight .gr { color: #ef2929 } /* Generic.Error */ 26 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 27 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 28 | .highlight .go { color: #888888 } /* Generic.Output */ 29 | .highlight .gp { color: #745334 } /* Generic.Prompt */ 30 | .highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ 31 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 32 | .highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ 33 | .highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ 34 | .highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ 35 | .highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ 36 | .highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ 37 | .highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ 38 | .highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ 39 | .highlight .ld { color: #000000 } /* Literal.Date */ 40 | .highlight .m { color: #990000 } /* Literal.Number */ 41 | .highlight .s { color: #4e9a06 } /* Literal.String */ 42 | .highlight .na { color: #c4a000 } /* Name.Attribute */ 43 | .highlight .nb { color: #004461 } /* Name.Builtin */ 44 | .highlight .nc { color: #000000 } /* Name.Class */ 45 | .highlight .no { color: #000000 } /* Name.Constant */ 46 | .highlight .nd { color: #888888 } /* Name.Decorator */ 47 | .highlight .ni { color: #ce5c00 } /* Name.Entity */ 48 | .highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ 49 | .highlight .nf { color: #000000 } /* Name.Function */ 50 | .highlight .nl { color: #f57900 } /* Name.Label */ 51 | .highlight .nn { color: #000000 } /* Name.Namespace */ 52 | .highlight .nx { color: #000000 } /* Name.Other */ 53 | .highlight .py { color: #000000 } /* Name.Property */ 54 | .highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ 55 | .highlight .nv { color: #000000 } /* Name.Variable */ 56 | .highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ 57 | .highlight .pm { color: #000000; font-weight: bold } /* Punctuation.Marker */ 58 | .highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ 59 | .highlight .mb { color: #990000 } /* Literal.Number.Bin */ 60 | .highlight .mf { color: #990000 } /* Literal.Number.Float */ 61 | .highlight .mh { color: #990000 } /* Literal.Number.Hex */ 62 | .highlight .mi { color: #990000 } /* Literal.Number.Integer */ 63 | .highlight .mo { color: #990000 } /* Literal.Number.Oct */ 64 | .highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ 65 | .highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ 66 | .highlight .sc { color: #4e9a06 } /* Literal.String.Char */ 67 | .highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ 68 | .highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ 69 | .highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ 70 | .highlight .se { color: #4e9a06 } /* Literal.String.Escape */ 71 | .highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ 72 | .highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ 73 | .highlight .sx { color: #4e9a06 } /* Literal.String.Other */ 74 | .highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ 75 | .highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ 76 | .highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ 77 | .highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ 78 | .highlight .fm { color: #000000 } /* Name.Function.Magic */ 79 | .highlight .vc { color: #000000 } /* Name.Variable.Class */ 80 | .highlight .vg { color: #000000 } /* Name.Variable.Global */ 81 | .highlight .vi { color: #000000 } /* Name.Variable.Instance */ 82 | .highlight .vm { color: #000000 } /* Name.Variable.Magic */ 83 | .highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | parent.insertBefore( 33 | span, 34 | parent.insertBefore( 35 | document.createTextNode(val.substr(pos + text.length)), 36 | node.nextSibling 37 | ) 38 | ); 39 | node.nodeValue = val.substr(0, pos); 40 | 41 | if (isInSVG) { 42 | const rect = document.createElementNS( 43 | "http://www.w3.org/2000/svg", 44 | "rect" 45 | ); 46 | const bbox = parent.getBBox(); 47 | rect.x.baseVal.value = bbox.x; 48 | rect.y.baseVal.value = bbox.y; 49 | rect.width.baseVal.value = bbox.width; 50 | rect.height.baseVal.value = bbox.height; 51 | rect.setAttribute("class", className); 52 | addItems.push({ parent: parent, target: rect }); 53 | } 54 | } 55 | } else if (node.matches && !node.matches("button, select, textarea")) { 56 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 57 | } 58 | }; 59 | const _highlightText = (thisNode, text, className) => { 60 | let addItems = []; 61 | _highlight(thisNode, addItems, text, className); 62 | addItems.forEach((obj) => 63 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 64 | ); 65 | }; 66 | 67 | /** 68 | * Small JavaScript module for the documentation. 69 | */ 70 | const SphinxHighlight = { 71 | 72 | /** 73 | * highlight the search words provided in localstorage in the text 74 | */ 75 | highlightSearchWords: () => { 76 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 77 | 78 | // get and clear terms from localstorage 79 | const url = new URL(window.location); 80 | const highlight = 81 | localStorage.getItem("sphinx_highlight_terms") 82 | || url.searchParams.get("highlight") 83 | || ""; 84 | localStorage.removeItem("sphinx_highlight_terms") 85 | url.searchParams.delete("highlight"); 86 | window.history.replaceState({}, "", url); 87 | 88 | // get individual terms from highlight string 89 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 90 | if (terms.length === 0) return; // nothing to do 91 | 92 | // There should never be more than one element matching "div.body" 93 | const divBody = document.querySelectorAll("div.body"); 94 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 95 | window.setTimeout(() => { 96 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 97 | }, 10); 98 | 99 | const searchBox = document.getElementById("searchbox"); 100 | if (searchBox === null) return; 101 | searchBox.appendChild( 102 | document 103 | .createRange() 104 | .createContextualFragment( 105 | '" 109 | ) 110 | ); 111 | }, 112 | 113 | /** 114 | * helper function to hide the search marks again 115 | */ 116 | hideSearchWords: () => { 117 | document 118 | .querySelectorAll("#searchbox .highlight-link") 119 | .forEach((el) => el.remove()); 120 | document 121 | .querySelectorAll("span.highlighted") 122 | .forEach((el) => el.classList.remove("highlighted")); 123 | localStorage.removeItem("sphinx_highlight_terms") 124 | }, 125 | 126 | initEscapeListener: () => { 127 | // only install a listener if it is really needed 128 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 129 | 130 | document.addEventListener("keydown", (event) => { 131 | // bail for input elements 132 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 133 | // bail with special keys 134 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 135 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 136 | SphinxHighlight.hideSearchWords(); 137 | event.preventDefault(); 138 | } 139 | }); 140 | }, 141 | }; 142 | 143 | _ready(SphinxHighlight.highlightSearchWords); 144 | _ready(SphinxHighlight.initEscapeListener); 145 | -------------------------------------------------------------------------------- /docs/build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Index — flowsig 0.1.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 | 28 | 29 |
30 | 31 | 32 |

Index

33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 |
43 | 83 |
84 |
85 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Welcome to flowsig’s documentation! — flowsig 0.1.0 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 |

Welcome to flowsig’s documentation!

34 |
35 |
36 |
37 |
38 |

Indices and tables

39 | 44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 | 91 |
92 |
93 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Search — flowsig 0.1.0 documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 |
36 | 37 |

Search

38 | 39 | 47 | 48 | 49 |

50 | Searching for multiple words only shows matches that contain 51 | all words. 52 |

53 | 54 | 55 |
56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 |
64 | 65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 102 |
103 |
104 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Welcome to flowsig\u2019s documentation!"], "terms": {"index": 0, "modul": 0, "search": 0, "page": 0}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"welcom": 0, "flowsig": 0, "": 0, "document": 0, "indic": 0, "tabl": 0}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 57}, "alltitles": {"Welcome to flowsig\u2019s documentation!": [[0, "welcome-to-flowsig-s-documentation"]], "Indices and tables": [[0, "indices-and-tables"]]}, "indexentries": {}}) -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/docs/source/.DS_Store -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'flowsig' 10 | copyright = '2024, Axel A. Almet' 11 | author = 'Axel A. Almet' 12 | release = '0.1.2' 13 | 14 | # -- General configuration --------------------------------------------------- 15 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 16 | 17 | extensions = [] 18 | 19 | templates_path = ['_templates'] 20 | exclude_patterns = [] 21 | 22 | 23 | 24 | # -- Options for HTML output ------------------------------------------------- 25 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 26 | 27 | html_theme = 'alabaster' 28 | html_static_path = ['_static'] 29 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. flowsig documentation master file, created by 2 | sphinx-quickstart on Fri Jul 7 14:47:42 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to flowsig's documentation! 7 | =================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /flowsig.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE.md 2 | README.md 3 | pyproject.toml 4 | setup.cfg 5 | setup.py 6 | FlowSig.egg-info/PKG-INFO 7 | FlowSig.egg-info/SOURCES.txt 8 | FlowSig.egg-info/dependency_links.txt 9 | FlowSig.egg-info/requires.txt 10 | FlowSig.egg-info/top_level.txt 11 | flowsig/__init__.py 12 | flowsig.egg-info/PKG-INFO 13 | flowsig.egg-info/SOURCES.txt 14 | flowsig.egg-info/dependency_links.txt 15 | flowsig.egg-info/top_level.txt 16 | flowsig/data/allTFs_human.txt 17 | flowsig/data/allTFs_mouse.txt 18 | flowsig/data/allTFs_zebrafish.txt 19 | flowsig/plotting/__init__.py 20 | flowsig/plotting/_plotting.py 21 | flowsig/preprocessing/__init__.py 22 | flowsig/preprocessing/_flow_expressions.py 23 | flowsig/preprocessing/_flow_preprocessing.py 24 | flowsig/preprocessing/_gem_construction.py 25 | flowsig/preprocessing/_spatial_blocking.py 26 | flowsig/preprocessing/_townes_nsf_utils.py 27 | flowsig/tools/__init__.py 28 | flowsig/tools/_network.py 29 | flowsig/tools/_spatial.py 30 | flowsig/tools/_validate_network.py 31 | flowsig/utilities/__init__.py 32 | flowsig/utilities/_utils.py -------------------------------------------------------------------------------- /flowsig.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flowsig.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | anndata 4 | pandas 5 | scanpy 6 | spatial-factorization@ git+https://github.com/willtownes/spatial-factorization-py.git@c7c7fbb22a7ed9abccca94b759e4601b78d874b 7 | matplotlib 8 | seaborn 9 | networkx 10 | causaldag 11 | graphical_models 12 | tensorflow_probability 13 | pyliger 14 | joblib 15 | squidpy 16 | -------------------------------------------------------------------------------- /flowsig.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | flowsig 2 | -------------------------------------------------------------------------------- /flowsig/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/.DS_Store -------------------------------------------------------------------------------- /flowsig/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__ file 2 | import warnings 3 | warnings.filterwarnings('ignore') 4 | 5 | from . import preprocessing as pp 6 | from . import tools as tl 7 | from . import plotting as pl 8 | from . import utilities as ul 9 | 10 | import sys 11 | sys.modules.update({f'{__name__}.{m}': globals()[m] for m in ['pp','tl','pl', 'ul']}) -------------------------------------------------------------------------------- /flowsig/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /flowsig/data/cellchat_interactions_and_tfs_human.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/data/cellchat_interactions_and_tfs_human.csv.gz -------------------------------------------------------------------------------- /flowsig/data/cellchat_interactions_and_tfs_mouse.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/data/cellchat_interactions_and_tfs_mouse.csv.gz -------------------------------------------------------------------------------- /flowsig/data/cellchat_interactions_tfs_human.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/data/cellchat_interactions_tfs_human.csv.gz -------------------------------------------------------------------------------- /flowsig/data/cellchat_interactions_tfs_mouse.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/data/cellchat_interactions_tfs_mouse.csv.gz -------------------------------------------------------------------------------- /flowsig/plotting/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plotting import plot_differentially_flowing_signals, plot_intercellular_flows -------------------------------------------------------------------------------- /flowsig/plotting/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/plotting/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/plotting/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/plotting/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/plotting/__pycache__/_plotting.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/plotting/__pycache__/_plotting.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/plotting/__pycache__/_plotting.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/plotting/__pycache__/_plotting.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__init__.py: -------------------------------------------------------------------------------- 1 | from ._flow_expressions import FlowSigConfig, construct_flows_from_cellchat, construct_flows_from_commot, construct_flows_from_cellphonedb, construct_flows_from_liana, construct_flow_expressions 2 | from ._flow_preprocessing import subset_for_flow_type, filter_flow_vars, determine_differentially_flowing_vars, determine_spatially_flowing_vars, determine_informative_variables 3 | from ._gem_construction import construct_gems_using_pyliger, construct_gems_using_nsf, construct_gems_using_nmf, construct_gems_using_cnmf 4 | from ._spatial_blocking import construct_spatial_blocks 5 | -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_flow_expressions.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_flow_expressions.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_flow_expressions.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_flow_expressions.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_flow_expressions.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_flow_expressions.cpython-39.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_flow_preprocessing.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_flow_preprocessing.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_flow_preprocessing.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_flow_preprocessing.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_flow_preprocessing.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_flow_preprocessing.cpython-39.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_gem_construction.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_gem_construction.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_gem_construction.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_gem_construction.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_gem_construction.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_gem_construction.cpython-39.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_spatial_blocking.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_spatial_blocking.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_spatial_blocking.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_spatial_blocking.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_townes_nsf_utils.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_townes_nsf_utils.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/__pycache__/_townes_nsf_utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/preprocessing/__pycache__/_townes_nsf_utils.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/preprocessing/_flow_preprocessing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Tuple, Optional, Sequence, Literal 3 | import numpy as np 4 | import scanpy as sc 5 | from anndata import AnnData 6 | import squidpy as sq 7 | import pandas as pd 8 | from ..preprocessing import FlowSigConfig 9 | 10 | def _make_flow_adata( 11 | adata: AnnData, 12 | config: FlowSigConfig, 13 | var_mask: np.ndarray | None = None, 14 | ) -> AnnData: 15 | 16 | flowsig_expr_key = config.flowsig_expr_key 17 | flowsig_network_key = config.flowsig_network_key 18 | 19 | if flowsig_expr_key not in adata.obsm: 20 | raise ValueError(f"Could not find {flowsig_expr_key} in adata.obsm") 21 | 22 | if flowsig_network_key not in adata.uns: 23 | raise ValueError(f"Could not find {flowsig_network_key} in adata.uns") 24 | 25 | X = adata.obsm[flowsig_expr_key] 26 | 27 | adata_flow = AnnData(X=X, obs=adata.obs.copy()) 28 | adata_flow.var = adata.uns[flowsig_network_key]["flow_var_info"].copy() 29 | 30 | if var_mask is not None: 31 | adata_flow = adata_flow[:, var_mask] 32 | 33 | return adata_flow 34 | 35 | def _log_fold_cohens(adata: AnnData, 36 | condition_key: str, 37 | group1: str, 38 | group2: str) -> pd.DataFrame: 39 | """Calculate a standardised log-fold change somewhat equivalent to Cohen's d between group1 and group2. 40 | This is adapted from the scoreMarkers function in scran (see here: https://rdrr.io/github/MarioniLab/scran/man/scoreMarkers.html). 41 | """ 42 | 43 | # Calculate the mean and standard deviation for each group 44 | expr_group1 = adata[adata.obs[condition_key] == group1].X 45 | expr_group2 = adata[adata.obs[condition_key] == group2].X 46 | mean1 = expr_group1.mean(axis=0) 47 | mean2 = expr_group2.mean(axis=0) 48 | std1 = expr_group1.std(axis=0) 49 | std2 = expr_group2.std(axis=0) 50 | 51 | log_means_diff = mean1 - mean2 52 | lfc_cohen = log_means_diff / ( 0.5 * (std1**2.0 + std2**2.0) )** 0.5 53 | lfc_cohens_results = pd.DataFrame(data=lfc_cohen, index=adata.var_names, columns=['logfoldchanges_cohen']) 54 | 55 | return lfc_cohens_results 56 | 57 | def _select_vars_union( 58 | logfoldchanges_per_group: dict[str, pd.DataFrame], 59 | logfc_threshold: float, 60 | qval_threshold: float = None, 61 | construction: Literal['v1', 'v2'] = 'v1' 62 | ) -> list[str]: 63 | """Return union of gene names passing both thresholds in any group.""" 64 | selected: set[str] = set() 65 | 66 | if construction == 'v1': 67 | for df in logfoldchanges_per_group.values(): 68 | keep = (np.abs(df["logfoldchanges"]) > logfc_threshold) & (df["pvals_adj"] < qval_threshold) 69 | 70 | selected |= set(df.loc[keep, "names"]) # this is equivalent to selected = selected | set(df.loc[keep, "names"]) 71 | 72 | else: 73 | for df in logfoldchanges_per_group.values(): 74 | keep = (np.abs(df["logfoldchanges_cohen"]) > logfc_threshold) 75 | 76 | selected |= set(df.index[keep]) 77 | 78 | return list(selected) 79 | 80 | def subset_for_flow_type( 81 | adata: AnnData, 82 | var_type: str = "all", 83 | config: FlowSigConfig = FlowSigConfig(), 84 | ) -> AnnData: 85 | 86 | flowsig_network_key = config.flowsig_network_key 87 | 88 | if flowsig_network_key not in adata.uns: 89 | raise ValueError(f"Could not find {flowsig_network_key} in adata.uns") 90 | 91 | if var_type not in {"all", "inflow", "module", "outflow"}: 92 | raise ValueError("var_type must be 'all', 'inflow', 'module', or 'outflow'.") 93 | 94 | info = adata.uns[config.flowsig_network_key]["flow_var_info"] 95 | mask = None if var_type == "all" else (info["Type"].to_numpy() == var_type) 96 | return _make_flow_adata(adata, config, mask) 97 | 98 | def filter_flow_vars( 99 | adata: AnnData, 100 | vars_subset: Sequence[str], 101 | config: FlowSigConfig = FlowSigConfig(), 102 | ) -> None: 103 | 104 | flowsig_network_key = config.flowsig_network_key 105 | flowsig_expr_key = config.flowsig_expr_key 106 | 107 | if flowsig_network_key not in adata.uns: 108 | raise ValueError(f"Could not find {flowsig_network_key} in adata.uns") 109 | if flowsig_expr_key not in adata.obsm: 110 | raise ValueError(f"Could not find {flowsig_expr_key} in adata.obsm") 111 | 112 | full_info = adata.uns[config.flowsig_network_key]["flow_var_info"] 113 | mask = full_info.index.isin(vars_subset) 114 | 115 | # no change → return early 116 | if mask.all(): 117 | return 118 | 119 | # backup originals once 120 | adata.obsm.setdefault(f"{config.flowsig_expr_key}_orig", adata.obsm[config.flowsig_expr_key]) 121 | adata.uns.setdefault(f"{config.flowsig_network_key}_orig", {"flow_var_info": full_info}) 122 | 123 | adata.obsm[flowsig_expr_key] = adata.obsm[config.flowsig_expr_key][:, mask] 124 | adata.uns[flowsig_network_key] = {"flow_var_info": full_info.loc[mask]} 125 | 126 | def determine_differentially_flowing_vars( 127 | adata: AnnData, 128 | condition_key: str, 129 | control: str, 130 | *, 131 | config: FlowSigConfig = FlowSigConfig(), 132 | logfc_threshold: float = None, 133 | qval_threshold: float = None, 134 | construction: Literal['v1', 'v2'] = 'v1' 135 | ) -> None: 136 | 137 | flowsig_expr_key = config.flowsig_expr_key 138 | if flowsig_expr_key not in adata.obsm: 139 | raise ValueError(f"Could not find {flowsig_expr_key} in adata.obsm") 140 | 141 | flowsig_network_key = config.flowsig_network_key 142 | if flowsig_network_key not in adata.uns: 143 | raise ValueError(f"Could not find {flowsig_network_key} in adata.uns") 144 | 145 | pert_conds = [cond for cond in adata.obs[condition_key].unique() if cond != control] 146 | info = adata.uns[config.flowsig_network_key]["flow_var_info"] 147 | 148 | inflow_mask = info["Type"].eq("inflow").to_numpy() 149 | outflow_mask = info["Type"].eq("outflow").to_numpy() 150 | 151 | ad_in = _make_flow_adata(adata, config, inflow_mask) 152 | ad_out = _make_flow_adata(adata, config, outflow_mask) 153 | 154 | vars_keep = None 155 | 156 | def _collect(ad, construction) -> list[str]: 157 | 158 | pert_cond = {} 159 | if construction == 'v1': 160 | pert_cond = {cond: sc.get.rank_genes_groups_df(ad, group=cond) for cond in pert_conds} 161 | 162 | else: # Just select based on logfc_thr for logfoldchanges_cohen 163 | pert_cond = {cond: _log_fold_cohens(ad, condition_key=condition_key, group1=cond, group2=control) for cond in pert_conds} 164 | 165 | return _select_vars_union(pert_cond, logfc_threshold, qval_threshold, construction) 166 | 167 | if construction == 'v1': 168 | 169 | for ad in (ad_in, ad_out): 170 | ad.uns["log1p"] = {"base": None} # prevent scanpy warning 171 | sc.tl.rank_genes_groups(ad, groupby=condition_key, method="wilcoxon") 172 | 173 | vars_keep = _collect(ad_in, construction) + _collect(ad_out, construction) + info[info["Type"] == "module"].index.tolist() 174 | 175 | 176 | filter_flow_vars(adata, vars_keep, config) 177 | 178 | def determine_spatially_flowing_vars( 179 | adata: AnnData, 180 | *, 181 | config: FlowSigConfig = FlowSigConfig(), 182 | moran_threshold: float = 0.1, 183 | coord_type: str = "grid", 184 | n_neighs: int = 6, 185 | library_key: str | None = None, 186 | n_perms: int | None = None, 187 | n_jobs: int | None = None, 188 | ) -> None: 189 | 190 | if "spatial" not in adata.obsm: 191 | raise ValueError("adata.obsm['spatial'] not found.") 192 | 193 | flowsig_network_key = config.flowsig_network_key 194 | if flowsig_network_key not in adata.uns: 195 | raise ValueError(f"Could not find {flowsig_network_key} in adata.uns") 196 | 197 | info = adata.uns[flowsig_network_key]["flow_var_info"] 198 | masks = {var_type: info["Type"].eq(var_type).to_numpy() for var_type in ("inflow", "outflow")} 199 | 200 | adatas = { 201 | var_type: _make_flow_adata(adata, config, mask) for var_type, mask in masks.items() 202 | } 203 | for ad in adatas.values(): 204 | ad.obsm["spatial"] = adata.obsm["spatial"] 205 | 206 | # build neighbours only once per coord_type/param set 207 | for ad in adatas.values(): 208 | if "spatial_connectivities" not in ad.obsp: 209 | sq.gr.spatial_neighbors(ad, coord_type=coord_type, n_neighs=n_neighs, library_key=library_key) 210 | sq.gr.spatial_autocorr(ad, genes=ad.var_names, n_perms=n_perms, n_jobs=n_jobs) 211 | 212 | spatially_varying_vars = [] 213 | for var_type, ad in adatas.items(): 214 | spatially_varying_vars.extend(ad.uns["moranI"].query("I > @moran_threshold").index) 215 | 216 | vars_keep = spatially_varying_vars + info[info["Type"] == "module"].index.tolist() 217 | filter_flow_vars(adata, vars_keep, config) 218 | 219 | def determine_informative_variables( 220 | adata: AnnData, 221 | *, 222 | config: FlowSigConfig = FlowSigConfig(), 223 | spatial: bool = False, 224 | condition_key: str | None = None, 225 | control: str | None = None, 226 | **kwargs, 227 | ) -> None: 228 | 229 | """Wrapper that chooses spatial vs. differential selection.""" 230 | if spatial: 231 | determine_spatially_flowing_vars(adata, config=config, **kwargs) 232 | else: 233 | if condition_key is None or control is None: 234 | raise ValueError("condition_key and control must be provided for differential selection.") 235 | determine_differentially_flowing_vars( 236 | adata, 237 | condition_key, 238 | control, 239 | config=config, 240 | **kwargs, 241 | ) -------------------------------------------------------------------------------- /flowsig/preprocessing/_gem_construction.py: -------------------------------------------------------------------------------- 1 | from anndata import AnnData 2 | import numpy as np 3 | import pandas as pd 4 | import pyliger 5 | from tensorflow_probability import math as tm 6 | tfk = tm.psd_kernels 7 | import spatial_factorization as sf 8 | from typing import Optional 9 | from scipy.sparse import csr_matrix 10 | from ._townes_nsf_utils import * 11 | from sklearn.decomposition import NMF 12 | 13 | def construct_gems_using_pyliger(adata: AnnData, 14 | n_gems: int, 15 | layer_key: str, 16 | condition_key: str): 17 | 18 | conditions = adata.obs[condition_key].unique().tolist() 19 | ad = adata.copy() 20 | 21 | ad.X = csr_matrix(ad.layers[layer_key].copy()) 22 | ad.obs.index.name = 'index' 23 | ad.var.index.name = 'index' 24 | 25 | # Create LIGER object 26 | adata_list = [] 27 | for cond in conditions: 28 | adata_cond = ad[ad.obs[condition_key] == cond].copy() 29 | adata_cond.uns['sample_name'] = cond 30 | adata_list.append(adata_cond) 31 | 32 | adata_liger = pyliger.create_liger(adata_list, make_sparse=True) 33 | 34 | pyliger.normalize(adata_liger) 35 | pyliger.select_genes(adata_liger) 36 | pyliger.scale_not_center(adata_liger) 37 | 38 | # Save the var_names that were used for the NMF 39 | # adata_burkhardt.uns['pyliger_vars'] = burkhardt_liger.adata_list[0].var_names.tolist() 40 | 41 | pyliger.optimize_ALS(adata_liger, k = n_gems) 42 | 43 | # # Save results to adata 44 | X_gem = np.zeros((adata.n_obs, n_gems)) 45 | pyliger_info = {} 46 | 47 | for i, cond in enumerate(conditions): 48 | cond_indices = np.where(adata.obs[condition_key] == cond)[0] 49 | X_gem[cond_indices] = adata_liger.adata_list[i].obsm['H'] 50 | 51 | pyliger_info[cond] = {'H': adata_liger.adata_list[i].obsm['H'], 52 | 'W': adata_liger.adata_list[i].varm['W'], 53 | 'V': adata_liger.adata_list[i].varm['V']} 54 | 55 | adata.uns['pyliger_info'] = pyliger_info 56 | adata.uns['pyliger_info']['vars'] = adata_liger.adata_list[0].var_names.tolist() 57 | adata.uns['pyliger_info']['n_gems'] = n_gems 58 | adata.obsm['X_gem'] = X_gem 59 | 60 | def construct_gems_using_nsf(adata: AnnData, 61 | n_gems: int, 62 | layer_key: str, 63 | spatial_key: str = "spatial", 64 | n_inducing_pts: int = 500, 65 | length_scale: float = 10.0): 66 | 67 | ad = adata.copy() 68 | 69 | X = ad.obsm[spatial_key] 70 | # Take raw count data for NSF 71 | training_fraction = 1.0 72 | D,Dval = anndata_to_train_val(ad, 73 | layer=layer_key, 74 | train_frac=training_fraction, 75 | flip_yaxis=True) 76 | Ntr,J = D["Y"].shape 77 | Xtr = D["X"] 78 | ad = adata[:Ntr,:] 79 | #convert to tensorflow objects 80 | Dtf = prepare_datasets_tf(D,Dval=Dval) 81 | 82 | Z = kmeans_inducing_pts(Xtr, n_inducing_pts) 83 | M = Z.shape[0] #number of inducing points 84 | ker = tfk.MaternThreeHalves 85 | 86 | fit = sf.SpatialFactorization(J, n_gems, Z, psd_kernel=ker, length_scale=length_scale, nonneg=True, lik="poi") 87 | fit.init_loadings(D["Y"], X=Xtr, sz=D["sz"], shrinkage=0.3) 88 | tro = sf.ModelTrainer(fit) 89 | tro.train_model(*Dtf, status_freq=50) #about 3 mins 90 | 91 | insf = interpret_nsf(fit,Xtr,S=100,lda_mode=False) 92 | 93 | adata.uns['nsf_info'] = insf 94 | adata.uns['nsf_info']['vars'] = adata.var_names.tolist() 95 | adata.uns['nsf_info']['n_gems'] = n_gems 96 | adata.obsm['X_gem'] = insf['factors'] 97 | 98 | def construct_gems_using_nmf(adata: AnnData, 99 | n_gems: int, 100 | layer_key: str, 101 | random_state: int = 0, 102 | max_iter: int = 1000): 103 | 104 | X_expr = adata.layers[layer_key].copy() 105 | 106 | model = NMF(n_components=n_gems, init='random', random_state=random_state, max_iter=max_iter) 107 | 108 | W = model.fit_transform(X_expr) 109 | H = model.components_ 110 | 111 | W_sum = W.sum(axis=0) 112 | W_lda = W / W_sum 113 | 114 | H_scaled = H.T * W_sum 115 | H_sum = H_scaled.sum(axis=1) 116 | H_lda = (H_scaled.T / H_sum).T 117 | 118 | fact_orders = np.argsort(-H_lda.sum(axis=0)) 119 | 120 | W_lda = W_lda[:, fact_orders] 121 | H_lda = H_lda[:, fact_orders].T 122 | 123 | adata.uns['nmf_info'] = {'n_gems': n_gems, 124 | 'vars': adata.var_names.tolist(), 125 | 'factors':W_lda, 126 | 'loadings':H_lda, 127 | 'totals':W_sum} 128 | 129 | adata.obsm['X_gem'] = W_lda 130 | 131 | def construct_gems_using_cnmf(adata: AnnData, 132 | n_gems: int, 133 | usage_norms: np.ndarray | pd.DataFrame, 134 | spectra_scores: np.ndarray | pd.DataFrame, 135 | spectra_tpm: np.ndarray | pd.DataFrame, 136 | cnmf_vars: Optional[list] = None): 137 | 138 | if isinstance(spectra_scores, np.ndarray) and cnmf_vars is None: 139 | raise(ValueError("Must provide cNMF vars list if spectra_scores is a numpy array")) 140 | else: 141 | cnmf_vars = spectra_scores.index.tolist() 142 | spectra_scores = spectra_scores.values 143 | spectra_tpm = spectra_tpm.values 144 | 145 | cnmf_info = {'spectra_score': spectra_scores, 146 | 'spectra_tpm': spectra_tpm, 147 | 'n_gems': n_gems, 148 | 'vars': cnmf_vars} 149 | 150 | adata.uns['cnmf_info'] = cnmf_info 151 | 152 | # Account for the fact that usage_norm could be a Pandas dataframe 153 | if isinstance(usage_norms, pd.DataFrame): 154 | usage_norms = usage_norms.values 155 | elif isinstance(usage_norms, np.ndarray): 156 | pass 157 | else: 158 | raise ValueError("usage_norms must be a NumPy array or Pandas dataframe") 159 | 160 | adata.obsm['X_gem'] = usage_norms -------------------------------------------------------------------------------- /flowsig/preprocessing/_spatial_blocking.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import scanpy as sc 3 | from anndata import AnnData 4 | from sklearn.cluster import KMeans 5 | 6 | def construct_spatial_blocks(adata: AnnData, 7 | n_blocks: int, 8 | use_graph: bool = False, 9 | graph_adjacency: str = 'spatial_connectivities', 10 | resolution: float = None, 11 | spatial_block_key: str = "spatial_block", 12 | spatial_key: str = "spatial"): 13 | 14 | # If we want to construct spatial clusters from the graph directly, we use leiden clustering 15 | if use_graph: 16 | 17 | if resolution is None: 18 | ValueError('Need to specify a clustering resolution to construct blocks from spatial graph.') 19 | 20 | else: 21 | sc.tl.leiden(adata, resolution=resolution, key_added=spatial_block_key, adjacency=graph_adjacency) 22 | 23 | else: # Run k-means clustering on the spatial coordinates (produces more "even" blocks) 24 | 25 | kmeans = KMeans(n_clusters=n_blocks).fit(adata.obsm[spatial_key]) 26 | adata.obs[spatial_block_key] = pd.Series(kmeans.labels_, dtype='category').values -------------------------------------------------------------------------------- /flowsig/preprocessing/_townes_nsf_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from contextlib import suppress 3 | from math import ceil 4 | from tensorflow import constant 5 | from tensorflow.data import Dataset 6 | from sklearn.cluster import KMeans 7 | from copy import deepcopy 8 | 9 | # All of this functionaliy has been taken from https://github.com/willtownes/nsf-paper/blob/main/utils/preprocess.py 10 | # and https://github.com/willtownes/nsf-paper/blob/main/utils/postprocess.py 11 | def rescale_spatial_coords(X,box_side=4): 12 | """ 13 | X is an NxD matrix of spatial coordinates 14 | Returns a rescaled version of X such that aspect ratio is preserved 15 | But data are centered at zero and area of equivalent bounding box set to 16 | box_side^D 17 | Goal is to rescale X to be similar to a N(0,1) distribution in all axes 18 | box_side=4 makes the data fit in range (-2,2) 19 | """ 20 | xmin = X.min(axis=0) 21 | X -= xmin 22 | x_gmean = np.exp(np.mean(np.log(X.max(axis=0)))) 23 | X *= box_side/x_gmean 24 | return X - X.mean(axis=0) 25 | 26 | def scanpy_sizefactors(Y): 27 | sz = Y.sum(axis=1,keepdims=True) 28 | return sz/np.median(sz) 29 | 30 | def anndata_to_train_val(ad, layer=None, nfeat=None, train_frac=0.95, 31 | sz="constant", dtp="float32", flip_yaxis=True): 32 | """ 33 | Convert anndata object ad to a training data dictionary 34 | and a validation data dictionary 35 | Requirements: 36 | * rows of ad are pre-shuffled to ensure random split of train/test 37 | * spatial coordinates in ad.obsm['spatial'] 38 | * features (cols) of ad sorted in decreasing importance (eg with deviance) 39 | """ 40 | if nfeat is not None: ad = ad[:,:nfeat] 41 | N = ad.shape[0] 42 | Ntr = round(train_frac*N) 43 | X = ad.obsm["spatial"].copy().astype(dtp) 44 | if flip_yaxis: X[:,1] = -X[:,1] 45 | X = rescale_spatial_coords(X) 46 | if layer is None: Y = ad.X 47 | else: Y = ad.layers[layer] 48 | with suppress(AttributeError): 49 | Y = Y.toarray() #in case Y is a sparse matrix 50 | Y = Y.astype(dtp) 51 | Dtr = {"X":X[:Ntr,:], "Y":Y[:Ntr,:]} 52 | Dval = {"X":X[Ntr:,:], "Y":Y[Ntr:,:]} 53 | if sz=="constant": 54 | Dtr["sz"] = np.ones((Ntr,1),dtype=dtp) 55 | Dval["sz"] = np.ones((N-Ntr,1),dtype=dtp) 56 | elif sz=="mean": 57 | Dtr["sz"] = Dtr["Y"].mean(axis=1,keepdims=True) 58 | Dval["sz"] = Dval["Y"].mean(axis=1,keepdims=True) 59 | elif sz=="scanpy": 60 | Dtr["sz"] = scanpy_sizefactors(Dtr["Y"]) 61 | Dval["sz"] = scanpy_sizefactors(Dval["Y"]) 62 | else: 63 | raise ValueError("unrecognized size factors 'sz'") 64 | Dtr["idx"] = np.arange(Ntr) 65 | if Ntr>=N: Dval = None #avoid returning an empty array 66 | return Dtr,Dval 67 | 68 | def minibatch_size_adjust(num_obs,batch_size): 69 | """ 70 | Calculate adjusted minibatch size that divides 71 | num_obs as evenly as possible 72 | num_obs : number of observations in full data 73 | batch_size : maximum size of a minibatch 74 | """ 75 | nbatch = ceil(num_obs/float(batch_size)) 76 | return int(ceil(num_obs/nbatch)) 77 | 78 | def prepare_datasets_tf(Dtrain,Dval=None,shuffle=False,batch_size=None): 79 | """ 80 | Dtrain and Dval are dicts containing numpy np.arrays of data. 81 | Dtrain must contain the key "Y" 82 | Returns a from_tensor_slices conversion of Dtrain and a dict of tensors for Dval 83 | """ 84 | Ntr = Dtrain["Y"].shape[0] 85 | if batch_size is None: 86 | #ie one batch containing all observations by default 87 | batch_size = Ntr 88 | else: 89 | batch_size = minibatch_size_adjust(Ntr,batch_size) 90 | Dtrain = Dataset.from_tensor_slices(Dtrain) 91 | if shuffle: 92 | Dtrain = Dtrain.shuffle(Ntr) 93 | Dtrain = Dtrain.batch(batch_size) 94 | if Dval is not None: 95 | Dval = {i:constant(Dval[i]) for i in Dval} 96 | return Dtrain, Ntr, Dval 97 | 98 | def kmeans_inducing_pts(X,M): 99 | M = int(M) 100 | Z = np.unique(X, axis=0) 101 | unique_locs = Z.shape[0] 102 | if M np.ndarray: 24 | n = X.shape[0] 25 | if indices_by_blocks is None: # Sample individual cells 26 | return X[rng.integers(0, n, n)] 27 | 28 | # Block bootstrapping for spatial data 29 | X_rs = X.copy() 30 | for indices in indices_by_blocks: 31 | X_rs[indices] = X[rng.choice(indices, size=indices.size)] 32 | return X_rs 33 | 34 | def _indices_by_block(labels: np.ndarray) -> list[np.ndarray]: 35 | # inverse gives the block‑id of each row in a single pass 36 | _, inverse = np.unique(labels, return_inverse=True) 37 | n_blocks = inverse.max() + 1 38 | return [np.where(inverse == b)[0] for b in range(n_blocks)] 39 | 40 | def _drop_zero_sd_cols( 41 | matrices: Sequence[np.ndarray] 42 | ) -> Tuple[np.ndarray, list[np.ndarray]]: 43 | 44 | # boolean masks of shape (n_features,) for each matrix 45 | non_zero_masks = [(m.std(0) != 0) for m in matrices] 46 | keep_mask = np.logical_and.reduce(non_zero_masks) 47 | keep_idx = np.where(keep_mask)[0] 48 | filtered = [m[:, keep_idx] for m in matrices] 49 | return keep_idx, filtered 50 | 51 | def _bootstrap_network( 52 | X_ctrl: np.ndarray, 53 | X_pert_list: Optional[list[np.ndarray]] = None, 54 | *, 55 | indices_by_blocks_ctrl: Optional[np.ndarray] = None, 56 | indices_by_blocks_pert: Optional[list[np.ndarray]] = None, 57 | rng: np.random.Generator, 58 | learner: Callable[..., Tuple[np.ndarray, ...]], 59 | ) -> tuple[np.ndarray, np.ndarray] | tuple[np.ndarray, np.ndarray, list[Set[int]]]: 60 | """ 61 | # Bootstrap samples from data matrix X, either by individual cells or by blocks (for spatial data). 62 | # Also returns which features have zero standard deviation and should be dropped for instance. 63 | """ 64 | X_ctrl_rs = _resample_indices(X_ctrl, rng, indices_by_blocks=indices_by_blocks_ctrl) 65 | 66 | if X_pert_list is not None: 67 | X_pert_rs_list = [ 68 | _resample_indices(X_pert, rng, indices_by_blocks=indices_by_blocks_pert) 69 | for X_pert, indices_by_blocks in zip(X_pert_list, indices_by_blocks_pert or [None]*len(X_pert_list)) 70 | ] 71 | keep_idx, mats = _drop_zero_sd_cols([X_ctrl_rs, *X_pert_rs_list]) 72 | X_ctrl_rs, *X_pert_rs_list = mats 73 | 74 | A, pert_targets = learner(X_ctrl_rs, X_pert_rs_list) 75 | return keep_idx, A, pert_targets 76 | else: 77 | # GSP / no perturbed conditions 78 | keep_idx, (X_ctrl_rs,) = _drop_zero_sd_cols([X_ctrl_rs]) 79 | A = learner(X_ctrl_rs) 80 | return keep_idx, A 81 | 82 | def _learn_gsp(X: np.ndarray, 83 | alpha: float = 1e-3 84 | ) -> np.ndarray: 85 | suff = partial_correlation_suffstat(X, invert=True) 86 | ci = MemoizedCI_Tester(partial_correlation_test, suff, alpha) 87 | return gsp(set(range(X.shape[1])), ci, nruns=20).cpdag().to_amat()[0] 88 | 89 | def _learn_utigsp(X_ctrl: np.ndarray, 90 | X_pert_list: list[np.ndarray], 91 | alpha: float = 1e-3, 92 | alpha_inv: float = 1e-3) -> Tuple[np.ndarray, list[Set[int]]]: 93 | """ 94 | Learner function for the UT-IGSP algorithm. 95 | """ 96 | suff_ctrl = partial_correlation_suffstat(X_ctrl, invert=True) 97 | ci = MemoizedCI_Tester(partial_correlation_test, suff_ctrl, alpha=alpha) 98 | 99 | # Invariance testing 100 | suff_inv = gauss_invariance_suffstat(X_ctrl, X_pert_list) 101 | invariance_tester = MemoizedInvarianceTester(gauss_invariance_test, suff_inv, alpha=alpha_inv) 102 | 103 | # Assume unknown interventions for UT-IGSP 104 | setting_list = [dict(known_interventions=[]) for _ in X_pert_list] 105 | 106 | # Run the UT-IGSP algorithm 107 | est_dag, est_targets_list = unknown_target_igsp(setting_list, 108 | set(range(X_ctrl.shape[1])), 109 | ci, 110 | invariance_tester, 111 | nruns=20) 112 | 113 | est_icpdag = est_dag.interventional_cpdag(est_targets_list, cpdag=est_dag.cpdag()) 114 | 115 | return est_icpdag.to_amat()[0], est_targets_list 116 | 117 | def run_gsp(samples: np.ndarray, 118 | use_spatial: bool = False, 119 | indices_by_blocks: Optional[list[np.ndarray]] = None, 120 | alpha: float = 1e-3, 121 | seed: int = 0) -> dict: 122 | 123 | if use_spatial and indices_by_blocks is None: 124 | raise ValueError("Block labels must be provided for spatial data.") 125 | 126 | # Reseed the random number generator 127 | rng = np.random.default_rng(seed) 128 | 129 | keep, A = _bootstrap_network(X_ctrl=samples, 130 | rng=rng, 131 | learner=lambda X: _learn_gsp(X, alpha), 132 | indices_by_blocks_ctrl=indices_by_blocks if use_spatial else None) 133 | return {"flow_var_indices": keep, "adjacency_cpdag": A} 134 | 135 | def run_utigsp(samples_ctrl: np.ndarray, 136 | samples_pert_list: list[np.ndarray], 137 | use_spatial: bool = False, 138 | indices_by_blocks_ctrl: Optional[list[np.ndarray]] = None, 139 | indices_by_blocks_pert: Optional[list[np.ndarray]] = None, 140 | alpha: float=1e-3, 141 | alpha_inv: float = 1e-3, 142 | seed: int = 0) -> dict: 143 | 144 | if use_spatial and (indices_by_blocks_ctrl is None or indices_by_blocks_pert is None): 145 | raise ValueError("Block labels must be provided for spatial data (separate for control and perturbed).") 146 | 147 | # Reseed the random number generator 148 | rng = np.random.default_rng(seed) 149 | 150 | keep, A, pert_targets = _bootstrap_network(X_ctrl=samples_ctrl, 151 | X_pert_list=samples_pert_list, 152 | rng=rng, 153 | learner=lambda Xc, Xp: _learn_utigsp(Xc, Xp, alpha, alpha_inv), 154 | indices_by_blocks_ctrl=indices_by_blocks_ctrl if use_spatial else None, 155 | indices_by_blocks_pert=indices_by_blocks_pert if use_spatial else None) 156 | 157 | return {'flow_var_indices':keep, 'adjacency_cpdag':A,'perturbed_targets_indices':pert_targets} 158 | 159 | # Class to help with bootstrapping 160 | @dataclass(frozen=True, slots=True) 161 | class BootstrapPlan: 162 | job_id: int 163 | seed: int 164 | ctrl_X: np.ndarray 165 | pert_Xs: Optional[list[np.ndarray]] 166 | indices_by_blocks_ctrl: Optional[list[np.ndarray]] 167 | indices_by_blocks_pert: Optional[list[np.ndarray]] 168 | learner: Callable[..., tuple[np.ndarray, ...]] 169 | alpha_ci: float 170 | alpha_inv: float 171 | 172 | def run(self) -> dict: 173 | rng = np.random.default_rng(self.seed) 174 | if self.pert_Xs is None: 175 | keep, A = _bootstrap_network( 176 | self.ctrl_X, 177 | rng=rng, 178 | indices_by_blocks_ctrl=self.indices_by_blocks_ctrl, 179 | learner=lambda X: self.learner(X, self.alpha_ci), 180 | ) 181 | return {"keep": keep, "A": A} 182 | else: 183 | keep, A, pert = _bootstrap_network( 184 | self.ctrl_X, 185 | self.pert_Xs, 186 | rng=rng, 187 | indices_by_blocks_ctrl=self.indices_by_blocks_ctrl, 188 | indices_by_blocks_pert=self.indices_by_blocks_pert, 189 | learner=lambda Xc, Xp: self.learner( 190 | Xc, Xp, self.alpha_ci, self.alpha_inv 191 | ), 192 | ) 193 | return {"keep": keep, "A": A, "targets": pert} 194 | 195 | def learn_intercellular_flows(adata: AnnData, 196 | condition_key: str | None = None, 197 | control: str | None = None, 198 | use_spatial: bool = False, 199 | block_key: str | None = None, 200 | n_jobs: int = 1, 201 | n_bootstraps: int = 100, 202 | alpha_ci: float = 1e-3, 203 | alpha_inv: float = 1e-3, 204 | config: FlowSigConfig = FlowSigConfig(), 205 | ) -> None: 206 | """ 207 | Learn the causal signaling network from cell-type-ligand expression constructed 208 | from scRNA-seq and a base network derived from cell-cell communication inference. 209 | 210 | This method splits the cell-type-ligand expression into control and perturbed 211 | samples (one sample for each perturbed condition). We then use UT-IGSP [Squires2020] 212 | and partial correlation testing to learn the causal signaling DAG and the list of 213 | perturbed (intervention) targets. 214 | 215 | The base network is also used as a list of initial node permutations for DAG learning. 216 | To overcome the DAG assumption, as cell-cell communication networks are not necessarily 217 | DAGS, we use bootstrap aggregation to cover as many possible causal edges and the list 218 | of node permutations is constructed from all possible DAG subsets of the base network. 219 | Each boostrap sample is generated by sampling with replacement. 220 | 221 | Parameters 222 | ---------- 223 | adata 224 | The annotated dataframe (typically from Scanpy) of the single-cell data. 225 | Must contain constructed flow expression matrices and knowledge of 226 | possible cellular flow variables. 227 | 228 | condition_key 229 | The label in adata.obs which we use to partition the data. 230 | 231 | control 232 | The category in adata.obs[condition_key] that specifies which cells belong 233 | to the control condition, which is known in causal inference as the observational 234 | data. 235 | 236 | flowsig_network_key 237 | The label for which output will be stored in adata.uns 238 | 239 | flow_expr_key 240 | The label for which the augmente dflow expression expression is stored in adata.obsm 241 | 242 | use_spatial 243 | Boolean for whether or not we are analysing spatial data, and thus need to use 244 | block bootstrapping rather than normal bootstrapping, where we resample across all 245 | cells. 246 | 247 | block_key 248 | The label that specfies from which observation key we use to construct (hopefully) 249 | spatially correlated blocks used for block bootstrapping to learn spatially resolved 250 | cellular flows. These blocks can be simply just dividing the tissue into rougly 251 | equally spaced tissue regions, or can be based on tissue annotation (e.g. organ, cell type). 252 | 253 | n_jobs 254 | Number of CPU cores that are used during bootstrap aggregation. If n_jobs > 1, jobs 255 | are submitted in parallel using multiprocessing 256 | 257 | n_boostraps 258 | Number of bootstrap samples to generate for causal DAG learning. 259 | 260 | alpha_ci 261 | The significance level used to test for conditional independence 262 | 263 | alpha_inv 264 | The significance level used to test for conditional invariance. 265 | 266 | Returns 267 | ------- 268 | flow_vars 269 | The list of cell-type-ligand pairs used during causal structure learning, 270 | stored in adata.uns[flowsig_network_key]['flow_vars']. 271 | 272 | adjacency 273 | The weighted adjacency matrix encoding a bagged CPDAG, 274 | where weights are determined from bootstrap aggregation. Stored in 275 | adata.uns[flowsig_network_key]['adjacency'] 276 | 277 | perturbed_targets 278 | The list of inferred perturbed targets, as determined by conditional invariance 279 | testing and their bootstrapped probability of perturbations. Stored in 280 | adata.uns[flowsig_network_key]['perturbed_targets'] 281 | 282 | References 283 | ---------- 284 | 285 | .. [Squires2020] Squires, C., Wang, Y., & Uhler, C. (2020, August). Permutation-based 286 | causal structure learning with unknown intervention targets. In Conference on 287 | Uncertainty in Artificial Intelligence (pp. 1039-1048). PMLR. 288 | 289 | """ 290 | 291 | # Extract the control and perturbed samples 292 | flow_expr_key = config.flowsig_expr_key 293 | flowsig_network_key = config.flowsig_network_key 294 | 295 | if flow_expr_key not in adata.obsm.keys(): 296 | raise ValueError(f'flow expression key {flow_expr_key} not found in adata.obsm') 297 | if flowsig_network_key not in adata.uns.keys(): 298 | raise ValueError(f'flow signature key {flowsig_network_key} not found in adata.uns') 299 | if use_spatial and block_key is None: 300 | raise ValueError("Block key must be provided for spatial data.") 301 | if block_key and block_key not in adata.obs.keys(): 302 | raise ValueError(f'block key {block_key} not found in adata.obs') 303 | 304 | # Get the flow expression matrices 305 | X_all = adata.obsm[flow_expr_key] 306 | blocks_all = adata.obs[block_key].values if (use_spatial and block_key) else None 307 | flow_vars = adata.uns[flowsig_network_key]["flow_var_info"].index.to_list() 308 | 309 | # Use GSP if no perturbation specified 310 | if condition_key is None: 311 | ctrl_X, pert_Xs = X_all, None 312 | ctrl_blocks, pert_blocks = blocks_all, None 313 | learner = _learn_gsp 314 | # Use UT-IGSP for ctrl vs. perturbed 315 | else: 316 | if control is None: 317 | raise ValueError("control must be specified when condition_key is given") 318 | 319 | mask_ctrl = adata.obs[condition_key] == control 320 | ctrl_X = X_all[mask_ctrl] 321 | ctrl_blocks= blocks_all[mask_ctrl] if blocks_all is not None else None 322 | 323 | pert_keys = [k for k in adata.obs[condition_key].unique() if k != control] 324 | pert_Xs = [X_all[adata.obs[condition_key] == k] for k in pert_keys] 325 | pert_blocks = ( 326 | [blocks_all[adata.obs[condition_key] == k] for k in pert_keys] 327 | if blocks_all is not None else None 328 | ) 329 | learner = _learn_utigsp 330 | 331 | indices_by_blocks_ctrl = _indices_by_block(ctrl_blocks) if use_spatial else None 332 | indices_by_blocks_pert = [_indices_by_block(b) for b in pert_blocks] if (use_spatial and pert_blocks) else None 333 | 334 | root_rng = np.random.default_rng(0) 335 | seeds = root_rng.integers(0, 2**32 - 1, size=n_bootstraps) 336 | 337 | plans = [ 338 | BootstrapPlan( 339 | job_id=i, 340 | seed=int(seeds[i]), 341 | ctrl_X=ctrl_X, 342 | pert_Xs=pert_Xs, 343 | indices_by_blocks_ctrl=indices_by_blocks_ctrl, 344 | indices_by_blocks_pert=indices_by_blocks_pert, 345 | learner=learner, 346 | alpha_ci=alpha_ci, 347 | alpha_inv=alpha_inv, 348 | ) 349 | for i in range(n_bootstraps) 350 | ] 351 | 352 | print(f"Starting {n_bootstraps} bootstraps on {n_jobs} cores …") 353 | t0 = time.perf_counter() 354 | with Parallel(n_jobs=n_jobs, prefer="processes") as pool, tqdm(total=n_bootstraps) as bar: 355 | results = [] 356 | for res in pool(delayed(BootstrapPlan.run)(p) for p in plans): 357 | results.append(res) 358 | bar.update() 359 | elapsed = time.perf_counter() - t0 360 | print(f"Finished in {elapsed:,.1f} s") 361 | 362 | # Bootstrap aggregation 363 | bagged_A = np.zeros((len(flow_vars), len(flow_vars)), dtype=float) 364 | if pert_Xs is not None: 365 | bagged_targets = defaultdict(lambda: np.zeros(len(flow_vars), dtype=float)) 366 | 367 | for res in results: 368 | keep = res["keep"] 369 | bagged_A[np.ix_(keep, keep)] += res["A"] 370 | if "targets" in res: 371 | for i, tset in enumerate(res["targets"]): 372 | bagged_targets[i][list(tset)] += 1 373 | 374 | bagged_A /= n_bootstraps 375 | if pert_Xs is not None: 376 | bagged_targets = {k: v / n_bootstraps for k, v in bagged_targets.items()} 377 | 378 | # Store output 379 | adata.uns[flowsig_network_key]["network"] = ( 380 | { 381 | "flow_vars": flow_vars, 382 | "adjacency": bagged_A, 383 | "perturbed_targets": list(bagged_targets.values()), 384 | } 385 | if pert_Xs is not None 386 | else {"flow_vars": flow_vars, "adjacency": bagged_A} 387 | ) -------------------------------------------------------------------------------- /flowsig/tools/_spatial.py: -------------------------------------------------------------------------------- 1 | 2 | from ..utilities import utils 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /flowsig/tools/_validate_network.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Callable, Tuple 2 | from anndata import AnnData 3 | import networkx as nx 4 | import numpy as np 5 | import pandas as pd 6 | import graphical_models as gpm 7 | 8 | def _total_edge_weights( 9 | adjacency: np.ndarray 10 | ) -> dict[Tuple[int, int], float]: 11 | nz_rows, nz_cols = adjacency.nonzero() 12 | edge_weights: dict[Tuple[int, int], float] = {} 13 | for i, j in zip(nz_rows, nz_cols): 14 | a, b = (i, j) if i < j else (j, i) 15 | edge_weights[(a, b)] = edge_weights.get((a, b), 0.0) + adjacency[i, j] 16 | return edge_weights 17 | 18 | def _filter_edges_by_weight( 19 | cpdag: gpm.PDAG, 20 | adjacency: np.ndarray, 21 | keep_if: Callable[[int, int], bool], 22 | thr: float, 23 | ) -> np.ndarray: 24 | adjacency_new = np.zeros_like(adjacency) 25 | for arc in cpdag.arcs: 26 | i, j = arc 27 | edge_weight = adjacency[i, j] 28 | if keep_if(i, j) and edge_weight >= thr: 29 | adjacency_new[i, j] = edge_weight 30 | for i, j in cpdag.edges: 31 | edge_weight = adjacency[i, j] + adjacency[j, i] 32 | if keep_if(i, j) and edge_weight >= thr: 33 | adjacency_new[i, j] = adjacency[i, j] 34 | adjacency_new[j, i] = adjacency[j, i] 35 | return adjacency_new 36 | 37 | def filter_low_confidence_edges( 38 | adata: AnnData, 39 | edge_threshold: float, 40 | *, 41 | flowsig_network_key: str = "flowsig_network", 42 | adjacency_key: str = "adjacency", 43 | filtered_key: str = "filtered", 44 | ) -> None: 45 | """Keep only bootstrap‑stable edges (no biology applied).""" 46 | 47 | net = adata.uns[flowsig_network_key]["network"] 48 | adjacency = net[adjacency_key] 49 | cpdag = gpm.PDAG.from_amat(adjacency) 50 | 51 | # simply weight ≥ threshold, no type constraints 52 | keep = lambda *_: True 53 | adjacency_filtered = _filter_edges_by_weight(cpdag, adjacency, keep, edge_threshold) 54 | 55 | net[f"{adjacency_key}_{filtered_key}"] = adjacency_filtered 56 | adata.uns[flowsig_network_key]["network"] = net 57 | 58 | def apply_biological_flow( 59 | adata: AnnData, 60 | *, 61 | flowsig_network_key: str = "flowsig_network", 62 | adjacency_key: str = "adjacency", 63 | validated_key: str = "validated", 64 | ) -> None: 65 | """ 66 | Enforce inflow → module → (outflow | module)x ordering on a CPDAG. 67 | Undirected module–module edges are kept bidirectionally. 68 | """ 69 | flowsig_network = adata.uns[flowsig_network_key]["network"] 70 | flow_var_info: pd.DataFrame = adata.uns[flowsig_network_key]["flow_var_info"] 71 | flow_var_types = flow_var_info["Type"].to_numpy() 72 | adjacency = flowsig_network[adjacency_key] 73 | cpdag = gpm.PDAG.from_amat(adjacency) 74 | 75 | def biological(i: int, j: int) -> bool: 76 | type_i, type_j = flow_var_types[i], flow_var_types[j] 77 | return ( 78 | (type_i == "inflow" and type_j == "module") 79 | or (type_i == "module" and type_j in {"outflow", "module"}) 80 | ) 81 | 82 | validated = _filter_edges_by_weight(cpdag, adjacency, biological, 0.0) 83 | flowsig_network[f"{adjacency_key}_{validated_key}"] = validated 84 | 85 | def construct_intercellular_flow_network( 86 | adata: AnnData, 87 | *, 88 | flowsig_network_key: str = "flowsig_network", 89 | adjacency_key: str = "adjacency", 90 | ) -> nx.DiGraph: 91 | """ 92 | Convert a (possibly filtered / validated) CPDAG adjacency into a weighted 93 | directed NetworkX graph, orienting undirected edges by the same biological 94 | rules as `apply_biological_flow`. 95 | """ 96 | net = adata.uns[flowsig_network_key]["network"] 97 | flow_var_info: pd.DataFrame = adata.uns[flowsig_network_key]["flow_var_info"] 98 | types = flow_var_info["Type"].to_numpy() 99 | names = flow_var_info.index.to_numpy() 100 | adjacency = net[adjacency_key] 101 | totals = _total_edge_weights(adjacency) 102 | cpdag = gpm.PDAG.from_amat(adjacency) 103 | 104 | flowsig_network = nx.DiGraph() 105 | 106 | def add_edge(i: int, j: int, w: float) -> None: 107 | flowsig_network.add_edge(names[i], names[j], weight=w) 108 | flowsig_network.nodes[names[i]]["type"] = types[i] 109 | flowsig_network.nodes[names[j]]["type"] = types[j] 110 | 111 | # Add directed edges 112 | for i, j in cpdag.arcs: 113 | if types[i] == "inflow" and types[j] == "module": 114 | add_edge(i, j, adjacency[i, j] / totals[(min(i, j), max(i, j))]) 115 | elif types[i] == "module" and types[j] in {"outflow", "module"}: 116 | add_edge(i, j, adjacency[i, j] / totals[(min(i, j), max(i, j))]) 117 | 118 | # Add undirected edges 119 | for i, j in cpdag.edges: 120 | pair = (min(i, j), max(i, j)) 121 | edge_weight = min(totals[pair], 1.0) 122 | if types[i] == "inflow" and types[j] == "module": 123 | add_edge(i, j, edge_weight) 124 | elif types[i] == "module" and types[j] == "inflow": 125 | add_edge(j, i, edge_weight) 126 | elif types[i] == "module" and types[j] == "outflow": 127 | add_edge(i, j, edge_weight) 128 | elif types[i] == "outflow" and types[j] == "module": 129 | add_edge(j, i, edge_weight) 130 | elif types[i] == types[j] == "module": 131 | add_edge(i, j, edge_weight) 132 | add_edge(j, i, edge_weight) 133 | 134 | return flowsig_network -------------------------------------------------------------------------------- /flowsig/tutorials/.ipynb_checkpoints/mouse_embryo_stereoseq_example-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6b0d2e44", 6 | "metadata": {}, 7 | "source": [ 8 | "Here, we show how to apply FlowSig to a spatial Stereo-seq dataset of an E9.5 mouse embryo, as originally studied in [Chen et al. (2022)](https://doi.org/10.1016/j.cell.2022.04.003).\n", 9 | "The processed data and cell-cell communication inference, which was obtained using [COMMOT](https://commot.readthedocs.io/en/latest/tutorials.html),\n", 10 | "can be downloaded from the following Zenodo [repository](https://zenodo.org/doi/10.5281/zenodo.10850397)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "57d00257", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import scanpy as sc\n", 21 | "import pandas as pd\n", 22 | "import flowsig as fs" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "id": "e29e4888-4699-4dee-9389-40fa79bcb1f1", 28 | "metadata": {}, 29 | "source": [ 30 | "Load in the data, which has been subsetted for spatially variable genes, as determined by the global Moran's I. In this case, we only retain genes where $I > 0.1$." 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "id": "4878ee0a-2276-42c0-b257-4ececd610866", 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "adata = sc.read('data/chen22_svg_E9.5.h5ad')\n", 41 | "condition_key = 'Condition'" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "b17ef157-d367-407c-82b1-8b11fbe41b05", 47 | "metadata": {}, 48 | "source": [ 49 | "We construct 20 gene expression modules from the unnormalized spot counts using [NSF](https://www.nature.com/articles/s41592-022-01687-w)." 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "efe18728-2de6-4358-8da6-5af05888f27c", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "fs.construct_gems_using_nsf(adata,\n", 60 | " n_gems = 20,\n", 61 | " layer_key = 'count',\n", 62 | " length_scale = 5.0)\n", 63 | "\n", 64 | "commot_output_key = 'commot-cellchat'" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "id": "57ce6e0c-bf9c-4e8f-bd57-105f57c46eb3", 70 | "metadata": {}, 71 | "source": [ 72 | "We first construct the potential cellular flows from the COMMOT output, which has been run previously." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "6ab366eb-abc1-47c3-8e36-09873caca7b2", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "fs.construct_flows_from_commot(adata,\n", 83 | " commot_output_key,\n", 84 | " gem_expr_key = 'X_gem',\n", 85 | " scale_gem_expr = True,\n", 86 | " flowsig_network_key = 'flowsig_network',\n", 87 | " flowsig_expr_key = 'X_flow')" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "id": "c0e0cf5f-2b7c-4bec-b363-9f84ed90792c", 93 | "metadata": {}, 94 | "source": [ 95 | "Then we subset for \"spatially flowing\" variables" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": null, 101 | "id": "7fc81d1d-b608-4a51-b7c0-a4eafe7ea214", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "fs.determine_informative_variables(adata, \n", 106 | " flowsig_expr_key = 'X_flow',\n", 107 | " flowsig_network_key = 'flowsig_network',\n", 108 | " spatial = True,\n", 109 | " moran_threshold = 0.15,\n", 110 | " coord_type = 'grid',\n", 111 | " n_neighbours = 8)" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "id": "c7a5434e-a0c5-414a-ab01-38a3bc94b40f", 117 | "metadata": {}, 118 | "source": [ 119 | "For spatial data, we need to construct spatial blocks that are used for block bootstrapping, to preserve the spatial correlation of the gene expression data. The idea is that by sampling within these spatial blocks, we will better preserve these spatial correlation structures during bootstrapping." 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "id": "51d34928-1a06-451e-b075-3356a810aa86", 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "fs.construct_spatial_blocks(adata,\n", 130 | " n_blocks=20,\n", 131 | " use_graph=False,\n", 132 | " spatial_block_key: str = \"spatial_block\",\n", 133 | " spatial_key: str = \"spatial\")" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "id": "d2e780f2-b905-4d21-9b94-d4f749be326a", 139 | "metadata": {}, 140 | "source": [ 141 | "Now we are ready to learn the network\n" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "id": "95d53925-0d75-4634-b2f6-34ca6328cca2", 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "fs.learn_intercellular_flows(adata,\n", 152 | " flowsig_key = 'flowsig_network',\n", 153 | " flow_expr_key = 'X_flow',\n", 154 | " use_spatial = True,\n", 155 | " block_key = 'spatial_block',\n", 156 | " n_jobs = 1,\n", 157 | " n_bootstraps = 10)" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "id": "6f7cb462-f6b5-4a5c-a51d-24e7af77bbfd", 163 | "metadata": {}, 164 | "source": [ 165 | "Now we do post-learning validation to reorient undirected edges from the learnt CPDAG so that they flow from inflow to GEM to outflow. After that, we remove low-confidence edges." 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "id": "b8136488-4819-4009-afac-c2d1a1a19636", 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "# This part is key for reducing false positives\n", 176 | "fs.apply_biological_flow(adata,\n", 177 | " flowsig_network_key = 'flowsig_network',\n", 178 | " adjacency_key = 'adjacency',\n", 179 | " validated_adjacency_key = 'adjacency_validated')\n", 180 | "\n", 181 | "edge_threshold = 0.7\n", 182 | "\n", 183 | "fs.filter_low_confidence_edges(adata,\n", 184 | " edge_threshold = edge_threshold,\n", 185 | " flowsig_network_key = 'flowsig_network',\n", 186 | " adjacency_key = 'adjacency',\n", 187 | " filtered_adjacency_key = 'adjacency_filtered')\n", 188 | "\n", 189 | "adata.write('data/chen22_svg_E9.5.h5ad', compression='gzip')" 190 | ] 191 | } 192 | ], 193 | "metadata": { 194 | "kernelspec": { 195 | "display_name": "Python 3 (ipykernel)", 196 | "language": "python", 197 | "name": "python3" 198 | }, 199 | "language_info": { 200 | "codemirror_mode": { 201 | "name": "ipython", 202 | "version": 3 203 | }, 204 | "file_extension": ".py", 205 | "mimetype": "text/x-python", 206 | "name": "python", 207 | "nbconvert_exporter": "python", 208 | "pygments_lexer": "ipython3", 209 | "version": "3.8.16" 210 | } 211 | }, 212 | "nbformat": 4, 213 | "nbformat_minor": 5 214 | } 215 | -------------------------------------------------------------------------------- /flowsig/tutorials/.ipynb_checkpoints/mouse_embryo_stereoseq_example_script-checkpoint.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | import pandas as pd 3 | import flowsig as fs 4 | 5 | adata = sc.read('chen22_svg_E9.5.h5ad') 6 | condition_key = 'Condition' 7 | 8 | # We construct 10 gene expression modules using the raw cell count. 9 | fs.construct_gems_using_pyliger(adata, 10 | n_gems = 10, 11 | layer_key = 'count') 12 | 13 | commot_output_key = 'commot-cellchat' 14 | 15 | # We first construct the potential cellular flows from the commot output 16 | fs.construct_flows_from_commot(adata, 17 | commot_output_key, 18 | gem_expr_key = 'X_gem', 19 | scale_gem_expr = True, 20 | flowsig_network_key = 'flowsig_network', 21 | flowsig_expr_key = 'X_flow') 22 | 23 | # Then we subset for "spatially flowing" variables 24 | # (How do we turn the squidpy arguments into a kwargs) 25 | fs.determine_informative_variables(adata, 26 | flowsig_expr_key = 'X_flow', 27 | flowsig_network_key = 'flowsig_network', 28 | spatial = True, 29 | moran_threshold = 0.15, 30 | coord_type = 'grid', 31 | n_neighbours = 8, 32 | library_key = None) 33 | 34 | # For spatial data, we need to construct spatial blocks that are used 35 | # for block bootstrapping, to preserve the spatial correlation of the 36 | # gene expression data. 37 | 38 | 39 | # Now we are ready to learn the network 40 | fs.learn_intercellular_flows(adata, 41 | flowsig_key = 'flowsig_network', 42 | flow_expr_key = 'X_flow', 43 | use_spatial = True, 44 | block_key = 'spatial_block', 45 | n_jobs = 1, 46 | n_bootstraps = 10) 47 | 48 | # Now we do post-learning validation to reorient the network and remove low-quality edges. 49 | # This part is key for reducing false positives 50 | fs.apply_biological_flow(adata, 51 | flowsig_network_key = 'flowsig_network', 52 | adjacency_key = 'adjacency', 53 | validated_adjacency_key = 'adjacency_validated') 54 | 55 | edge_threshold = 0.7 56 | 57 | fs.filter_low_confidence_edges(adata, 58 | edge_threshold = edge_threshold, 59 | flowsig_network_key = 'flowsig_network', 60 | adjacency_key = 'adjacency', 61 | filtered_adjacency_key = 'adjacency_filtered') 62 | 63 | adata.write('data/chen22_svg_E9.5.h5ad', compression='gzip') -------------------------------------------------------------------------------- /flowsig/tutorials/.ipynb_checkpoints/pancreatic_islets_example_script-checkpoint.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | import pandas as pd 3 | import flowsig as fs 4 | 5 | adata = sc.read('burkhardt21_merged.h5ad') 6 | condition_key = 'Condition' 7 | 8 | # We construct 10 gene expression modules using the raw cell count. 9 | fs.construct_gems_using_pyliger(adata, 10 | n_gems = 10, 11 | layer_key = 'counts', 12 | condition_key = condition_key) 13 | 14 | # Make sure your keys for these align with their condition labels 15 | cellchat_Ctrl = pd.read('burkhardt21_leiden_communications_Ctrl.csv') 16 | cellchat_IFNg = pd.read('burkhardt21_leiden_communications_IFNg.csv') 17 | 18 | cellchat_output_key = 'cellchat_output' 19 | adata.uns[cellchat_output_key] = {'Ctrl': cellchat_Ctrl, 20 | 'IFNg': cellchat_IFNg} 21 | 22 | # We first construct the potential cellular flows from the cellchat output 23 | fs.construct_flows_from_cellchat(adata, 24 | cellchat_output_key, 25 | gem_expr_key = 'X_gem', 26 | scale_gem_expr = True, 27 | model_organism = 'human', 28 | flowsig_network_key = 'flowsig_network', 29 | flowsig_expr_key = 'X_flow') 30 | 31 | # Then we subset for "differentially flowing" variables 32 | fs.determine_informative_variables(adata, 33 | flowsig_expr_key = 'X_flow', 34 | flowsig_network_key = 'flowsig_network', 35 | spatial = False, 36 | condition_key = condition_key, 37 | qval_threshold = 0.05, 38 | logfc_threshold = 0.5) 39 | 40 | # Now we are ready to learn the network 41 | fs.learn_intercellular_flows(adata, 42 | condition_key = condition_key, 43 | control_key = 'Ctrl', 44 | flowsig_key = 'flowsig_network', 45 | flow_expr_key = 'X_flow', 46 | use_spatial = False, 47 | n_jobs = 1, 48 | n_bootstraps = 10) 49 | 50 | # Now we do post-learning validation to reorient the network and remove low-quality edges. 51 | # This part is key for reducing false positives 52 | fs.apply_biological_flow(adata, 53 | flowsig_network_key = 'flowsig_network', 54 | adjacency_key = 'adjacency', 55 | validated_adjacency_key = 'adjacency_validated') 56 | 57 | edge_threshold = 0.7 58 | 59 | fs.filter_low_confidence_edges(adata, 60 | edge_threshold = edge_threshold, 61 | flowsig_network_key = 'flowsig_network', 62 | adjacency_key = 'adjacency', 63 | filtered_adjacency_key = 'adjacency_filtered') 64 | 65 | adata.write('data/burkhardt21_merged.h5ad', compression='gzip') -------------------------------------------------------------------------------- /flowsig/tutorials/.ipynb_checkpoints/pancreatic_islets_scrnaseq_example-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f1b8796f-f213-4022-b010-a9aaf8c9d845", 6 | "metadata": {}, 7 | "source": [ 8 | "Here, we show how to apply FlowSig to an scRNA-seq dataset of wildtype\n", 9 | "and stimulated human pancreatic islets, as originally studied in [Burkhardt et al. (2021)](https://www.nature.com/articles/s41587-020-00803-5)." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "id": "b67032be-a6c9-45e3-a6f2-d6f51c8ed92e", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import scanpy as sc\n", 20 | "import pandas as pd\n", 21 | "import flowsig as fs" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "id": "367dffee-3d13-4d0f-bf03-f3d4918ea4c7", 27 | "metadata": {}, 28 | "source": [ 29 | "Load the data and set the observation key that we will use to split the data into control (observational) and perturbed (interventional data). " 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "c2498c9d-3faf-471b-afea-c3114812e485", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "adata = sc.read('data/burkhardt21_merged.h5ad')\n", 40 | "condition_key = 'Condition'" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "id": "98370748-a225-42f9-913e-0ab1424435cd", 46 | "metadata": {}, 47 | "source": [ 48 | "Load the cell-cell communication output from [CellChat](https://www.nature.com/articles/s41467-021-21246-9), which has been run for each condition." 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "id": "f829f49a-574a-46d1-a388-36169ab2fff7", 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "cellchat_Ctrl = pd.read('data/burkhardt21_leiden_communications_Ctrl.csv')\n", 59 | "cellchat_IFNg = pd.read('data/burkhardt21_leiden_communications_IFNg.csv')\n", 60 | "\n", 61 | "cellchat_output_key = 'cellchat_output'\n", 62 | "# Make sure your keys for these align with their condition labels\n", 63 | "adata.uns[cellchat_output_key] = {'Ctrl': cellchat_Ctrl,\n", 64 | " 'IFNg': cellchat_IFNg}" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "id": "54627bb0-2c48-410a-a051-9bc1cd97d36f", 70 | "metadata": {}, 71 | "source": [ 72 | "We construct 10 gene expression modules from the unnormalized gene expression counts using [pyLIGER](https://academic.oup.com/bioinformatics/article/38/10/2946/6561542), which uses iNMF to construct GEMs that account for shared and specific expression across the control and perturbed conditions." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "8bb9ffaa-0508-4a90-88e0-3d5262fa7bf4", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# We construct 10 gene expression modules using the raw cell count.\n", 83 | "fs.construct_gems_using_pyliger(adata,\n", 84 | " n_gems = 10,\n", 85 | " layer_key = 'counts',\n", 86 | " condition_key = condition_key)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "id": "d4204bf7-ae31-4962-b8d1-74b2448c9265", 92 | "metadata": {}, 93 | "source": [ 94 | "We first construct the potential cellular flows from the cellchat output, i.e., separate the inflows from the outflows." 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "id": "bb3381de-19c5-4fc9-be80-1dde06c1fcf7", 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "fs.construct_flows_from_cellchat(adata,\n", 105 | " cellchat_output_key,\n", 106 | " gem_expr_key = 'X_gem',\n", 107 | " scale_gem_expr = True,\n", 108 | " model_organism = 'human',\n", 109 | " flowsig_network_key = 'flowsig_network',\n", 110 | " flowsig_expr_key = 'X_flow')\n" 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "id": "e815fd02-b563-4f51-8e43-99b9ac9798fb", 116 | "metadata": {}, 117 | "source": [ 118 | "Then we subset for \"differentially flowing\" variables, using a Mann-Whitney U test on the inflow and outflow expressions, separately." 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "id": "e3b91ba6-c969-4948-8a1d-90600e6e8a08", 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "fs.determine_informative_variables(adata, \n", 129 | " flowsig_expr_key = 'X_flow',\n", 130 | " flowsig_network_key = 'flowsig_network',\n", 131 | " spatial = False,\n", 132 | " condition_key = condition_key,\n", 133 | " qval_threshold = 0.05,\n", 134 | " logfc_threshold = 0.5)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "a5db5871-72dd-40dc-ad36-6227f86d1521", 140 | "metadata": {}, 141 | "source": [ 142 | "Now we are ready to learn the network" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "id": "b63e2fa5-f5bd-4b04-89dd-e420f71d0308", 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "fs.learn_intercellular_flows(adata,\n", 153 | " condition_key = condition_key,\n", 154 | " control_key = 'Ctrl', \n", 155 | " flowsig_key = 'flowsig_network',\n", 156 | " flow_expr_key = 'X_flow',\n", 157 | " use_spatial = False,\n", 158 | " n_jobs = 1,\n", 159 | " n_bootstraps = 10)" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "id": "41c55d7d-3e68-4276-bc49-8857e6d3b3e9", 165 | "metadata": {}, 166 | "source": [ 167 | "Now we do post-learning validation to reorient the network and remove low-quality edges.\n" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "id": "93f575a0-3945-4ce4-8c6b-7d432fec46da", 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [ 177 | "# This part is key for reducing false positives\n", 178 | "fs.apply_biological_flow(adata,\n", 179 | " flowsig_network_key = 'flowsig_network',\n", 180 | " adjacency_key = 'adjacency',\n", 181 | " validated_adjacency_key = 'adjacency_validated')\n", 182 | "\n", 183 | "edge_threshold = 0.7\n", 184 | "\n", 185 | "fs.filter_low_confidence_edges(adata,\n", 186 | " edge_threshold = edge_threshold,\n", 187 | " flowsig_network_key = 'flowsig_network',\n", 188 | " adjacency_key = 'adjacency',\n", 189 | " filtered_adjacency_key = 'adjacency_filtered')\n", 190 | "\n", 191 | "adata.write('data/burkhardt21_merged.h5ad', compression='gzip')" 192 | ] 193 | } 194 | ], 195 | "metadata": { 196 | "kernelspec": { 197 | "display_name": "Python 3 (ipykernel)", 198 | "language": "python", 199 | "name": "python3" 200 | }, 201 | "language_info": { 202 | "codemirror_mode": { 203 | "name": "ipython", 204 | "version": 3 205 | }, 206 | "file_extension": ".py", 207 | "mimetype": "text/x-python", 208 | "name": "python", 209 | "nbconvert_exporter": "python", 210 | "pygments_lexer": "ipython3", 211 | "version": "3.8.16" 212 | } 213 | }, 214 | "nbformat": 4, 215 | "nbformat_minor": 5 216 | } 217 | -------------------------------------------------------------------------------- /flowsig/tutorials/mouse_embryo_stereoseq_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "6b0d2e44", 6 | "metadata": {}, 7 | "source": [ 8 | "Here, we show how to apply FlowSig to a spatial Stereo-seq dataset of an E9.5 mouse embryo, as originally studied in [Chen et al. (2022)](https://doi.org/10.1016/j.cell.2022.04.003).\n", 9 | "The processed data and cell-cell communication inference, which was obtained using [COMMOT](https://commot.readthedocs.io/en/latest/tutorials.html),\n", 10 | "can be downloaded from the following Zenodo [repository](https://zenodo.org/doi/10.5281/zenodo.10850397)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "57d00257", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import scanpy as sc\n", 21 | "import pandas as pd\n", 22 | "import flowsig as fs" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "id": "e29e4888-4699-4dee-9389-40fa79bcb1f1", 28 | "metadata": {}, 29 | "source": [ 30 | "Load in the data, which has been subsetted for spatially variable genes, as determined by the global Moran's I. In this case, we only retain genes where $I > 0.1$." 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": null, 36 | "id": "4878ee0a-2276-42c0-b257-4ececd610866", 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "adata = sc.read('data/chen22_svg_E9.5.h5ad')" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "id": "b17ef157-d367-407c-82b1-8b11fbe41b05", 46 | "metadata": {}, 47 | "source": [ 48 | "We construct 20 gene expression modules from the unnormalized spot counts using [NSF](https://www.nature.com/articles/s41592-022-01687-w)." 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "id": "efe18728-2de6-4358-8da6-5af05888f27c", 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "fs.pp.construct_gems_using_nsf(adata,\n", 59 | " n_gems = 20,\n", 60 | " layer_key = 'count',\n", 61 | " length_scale = 5.0)\n", 62 | "\n", 63 | "commot_output_key = 'commot-cellchat'" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "id": "57ce6e0c-bf9c-4e8f-bd57-105f57c46eb3", 69 | "metadata": {}, 70 | "source": [ 71 | "We first construct the potential cellular flows from the COMMOT output, which has been run previously." 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": null, 77 | "id": "6ab366eb-abc1-47c3-8e36-09873caca7b2", 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "fs.pp.construct_flow_expressions(adata,\n", 82 | " commot_output_key=commot_output_key,\n", 83 | " spatial=True)" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "id": "c0e0cf5f-2b7c-4bec-b363-9f84ed90792c", 89 | "metadata": {}, 90 | "source": [ 91 | "Then we subset for \"spatially flowing\" variables" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "id": "7fc81d1d-b608-4a51-b7c0-a4eafe7ea214", 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "fs.pp.determine_informative_variables(adata, \n", 102 | " spatial = True,\n", 103 | " moran_threshold = 0.15,\n", 104 | " coord_type = 'grid',\n", 105 | " n_neighbours = 8,\n", 106 | " library_key = None)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "id": "c7a5434e-a0c5-414a-ab01-38a3bc94b40f", 112 | "metadata": {}, 113 | "source": [ 114 | "For spatial data, we need to construct spatial blocks that are used for block bootstrapping, to preserve the spatial correlation of the gene expression data. The idea is that by sampling within these spatial blocks, we will better preserve these spatial correlation structures during bootstrapping. We construct the blocks using simple K-Means clustering over the spatial locations." 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "id": "51d34928-1a06-451e-b075-3356a810aa86", 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "fs.pp.construct_spatial_blocks(adata,\n", 125 | " n_blocks=20,\n", 126 | " use_graph=False,\n", 127 | " spatial_block_key = \"spatial_block\",\n", 128 | " spatial_key = \"spatial\")" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "id": "d2e780f2-b905-4d21-9b94-d4f749be326a", 134 | "metadata": {}, 135 | "source": [ 136 | "Now we are ready to learn the network\n" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "id": "95d53925-0d75-4634-b2f6-34ca6328cca2", 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [ 146 | "fs.tl.learn_intercellular_flows(adata,\n", 147 | " use_spatial = True,\n", 148 | " block_key = 'spatial_block',\n", 149 | " n_jobs = 1,\n", 150 | " n_bootstraps = 10)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "id": "6f7cb462-f6b5-4a5c-a51d-24e7af77bbfd", 156 | "metadata": {}, 157 | "source": [ 158 | "Now we do post-learning validation to reorient undirected edges from the learnt CPDAG so that they flow from inflow to GEM to outflow. After that, we remove low-confidence edges." 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "id": "b8136488-4819-4009-afac-c2d1a1a19636", 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "# This part is key for reducing false positives\n", 169 | "fs.tl.apply_biological_flow(adata,\n", 170 | " flowsig_network_key = 'flowsig_network',\n", 171 | " adjacency_key = 'adjacency',\n", 172 | " validated_key = 'adjacency_validated')\n", 173 | "\n", 174 | "edge_threshold = 0.7\n", 175 | "\n", 176 | "fs.tl.filter_low_confidence_edges(adata,\n", 177 | " edge_threshold = edge_threshold,\n", 178 | " flowsig_network_key = 'flowsig_network',\n", 179 | " adjacency_key = 'adjacency_validated',\n", 180 | " filtered_key = 'filtered')\n", 181 | "\n", 182 | "adata.write('data/chen22_svg_E9.5.h5ad', compression='gzip')" 183 | ] 184 | } 185 | ], 186 | "metadata": { 187 | "kernelspec": { 188 | "display_name": "Python 3 (ipykernel)", 189 | "language": "python", 190 | "name": "python3" 191 | }, 192 | "language_info": { 193 | "codemirror_mode": { 194 | "name": "ipython", 195 | "version": 3 196 | }, 197 | "file_extension": ".py", 198 | "mimetype": "text/x-python", 199 | "name": "python", 200 | "nbconvert_exporter": "python", 201 | "pygments_lexer": "ipython3", 202 | "version": "3.8.16" 203 | } 204 | }, 205 | "nbformat": 4, 206 | "nbformat_minor": 5 207 | } 208 | -------------------------------------------------------------------------------- /flowsig/tutorials/mouse_embryo_stereoseq_example_script.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | import pandas as pd 3 | import flowsig as fs 4 | 5 | adata = sc.read('data/chen22_svg_E9.5.h5ad') 6 | 7 | # We construct 10 gene expression modules using the raw cell count. 8 | fs.pp._flow_preprocessingconstruct_gems_using_nsf(adata, 9 | n_gems = 20, 10 | layer_key = 'count', 11 | length_scale = 5.0) 12 | 13 | commot_output_key = 'commot-cellchat' 14 | 15 | # For spatial data, we need to construct spatial blocks that are used for block bootstrapping, 16 | # to preserve the spatial correlation of the gene expression data. The idea is that by sampling 17 | # within these spatial blocks, we will better preserve these spatial correlation structures 18 | # during bootstrapping. We construct the blocks using simple K-Means clustering over the spatial 19 | # locations. 20 | fs.pp.construct_spatial_blocks(adata, 21 | n_blocks=20, 22 | use_graph=False, 23 | spatial_block_key = "spatial_block", 24 | spatial_key = "spatial") 25 | 26 | # We first construct the potential cellular flows from the commot output 27 | fs.pp.construct_flow_expressions(adata, 28 | commot_output_key=commot_output_key, 29 | spatial=True) 30 | 31 | # Then we subset for "spatially flowing" variables 32 | # (How do we turn the squidpy arguments into a kwargs) 33 | fs.pp.determine_informative_variables(adata, 34 | spatial = True, 35 | moran_threshold = 0.15, 36 | coord_type = 'grid', 37 | n_neighbours = 8, 38 | library_key = None) 39 | 40 | # For spatial data, we need to construct spatial blocks that are used 41 | # for block bootstrapping, to preserve the spatial correlation of the 42 | # gene expression data. 43 | 44 | 45 | # Now we are ready to learn the network 46 | fs.tl.learn_intercellular_flows(adata, 47 | use_spatial = True, 48 | block_key = 'spatial_block', 49 | n_jobs = 1, 50 | n_bootstraps = 10) 51 | 52 | # Now we do post-learning validation to reorient the network and remove low-quality edges. 53 | # This part is key for reducing false positives 54 | fs.tl.apply_biological_flow(adata, 55 | flowsig_network_key = 'flowsig_network', 56 | adjacency_key = 'adjacency', 57 | validated_key = 'adjacency_validated') 58 | 59 | edge_threshold = 0.7 60 | 61 | fs.tl.filter_low_confidence_edges(adata, 62 | edge_threshold = edge_threshold, 63 | flowsig_network_key = 'flowsig_network', 64 | adjacency_key = 'adjacency_validated', 65 | filtered_key = 'filtered') 66 | 67 | adata.write('data/chen22_svg_E9.5.h5ad', compression='gzip') -------------------------------------------------------------------------------- /flowsig/tutorials/pancreatic_islets_example_script.py: -------------------------------------------------------------------------------- 1 | import scanpy as sc 2 | import pandas as pd 3 | import flowsig as fs 4 | 5 | adata = sc.read('data/burkhardt21_merged.h5ad') 6 | condition_key = 'Condition' 7 | 8 | # We construct 10 gene expression modules using the raw cell count. 9 | fs.pp.construct_gems_using_pyliger(adata, 10 | n_gems = 10, 11 | layer_key = 'counts', 12 | condition_key = condition_key) 13 | 14 | # Make sure your keys for these align with their condition labels 15 | cellchat_Ctrl = pd.read('data/burkhardt21_leiden_communications_Ctrl.csv') 16 | cellchat_IFNg = pd.read('data/burkhardt21_leiden_communications_IFNg.csv') 17 | 18 | cellchat_output_key = 'cellchat_output' 19 | adata.uns[cellchat_output_key] = {'Ctrl': cellchat_Ctrl, 20 | 'IFNg': cellchat_IFNg} 21 | 22 | # We first construct the potential cellular flows from the cellchat output 23 | fs.pp.construct_flow_expressions(adata, 24 | cellchat_output_key=cellchat_output_key, 25 | model_organism = 'human', 26 | spatial = False, 27 | method = 'cellchat' 28 | ) 29 | 30 | # Then we subset for "differentially flowing" variables 31 | fs.pp.determine_informative_variables(adata, 32 | spatial = False, 33 | condition_key = 'Condition', 34 | control = 'Ctrl', 35 | qval_threshold = 0.05, 36 | logfc_threshold = 0.5) 37 | 38 | # Now we are ready to learn the network 39 | fs.tl.learn_intercellular_flows(adata, 40 | condition_key = condition_key, 41 | control = 'Ctrl', 42 | use_spatial = False, 43 | n_jobs = 1, 44 | n_bootstraps = 10) 45 | 46 | # Now we do post-learning validation to reorient the network and remove low-quality edges. 47 | # This part is key for reducing false positives 48 | fs.tl.apply_biological_flow(adata, 49 | flowsig_network_key = 'flowsig_network', 50 | adjacency_key = 'adjacency', 51 | validated_key = 'validated') 52 | 53 | edge_threshold = 0.7 54 | 55 | fs.tl.filter_low_confidence_edges(adata, 56 | edge_threshold = edge_threshold, 57 | flowsig_network_key = 'flowsig_network', 58 | adjacency_key = 'adjacency_validated', 59 | filtered_key = 'adjacency_filtered') 60 | 61 | # adata.write('data/burkhardt21_merged.h5ad', compression='gzip') -------------------------------------------------------------------------------- /flowsig/tutorials/pancreatic_islets_scrnaseq_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f1b8796f-f213-4022-b010-a9aaf8c9d845", 6 | "metadata": {}, 7 | "source": [ 8 | "Here, we show how to apply FlowSig to an scRNA-seq dataset of wildtype\n", 9 | "and stimulated human pancreatic islets, as originally studied in [Burkhardt et al. (2021)](https://www.nature.com/articles/s41587-020-00803-5)." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "id": "b67032be-a6c9-45e3-a6f2-d6f51c8ed92e", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import scanpy as sc\n", 20 | "import pandas as pd\n", 21 | "import flowsig as fs" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "id": "367dffee-3d13-4d0f-bf03-f3d4918ea4c7", 27 | "metadata": {}, 28 | "source": [ 29 | "Load the data and set the observation key that we will use to split the data into control (observational) and perturbed (interventional data). " 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "c2498c9d-3faf-471b-afea-c3114812e485", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "adata = sc.read('data/burkhardt21_merged.h5ad')\n", 40 | "condition_key = 'Condition'" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "id": "98370748-a225-42f9-913e-0ab1424435cd", 46 | "metadata": {}, 47 | "source": [ 48 | "Load the cell-cell communication output from [CellChat](https://www.nature.com/articles/s41467-021-21246-9), which has been run for each condition." 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "id": "f829f49a-574a-46d1-a388-36169ab2fff7", 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "cellchat_Ctrl = pd.read('data/burkhardt21_leiden_communications_Ctrl.csv')\n", 59 | "cellchat_IFNg = pd.read('data/burkhardt21_leiden_communications_IFNg.csv')\n", 60 | "\n", 61 | "cellchat_output_key = 'cellchat_output'\n", 62 | "# Make sure your keys for these align with their condition labels\n", 63 | "adata.uns[cellchat_output_key] = {'Ctrl': cellchat_Ctrl,\n", 64 | " 'IFNg': cellchat_IFNg}" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "id": "54627bb0-2c48-410a-a051-9bc1cd97d36f", 70 | "metadata": {}, 71 | "source": [ 72 | "We construct 10 gene expression modules from the unnormalized gene expression counts using [pyLIGER](https://academic.oup.com/bioinformatics/article/38/10/2946/6561542), which uses iNMF to construct GEMs that account for shared and specific expression across the control and perturbed conditions." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "8bb9ffaa-0508-4a90-88e0-3d5262fa7bf4", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "# We construct 10 gene expression modules using the raw cell count.\n", 83 | "fs.pp.construct_gems_using_pyliger(adata,\n", 84 | " n_gems = 10,\n", 85 | " layer_key = 'counts',\n", 86 | " condition_key = condition_key)" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "id": "d4204bf7-ae31-4962-b8d1-74b2448c9265", 92 | "metadata": {}, 93 | "source": [ 94 | "We first construct the potential cellular flows from the cellchat output, i.e., separate the inflows from the outflows." 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "id": "bb3381de-19c5-4fc9-be80-1dde06c1fcf7", 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "fs.pp.construct_flow_expressions(adata,\n", 105 | " cellchat_output_key=cellchat_output_key,\n", 106 | " model_organism = 'human',\n", 107 | " spatial = False,\n", 108 | " method = 'cellchat'\n", 109 | " )" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "id": "e815fd02-b563-4f51-8e43-99b9ac9798fb", 115 | "metadata": {}, 116 | "source": [ 117 | "Then we subset for \"differentially flowing\" variables, using a Mann-Whitney U test on the inflow and outflow expressions, separately." 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "id": "e3b91ba6-c969-4948-8a1d-90600e6e8a08", 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "fs.pp.determine_informative_variables(adata, \n", 128 | " spatial = False,\n", 129 | " condition_key = 'Condition',\n", 130 | " control = 'Ctrl',\n", 131 | " qval_threshold = 0.05,\n", 132 | " logfc_threshold = 0.5)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "id": "a5db5871-72dd-40dc-ad36-6227f86d1521", 138 | "metadata": {}, 139 | "source": [ 140 | "Now we are ready to learn the network" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "id": "b63e2fa5-f5bd-4b04-89dd-e420f71d0308", 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "fs.tl.learn_intercellular_flows(adata,\n", 151 | " condition_key = condition_key,\n", 152 | " control = 'Ctrl', \n", 153 | " use_spatial = False,\n", 154 | " n_jobs = 1,\n", 155 | " n_bootstraps = 10)" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "id": "41c55d7d-3e68-4276-bc49-8857e6d3b3e9", 161 | "metadata": {}, 162 | "source": [ 163 | "Now we do post-learning validation to reorient the network and remove low-quality edges.\n" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "id": "93f575a0-3945-4ce4-8c6b-7d432fec46da", 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "# This part is key for reducing false positives\n", 174 | "fs.tl.apply_biological_flow(adata,\n", 175 | " flowsig_network_key = 'flowsig_network',\n", 176 | " adjacency_key = 'adjacency',\n", 177 | " validated_key = 'adjacency_validated')\n", 178 | "\n", 179 | "edge_threshold = 0.7\n", 180 | "\n", 181 | "fs.tl.filter_low_confidence_edges(adata,\n", 182 | " edge_threshold = edge_threshold,\n", 183 | " flowsig_network_key = 'flowsig_network',\n", 184 | " adjacency_key = 'adjacency',\n", 185 | " filtered_key = 'adjacency_filtered')\n", 186 | "\n", 187 | "adata.write('data/burkhardt21_merged.h5ad', compression='gzip')" 188 | ] 189 | } 190 | ], 191 | "metadata": { 192 | "kernelspec": { 193 | "display_name": "Python 3 (ipykernel)", 194 | "language": "python", 195 | "name": "python3" 196 | }, 197 | "language_info": { 198 | "codemirror_mode": { 199 | "name": "ipython", 200 | "version": 3 201 | }, 202 | "file_extension": ".py", 203 | "mimetype": "text/x-python", 204 | "name": "python", 205 | "nbconvert_exporter": "python", 206 | "pygments_lexer": "ipython3", 207 | "version": "3.8.16" 208 | } 209 | }, 210 | "nbformat": 4, 211 | "nbformat_minor": 5 212 | } 213 | -------------------------------------------------------------------------------- /flowsig/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | from ._utils import get_top_gem_genes -------------------------------------------------------------------------------- /flowsig/utilities/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/utilities/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/utilities/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/utilities/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/utilities/__pycache__/_utils.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/utilities/__pycache__/_utils.cpython-310.pyc -------------------------------------------------------------------------------- /flowsig/utilities/__pycache__/_utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axelalmet/flowsig/5fadf4435b57cf609950998dfa787646c8d785dc/flowsig/utilities/__pycache__/_utils.cpython-38.pyc -------------------------------------------------------------------------------- /flowsig/utilities/_utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from anndata import AnnData 4 | from typing import Union, Sequence 5 | 6 | def get_top_nmf_genes(adata: AnnData, 7 | gems: Union[str, Sequence[str]], 8 | n_genes: int, 9 | gene_type: str = 'all', 10 | model_organism: str = 'human'): 11 | 12 | gene_types = ['all', 'tf'] 13 | 14 | if gene_type not in gene_types: 15 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 16 | 17 | model_organisms = ['human', 'mouse'] 18 | 19 | if model_organism not in model_organisms: 20 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 21 | 22 | # Load the TFs if we need to 23 | if gene_type == 'tf': 24 | tfs_list = pd.read_csv('../data/allTFs_' + model_organism + '.txt', header=None)[0].tolist() 25 | 26 | n_gems = adata.uns['nmf_info']['n_gems'] 27 | nmf_vars = np.array(adata.uns['nmf_info']['vars'], dtype=object) 28 | nmf_loadings = adata.uns['nmf_info']['loadings'] 29 | 30 | all_gems = ['GEM-' + str(i + 1) for i in range(n_gems)] 31 | 32 | top_genes_in_gems = [] 33 | gem_labels = [] 34 | gem_weights = [] 35 | 36 | for gem in gems: 37 | 38 | gem_index = all_gems.index(all_gems) 39 | 40 | # Get the loadings corresponding tot his gem 41 | gem_loadings = nmf_loadings[gem_index, :] 42 | 43 | # Sort the genes in their order by loading 44 | sorted_gem_loadings = gem_loadings[np.argsort(-gem_loadings)] 45 | ordered_genes = nmf_vars[np.argsort(-gem_loadings)] 46 | 47 | if gene_type == 'all': 48 | 49 | for i in range(n_genes): 50 | 51 | top_genes_in_gems.append(ordered_genes[i]) 52 | gem_labels.append(gem) 53 | gem_weights.append(sorted_gem_loadings[i]) 54 | 55 | else: # We only take TFs from each GEM 56 | 57 | ordered_tfs = [gene for gene in ordered_genes if gene in tfs_list] 58 | sorted_tf_loadings = [sorted_gem_loadings[i] for i, gene in enumerate(ordered_genes) if gene in tfs_list] 59 | 60 | for i in range(n_genes): 61 | 62 | top_genes_in_gems.append(ordered_tfs[i]) 63 | gem_labels.append(gem) 64 | gem_weights.append(sorted_tf_loadings[i]) 65 | 66 | top_nmf_genes_df = pd.DataFrame(data={'Gene': top_genes_in_gems, 67 | 'GEM': gem_labels, 68 | 'Weight': gem_weights}) 69 | return top_nmf_genes_df 70 | 71 | def get_top_pyliger_genes(adata: AnnData, 72 | gems: Union[str, Sequence[str]], 73 | n_genes: int, 74 | gene_type: str = 'all', 75 | model_organism: str = 'human'): 76 | 77 | gene_types = ['all', 'tf'] 78 | 79 | if gene_type not in gene_types: 80 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 81 | 82 | model_organisms = ['human', 'mouse'] 83 | 84 | if model_organism not in model_organisms: 85 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 86 | 87 | # Load the TFs if we need to 88 | if gene_type == 'tf': 89 | tfs_list = pd.read_csv('../data/allTFs_' + model_organism + '.txt', header=None)[0].tolist() 90 | 91 | pyliger_conds = [key for key in adata.uns['pyliger_info'].keys() if key not in ['n_vars', 'vars']] 92 | 93 | n_gems = adata.uns['pyliger_info']['n_gems'] 94 | pyliger_vars = np.array(adata.uns['pyliger_info']['vars'], dtype=object) 95 | 96 | all_gems = ['GEM-' + str(i + 1) for i in range(n_gems)] 97 | 98 | top_genes_in_gems = [] 99 | gem_labels = [] 100 | gem_weights = [] 101 | 102 | for gem in gems: 103 | 104 | gem_index = all_gems.index(all_gems) 105 | 106 | all_gem_loadings = {cond: adata.uns['pyliger_info'][cond]['W'][:, gem_index] \ 107 | + adata.uns['pyliger_info'][cond]['V'][:, gem_index] for cond in pyliger_conds} 108 | 109 | stacked_gem_loadings = np.hstack([all_gem_loadings[cond] for cond in all_gem_loadings]) 110 | stacked_gem_genes = np.hstack([pyliger_vars for cond in all_gem_loadings]) 111 | sorted_gem_loadings = stacked_gem_loadings[np.argsort(-stacked_gem_loadings)] 112 | init_top_gem_genes = stacked_gem_genes[np.argsort(-stacked_gem_loadings)] 113 | 114 | top_genes = [] 115 | 116 | for i, gene in enumerate(init_top_gem_genes): 117 | 118 | if gene_type == 'tf': 119 | 120 | if (gene in tfs_list)&(gene not in top_genes): 121 | 122 | top_genes.append(gene) # This tracks repeats, given that we're stacking the list of genes 123 | top_genes_in_gems.append(gene) 124 | gem_labels.append(gem) 125 | gem_weights.append(sorted_gem_loadings[i]) 126 | 127 | if len(top_genes) == n_genes: 128 | break 129 | else: 130 | 131 | if gene not in top_genes: 132 | 133 | top_genes.append(gene) # This tracks repeated names, given that we're stacking the list of genes 134 | top_genes_in_gems.append(gene) 135 | gem_labels.append(gem) 136 | gem_weights.append(sorted_gem_loadings[i]) 137 | 138 | if len(top_genes) == n_genes: 139 | break 140 | 141 | 142 | 143 | top_pyliger_genes_df = pd.DataFrame(data={'Gene': top_genes_in_gems, 144 | 'GEM': gem_labels, 145 | 'Weight': gem_weights}) 146 | return top_pyliger_genes_df 147 | 148 | def get_top_nsf_genes(adata: AnnData, 149 | gems: Union[str, Sequence[str]], 150 | n_genes: int, 151 | gene_type: str = 'all', 152 | model_organism: str = 'human'): 153 | 154 | gene_types = ['all', 'tf'] 155 | 156 | if gene_type not in gene_types: 157 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 158 | 159 | model_organisms = ['human', 'mouse'] 160 | 161 | if model_organism not in model_organisms: 162 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 163 | 164 | # Load the TFs if we need to 165 | if gene_type == 'tf': 166 | tfs_list = pd.read_csv('../data/allTFs_' + model_organism + '.txt', header=None)[0].tolist() 167 | 168 | n_gems = adata.uns['nsf_info']['n_gems'] 169 | nsf_vars = np.array(adata.uns['nsf_info']['vars'], dtype=object) 170 | nsf_loadings = adata.uns['nsf_info']['loadings'].T 171 | 172 | all_gems = ['GEM-' + str(i + 1) for i in range(n_gems)] 173 | 174 | top_genes_in_gems = [] 175 | gem_labels = [] 176 | gem_weights = [] 177 | 178 | for gem in gems: 179 | 180 | gem_index = all_gems.index(all_gems) 181 | 182 | # Get the loadings corresponding tot his gem 183 | gem_loadings = nsf_loadings[gem_index, :] 184 | 185 | # Sort the genes in their order by loading 186 | sorted_gem_loadings = gem_loadings[np.argsort(-gem_loadings)] 187 | ordered_genes = nsf_vars[np.argsort(-gem_loadings)] 188 | 189 | if gene_type == 'all': 190 | 191 | for i in range(n_genes): 192 | 193 | top_genes_in_gems.append(ordered_genes[i]) 194 | gem_labels.append(gem) 195 | gem_weights.append(sorted_gem_loadings[i]) 196 | 197 | else: # We only take TFs from each GEM 198 | 199 | ordered_tfs = [gene for gene in ordered_genes if gene in tfs_list] 200 | sorted_tf_loadings = [sorted_gem_loadings[i] for i, gene in enumerate(ordered_genes) if gene in tfs_list] 201 | 202 | for i in range(n_genes): 203 | 204 | top_genes_in_gems.append(ordered_tfs[i]) 205 | gem_labels.append(gem) 206 | gem_weights.append(sorted_tf_loadings[i]) 207 | 208 | top_nsf_genes_df = pd.DataFrame(data={'Gene': top_genes_in_gems, 209 | 'GEM': gem_labels, 210 | 'Weight': gem_weights}) 211 | return top_nsf_genes_df 212 | 213 | def get_top_gem_genes(adata: AnnData, 214 | gems: Union[str, Sequence[str]], 215 | n_genes: int, 216 | gem_key: str, 217 | method: str = 'pyliger', 218 | gene_type: str = 'all', 219 | model_organism: str = 'human'): 220 | 221 | # Perform a bunch of checks that we're specifying the right options 222 | methods = ['nmf', 'pyliger', 'nsf'] 223 | if method not in methods: 224 | raise ValueError ("Invalid method. Please select one of: %s" % methods) 225 | 226 | gene_types = ['all', 'tf'] 227 | 228 | if gene_type not in gene_types: 229 | raise ValueError ("Invalid gene type. Please select one of: %s" % gene_types) 230 | 231 | model_organisms = ['human', 'mouse'] 232 | 233 | if model_organism not in model_organisms: 234 | raise ValueError ("Invalid model organism. Please select one of: %s" % model_organisms) 235 | 236 | if method == 'nmf': 237 | 238 | return get_top_nmf_genes(adata, gems, n_genes, gem_key, gene_type, model_organism) 239 | 240 | elif method == 'pyliger': 241 | 242 | return get_top_pyliger_genes(adata, gems, n_genes, gem_key, gene_type, model_organism) 243 | 244 | else: # Should be NSF 245 | 246 | return get_top_nsf_genes(adata, gems, n_genes, gem_key, gene_type, model_organism) 247 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "flowsig" 3 | version = "0.3.0" 4 | description = "A package to infer dependent intercellular communication from single-cell and spatial transcriptomics data." 5 | authors = [ 6 | {name = "Axel A. Almet"} 7 | ] 8 | license = {text = "MIT License"} 9 | readme = "README.md" 10 | requires-python = ">=3.10,<3.11" 11 | dependencies = [ 12 | "scanpy (>=1.11.0,<2.0.0)", 13 | "squidpy (>=1.6.5,<2.0.0)", 14 | "numpy (>=1.2.4,<2.0.0)", 15 | "scipy (>=1.10.1,<2.0.0)", 16 | "causaldag (>=0.1a163,<0.2)", 17 | "anndata (>=0.11.3,<0.12.0)", 18 | "pandas (>=2.2.3,<3.0.0)", 19 | "networkx (>=3.4.2,<4.0.0)", 20 | "pyliger (>=0.2.4,<0.3.0)", 21 | "seaborn (>=0.13.2,<0.14.0)", 22 | "matplotlib (>=3.10.1,<4.0.0)", 23 | "joblib (>=1.4.2,<2.0.0)", 24 | "graphical-models (>=0.1a21,<0.2)", 25 | "tensorflow-probability (>=0.25.0,<0.26.0)", 26 | "tensorflow (>=2.19.0,<3.0.0)", 27 | "tf-keras (>=2.19.0,<3.0.0)", 28 | "spatial-factorization @ git+https://github.com/willtownes/spatial-factorization-py.git", 29 | "tqdm (>=4.67.1,<5.0.0)", 30 | "cnmf (>=1.7.0,<2.0.0)" 31 | ] 32 | 33 | 34 | [build-system] 35 | requires = ["poetry-core>=2.0.0,<3.0.0"] 36 | build-backend = "poetry.core.masonry.api" 37 | --------------------------------------------------------------------------------