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 +5 -0
- netcoloc/cli.py +55 -0
- netcoloc/netcoloc_utils.py +87 -0
- netcoloc/netprop.py +214 -0
- netcoloc/netprop_zscore.py +277 -0
- netcoloc/network_colocalization.py +741 -0
- netcoloc/validation.py +578 -0
- netcoloc-1.0.0.dist-info/METADATA +298 -0
- netcoloc-1.0.0.dist-info/RECORD +14 -0
- netcoloc-1.0.0.dist-info/WHEEL +5 -0
- netcoloc-1.0.0.dist-info/entry_points.txt +2 -0
- netcoloc-1.0.0.dist-info/licenses/AUTHORS.rst +15 -0
- netcoloc-1.0.0.dist-info/licenses/LICENSE +11 -0
- netcoloc-1.0.0.dist-info/top_level.txt +1 -0
netcoloc/__init__.py
ADDED
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
|