risk-network 0.0.12b0__py3-none-any.whl → 0.0.12b1__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 -1
- risk/annotations/__init__.py +10 -0
- risk/annotations/annotations.py +354 -0
- risk/annotations/io.py +241 -0
- risk/annotations/nltk_setup.py +86 -0
- risk/log/__init__.py +11 -0
- risk/log/console.py +141 -0
- risk/log/parameters.py +171 -0
- risk/neighborhoods/__init__.py +7 -0
- risk/neighborhoods/api.py +442 -0
- risk/neighborhoods/community.py +441 -0
- risk/neighborhoods/domains.py +360 -0
- risk/neighborhoods/neighborhoods.py +514 -0
- risk/neighborhoods/stats/__init__.py +13 -0
- risk/neighborhoods/stats/permutation/__init__.py +6 -0
- risk/neighborhoods/stats/permutation/permutation.py +240 -0
- risk/neighborhoods/stats/permutation/test_functions.py +70 -0
- risk/neighborhoods/stats/tests.py +275 -0
- risk/network/__init__.py +4 -0
- risk/network/graph/__init__.py +4 -0
- risk/network/graph/api.py +200 -0
- risk/network/graph/graph.py +268 -0
- risk/network/graph/stats.py +166 -0
- risk/network/graph/summary.py +253 -0
- risk/network/io.py +693 -0
- risk/network/plotter/__init__.py +4 -0
- risk/network/plotter/api.py +54 -0
- risk/network/plotter/canvas.py +291 -0
- risk/network/plotter/contour.py +329 -0
- risk/network/plotter/labels.py +935 -0
- risk/network/plotter/network.py +294 -0
- risk/network/plotter/plotter.py +141 -0
- risk/network/plotter/utils/colors.py +419 -0
- risk/network/plotter/utils/layout.py +94 -0
- risk_network-0.0.12b1.dist-info/METADATA +122 -0
- risk_network-0.0.12b1.dist-info/RECORD +40 -0
- {risk_network-0.0.12b0.dist-info → risk_network-0.0.12b1.dist-info}/WHEEL +1 -1
- risk_network-0.0.12b0.dist-info/METADATA +0 -796
- risk_network-0.0.12b0.dist-info/RECORD +0 -7
- {risk_network-0.0.12b0.dist-info → risk_network-0.0.12b1.dist-info}/licenses/LICENSE +0 -0
- {risk_network-0.0.12b0.dist-info → risk_network-0.0.12b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,419 @@
|
|
1
|
+
"""
|
2
|
+
risk/network/plotter/utils/colors
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
4
|
+
"""
|
5
|
+
|
6
|
+
from typing import Any, Dict, List, Tuple, Union
|
7
|
+
|
8
|
+
import matplotlib
|
9
|
+
import matplotlib.colors as mcolors
|
10
|
+
import numpy as np
|
11
|
+
|
12
|
+
from risk.network.graph.graph import Graph
|
13
|
+
|
14
|
+
|
15
|
+
def get_annotated_domain_colors(
|
16
|
+
graph: Graph,
|
17
|
+
cmap: str = "gist_rainbow",
|
18
|
+
color: Union[str, List, Tuple, np.ndarray, None] = None,
|
19
|
+
blend_colors: bool = False,
|
20
|
+
blend_gamma: float = 2.2,
|
21
|
+
min_scale: float = 0.8,
|
22
|
+
max_scale: float = 1.0,
|
23
|
+
scale_factor: float = 1.0,
|
24
|
+
ids_to_colors: Union[Dict[int, Any], None] = None,
|
25
|
+
random_seed: int = 888,
|
26
|
+
) -> np.ndarray:
|
27
|
+
"""Get colors for the domains based on node annotations, or use a specified color.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
graph (Graph): The network data and attributes to be visualized.
|
31
|
+
cmap (str, optional): Colormap to use for generating domain colors. Defaults to "gist_rainbow".
|
32
|
+
color (str, List, Tuple, np.ndarray, or None, optional): Color to use for the domains. Can be a single color or an array of colors.
|
33
|
+
If None, the colormap will be used. Defaults to None.
|
34
|
+
blend_colors (bool, optional): Whether to blend colors for nodes with multiple domains. Defaults to False.
|
35
|
+
blend_gamma (float, optional): Gamma correction factor for perceptual color blending. Defaults to 2.2.
|
36
|
+
min_scale (float, optional): Minimum scale for color intensity when generating domain colors. Defaults to 0.8.
|
37
|
+
max_scale (float, optional): Maximum scale for color intensity when generating domain colors. Defaults to 1.0.
|
38
|
+
scale_factor (float, optional): Factor for adjusting the contrast in the colors generated based on significance. Higher values
|
39
|
+
increase the contrast. Defaults to 1.0.
|
40
|
+
ids_to_colors (Dict[int, Any], None, optional): Mapping of domain IDs to specific colors. Defaults to None.
|
41
|
+
random_seed (int, optional): Seed for random number generation to ensure reproducibility. Defaults to 888.
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
np.ndarray: Array of RGBA colors for each domain.
|
45
|
+
"""
|
46
|
+
# Generate domain colors based on the significance data
|
47
|
+
node_colors = get_domain_colors(
|
48
|
+
graph=graph,
|
49
|
+
cmap=cmap,
|
50
|
+
color=color,
|
51
|
+
blend_colors=blend_colors,
|
52
|
+
blend_gamma=blend_gamma,
|
53
|
+
min_scale=min_scale,
|
54
|
+
max_scale=max_scale,
|
55
|
+
scale_factor=scale_factor,
|
56
|
+
ids_to_colors=ids_to_colors,
|
57
|
+
random_seed=random_seed,
|
58
|
+
)
|
59
|
+
annotated_colors = []
|
60
|
+
for _, node_ids in graph.domain_id_to_node_ids_map.items():
|
61
|
+
if len(node_ids) > 1:
|
62
|
+
# For multi-node domains, choose the brightest color based on RGB sum
|
63
|
+
domain_colors = np.array([node_colors[node] for node in node_ids])
|
64
|
+
color = domain_colors[np.argmax(domain_colors[:, :3].sum(axis=1))] # Sum the RGB values
|
65
|
+
else:
|
66
|
+
# Single-node domains default to white (RGBA)
|
67
|
+
color = np.array([1.0, 1.0, 1.0, 1.0])
|
68
|
+
|
69
|
+
annotated_colors.append(color)
|
70
|
+
|
71
|
+
return annotated_colors
|
72
|
+
|
73
|
+
|
74
|
+
def get_domain_colors(
|
75
|
+
graph: Graph,
|
76
|
+
cmap: str = "gist_rainbow",
|
77
|
+
color: Union[str, List, Tuple, np.ndarray, None] = None,
|
78
|
+
blend_colors: bool = False,
|
79
|
+
blend_gamma: float = 2.2,
|
80
|
+
min_scale: float = 0.8,
|
81
|
+
max_scale: float = 1.0,
|
82
|
+
scale_factor: float = 1.0,
|
83
|
+
ids_to_colors: Union[Dict[int, Any], None] = None,
|
84
|
+
random_seed: int = 888,
|
85
|
+
) -> np.ndarray:
|
86
|
+
"""Generate composite colors for domains based on significance or specified colors.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
graph (Graph): The network data and attributes to be visualized.
|
90
|
+
cmap (str, optional): Name of the colormap to use for generating domain colors. Defaults to "gist_rainbow".
|
91
|
+
color (str, List, Tuple, np.ndarray, or None, optional): A specific color or array of colors to use for all domains.
|
92
|
+
If None, the colormap will be used. Defaults to None.
|
93
|
+
blend_colors (bool, optional): Whether to blend colors for nodes with multiple domains. Defaults to False.
|
94
|
+
blend_gamma (float, optional): Gamma correction factor for perceptual color blending. Defaults to 2.2.
|
95
|
+
min_scale (float, optional): Minimum intensity scale for the colors generated by the colormap. Controls the dimmest colors.
|
96
|
+
Defaults to 0.8.
|
97
|
+
max_scale (float, optional): Maximum intensity scale for the colors generated by the colormap. Controls the brightest colors.
|
98
|
+
Defaults to 1.0.
|
99
|
+
scale_factor (float, optional): Exponent for adjusting the color scaling based on significance scores. Higher values increase
|
100
|
+
contrast by dimming lower scores more. Defaults to 1.0.
|
101
|
+
ids_to_colors (Dict[int, Any], None, optional): Mapping of domain IDs to specific colors. Defaults to None.
|
102
|
+
random_seed (int, optional): Seed for random number generation to ensure reproducibility of color assignments. Defaults to 888.
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
np.ndarray: Array of RGBA colors generated for each domain, based on significance or the specified color.
|
106
|
+
"""
|
107
|
+
# Get colors for each domain
|
108
|
+
domain_ids_to_colors = _get_domain_ids_to_colors(
|
109
|
+
graph=graph, cmap=cmap, color=color, ids_to_colors=ids_to_colors, random_seed=random_seed
|
110
|
+
)
|
111
|
+
# Generate composite colors for nodes
|
112
|
+
node_colors = _get_composite_node_colors(
|
113
|
+
graph=graph,
|
114
|
+
domain_ids_to_colors=domain_ids_to_colors,
|
115
|
+
blend_colors=blend_colors,
|
116
|
+
blend_gamma=blend_gamma,
|
117
|
+
)
|
118
|
+
# Transform colors to ensure proper alpha values and intensity
|
119
|
+
transformed_colors = _transform_colors(
|
120
|
+
node_colors,
|
121
|
+
graph.node_significance_sums,
|
122
|
+
min_scale=min_scale,
|
123
|
+
max_scale=max_scale,
|
124
|
+
scale_factor=scale_factor,
|
125
|
+
)
|
126
|
+
return transformed_colors
|
127
|
+
|
128
|
+
|
129
|
+
def _get_domain_ids_to_colors(
|
130
|
+
graph: Graph,
|
131
|
+
cmap: str = "gist_rainbow",
|
132
|
+
color: Union[str, List, Tuple, np.ndarray, None] = None,
|
133
|
+
ids_to_colors: Union[Dict[int, Any], None] = None,
|
134
|
+
random_seed: int = 888,
|
135
|
+
) -> Dict[int, Any]:
|
136
|
+
"""Get colors for each domain.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
graph (Graph): The network data and attributes to be visualized.
|
140
|
+
cmap (str, optional): The name of the colormap to use. Defaults to "gist_rainbow".
|
141
|
+
color (str, List, Tuple, np.ndarray, or None, optional): A specific color or array of colors to use for the domains.
|
142
|
+
If None, the colormap will be used. Defaults to None.
|
143
|
+
ids_to_colors (Dict[int, Any], None, optional): Mapping of domain IDs to specific colors. Defaults to None.
|
144
|
+
random_seed (int, optional): Seed for random number generation. Defaults to 888.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
Dict[int, Any]: A dictionary mapping domain keys to their corresponding RGBA colors.
|
148
|
+
"""
|
149
|
+
# Get colors for each domain based on node positions
|
150
|
+
domain_colors = _get_colors(
|
151
|
+
graph.domain_id_to_node_ids_map,
|
152
|
+
cmap=cmap,
|
153
|
+
color=color,
|
154
|
+
random_seed=random_seed,
|
155
|
+
)
|
156
|
+
# Assign colors to domains either based on the generated colormap or the user-specified colors
|
157
|
+
domain_ids_to_colors = {}
|
158
|
+
for domain_id, domain_color in zip(graph.domain_id_to_node_ids_map.keys(), domain_colors):
|
159
|
+
if ids_to_colors and domain_id in ids_to_colors:
|
160
|
+
# Convert user-specified colors to RGBA format
|
161
|
+
user_rgba = to_rgba(ids_to_colors[domain_id])
|
162
|
+
domain_ids_to_colors[domain_id] = user_rgba
|
163
|
+
else:
|
164
|
+
domain_ids_to_colors[domain_id] = domain_color
|
165
|
+
|
166
|
+
return domain_ids_to_colors
|
167
|
+
|
168
|
+
|
169
|
+
def _get_composite_node_colors(
|
170
|
+
graph: Graph,
|
171
|
+
domain_ids_to_colors: Dict[int, Any],
|
172
|
+
blend_colors: bool = False,
|
173
|
+
blend_gamma: float = 2.2,
|
174
|
+
) -> np.ndarray:
|
175
|
+
"""Generate composite colors for nodes based on domain colors and significance values, with optional color blending.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
graph (Graph): The network data and attributes to be visualized.
|
179
|
+
domain_ids_to_colors (Dict[int, Any]): Mapping of domain IDs to RGBA colors.
|
180
|
+
blend_colors (bool): Whether to blend colors for nodes with multiple domains. Defaults to False.
|
181
|
+
blend_gamma (float, optional): Gamma correction factor to be used for perceptual color blending.
|
182
|
+
This parameter is only relevant if blend_colors is True. Defaults to 2.2.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
np.ndarray: Array of composite colors for each node.
|
186
|
+
"""
|
187
|
+
# Determine the number of nodes
|
188
|
+
num_nodes = len(graph.node_coordinates)
|
189
|
+
# Initialize composite colors array with shape (number of nodes, 4) for RGBA
|
190
|
+
composite_colors = np.zeros((num_nodes, 4))
|
191
|
+
# If blending is not required, directly assign domain colors to nodes
|
192
|
+
if not blend_colors:
|
193
|
+
for domain_id, nodes in graph.domain_id_to_node_ids_map.items():
|
194
|
+
color = domain_ids_to_colors[domain_id]
|
195
|
+
for node in nodes:
|
196
|
+
composite_colors[node] = color
|
197
|
+
|
198
|
+
# If blending is required
|
199
|
+
else:
|
200
|
+
for node, node_info in graph.node_id_to_domain_ids_and_significance_map.items():
|
201
|
+
domains = node_info["domains"] # List of domain IDs
|
202
|
+
significances = node_info["significances"] # List of significance values
|
203
|
+
# Filter domains and significances to keep only those with corresponding colors in domain_ids_to_colors
|
204
|
+
filtered_domains_significances = [
|
205
|
+
(domain_id, significance)
|
206
|
+
for domain_id, significance in zip(domains, significances)
|
207
|
+
if domain_id in domain_ids_to_colors
|
208
|
+
]
|
209
|
+
# If no valid domains exist, skip this node
|
210
|
+
if not filtered_domains_significances:
|
211
|
+
continue
|
212
|
+
|
213
|
+
# Unpack filtered domains and significances
|
214
|
+
filtered_domains, filtered_significances = zip(*filtered_domains_significances)
|
215
|
+
# Get the colors corresponding to the valid filtered domains
|
216
|
+
colors = [domain_ids_to_colors[domain_id] for domain_id in filtered_domains]
|
217
|
+
# Blend the colors using the given gamma (default is 2.2 if None)
|
218
|
+
gamma = blend_gamma if blend_gamma is not None else 2.2
|
219
|
+
composite_color = _blend_colors_perceptually(colors, filtered_significances, gamma)
|
220
|
+
# Assign the composite color to the node
|
221
|
+
composite_colors[node] = composite_color
|
222
|
+
|
223
|
+
return composite_colors
|
224
|
+
|
225
|
+
|
226
|
+
def _get_colors(
|
227
|
+
domain_id_to_node_ids_map: Dict[int, Any],
|
228
|
+
cmap: str = "gist_rainbow",
|
229
|
+
color: Union[str, List, Tuple, np.ndarray, None] = None,
|
230
|
+
random_seed: int = 888,
|
231
|
+
) -> List[Tuple]:
|
232
|
+
"""Generate a list of RGBA colors for domains, ensuring maximally separated colors for nearby domains.
|
233
|
+
|
234
|
+
Args:
|
235
|
+
domain_id_to_node_ids_map (Dict[int, Any]): Mapping from domain IDs to lists of node IDs.
|
236
|
+
cmap (str, optional): The name of the colormap to use. Defaults to "gist_rainbow".
|
237
|
+
color (str, List, Tuple, np.ndarray, or None, optional): A specific color or array of colors to use.
|
238
|
+
If None, the colormap will be used. Defaults to None.
|
239
|
+
random_seed (int, optional): Seed for random number generation. Defaults to 888.
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
List[Tuple]: List of RGBA colors for each domain.
|
243
|
+
"""
|
244
|
+
np.random.seed(random_seed)
|
245
|
+
num_domains = len(domain_id_to_node_ids_map)
|
246
|
+
if color:
|
247
|
+
# If a single color is specified, apply it to all domains
|
248
|
+
rgba = to_rgba(color, num_repeats=num_domains)
|
249
|
+
return rgba
|
250
|
+
|
251
|
+
# Load colormap and generate a large, maximally separated set of colors
|
252
|
+
colormap = matplotlib.colormaps.get_cmap(cmap)
|
253
|
+
color_positions = np.linspace(0, 1, num_domains, endpoint=False)
|
254
|
+
# Shuffle color positions to avoid spatial clustering of similar colors
|
255
|
+
np.random.shuffle(color_positions)
|
256
|
+
# Assign colors based on positions in the colormap
|
257
|
+
colors = [colormap(pos) for pos in color_positions]
|
258
|
+
|
259
|
+
return colors
|
260
|
+
|
261
|
+
|
262
|
+
def _blend_colors_perceptually(
|
263
|
+
colors: Union[List, Tuple, np.ndarray], significances: List[float], gamma: float = 2.2
|
264
|
+
) -> Tuple[float, float, float, float]:
|
265
|
+
"""Blends a list of RGBA colors using gamma correction for perceptually uniform color mixing.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
colors (List, Tuple, np.ndarray): List of RGBA colors. Can be a list, tuple, or NumPy array of RGBA values.
|
269
|
+
significances (List[float]): Corresponding list of significance values.
|
270
|
+
gamma (float, optional): Gamma correction factor, default is 2.2 (typical for perceptual blending).
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
Tuple[float, float, float, float]: The blended RGBA color.
|
274
|
+
"""
|
275
|
+
# Normalize significances so they sum up to 1 (proportions)
|
276
|
+
total_significance = sum(significances)
|
277
|
+
proportions = [significance / total_significance for significance in significances]
|
278
|
+
# Convert colors to gamma-corrected space (apply gamma correction to RGB channels)
|
279
|
+
gamma_corrected_colors = [[channel**gamma for channel in color[:3]] for color in colors]
|
280
|
+
# Blend the colors in gamma-corrected space
|
281
|
+
blended_color = np.dot(proportions, gamma_corrected_colors)
|
282
|
+
# Convert back from gamma-corrected space to linear space (by applying inverse gamma correction)
|
283
|
+
blended_color = [channel ** (1 / gamma) for channel in blended_color]
|
284
|
+
# Average the alpha channel separately (no gamma correction on alpha)
|
285
|
+
alpha = np.dot(proportions, [color[3] for color in colors])
|
286
|
+
return tuple(blended_color + [alpha])
|
287
|
+
|
288
|
+
|
289
|
+
def _transform_colors(
|
290
|
+
colors: np.ndarray,
|
291
|
+
significance_sums: np.ndarray,
|
292
|
+
min_scale: float = 0.8,
|
293
|
+
max_scale: float = 1.0,
|
294
|
+
scale_factor: float = 1.0,
|
295
|
+
) -> np.ndarray:
|
296
|
+
"""Transform colors using power scaling to emphasize high significance sums more. Black colors are replaced with
|
297
|
+
very dark grey to avoid issues with color scaling (rgb(0.1, 0.1, 0.1)).
|
298
|
+
|
299
|
+
Args:
|
300
|
+
colors (np.ndarray): An array of RGBA colors.
|
301
|
+
significance_sums (np.ndarray): An array of significance sums corresponding to the colors.
|
302
|
+
min_scale (float, optional): Minimum scale for color intensity. Defaults to 0.8.
|
303
|
+
max_scale (float, optional): Maximum scale for color intensity. Defaults to 1.0.
|
304
|
+
scale_factor (float, optional): Exponent for scaling, where values > 1 increase contrast by dimming small
|
305
|
+
values more. Defaults to 1.0.
|
306
|
+
|
307
|
+
Returns:
|
308
|
+
np.ndarray: The transformed array of RGBA colors with adjusted intensities.
|
309
|
+
"""
|
310
|
+
# Ensure that min_scale is less than max_scale
|
311
|
+
if min_scale == max_scale:
|
312
|
+
min_scale = max_scale - 10e-6 # Avoid division by zero
|
313
|
+
|
314
|
+
# Replace invalid values in colors early
|
315
|
+
colors = np.nan_to_num(colors, nan=0.0) # Replace NaN with black
|
316
|
+
# Replace black colors (#000000) with very dark grey (#1A1A1A)
|
317
|
+
black_color = np.array([0.0, 0.0, 0.0]) # Pure black RGB
|
318
|
+
dark_grey = np.array([0.1, 0.1, 0.1]) # Very dark grey RGB (#1A1A1A)
|
319
|
+
is_black = np.all(colors[:, :3] == black_color, axis=1)
|
320
|
+
colors[is_black, :3] = dark_grey
|
321
|
+
|
322
|
+
# Handle invalid or zero significance sums
|
323
|
+
max_significance = np.max(significance_sums)
|
324
|
+
if max_significance == 0:
|
325
|
+
max_significance = 1 # Avoid division by zero
|
326
|
+
normalized_sums = significance_sums / max_significance
|
327
|
+
# Replace NaN values in normalized sums
|
328
|
+
normalized_sums = np.nan_to_num(normalized_sums, nan=0.0)
|
329
|
+
|
330
|
+
# Apply power scaling to emphasize higher significance values
|
331
|
+
scaled_sums = normalized_sums**scale_factor
|
332
|
+
# Linearly scale the normalized sums to the range [min_scale, max_scale]
|
333
|
+
scaled_sums = min_scale + (max_scale - min_scale) * scaled_sums
|
334
|
+
# Replace NaN or invalid scaled sums
|
335
|
+
scaled_sums = np.nan_to_num(scaled_sums, nan=min_scale)
|
336
|
+
# Adjust RGB values based on scaled sums
|
337
|
+
for i in range(3): # Only adjust RGB values
|
338
|
+
colors[:, i] = scaled_sums * colors[:, i]
|
339
|
+
|
340
|
+
return colors
|
341
|
+
|
342
|
+
|
343
|
+
def to_rgba(
|
344
|
+
color: Union[str, List, Tuple, np.ndarray, None],
|
345
|
+
alpha: Union[float, None] = None,
|
346
|
+
num_repeats: Union[int, None] = None,
|
347
|
+
) -> np.ndarray:
|
348
|
+
"""Convert color(s) to RGBA format, applying alpha and repeating as needed.
|
349
|
+
|
350
|
+
Args:
|
351
|
+
color (str, List, Tuple, np.ndarray, None): The color(s) to convert. Can be a string (e.g., 'red'), a list or tuple of RGB/RGBA values,
|
352
|
+
or an `np.ndarray` of colors. If None, the function will return an array of white (RGBA) colors.
|
353
|
+
alpha (float, None, optional): Alpha value (transparency) to apply. If provided, it overrides any existing alpha values found
|
354
|
+
in color.
|
355
|
+
num_repeats (int, None, optional): If provided, the color(s) will be repeated this many times. Defaults to None.
|
356
|
+
|
357
|
+
Returns:
|
358
|
+
np.ndarray: Array of RGBA colors repeated `num_repeats` times, if applicable.
|
359
|
+
|
360
|
+
Raises:
|
361
|
+
ValueError: If the provided color format is invalid or cannot be converted to RGBA.
|
362
|
+
"""
|
363
|
+
|
364
|
+
def convert_to_rgba(c: Union[str, List, Tuple, np.ndarray]) -> np.ndarray:
|
365
|
+
"""Convert a single color to RGBA format, handling strings, hex, and RGB/RGBA lists."""
|
366
|
+
# NOTE: if no alpha is provided, the default alpha value is 1.0 by mcolors.to_rgba
|
367
|
+
if isinstance(c, str):
|
368
|
+
# Convert color names or hex values (e.g., 'red', '#FF5733') to RGBA
|
369
|
+
rgba = np.array(mcolors.to_rgba(c))
|
370
|
+
elif isinstance(c, (list, tuple, np.ndarray)) and len(c) in [3, 4]:
|
371
|
+
# Convert RGB (3) or RGBA (4) values to RGBA format
|
372
|
+
rgba = np.array(mcolors.to_rgba(c))
|
373
|
+
else:
|
374
|
+
raise ValueError(
|
375
|
+
f"Invalid color format: {c}. Must be a valid string or RGB/RGBA sequence."
|
376
|
+
)
|
377
|
+
|
378
|
+
if alpha is not None: # Override alpha if provided
|
379
|
+
rgba[3] = alpha
|
380
|
+
|
381
|
+
return rgba
|
382
|
+
|
383
|
+
# Default to white if no color is provided
|
384
|
+
if color is None:
|
385
|
+
color = "white"
|
386
|
+
|
387
|
+
# If color is a 2D array of RGBA values, convert it to a list of lists
|
388
|
+
if isinstance(color, np.ndarray) and color.ndim == 2 and color.shape[1] == 4:
|
389
|
+
color = [list(c) for c in color]
|
390
|
+
|
391
|
+
# Handle a single color (string or RGB/RGBA list/tuple)
|
392
|
+
if (
|
393
|
+
isinstance(color, str)
|
394
|
+
or isinstance(color, (list, tuple, np.ndarray))
|
395
|
+
and not any(isinstance(c, (str, list, tuple, np.ndarray)) for c in color)
|
396
|
+
):
|
397
|
+
rgba_color = convert_to_rgba(color)
|
398
|
+
if num_repeats:
|
399
|
+
return np.tile(
|
400
|
+
rgba_color, (num_repeats, 1)
|
401
|
+
) # Repeat the color if num_repeats is provided
|
402
|
+
|
403
|
+
return rgba_color
|
404
|
+
|
405
|
+
# Handle a list/array of colors
|
406
|
+
if isinstance(color, (list, tuple, np.ndarray)):
|
407
|
+
rgba_colors = np.array(
|
408
|
+
[convert_to_rgba(c) for c in color]
|
409
|
+
) # Convert each color in the list to RGBA
|
410
|
+
# Handle repetition if num_repeats is provided
|
411
|
+
if num_repeats:
|
412
|
+
repeated_colors = np.array(
|
413
|
+
[rgba_colors[i % len(rgba_colors)] for i in range(num_repeats)]
|
414
|
+
)
|
415
|
+
return repeated_colors
|
416
|
+
|
417
|
+
return rgba_colors
|
418
|
+
|
419
|
+
raise ValueError("Color must be a valid string, RGB/RGBA, or array of RGB/RGBA colors.")
|
@@ -0,0 +1,94 @@
|
|
1
|
+
"""
|
2
|
+
risk/network/plotter/utils/layout
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
4
|
+
"""
|
5
|
+
|
6
|
+
from typing import Any, Dict, List, Tuple
|
7
|
+
|
8
|
+
import networkx as nx
|
9
|
+
import numpy as np
|
10
|
+
|
11
|
+
|
12
|
+
def calculate_bounding_box(
|
13
|
+
node_coordinates: np.ndarray, radius_margin: float = 1.05
|
14
|
+
) -> Tuple[np.ndarray, float]:
|
15
|
+
"""Calculate the bounding box of the network based on node coordinates.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
node_coordinates (np.ndarray): Array of node coordinates (x, y).
|
19
|
+
radius_margin (float, optional): Margin factor to apply to the bounding box radius. Defaults to 1.05.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
tuple: Center of the bounding box and the radius (adjusted by the radius margin).
|
23
|
+
"""
|
24
|
+
# Find minimum and maximum x, y coordinates
|
25
|
+
x_min, y_min = np.min(node_coordinates, axis=0)
|
26
|
+
x_max, y_max = np.max(node_coordinates, axis=0)
|
27
|
+
# Calculate the center of the bounding box
|
28
|
+
center = np.array([(x_min + x_max) / 2, (y_min + y_max) / 2])
|
29
|
+
# Calculate the radius of the bounding box, adjusted by the margin
|
30
|
+
radius = max(x_max - x_min, y_max - y_min) / 2 * radius_margin
|
31
|
+
return center, radius
|
32
|
+
|
33
|
+
|
34
|
+
def refine_center_iteratively(
|
35
|
+
node_coordinates: np.ndarray,
|
36
|
+
radius_margin: float = 1.05,
|
37
|
+
max_iterations: int = 10,
|
38
|
+
tolerance: float = 1e-2,
|
39
|
+
) -> Tuple[np.ndarray, float]:
|
40
|
+
"""Refine the center of the graph iteratively to minimize skew in node distribution.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
node_coordinates (np.ndarray): Array of node coordinates (x, y).
|
44
|
+
radius_margin (float, optional): Margin factor to apply to the bounding box radius. Defaults to 1.05.
|
45
|
+
max_iterations (int, optional): Maximum number of iterations for refining the center. Defaults to 10.
|
46
|
+
tolerance (float, optional): Stopping tolerance for center adjustment. Defaults to 1e-2.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
tuple: Refined center and the final radius.
|
50
|
+
"""
|
51
|
+
# Initial center and radius based on the bounding box
|
52
|
+
center, _ = calculate_bounding_box(node_coordinates, radius_margin)
|
53
|
+
for _ in range(max_iterations):
|
54
|
+
# Shift the coordinates based on the current center
|
55
|
+
shifted_coordinates = node_coordinates - center
|
56
|
+
# Calculate skew (difference in distance from the center)
|
57
|
+
skew = np.mean(shifted_coordinates, axis=0)
|
58
|
+
# If skew is below tolerance, stop
|
59
|
+
if np.linalg.norm(skew) < tolerance:
|
60
|
+
break
|
61
|
+
|
62
|
+
# Adjust the center by moving it in the direction opposite to the skew
|
63
|
+
center += skew
|
64
|
+
|
65
|
+
# After refinement, recalculate the bounding radius
|
66
|
+
shifted_coordinates = node_coordinates - center
|
67
|
+
new_radius = np.max(np.linalg.norm(shifted_coordinates, axis=1)) * radius_margin
|
68
|
+
|
69
|
+
return center, new_radius
|
70
|
+
|
71
|
+
|
72
|
+
def calculate_centroids(
|
73
|
+
network: nx.Graph, domain_id_to_node_ids_map: Dict[int, Any]
|
74
|
+
) -> List[Tuple[float, float]]:
|
75
|
+
"""Calculate the centroid for each domain based on node x and y coordinates in the network.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
network (nx.Graph): The graph representing the network.
|
79
|
+
domain_id_to_node_ids_map (Dict[int, Any]): Mapping from domain IDs to lists of node IDs.
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
List[Tuple[float, float]]: List of centroids (x, y) for each domain.
|
83
|
+
"""
|
84
|
+
centroids = []
|
85
|
+
for _, node_ids in domain_id_to_node_ids_map.items():
|
86
|
+
# Extract x and y coordinates from the network nodes
|
87
|
+
node_positions = np.array(
|
88
|
+
[[network.nodes[node_id]["x"], network.nodes[node_id]["y"]] for node_id in node_ids]
|
89
|
+
)
|
90
|
+
# Compute the centroid as the mean of the x and y coordinates
|
91
|
+
centroid = np.mean(node_positions, axis=0)
|
92
|
+
centroids.append(tuple(centroid))
|
93
|
+
|
94
|
+
return centroids
|
@@ -0,0 +1,122 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: risk-network
|
3
|
+
Version: 0.0.12b1
|
4
|
+
Summary: A Python package for biological network analysis
|
5
|
+
Author-email: Ira Horecka <ira89@icloud.com>
|
6
|
+
License: GPL-3.0-or-later
|
7
|
+
Project-URL: Homepage, https://github.com/riskportal/network
|
8
|
+
Classifier: Intended Audience :: Developers
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
18
|
+
Classifier: Development Status :: 4 - Beta
|
19
|
+
Requires-Python: >=3.8
|
20
|
+
Description-Content-Type: text/markdown
|
21
|
+
License-File: LICENSE
|
22
|
+
Requires-Dist: ipywidgets
|
23
|
+
Requires-Dist: leidenalg
|
24
|
+
Requires-Dist: markov_clustering
|
25
|
+
Requires-Dist: matplotlib
|
26
|
+
Requires-Dist: networkx
|
27
|
+
Requires-Dist: nltk
|
28
|
+
Requires-Dist: numpy
|
29
|
+
Requires-Dist: openpyxl
|
30
|
+
Requires-Dist: pandas
|
31
|
+
Requires-Dist: python-igraph
|
32
|
+
Requires-Dist: python-louvain
|
33
|
+
Requires-Dist: scikit-learn
|
34
|
+
Requires-Dist: scipy
|
35
|
+
Requires-Dist: statsmodels
|
36
|
+
Requires-Dist: threadpoolctl
|
37
|
+
Requires-Dist: tqdm
|
38
|
+
Dynamic: license-file
|
39
|
+
|
40
|
+
# RISK Network
|
41
|
+
|
42
|
+
<p align="center">
|
43
|
+
<img src="https://i.imgur.com/8TleEJs.png" width="50%" />
|
44
|
+
</p>
|
45
|
+
|
46
|
+
<br>
|
47
|
+
|
48
|
+

|
49
|
+
[](https://pypi.python.org/pypi/risk-network)
|
50
|
+

|
51
|
+
[](https://doi.org/10.5281/zenodo.xxxxxxx)
|
52
|
+

|
53
|
+

|
54
|
+
|
55
|
+
**RISK** (Regional Inference of Significant Kinships) is a next-generation tool for biological network annotation and visualization. RISK integrates community detection-based clustering, rigorous statistical enrichment analysis, and a modular framework to uncover biologically meaningful relationships and generate high-resolution visualizations. RISK supports diverse data formats and is optimized for large-scale network analysis, making it a valuable resource for researchers in systems biology and beyond.
|
56
|
+
|
57
|
+
## Documentation and Tutorial
|
58
|
+
|
59
|
+
Full documentation is available at:
|
60
|
+
|
61
|
+
- **Docs:** [https://riskportal.github.io/network-tutorial](https://riskportal.github.io/network-tutorial)
|
62
|
+
- **Tutorial Jupyter Notebook Repository:** [https://github.com/riskportal/network-tutorial](https://github.com/riskportal/network-tutorial)
|
63
|
+
|
64
|
+
## Installation
|
65
|
+
|
66
|
+
RISK is compatible with Python 3.8 or later and runs on all major operating systems. To install the latest version of RISK, run:
|
67
|
+
|
68
|
+
```bash
|
69
|
+
pip install risk-network --upgrade
|
70
|
+
```
|
71
|
+
|
72
|
+
## Features
|
73
|
+
|
74
|
+
- **Comprehensive Network Analysis**: Analyze biological networks (e.g., protein–protein interaction and genetic interaction networks) as well as non-biological networks.
|
75
|
+
- **Advanced Clustering Algorithms**: Supports Louvain, Leiden, Markov Clustering, Greedy Modularity, Label Propagation, Spinglass, and Walktrap for identifying structured network regions.
|
76
|
+
- **Flexible Visualization**: Produce customizable, high-resolution network visualizations with kernel density estimate overlays, adjustable node and edge attributes, and export options in SVG, PNG, and PDF formats.
|
77
|
+
- **Efficient Data Handling**: Supports multiple input/output formats, including JSON, CSV, TSV, Excel, Cytoscape, and GPickle.
|
78
|
+
- **Statistical Analysis**: Assess functional enrichment using hypergeometric, permutation (network-aware), binomial, chi-squared, Poisson, and z-score tests, ensuring statistical adaptability across datasets.
|
79
|
+
- **Cross-Domain Applicability**: Suitable for network analysis across biological and non-biological domains, including social and communication networks.
|
80
|
+
|
81
|
+
## Example Usage
|
82
|
+
|
83
|
+
We applied RISK to a *Saccharomyces cerevisiae* protein–protein interaction network from Michaelis et al. (2023), filtering for proteins with six or more interactions to emphasize core functional relationships. RISK identified compact, statistically enriched clusters corresponding to biological processes such as ribosomal assembly and mitochondrial organization.
|
84
|
+
|
85
|
+
[](https://i.imgur.com/lJHJrJr.jpeg)
|
86
|
+
|
87
|
+
This figure highlights RISK’s capability to detect both established and novel functional modules within the yeast interactome.
|
88
|
+
|
89
|
+
## Citation
|
90
|
+
|
91
|
+
If you use RISK in your research, please cite:
|
92
|
+
|
93
|
+
**Horecka et al.**, "RISK: a next-generation tool for biological network annotation and visualization", **Bioinformatics**, 2025. DOI: [10.1234/zenodo.xxxxxxx](https://doi.org/10.1234/zenodo.xxxxxxx)
|
94
|
+
|
95
|
+
## Software Architecture and Implementation
|
96
|
+
|
97
|
+
RISK features a streamlined, modular architecture designed to meet diverse research needs. RISK’s modular design enables users to run individual components—such as clustering, statistical testing, or visualization—independently or in combination, depending on the analysis workflow. It includes dedicated modules for:
|
98
|
+
|
99
|
+
- **Data I/O**: Supports JSON, CSV, TSV, Excel, Cytoscape, and GPickle formats.
|
100
|
+
- **Clustering**: Supports multiple clustering methods, including Louvain, Leiden, Markov Clustering, Greedy Modularity, Label Propagation, Spinglass, and Walktrap. Provides flexible distance metrics tailored to network structure.
|
101
|
+
- **Statistical Analysis**: Provides a suite of tests for overrepresentation analysis of annotations.
|
102
|
+
- **Visualization**: Offers customizable, high-resolution output in multiple formats, including SVG, PNG, and PDF.
|
103
|
+
- **Configuration Management**: Centralized parameters in risk.params ensure reproducibility and easy tuning for large-scale analyses.
|
104
|
+
|
105
|
+
## Performance and Efficiency
|
106
|
+
|
107
|
+
Benchmarking results demonstrate that RISK efficiently scales to networks exceeding hundreds of thousands of edges, maintaining low execution times and optimal memory usage across statistical tests.
|
108
|
+
|
109
|
+
## Contributing
|
110
|
+
|
111
|
+
We welcome contributions from the community:
|
112
|
+
|
113
|
+
- [Issues Tracker](https://github.com/riskportal/network/issues)
|
114
|
+
- [Source Code](https://github.com/riskportal/network/tree/main/risk)
|
115
|
+
|
116
|
+
## Support
|
117
|
+
|
118
|
+
If you encounter issues or have suggestions for new features, please use the [Issues Tracker](https://github.com/riskportal/network/issues) on GitHub.
|
119
|
+
|
120
|
+
## License
|
121
|
+
|
122
|
+
RISK is open source under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html).
|
@@ -0,0 +1,40 @@
|
|
1
|
+
risk/__init__.py,sha256=ADVxFWQawaQ0uFwnpUUmlwzs9FIxFA9tFem9_pB5rn4,127
|
2
|
+
risk/risk.py,sha256=_Zs8cC4V0eqzfaMbq9M50ir815dbYS-oyTPlrySuMLw,1121
|
3
|
+
risk/annotations/__init__.py,sha256=cW2qf4se_Ehmo69mZE3aKy_LzWlTnffcI8KOKqqCock,192
|
4
|
+
risk/annotations/annotations.py,sha256=KtFyCiCnoAkhin3HKDBtkNcz5imjpysrmEfQKUwyqh8,14737
|
5
|
+
risk/annotations/io.py,sha256=_VAr_6SqzrFzmYt75WZ029871EYlHrJ72yoo-kR5uBE,10748
|
6
|
+
risk/annotations/nltk_setup.py,sha256=VRLXIxYRBEzTwAsOD2X48NjRAQJHPwqJPDjLGW6SZCY,3554
|
7
|
+
risk/log/__init__.py,sha256=en-hKzuFtQWos4oZd8PxJ9u9Pe5bdihiqH9-qk_5ppw,217
|
8
|
+
risk/log/console.py,sha256=PgjyEvyhYLUSHXPUKEqOmxsDsfrjPICIgqo_cAHq0N8,4575
|
9
|
+
risk/log/parameters.py,sha256=yGbV5VWSgoTD5w9D3iC-McyOoAJioeuCwl2Z-T-951M,5808
|
10
|
+
risk/neighborhoods/__init__.py,sha256=CNS0VNbh_IdxldXjDkPqeTrxgY4hqi_Tc_MTADW_tzQ,182
|
11
|
+
risk/neighborhoods/api.py,sha256=ibU9uyOmIWVIjRnFIas77ylEo5cH2T78Bn4fZmUozT8,23355
|
12
|
+
risk/neighborhoods/community.py,sha256=0YdTh6wgMLiGMdtlaD7Vu_uxOVoZ9vDBjxbkglkrTV8,17808
|
13
|
+
risk/neighborhoods/domains.py,sha256=oveQhhhF-X5cef7PWGdAf1hp_e1fRTNAq_dgzkupzig,14790
|
14
|
+
risk/neighborhoods/neighborhoods.py,sha256=DuItwOhLc7IuuEDqNuYgdDnvLaNVyc0J_2IvkcnD2IM,21675
|
15
|
+
risk/neighborhoods/stats/__init__.py,sha256=1evLEAa7trCWj2DapCV4vW_f0zsyKHqTsn4--E_fDPg,306
|
16
|
+
risk/neighborhoods/stats/tests.py,sha256=RMGEULBIDueqgl95_mz_8YJjayj__llpKr_gqpjSmFU,12107
|
17
|
+
risk/neighborhoods/stats/permutation/__init__.py,sha256=V-uLSoi4SIPKjSRl7rhcDR4HJ4tquAn3QxNTXH9KzK8,169
|
18
|
+
risk/neighborhoods/stats/permutation/permutation.py,sha256=YJ9bvQy6BNWje5UZrjeOfpBudE0i-G7eRcCkWvYDfcE,10695
|
19
|
+
risk/neighborhoods/stats/permutation/test_functions.py,sha256=uppxrrlqSLOvZGCG67E1G8TpVPkGreU1T5quVCzq1PM,3149
|
20
|
+
risk/network/__init__.py,sha256=C9GisdyJM7fizwUIipy1vC4lbry9ceqLn1iBcW0-mZg,34
|
21
|
+
risk/network/io.py,sha256=KXtQPFXYMBe8QNZQtp75AJHzaQhzJX9au4ZrVCZhaJg,27886
|
22
|
+
risk/network/graph/__init__.py,sha256=iG6IlE8xtAyF6_NuCBUpsJrjrjd1vsXO1Ajsr0K8EA0,46
|
23
|
+
risk/network/graph/api.py,sha256=ThlQ3MCCa4EkDWfflY_RcBsK_ekxe-dyhkSLEY9-udo,8550
|
24
|
+
risk/network/graph/graph.py,sha256=69frjpl2z7JjIyVqBhlOUzv4keDXOBDzwKcrCNS1KGM,12334
|
25
|
+
risk/network/graph/stats.py,sha256=xITodchaF8Pgngynil_GM9IeMBtxuraPX-Gjp6WBEMY,7338
|
26
|
+
risk/network/graph/summary.py,sha256=_3zuPFSDegti23uRpa24pbE6sUYCt7ucYQXtFNKMbX4,10277
|
27
|
+
risk/network/plotter/__init__.py,sha256=9kdeONe8NaJvJ5FO7eOdZiobqL4CeR7q2MghG4z6Kt8,50
|
28
|
+
risk/network/plotter/api.py,sha256=GFP0n_0-tSdCZQd6cuaH3w55953sXCR02nwyJhAEqK4,1789
|
29
|
+
risk/network/plotter/canvas.py,sha256=U4u5XCER8k3rUB_au7YxB0S5yMDiFkz-6xHiaxdSGG0,13610
|
30
|
+
risk/network/plotter/contour.py,sha256=Ybqfz2UJZ2Woe4J48UkMYxUbmGB4D3elyZJla2P5ohU,15505
|
31
|
+
risk/network/plotter/labels.py,sha256=TJ67Oe4mN1ZPiCAUvbJESCcO3EkAg-l_ehy-rWBWOd8,46745
|
32
|
+
risk/network/plotter/network.py,sha256=0VySlJ4n3tkHsOhVVSa3yiSppT8y1dmIwa-DhRn0tcM,14131
|
33
|
+
risk/network/plotter/plotter.py,sha256=KJ_7Ye98ha6mchEaNY8kT0qCHAiGWZUzPiI3RCI9Wx0,5975
|
34
|
+
risk/network/plotter/utils/colors.py,sha256=Pqa5pYZi8qdP9AsdXVY6u4a8sEKM3cF8gBP9TQwDTfI,19015
|
35
|
+
risk/network/plotter/utils/layout.py,sha256=OPqV8jzV9dpnOhYU4SYMSfsIXalVzESrlBSI_Y43OGU,3640
|
36
|
+
risk_network-0.0.12b1.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
37
|
+
risk_network-0.0.12b1.dist-info/METADATA,sha256=Lyrx0dcjEsfc-hC8GS82asYFs-BSySsTI1UFhKdoX4A,6667
|
38
|
+
risk_network-0.0.12b1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
39
|
+
risk_network-0.0.12b1.dist-info/top_level.txt,sha256=NX7C2PFKTvC1JhVKv14DFlFAIFnKc6Lpsu1ZfxvQwVw,5
|
40
|
+
risk_network-0.0.12b1.dist-info/RECORD,,
|