netcoloc 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
netcoloc/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ __author__ = 'Brin Rosenthal, Sophie Liu, Sarah Wright'
4
+ __email__ = 'sbrosenthal@health.ucsd.edu, sol015@ucsd.edu, snwright@ucsd.edu'
5
+ __version__ = '1.0.0'
netcoloc/cli.py ADDED
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import click
4
+
5
+ from .netprop_zscore import *
6
+
7
+
8
+ @click.command(context_settings={"ignore_unknown_options": True})
9
+ @click.argument('seed-gene-file', type=click.Path(exists=True, resolve_path=True))
10
+ @click.option('--seed-gene-file-delimiter')
11
+ @click.option('--num-reps', default=10, type=int)
12
+ @click.option('--alpha', '-a', default=0.5, type=float)
13
+ @click.option('--minimum-bin-size', default=10, type=int)
14
+ @click.option('--interactome-file')
15
+ @click.option('--interactome-uuid', default='f93f402c-86d4-11e7-a10d-0ac135e8bacf', type=click.UUID)
16
+ @click.option('--ndex-server', default='ndexbio.org')
17
+ @click.option('--ndex-user')
18
+ @click.option('--ndex-password')
19
+ @click.option('--out-name', default='out')
20
+ @click.option('--save-z-scores/--no-save-z-scores', default=True)
21
+ @click.option('--save-final-heat/--no-save-final-heat', default=False)
22
+ @click.option('--save-random-final-heats/--no-save-random-final-heats', default=False)
23
+ def main(seed_gene_file, seed_gene_file_delimiter, num_reps, alpha,
24
+ minimum_bin_size, interactome_file,
25
+ interactome_uuid, ndex_server, ndex_user, ndex_password,
26
+ out_name, save_z_scores, save_final_heat, save_random_final_heats):
27
+ """
28
+
29
+ :param seed_gene_file:
30
+ :param seed_gene_file_delimiter:
31
+ :param num_reps:
32
+ :param alpha:
33
+ :param minimum_bin_size:
34
+ :param interactome_file:
35
+ :param interactome_uuid:
36
+ :param ndex_server:
37
+ :param ndex_user:
38
+ :param ndex_password:
39
+ :param out_name:
40
+ :param save_z_scores:
41
+ :param save_final_heat:
42
+ :param save_random_final_heats:
43
+ :return:
44
+ """
45
+ netprop_zscore(seed_gene_file, seed_gene_file_delimiter=seed_gene_file_delimiter,
46
+ num_reps=num_reps, alpha=alpha, minimum_bin_size=minimum_bin_size,
47
+ interactome_file=interactome_file, interactome_uuid=str(interactome_uuid),
48
+ ndex_server=ndex_server, ndex_user=ndex_user, ndex_password=ndex_password,
49
+ out_name=out_name, save_z_scores=save_z_scores,
50
+ save_final_heat=save_final_heat,
51
+ save_random_final_heats=save_random_final_heats)
52
+
53
+
54
+ if __name__ == "__main__": # pragma: no cover
55
+ main()
@@ -0,0 +1,87 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ '''Utility functions useful across multiple modules.
4
+ '''
5
+
6
+ import warnings
7
+
8
+
9
+ def get_degree_binning(node_to_degree_dict, min_bin_size, lengths=None):
10
+ """
11
+ Groups nodes by degree into similarly sized bins. This function
12
+ comes from
13
+ `network_utilities.py of emreg00/toolbox <https://github.com/emreg00/toolbox/blob/master/network_utilities.py>`__
14
+
15
+
16
+ Returns a tuple with following two values:
17
+
18
+ * **list of bins** where each bin contains a list of nodes of similar degree
19
+ * **mapping of degree to index of bin** dict mapping a degree to the index
20
+ of the bin in the bins list which contains nodes of that degree
21
+
22
+ :param node_to_degree_dict: Map of nodes to their degrees
23
+ :type node_to_degree_dict: dict
24
+ :param min_bin_size: minimum number of nodes each bin should contain.
25
+ :type min_bin_size: int
26
+ :param lengths: List of nodes to bin. If lengths is equal to None, then
27
+ all nodes will be binned
28
+ :type lengths: list
29
+ :return: (list of bins, mapping of degree to index of bin)
30
+ :rtype: tuple
31
+ """
32
+ # Create dictionary mapping degrees to nodes
33
+ assert min_bin_size <= len(node_to_degree_dict), f'Minimum bin size must be less than number of nodes {len(node_to_degree_dict)}'
34
+
35
+ if lengths is not None:
36
+ if len(lengths) == 0:
37
+ warnings.warn("Lengths is empty. Returning empty bins and degree to bin index dictionary.")
38
+ return [], {}
39
+ missing_nodes = [x for x in lengths if x not in node_to_degree_dict]
40
+ if len(missing_nodes) > 0:
41
+ warnings.warn(f"The following nodes are not in the degree dictionary: {missing_nodes}")
42
+
43
+ degree_to_nodes = {}
44
+ for node, degree in node_to_degree_dict.items():
45
+ if lengths is not None and node not in lengths:
46
+ continue
47
+ degree_to_nodes.setdefault(degree, []).append(node)
48
+
49
+ # Get sorted list of degrees
50
+ degrees = degree_to_nodes.keys()
51
+ degrees = list(degrees)
52
+ degrees.sort()
53
+
54
+ bins = []
55
+ bins_boundaries = []
56
+ degree_to_bin_index = {}
57
+
58
+ degree_index = 0
59
+ while degree_index < len(degrees):
60
+ # Add nodes of each degree to bin until bin reaches minimum bin size
61
+ low = degrees[degree_index]
62
+ nodes_of_certain_degree = degree_to_nodes[low]
63
+ while len(nodes_of_certain_degree) < min_bin_size:
64
+ degree_index += 1
65
+ if degree_index == len(degrees):
66
+ degree_index -= 1
67
+ break
68
+ nodes_of_certain_degree.extend(degree_to_nodes[degrees[degree_index]])
69
+
70
+ high = degrees[degree_index]
71
+ if len(nodes_of_certain_degree) >= min_bin_size:
72
+ # For each degree represented in bin, set degree to bin index
73
+ for deg in range(low, high + 1):
74
+ degree_to_bin_index[deg] = len(bins)
75
+ bins.append(nodes_of_certain_degree)
76
+ bins_boundaries.append((low, high))
77
+ else:
78
+ # Combine last bin with second last bin, if last bin is too small
79
+ bins[-1].extend(nodes_of_certain_degree)
80
+ low_of_previous_bin, high_of_previous_bin = bins_boundaries[-1]
81
+ bins_boundaries[-1] = (low_of_previous_bin, high)
82
+ for deg in range(high_of_previous_bin + 1, high + 1):
83
+ degree_to_bin_index[deg] = len(bins) - 1
84
+
85
+ degree_index += 1
86
+
87
+ return bins, degree_to_bin_index
netcoloc/netprop.py ADDED
@@ -0,0 +1,214 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ '''Functions for performing network propagation
4
+ '''
5
+
6
+ import networkx as nx
7
+ import numpy as np
8
+ import pandas as pd
9
+ import warnings
10
+
11
+
12
+ def get_normalized_adjacency_matrix(graph, conserve_heat=True, weighted=False):
13
+ """
14
+ Returns normalized adjacency matrix (W'), as detailed in:
15
+
16
+ Vanunu, Oron, et al. 'Associating genes and protein complexes with disease
17
+ via network propagation.'
18
+
19
+
20
+ With version `0.1.6` and newer, the :py:class:`networkx.Graph`
21
+ can be directly passed into
22
+ :py:func:`~netcoloc.netprop.get_individual_heats_matrix` and
23
+ this method will be invoked to create the normalized adjacency matrix
24
+
25
+ .. note::
26
+ Resulting matrix from this function can be saved to a file with :py:func:`numpy.save`
27
+ and loaded later with :py:func:`numpy.load`, but resulting file can be several gigabytes
28
+ and take a minute or more to save/load.
29
+
30
+ .. code-block:: python
31
+
32
+ numpy.save('nam.npy', adjacency_matrix)
33
+ adjacency_matrix = numpy.load('nam.npy')
34
+
35
+
36
+ :param graph: Interactome from which to calculate normalized
37
+ adjacency matrix.
38
+ :type graph: :py:class:`networkx.Graph`
39
+ :param conserve_heat: If ``True``, heat will be conserved
40
+ (ie. the sum of the heat vector will be equal to 1),
41
+ and the graph will be asymmetric. Otherwise, heat will
42
+ not be conserved, and the graph will be symmetric.
43
+ :type conserve_heat: bool
44
+ :param weighted: If ``True``, then the graph's edge weights
45
+ will be taken into account. Otherwise, all edge weights
46
+ will be set to 1.
47
+ :type weighted: bool
48
+ :return: Square normalized adjacency matrix
49
+ :rtype: :py:class:`numpy.ndarray`
50
+ """
51
+ if isinstance(graph, np.ndarray):
52
+ graph = nx.from_numpy_array(graph)
53
+
54
+ if isinstance(graph, nx.DiGraph) or isinstance(graph, nx.MultiGraph) or isinstance(graph, nx.MultiDiGraph):
55
+ raise ValueError("Input graph must be a networkx.Graph object. Directed and MultiGraphs are not supported.")
56
+
57
+
58
+ assert 0 not in dict(graph.degree).values(), "Graph cannot have nodes with degree=zero"
59
+ # assert graph is nx.Graph object
60
+
61
+ # Create graph
62
+ if conserve_heat:
63
+ # If conserving heat, make G_weighted a di-graph (not symmetric)
64
+ graph_weighted = nx.DiGraph()
65
+ else:
66
+ # If not conserving heat, make G_weighted a simple graph (symmetric)
67
+ graph_weighted = nx.Graph()
68
+
69
+ # Create edge weights
70
+ edge_weights = []
71
+ node_to_degree_dict = dict(graph.degree)
72
+ if weighted and not nx.is_weighted(G=graph):
73
+ warnings.warn("Input graph is not weighted. All edge weights will be set to 1.")
74
+
75
+ for e in graph.edges(data=True):
76
+ v1 = e[0]
77
+ v2 = e[1]
78
+ deg1 = node_to_degree_dict[v1]
79
+ deg2 = node_to_degree_dict[v2]
80
+
81
+ if weighted and nx.is_weighted(G=graph):
82
+ weight = e[2]['weight']
83
+ else:
84
+ weight = 1
85
+
86
+ if conserve_heat:
87
+ # created asymmetrically weighted edges - each directed edge u->v normalized by the degree of v
88
+ edge_weights.append((v1, v2, weight / float(deg1)))
89
+ edge_weights.append((v2, v1, weight / float(deg2)))
90
+ else:
91
+ # normalize single undirected edge by the degree of both endpoints as per Vanunu, Oron, et al. 2010
92
+ edge_weights.append((v1, v2, weight / np.sqrt(deg1 * deg2)))
93
+
94
+ # Apply edge weights to graph
95
+ graph_weighted.add_weighted_edges_from(edge_weights)
96
+
97
+ # Transform graph to adjacency array
98
+ if len(graph.nodes) != len(graph_weighted):
99
+ raise ValueError("Input graph has nodes with zero degrees. Please remove these nodes.")
100
+
101
+ w_prime = nx.to_numpy_array(graph_weighted, nodelist=graph.nodes())
102
+
103
+ return w_prime
104
+
105
+
106
+ def get_individual_heats_matrix(nam_or_graph, alpha=0.5,
107
+ conserve_heat=True, weighted=False):
108
+ """
109
+ Returns the pre-calculated contributions of each individual gene in the
110
+ interactome to the final heat of each other gene in the interactome after
111
+ propagation.
112
+
113
+ .. versionchanged:: 0.1.6
114
+ In addition, to a normalized adjacency matrix, this function
115
+ now also supports :py:class:`networkx.Graph` network as input
116
+
117
+
118
+ If a :py:class:`networkx.Graph` network is passed in as the **nam_or_graph**
119
+ parameter, the function :py:func:`~netcoloc.netprop.get_normalized_adjacency_matrix`
120
+ is called to generate the normalized adjacency matrix using **conserve_heat** and
121
+ **weighted** parameters
122
+
123
+ .. note::
124
+ Resulting matrix from this function can be saved to a file with :py:func:`numpy.save`
125
+ and loaded later with :py:func:`numpy.load`, but resulting file can be several gigabytes
126
+ and take a minute or more to save/load.
127
+
128
+ .. code-block:: python
129
+
130
+ numpy.save('heats_matrix.npy', w_double_prime)
131
+ w_double_prime = numpy.load('heats_matrix.npy')
132
+
133
+
134
+ :param nam_or_graph: square normalized
135
+ adjacency matrix or network
136
+ :type nam_or_graph: :py:class:`numpy.ndarray` or :py:class:`networkx.Graph`
137
+ :param alpha: heat dissipation coefficient between 1 and 0. The
138
+ contribution of the heat propagated from adjacent nodes in
139
+ determining the final heat of a node, as opposed to the contribution
140
+ from being a part of the gene set initially
141
+ :type alpha: float
142
+ :param conserve_heat: If ``True``, heat will be conserved
143
+ (ie. the sum of the heat vector will be equal to 1),
144
+ and the graph will be asymmetric. Otherwise, heat will
145
+ not be conserved, and the graph will be symmetric.
146
+ **NOTE:** Only applies if **nam_or_graph** is :py:class:`networkx.Graph`
147
+ :type conserve_heat: bool
148
+ :param weighted: If ``True``, then the graph's edge weights
149
+ will be taken into account. Otherwise, all edge weights
150
+ will be set to 1.
151
+ **NOTE:** Only applies if **nam_or_graph** is :py:class:`networkx.Graph`
152
+ :type weighted: bool
153
+ :return: square individual heats matrix
154
+ :rtype: :py:class:`numpy.ndarray`
155
+ """
156
+ assert 1 >= alpha >= 0, "Alpha must be between 0 and 1"
157
+
158
+ nam = nam_or_graph
159
+ if isinstance(nam_or_graph, nx.Graph):
160
+ nam = get_normalized_adjacency_matrix(nam_or_graph,
161
+ conserve_heat=conserve_heat,
162
+ weighted=weighted)
163
+ nam = np.transpose(nam)
164
+
165
+ d_name = np.linalg.inv(np.identity(nam.shape[0]) - alpha * nam) * (1 - alpha)
166
+
167
+ return d_name
168
+
169
+
170
+ def network_propagation(individual_heats_matrix, nodes, seed_genes):
171
+ """
172
+ Implements network propagation, as detailed in:
173
+
174
+ Vanunu, Oron, et al. 'Associating genes and protein complexes with
175
+ disease via network propagation.'
176
+
177
+ Using this function, the final heat of the network is calculated directly,
178
+ instead of iteratively. This method is faster when many different
179
+ propagations need to be performed on the same network (with different seed
180
+ gene sets). It is slower than
181
+ :py:func:`~netcoloc.netprop.iterative_network_propagation` for a
182
+ single propagation.
183
+
184
+ :param individual_heats_matrix: Square matrix that is the
185
+ output of :py:func:`~netcoloc.netprop.get_individual_heats_matrix`
186
+ :type individual_heats_matrix: :py:class:`numpy.ndarray`
187
+ :param nodes: List of nodes in the network represented by the
188
+ individual_heats_matrix, in the same order in which they were
189
+ supplied to :py:func:`~netcoloc.netprop.get_individual_heats_matrix`
190
+ :type nodes: list
191
+ :param seed_genes: Input list of genes/nodes for intializing the heat in network propagation.
192
+ Any items in `seed genes` that are not present in `nodes` will be ignored.
193
+ :type seed_genes: list
194
+ :return: Final heat of each node after propagation, with the name
195
+ of the nodes as the index
196
+ :rtype: :py:class:`pandas.Series`
197
+ """
198
+ # Remove genes that are not in network
199
+ seed_genes = list(np.intersect1d(nodes, seed_genes))
200
+
201
+ # Initialize results vector
202
+ F = np.zeros(len(nodes))
203
+
204
+ # Add up resulting heats from each gene in seed genes set
205
+ for gene in seed_genes:
206
+ # TODO check that this is the correct orientation
207
+ F += individual_heats_matrix[:,nodes.index(gene)]
208
+
209
+ # Normalize results by number of seed genes
210
+ F /= len(seed_genes)
211
+
212
+ #Return as pandas series
213
+ # TODO does this need to be a pandas series?
214
+ return pd.Series(F, index=nodes)
@@ -0,0 +1,277 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ '''Functions for getting z-scores from network propagation.
4
+ '''
5
+
6
+ # External library imports
7
+
8
+ import os
9
+ import warnings
10
+ from tqdm import tqdm
11
+ import ndex2
12
+ import pickle
13
+ # Internal module convenience imports
14
+ from netcoloc.netcoloc_utils import *
15
+ from netcoloc.netprop import *
16
+
17
+
18
+ def netprop_zscore(seed_gene_file, seed_gene_file_delimiter=None, num_reps=10, alpha=0.5, minimum_bin_size=10,
19
+ interactome_file=None, interactome_uuid='f93f402c-86d4-11e7-a10d-0ac135e8bacf',
20
+ ndex_server='public.ndexbio.org', ndex_user=None, ndex_password=None, out_name='out',
21
+ save_z_scores=False, save_final_heat=False, save_random_final_heats=False, verbose=True):
22
+ """
23
+ Performs network heat propagation on the given interactome with the given
24
+ seed genes, then returns the z-scores of the final heat values of each node
25
+ in the interactome.
26
+
27
+ The z-scores are calculated based on a null model, which is built by running
28
+ the network propagation multiple times using randomly selected seed genes
29
+ with similar degree distributions to the original seed gene set.
30
+
31
+ This method returns a tuple containing the following:
32
+
33
+ * :py:class:`pandas.Series` containing z-scores for each gene. Gene names comprise the index column
34
+ * :py:class:`numpy.ndarray` containing square matrix where each row contains the final heat scores
35
+ for each gene from a network propagation from random seed genes
36
+
37
+ :param seed_gene_file: Location of file containing a delimited list of
38
+ seed genes
39
+ :type seed_gene_file: str
40
+ :param seed_gene_file_delimiter: Delimiter used to separate genes in seed
41
+ gene file. Default any whitespace
42
+ :type seed_gene_file_delimiter: str
43
+ :param num_reps: Number of times the network propagation algorithm should
44
+ be run using random seed genes in order to build the null model
45
+ :type num_reps: int
46
+ :param alpha: Number between 0 and 1. Denotes the importance of the
47
+ propagation step in the network propagation, as opposed to the step
48
+ where heat is added to seed genes only. Recommended to be 0.5 or
49
+ greater
50
+ :type alpha: float
51
+ :param minimum_bin_size: minimum number of genes that should be in
52
+ each degree matching bin.
53
+ :type minimum_bin_size: int
54
+ :param interactome_file: Location of file containing the interactome in
55
+ NetworkX gpickle format. Either the interactome_file argument or the
56
+ interactome_uuid argument must be defined.
57
+ :type interactome_file: str
58
+ :param interactome_uuid: UUID of the interactome on NDEx. Either the
59
+ interactome_file argument or the interactome_uuid argument must be
60
+ defined. (Default: The UUID of PCNet, the Parsimonious Composite
61
+ Network: f93f402c-86d4-11e7-a10d-0ac135e8bacf)
62
+ :type interactome_uuid: str
63
+ :param ndex_server: NDEx server on which the interactome is stored.
64
+ Only needs to be defined if interactome_uuid is defined
65
+ :type ndex_server: str
66
+ :param ndex_user: NDEx user that the interactome belongs to. Only
67
+ needs to be defined if interactome_uuid is defined, and the
68
+ interactome is private
69
+ :type ndex_user: str
70
+ :param ndex_password: password of the NDEx user's account. Only needs
71
+ to be defined if interactome_uuid is defined, and the interactome is
72
+ private
73
+ :type ndex_password: str
74
+ :param out_name: Prefix for saving output files
75
+ :type out_name: str
76
+ :param save_z_scores:
77
+ :param save_final_heat: If ``True``, then the raw network
78
+ propagation heat scores for the original seed gene set will be saved
79
+ in the form of a tsv file in the current directory
80
+ :type save_final_heat: bool
81
+ :param save_random_final_heats: If ``True``, then the raw
82
+ network propagation heat scores for every repetition of the
83
+ algorithm using random seed genes will be saved in the form of a tsv
84
+ file in the current directory. (Beware: This can be a large file if
85
+ num_reps is large.)
86
+ :type save_random_final_heats: bool
87
+ :param verbose: If ``True``, then progress information will
88
+ be logged. Otherwise, nothing will be printed
89
+ :return: (:py:class:`pandas.Series`, :py:class:`numpy.ndarray`)
90
+ :rtype: tuple
91
+ :raises TypeError: If neither interactome_file or interactome_uuid is provided or if
92
+ **num_reps** is not an ``int``
93
+ """
94
+ # Process arguments
95
+
96
+ # seed_gene_file
97
+ seed_gene_file = os.path.abspath(seed_gene_file)
98
+ #num_reps
99
+ try:
100
+ num_reps = int(num_reps)
101
+ except:
102
+ raise TypeError("The num_reps argument should be an integer")
103
+ #int_file and int_uuid
104
+ if interactome_file is None and interactome_uuid is None:
105
+ raise TypeError("Either interactome_file or interactome_uuid argument must be provided")
106
+
107
+ # Load interactome
108
+ if verbose:
109
+ print('Loading interactome')
110
+ if interactome_file is not None:
111
+ interactome_file = os.path.abspath(interactome_file)
112
+ with open(interactome_file, 'rb') as f:
113
+ interactome = pickle.load(f)
114
+ else:
115
+ interactome = ndex2.create_nice_cx_from_server(
116
+ ndex_server,
117
+ username=ndex_user,
118
+ password=ndex_password,
119
+ uuid=interactome_uuid
120
+ ).to_networkx()
121
+ if 'None' in interactome.nodes():
122
+ interactome.remove_node('None')
123
+ nodes = list(interactome.nodes)
124
+
125
+ if len(nodes) == 0:
126
+ warnings.warn("Interactome is empty. Returning empty z-scores and random final heats.")
127
+ return pd.Series(), np.array([])
128
+
129
+ # Log interactome num nodes and edges for diagnostic purposes
130
+ if verbose:
131
+ print('Number of nodes: ' + str(len(interactome.nodes)))
132
+ print('Number of edges: ' + str(len(interactome.edges)))
133
+
134
+ # Load seed genes
135
+ with open(seed_gene_file, 'r') as seed_file:
136
+ seed_genes = list(np.intersect1d(nodes, seed_file.read().split(seed_gene_file_delimiter)))
137
+ try:
138
+ seed_genes = [int(x) for x in seed_genes]
139
+ except:
140
+ seed_genes = [str(x) for x in seed_genes]
141
+ if verbose:
142
+ print('\nNumber of seed genes in interactome: ' + str(len(seed_genes)))
143
+
144
+ # Calculate individual_heats_matrix from interactome
145
+ if verbose:
146
+ print('\nCalculating w_prime')
147
+ w_prime = get_normalized_adjacency_matrix(interactome, conserve_heat=True)
148
+ if verbose:
149
+ print('\nCalculating individual_heats_matrix')
150
+ individual_heats_matrix = get_individual_heats_matrix(w_prime, alpha)
151
+
152
+ # Calculate the z-score
153
+ if verbose:
154
+ print('\nCalculating z-scores: ' + seed_gene_file)
155
+ z_scores, final_heat, random_final_heats = calculate_heat_zscores(
156
+ individual_heats_matrix,
157
+ nodes,
158
+ dict(interactome.degree),
159
+ seed_genes,
160
+ num_reps=num_reps,
161
+ alpha=alpha,
162
+ minimum_bin_size=minimum_bin_size)
163
+
164
+ # Save z-score results
165
+ z_scores.name = 'z-scores'
166
+ if save_z_scores:
167
+ z_scores.to_csv(out_name + '_z_scores_' + str(num_reps) + '_reps.tsv', sep='\t')
168
+
169
+ # If save_final_heat is true, save out the final heat vector
170
+ if save_final_heat:
171
+ final_heat_df = pd.DataFrame(final_heat, columns=['z-scores'])
172
+ final_heat_df.to_csv(out_name + '_final_heat_' + str(num_reps) + '_reps.tsv', sep='\t')
173
+
174
+ # If save_random_final_heats is true, save out the vector of randoms (this can be a large file)
175
+ if save_random_final_heats:
176
+ random_final_heats_df = pd.DataFrame(
177
+ random_final_heats.T,
178
+ index=nodes,
179
+ columns=range(1, random_final_heats.shape[0] + 1)
180
+ )
181
+ random_final_heats_df.to_csv(out_name + '_final_heat_random_' + str(num_reps) + '_reps.tsv', sep='\t')
182
+
183
+ return z_scores, random_final_heats
184
+
185
+
186
+ def calculate_heat_zscores(individual_heats_matrix, nodes, degrees, seed_genes,
187
+ num_reps=10, alpha=0.5, minimum_bin_size=10, random_seed=1):
188
+ """
189
+ Helper function to perform network heat propagation using the given
190
+ individual heats matrix with the given seed genes and return the z-scores of
191
+ the final heat values of each node.
192
+
193
+ The z-scores are calculated based on a null model, which is built by running
194
+ the network propagation multiple times using randomly selected seed genes
195
+ with similar degree distributions to the original seed gene set.
196
+
197
+ The returned tuple contains the following:
198
+
199
+ * :py:class:`pandas.Series` containing z-scores for each gene. Gene names comprise the index column
200
+ * :py:class:`pandas.Series` containing the final heat scores for each gene. Gene names comprise the index column,
201
+ * :py:class:`numpy.ndarray` containing square matrix in which each row contains the final heat scores for each gene from a network propagation from random seed genes)
202
+
203
+ :param individual_heats_matrix: output of the
204
+ netprop.get_individual_heats_matrix. A square matrix containing the
205
+ final heat contributions of each gene
206
+ :type individual_heats_matrix: :py:class:`numpy.ndarray`
207
+ :param nodes: nodes, in the order in which they were supplied to
208
+ the :py:func:`~netcoloc.netprop.get_normalized_adjacency_matrix` method
209
+ which returns the precursor to the individual_heats_matrix
210
+ :type nodes: list
211
+ :param degrees: Mapping of node names to node degrees
212
+ :type degrees: dict
213
+ :param seed_genes: list of genes to use for network propagation. The
214
+ results of this network propagation will be compared to a set of
215
+ random results in order to obtain z-scores
216
+ :type seed_genes: list
217
+ :param num_reps: Number of times the network propagation algorithm should
218
+ be run using random seed genes in order to build the null model
219
+ :type num_reps: int
220
+ :param alpha: Number between 0 and 1. Denotes the importance of the
221
+ propagation step in the network propagation, as opposed to the step
222
+ where heat is added to seed genes only. Recommended to be 0.5 or
223
+ greater
224
+ :type alpha: float
225
+ :param minimum_bin_size: minimum number of genes that should be in
226
+ each degree matching bin
227
+ :type minimum_bin_size: int
228
+ :param random_seed:
229
+ :return: (:py:class:`pandas.Series`, :py:class:`pandas.Series`, :py:class:`numpy.ndarray`)
230
+ :rtype: tuple
231
+ """
232
+ # set random seed for reproducibility
233
+ np.random.seed(random_seed)
234
+
235
+ # Calculate network propagation results given gene set
236
+ seed_genes = list(np.intersect1d(nodes, seed_genes))
237
+ assert len(seed_genes) > 0, "No seed genes found in the interactome. Please check your seed gene file."
238
+
239
+ final_heat = network_propagation(individual_heats_matrix, nodes, seed_genes)
240
+
241
+ # Initialize empty matrix for results of random network propagations
242
+ random_final_heats = np.zeros([num_reps, len(final_heat)])
243
+
244
+ assert minimum_bin_size <= len(nodes), "Minimum bin size is larger than the number of nodes."
245
+ # Create bins containing genes of similar degree
246
+ bins, actual_degree_to_bin_index = get_degree_binning(degrees, minimum_bin_size)
247
+
248
+ # Perform network propagation many times with random seed genes
249
+ for repetition in tqdm(range(num_reps)):
250
+ # Create list of random, degree-matched seed genes
251
+ random_seed_genes = []
252
+ for gene in seed_genes:
253
+ # Find genes with similar degrees to focal gene degree
254
+ degree = degrees[gene]
255
+ genes_of_similar_degree = bins[actual_degree_to_bin_index[degree]]
256
+ # Shuffle the genes in the bin
257
+ np.random.shuffle(genes_of_similar_degree)
258
+
259
+ # Add genes to list that haven't already been added
260
+ index = 0
261
+ while genes_of_similar_degree[index] in random_seed_genes:
262
+ index += 1
263
+ random_seed_genes.append(genes_of_similar_degree[index])
264
+
265
+ # Perform network propagation with random seed genes
266
+ random_final_heat = network_propagation(individual_heats_matrix, nodes, random_seed_genes)
267
+ # Set seeds to NaN so they don't bias results
268
+ random_final_heat.loc[random_seed_genes] = np.nan
269
+ # Add results to random_final_heats matrix
270
+ random_final_heats[repetition] = random_final_heat
271
+
272
+ # Calculate z-scores
273
+ with warnings.catch_warnings():
274
+ warnings.simplefilter("ignore")
275
+ z_scores = (np.log(final_heat) - np.nanmean(np.log(random_final_heats), axis=0)) / np.nanstd(np.log(random_final_heats), axis=0)
276
+
277
+ return z_scores, final_heat, random_final_heats