risk-network 0.0.3b4__py3-none-any.whl → 0.0.4__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.
- risk/__init__.py +1 -4
- risk/annotations/annotations.py +4 -2
- risk/annotations/io.py +1 -1
- risk/neighborhoods/neighborhoods.py +15 -2
- risk/network/geometry.py +2 -2
- risk/network/graph.py +4 -4
- risk/network/io.py +234 -53
- risk/network/plot.py +179 -58
- risk/risk.py +187 -75
- risk/stats/__init__.py +4 -1
- risk/stats/fisher_exact.py +132 -0
- risk/stats/hypergeom.py +131 -0
- risk/stats/permutation/__init__.py +6 -0
- risk/stats/permutation/permutation.py +212 -0
- risk/stats/{permutation.py → permutation/test_functions.py} +12 -39
- risk/stats/stats.py +1 -212
- {risk_network-0.0.3b4.dist-info → risk_network-0.0.4.dist-info}/METADATA +6 -6
- risk_network-0.0.4.dist-info/RECORD +30 -0
- {risk_network-0.0.3b4.dist-info → risk_network-0.0.4.dist-info}/WHEEL +1 -1
- risk_network-0.0.3b4.dist-info/RECORD +0 -26
- {risk_network-0.0.3b4.dist-info → risk_network-0.0.4.dist-info}/LICENSE +0 -0
- {risk_network-0.0.3b4.dist-info → risk_network-0.0.4.dist-info}/top_level.txt +0 -0
risk/__init__.py
CHANGED
risk/annotations/annotations.py
CHANGED
@@ -171,7 +171,9 @@ def get_description(words_column: pd.Series) -> str:
|
|
171
171
|
stop_words = set(stopwords.words("english"))
|
172
172
|
# Tokenize the concatenated string and filter out stopwords and non-alphabetic words
|
173
173
|
words = [
|
174
|
-
|
174
|
+
(
|
175
|
+
word.lower() if word.istitle() else word
|
176
|
+
) # Lowercase all words except proper nouns (e.g., RNA, mRNA)
|
175
177
|
for word in word_tokenize(words_column.str.cat(sep=" "))
|
176
178
|
if word.isalpha() and word.lower() not in stop_words
|
177
179
|
]
|
@@ -195,7 +197,7 @@ def _simplify_word_list(words: List[str], threshold: float = 0.80) -> List[str]:
|
|
195
197
|
word_counts = Counter(words)
|
196
198
|
filtered_words = []
|
197
199
|
used_words = set()
|
198
|
-
|
200
|
+
# Iterate through the words to find similar words
|
199
201
|
for word in word_counts:
|
200
202
|
if word in used_words:
|
201
203
|
continue
|
risk/annotations/io.py
CHANGED
@@ -25,7 +25,7 @@ class AnnotationsIO:
|
|
25
25
|
def __init__(self):
|
26
26
|
pass
|
27
27
|
|
28
|
-
def
|
28
|
+
def load_json_annotation(self, filepath: str, network: nx.Graph) -> Dict[str, Any]:
|
29
29
|
"""Load annotations from a JSON file and convert them to a DataFrame.
|
30
30
|
|
31
31
|
Args:
|
@@ -170,7 +170,16 @@ def _impute_neighbors(
|
|
170
170
|
# Calculate shortest distances for each node to determine the distance threshold
|
171
171
|
shortest_distances = []
|
172
172
|
for node in network.nodes():
|
173
|
-
|
173
|
+
try:
|
174
|
+
neighbors = [
|
175
|
+
n for n in network.neighbors(node) if binary_enrichment_matrix[n].sum() != 0
|
176
|
+
]
|
177
|
+
except IndexError as e:
|
178
|
+
raise IndexError(
|
179
|
+
f"Failed to find neighbors for node '{node}': Ensure that the node exists in the network and that the binary enrichment matrix is correctly indexed."
|
180
|
+
) from e
|
181
|
+
|
182
|
+
# Calculate the shortest distance to a neighbor
|
174
183
|
if neighbors:
|
175
184
|
shortest_distance = min([_get_euclidean_distance(node, n, network) for n in neighbors])
|
176
185
|
shortest_distances.append(shortest_distance)
|
@@ -312,7 +321,11 @@ def _calculate_threshold(average_distances: list, distance_threshold: float) ->
|
|
312
321
|
rank_percentiles = np.linspace(0, 1, len(sorted_distances))
|
313
322
|
# Interpolating the ranks to 1000 evenly spaced percentiles
|
314
323
|
interpolated_percentiles = np.linspace(0, 1, 1000)
|
315
|
-
|
324
|
+
try:
|
325
|
+
smoothed_distances = np.interp(interpolated_percentiles, rank_percentiles, sorted_distances)
|
326
|
+
except ValueError as e:
|
327
|
+
raise ValueError("No significant annotations found.") from e
|
328
|
+
|
316
329
|
# Determine the index corresponding to the distance threshold
|
317
330
|
threshold_index = int(np.ceil(distance_threshold * len(smoothed_distances))) - 1
|
318
331
|
# Return the smoothed distance at the calculated index
|
risk/network/geometry.py
CHANGED
@@ -7,13 +7,13 @@ import networkx as nx
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
9
|
|
10
|
-
def
|
10
|
+
def assign_edge_lengths(
|
11
11
|
G: nx.Graph,
|
12
12
|
compute_sphere: bool = True,
|
13
13
|
surface_depth: float = 0.0,
|
14
14
|
include_edge_weight: bool = False,
|
15
15
|
) -> nx.Graph:
|
16
|
-
"""
|
16
|
+
"""Assign edge lengths in the graph, optionally mapping nodes to a sphere and including edge weights.
|
17
17
|
|
18
18
|
Args:
|
19
19
|
G (nx.Graph): The input graph.
|
risk/network/graph.py
CHANGED
@@ -49,8 +49,8 @@ class NetworkGraph:
|
|
49
49
|
self.trimmed_domains = trimmed_domains
|
50
50
|
self.node_label_to_id_map = node_label_to_id_map
|
51
51
|
self.node_enrichment_sums = node_enrichment_sums
|
52
|
-
# NOTE: self.
|
53
|
-
self.
|
52
|
+
# NOTE: self.network and self.node_coordinates are declared in _initialize_network
|
53
|
+
self.network = None
|
54
54
|
self.node_coordinates = None
|
55
55
|
self._initialize_network(network)
|
56
56
|
|
@@ -95,8 +95,8 @@ class NetworkGraph:
|
|
95
95
|
"""
|
96
96
|
# Unfold the network's 3D coordinates to 2D
|
97
97
|
G_2d = _unfold_sphere_to_plane(G)
|
98
|
-
# Assign the unfolded graph to self.
|
99
|
-
self.
|
98
|
+
# Assign the unfolded graph to self.network
|
99
|
+
self.network = G_2d
|
100
100
|
# Extract 2D coordinates of nodes
|
101
101
|
self.node_coordinates = _extract_node_coordinates(G_2d)
|
102
102
|
|
risk/network/io.py
CHANGED
@@ -6,6 +6,7 @@ This file contains the code for the RISK class and command-line access.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
import json
|
9
|
+
import os
|
9
10
|
import pickle
|
10
11
|
import shutil
|
11
12
|
import zipfile
|
@@ -14,7 +15,7 @@ from xml.dom import minidom
|
|
14
15
|
import networkx as nx
|
15
16
|
import pandas as pd
|
16
17
|
|
17
|
-
from risk.network.geometry import
|
18
|
+
from risk.network.geometry import assign_edge_lengths
|
18
19
|
from risk.log import params, print_header
|
19
20
|
|
20
21
|
|
@@ -29,25 +30,67 @@ class NetworkIO:
|
|
29
30
|
self,
|
30
31
|
compute_sphere: bool = True,
|
31
32
|
surface_depth: float = 0.0,
|
32
|
-
distance_metric: str = "dijkstra",
|
33
|
-
edge_length_threshold: float = 0.5,
|
34
|
-
louvain_resolution: float = 0.1,
|
35
33
|
min_edges_per_node: int = 0,
|
36
34
|
include_edge_weight: bool = True,
|
37
35
|
weight_label: str = "weight",
|
38
36
|
):
|
37
|
+
"""Initialize the NetworkIO class.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
|
41
|
+
surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
|
42
|
+
min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
|
43
|
+
include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
|
44
|
+
weight_label (str, optional): Label for edge weights. Defaults to "weight".
|
45
|
+
"""
|
39
46
|
self.compute_sphere = compute_sphere
|
40
47
|
self.surface_depth = surface_depth
|
48
|
+
self.min_edges_per_node = min_edges_per_node
|
41
49
|
self.include_edge_weight = include_edge_weight
|
42
50
|
self.weight_label = weight_label
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
51
|
+
params.log_network(
|
52
|
+
compute_sphere=compute_sphere,
|
53
|
+
surface_depth=surface_depth,
|
54
|
+
min_edges_per_node=min_edges_per_node,
|
55
|
+
include_edge_weight=include_edge_weight,
|
56
|
+
weight_label=weight_label,
|
57
|
+
)
|
47
58
|
|
48
|
-
|
59
|
+
@classmethod
|
60
|
+
def load_gpickle_network(
|
61
|
+
cls,
|
62
|
+
filepath: str,
|
63
|
+
compute_sphere: bool = True,
|
64
|
+
surface_depth: float = 0.0,
|
65
|
+
min_edges_per_node: int = 0,
|
66
|
+
include_edge_weight: bool = True,
|
67
|
+
weight_label: str = "weight",
|
68
|
+
) -> nx.Graph:
|
49
69
|
"""Load a network from a GPickle file.
|
50
70
|
|
71
|
+
Args:
|
72
|
+
filepath (str): Path to the GPickle file.
|
73
|
+
compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
|
74
|
+
surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
|
75
|
+
min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
|
76
|
+
include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
|
77
|
+
weight_label (str, optional): Label for edge weights. Defaults to "weight".
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
nx.Graph: Loaded and processed network.
|
81
|
+
"""
|
82
|
+
networkio = cls(
|
83
|
+
compute_sphere=compute_sphere,
|
84
|
+
surface_depth=surface_depth,
|
85
|
+
min_edges_per_node=min_edges_per_node,
|
86
|
+
include_edge_weight=include_edge_weight,
|
87
|
+
weight_label=weight_label,
|
88
|
+
)
|
89
|
+
return networkio._load_gpickle_network(filepath=filepath)
|
90
|
+
|
91
|
+
def _load_gpickle_network(self, filepath: str) -> nx.Graph:
|
92
|
+
"""Private method to load a network from a GPickle file.
|
93
|
+
|
51
94
|
Args:
|
52
95
|
filepath (str): Path to the GPickle file.
|
53
96
|
|
@@ -62,11 +105,43 @@ class NetworkIO:
|
|
62
105
|
|
63
106
|
return self._initialize_graph(G)
|
64
107
|
|
65
|
-
|
108
|
+
@classmethod
|
109
|
+
def load_networkx_network(
|
110
|
+
cls,
|
111
|
+
network: nx.Graph,
|
112
|
+
compute_sphere: bool = True,
|
113
|
+
surface_depth: float = 0.0,
|
114
|
+
min_edges_per_node: int = 0,
|
115
|
+
include_edge_weight: bool = True,
|
116
|
+
weight_label: str = "weight",
|
117
|
+
) -> nx.Graph:
|
66
118
|
"""Load a NetworkX graph.
|
67
119
|
|
68
120
|
Args:
|
69
|
-
|
121
|
+
network (nx.Graph): A NetworkX graph object.
|
122
|
+
compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
|
123
|
+
surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
|
124
|
+
min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
|
125
|
+
include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
|
126
|
+
weight_label (str, optional): Label for edge weights. Defaults to "weight".
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
nx.Graph: Loaded and processed network.
|
130
|
+
"""
|
131
|
+
networkio = cls(
|
132
|
+
compute_sphere=compute_sphere,
|
133
|
+
surface_depth=surface_depth,
|
134
|
+
min_edges_per_node=min_edges_per_node,
|
135
|
+
include_edge_weight=include_edge_weight,
|
136
|
+
weight_label=weight_label,
|
137
|
+
)
|
138
|
+
return networkio._load_networkx_network(network=network)
|
139
|
+
|
140
|
+
def _load_networkx_network(self, network: nx.Graph) -> nx.Graph:
|
141
|
+
"""Private method to load a NetworkX graph.
|
142
|
+
|
143
|
+
Args:
|
144
|
+
network (nx.Graph): A NetworkX graph object.
|
70
145
|
|
71
146
|
Returns:
|
72
147
|
nx.Graph: Processed network.
|
@@ -74,17 +149,60 @@ class NetworkIO:
|
|
74
149
|
filetype = "NetworkX"
|
75
150
|
params.log_network(filetype=filetype)
|
76
151
|
self._log_loading(filetype)
|
77
|
-
return self._initialize_graph(
|
152
|
+
return self._initialize_graph(network)
|
78
153
|
|
154
|
+
@classmethod
|
79
155
|
def load_cytoscape_network(
|
80
|
-
|
156
|
+
cls,
|
81
157
|
filepath: str,
|
82
158
|
source_label: str = "source",
|
83
159
|
target_label: str = "target",
|
160
|
+
compute_sphere: bool = True,
|
161
|
+
surface_depth: float = 0.0,
|
162
|
+
min_edges_per_node: int = 0,
|
163
|
+
include_edge_weight: bool = True,
|
164
|
+
weight_label: str = "weight",
|
84
165
|
view_name: str = "",
|
85
166
|
) -> nx.Graph:
|
86
167
|
"""Load a network from a Cytoscape file.
|
87
168
|
|
169
|
+
Args:
|
170
|
+
filepath (str): Path to the Cytoscape file.
|
171
|
+
source_label (str, optional): Source node label. Defaults to "source".
|
172
|
+
target_label (str, optional): Target node label. Defaults to "target".
|
173
|
+
view_name (str, optional): Specific view name to load. Defaults to None.
|
174
|
+
compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
|
175
|
+
surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
|
176
|
+
min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
|
177
|
+
include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
|
178
|
+
weight_label (str, optional): Label for edge weights. Defaults to "weight".
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
nx.Graph: Loaded and processed network.
|
182
|
+
"""
|
183
|
+
networkio = cls(
|
184
|
+
compute_sphere=compute_sphere,
|
185
|
+
surface_depth=surface_depth,
|
186
|
+
min_edges_per_node=min_edges_per_node,
|
187
|
+
include_edge_weight=include_edge_weight,
|
188
|
+
weight_label=weight_label,
|
189
|
+
)
|
190
|
+
return networkio._load_cytoscape_network(
|
191
|
+
filepath=filepath,
|
192
|
+
source_label=source_label,
|
193
|
+
target_label=target_label,
|
194
|
+
view_name=view_name,
|
195
|
+
)
|
196
|
+
|
197
|
+
def _load_cytoscape_network(
|
198
|
+
self,
|
199
|
+
filepath: str,
|
200
|
+
source_label: str = "source",
|
201
|
+
target_label: str = "target",
|
202
|
+
view_name: str = "",
|
203
|
+
) -> nx.Graph:
|
204
|
+
"""Private method to load a network from a Cytoscape file.
|
205
|
+
|
88
206
|
Args:
|
89
207
|
filepath (str): Path to the Cytoscape file.
|
90
208
|
source_label (str, optional): Source node label. Defaults to "source".
|
@@ -98,14 +216,20 @@ class NetworkIO:
|
|
98
216
|
params.log_network(filetype=filetype, filepath=str(filepath))
|
99
217
|
self._log_loading(filetype, filepath=filepath)
|
100
218
|
cys_files = []
|
219
|
+
tmp_dir = ".tmp_cytoscape"
|
101
220
|
# Try / finally to remove unzipped files
|
102
221
|
try:
|
103
|
-
#
|
222
|
+
# Create the temporary directory if it doesn't exist
|
223
|
+
if not os.path.exists(tmp_dir):
|
224
|
+
os.makedirs(tmp_dir)
|
225
|
+
|
226
|
+
# Unzip CYS file into the temporary directory
|
104
227
|
with zipfile.ZipFile(filepath, "r") as zip_ref:
|
105
228
|
cys_files = zip_ref.namelist()
|
106
|
-
zip_ref.extractall(
|
229
|
+
zip_ref.extractall(tmp_dir)
|
230
|
+
|
107
231
|
# Get first view and network instances
|
108
|
-
cys_view_files = [cf for cf in cys_files if "/views/" in cf]
|
232
|
+
cys_view_files = [os.path.join(tmp_dir, cf) for cf in cys_files if "/views/" in cf]
|
109
233
|
cys_view_file = (
|
110
234
|
cys_view_files[0]
|
111
235
|
if not view_name
|
@@ -127,7 +251,7 @@ class NetworkIO:
|
|
127
251
|
# Read the node attributes (from /tables/)
|
128
252
|
attribute_metadata_keywords = ["/tables/", "SHARED_ATTRS", "edge.cytable"]
|
129
253
|
attribute_metadata = [
|
130
|
-
cf
|
254
|
+
os.path.join(tmp_dir, cf)
|
131
255
|
for cf in cys_files
|
132
256
|
if all(keyword in cf for keyword in attribute_metadata_keywords)
|
133
257
|
][0]
|
@@ -174,14 +298,53 @@ class NetworkIO:
|
|
174
298
|
return self._initialize_graph(G)
|
175
299
|
|
176
300
|
finally:
|
177
|
-
# Remove
|
178
|
-
|
179
|
-
|
180
|
-
shutil.rmtree(dirname)
|
301
|
+
# Remove the temporary directory and its contents
|
302
|
+
if os.path.exists(tmp_dir):
|
303
|
+
shutil.rmtree(tmp_dir)
|
181
304
|
|
182
|
-
|
305
|
+
@classmethod
|
306
|
+
def load_cytoscape_json_network(
|
307
|
+
cls,
|
308
|
+
filepath: str,
|
309
|
+
source_label: str = "source",
|
310
|
+
target_label: str = "target",
|
311
|
+
compute_sphere: bool = True,
|
312
|
+
surface_depth: float = 0.0,
|
313
|
+
min_edges_per_node: int = 0,
|
314
|
+
include_edge_weight: bool = True,
|
315
|
+
weight_label: str = "weight",
|
316
|
+
) -> nx.Graph:
|
183
317
|
"""Load a network from a Cytoscape JSON (.cyjs) file.
|
184
318
|
|
319
|
+
Args:
|
320
|
+
filepath (str): Path to the Cytoscape JSON file.
|
321
|
+
source_label (str, optional): Source node label. Default is "source".
|
322
|
+
target_label (str, optional): Target node label. Default is "target".
|
323
|
+
compute_sphere (bool, optional): Whether to map nodes to a sphere. Defaults to True.
|
324
|
+
surface_depth (float, optional): Surface depth for the sphere. Defaults to 0.0.
|
325
|
+
min_edges_per_node (int, optional): Minimum number of edges per node. Defaults to 0.
|
326
|
+
include_edge_weight (bool, optional): Whether to include edge weights in calculations. Defaults to True.
|
327
|
+
weight_label (str, optional): Label for edge weights. Defaults to "weight".
|
328
|
+
|
329
|
+
Returns:
|
330
|
+
NetworkX graph: Loaded and processed network.
|
331
|
+
"""
|
332
|
+
networkio = cls(
|
333
|
+
compute_sphere=compute_sphere,
|
334
|
+
surface_depth=surface_depth,
|
335
|
+
min_edges_per_node=min_edges_per_node,
|
336
|
+
include_edge_weight=include_edge_weight,
|
337
|
+
weight_label=weight_label,
|
338
|
+
)
|
339
|
+
return networkio._load_cytoscape_json_network(
|
340
|
+
filepath=filepath,
|
341
|
+
source_label=source_label,
|
342
|
+
target_label=target_label,
|
343
|
+
)
|
344
|
+
|
345
|
+
def _load_cytoscape_json_network(self, filepath, source_label="source", target_label="target"):
|
346
|
+
"""Private method to load a network from a Cytoscape JSON (.cyjs) file.
|
347
|
+
|
185
348
|
Args:
|
186
349
|
filepath (str): Path to the Cytoscape JSON file.
|
187
350
|
source_label (str, optional): Source node label. Default is "source".
|
@@ -193,36 +356,46 @@ class NetworkIO:
|
|
193
356
|
filetype = "Cytoscape JSON"
|
194
357
|
params.log_network(filetype=filetype, filepath=str(filepath))
|
195
358
|
self._log_loading(filetype, filepath=filepath)
|
359
|
+
|
196
360
|
# Load the Cytoscape JSON file
|
197
361
|
with open(filepath, "r") as f:
|
198
362
|
cyjs_data = json.load(f)
|
199
363
|
|
200
364
|
# Create a graph
|
201
365
|
G = nx.Graph()
|
202
|
-
#
|
366
|
+
# Store node positions for later use
|
203
367
|
node_x_positions = {}
|
204
368
|
node_y_positions = {}
|
205
369
|
for node in cyjs_data["elements"]["nodes"]:
|
206
370
|
node_data = node["data"]
|
207
|
-
node_id = node_data["
|
371
|
+
node_id = node_data["id_original"]
|
208
372
|
node_x_positions[node_id] = node["position"]["x"]
|
209
373
|
node_y_positions[node_id] = node["position"]["y"]
|
210
|
-
G.add_node(node_id)
|
211
|
-
G.nodes[node_id]["label"] = node_data.get("name", node_id)
|
212
|
-
G.nodes[node_id]["x"] = node["position"]["x"]
|
213
|
-
G.nodes[node_id]["y"] = node["position"]["y"]
|
214
374
|
|
215
|
-
# Process edges
|
375
|
+
# Process edges and add them to the graph
|
216
376
|
for edge in cyjs_data["elements"]["edges"]:
|
217
377
|
edge_data = edge["data"]
|
218
|
-
source = edge_data[source_label]
|
219
|
-
target = edge_data[target_label]
|
378
|
+
source = edge_data[f"{source_label}_original"]
|
379
|
+
target = edge_data[f"{target_label}_original"]
|
380
|
+
# Add the edge to the graph, optionally including weights
|
220
381
|
if self.weight_label is not None and self.weight_label in edge_data:
|
221
382
|
weight = float(edge_data[self.weight_label])
|
222
383
|
G.add_edge(source, target, weight=weight)
|
223
384
|
else:
|
224
385
|
G.add_edge(source, target)
|
225
386
|
|
387
|
+
# Ensure nodes exist in the graph and add them if not present
|
388
|
+
if source not in G:
|
389
|
+
G.add_node(source)
|
390
|
+
if target not in G:
|
391
|
+
G.add_node(target)
|
392
|
+
|
393
|
+
# Add node attributes (like label, x, y positions)
|
394
|
+
for node in G.nodes():
|
395
|
+
G.nodes[node]["label"] = node
|
396
|
+
G.nodes[node]["x"] = node_x_positions.get(node, 0) # Use stored positions
|
397
|
+
G.nodes[node]["y"] = node_y_positions.get(node, 0) # Use stored positions
|
398
|
+
|
226
399
|
# Initialize the graph
|
227
400
|
return self._initialize_graph(G)
|
228
401
|
|
@@ -235,12 +408,13 @@ class NetworkIO:
|
|
235
408
|
Returns:
|
236
409
|
nx.Graph: The processed and validated graph.
|
237
410
|
"""
|
411
|
+
self._validate_nodes(G)
|
412
|
+
self._assign_edge_weights(G)
|
413
|
+
self._assign_edge_lengths(G)
|
414
|
+
self._remove_invalid_graph_properties(G)
|
238
415
|
# IMPORTANT: This is where the graph node labels are converted to integers
|
416
|
+
# Make sure to perform this step after all other processing
|
239
417
|
G = nx.relabel_nodes(G, {node: idx for idx, node in enumerate(G.nodes)})
|
240
|
-
self._remove_invalid_graph_properties(G)
|
241
|
-
self._validate_edges(G)
|
242
|
-
self._validate_nodes(G)
|
243
|
-
self._process_graph(G)
|
244
418
|
return G
|
245
419
|
|
246
420
|
def _remove_invalid_graph_properties(self, G: nx.Graph) -> None:
|
@@ -249,18 +423,26 @@ class NetworkIO:
|
|
249
423
|
Args:
|
250
424
|
G (nx.Graph): A NetworkX graph object.
|
251
425
|
"""
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
426
|
+
# First, Remove self-loop edges to ensure correct edge count
|
427
|
+
G.remove_edges_from(list(nx.selfloop_edges(G)))
|
428
|
+
# Then, iteratively remove nodes with fewer edges than the specified threshold
|
429
|
+
while True:
|
430
|
+
nodes_to_remove = [
|
431
|
+
node for node in G.nodes() if G.degree(node) < self.min_edges_per_node
|
432
|
+
]
|
433
|
+
if not nodes_to_remove:
|
434
|
+
break # Exit loop if no more nodes to remove
|
435
|
+
|
436
|
+
# Remove the nodes and their associated edges
|
437
|
+
G.remove_nodes_from(nodes_to_remove)
|
438
|
+
|
439
|
+
# Optionally: Remove any isolated nodes if needed
|
440
|
+
isolated_nodes = list(nx.isolates(G))
|
441
|
+
if isolated_nodes:
|
442
|
+
G.remove_nodes_from(isolated_nodes)
|
443
|
+
|
444
|
+
def _assign_edge_weights(self, G: nx.Graph) -> None:
|
445
|
+
"""Assign weights to the edges in the graph.
|
264
446
|
|
265
447
|
Args:
|
266
448
|
G (nx.Graph): A NetworkX graph object.
|
@@ -289,13 +471,13 @@ class NetworkIO:
|
|
289
471
|
), f"Node {node} is missing 'x' or 'y' position attributes."
|
290
472
|
assert "label" in attrs, f"Node {node} is missing a 'label' attribute."
|
291
473
|
|
292
|
-
def
|
474
|
+
def _assign_edge_lengths(self, G: nx.Graph) -> None:
|
293
475
|
"""Prepare the network by adjusting surface depth and calculating edge lengths.
|
294
476
|
|
295
477
|
Args:
|
296
478
|
G (nx.Graph): The input network graph.
|
297
479
|
"""
|
298
|
-
|
480
|
+
assign_edge_lengths(
|
299
481
|
G,
|
300
482
|
compute_sphere=self.compute_sphere,
|
301
483
|
surface_depth=self.surface_depth,
|
@@ -317,10 +499,9 @@ class NetworkIO:
|
|
317
499
|
print(f"Filetype: {filetype}")
|
318
500
|
if filepath:
|
319
501
|
print(f"Filepath: {filepath}")
|
320
|
-
print(f"Projection: {'Sphere' if self.compute_sphere else 'Plane'}")
|
321
|
-
if self.compute_sphere:
|
322
|
-
print(f"Surface depth: {self.surface_depth}")
|
323
|
-
print(f"Edge length threshold: {self.edge_length_threshold}")
|
324
502
|
print(f"Edge weight: {'Included' if self.include_edge_weight else 'Excluded'}")
|
325
503
|
if self.include_edge_weight:
|
326
504
|
print(f"Weight label: {self.weight_label}")
|
505
|
+
print(f"Projection: {'Sphere' if self.compute_sphere else 'Plane'}")
|
506
|
+
if self.compute_sphere:
|
507
|
+
print(f"Surface depth: {self.surface_depth}")
|