risk-network 0.0.6b2__tar.gz → 0.0.6b4__tar.gz

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.
Files changed (37) hide show
  1. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/PKG-INFO +1 -1
  2. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/__init__.py +1 -1
  3. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/annotations/io.py +29 -0
  4. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/neighborhoods/domains.py +24 -18
  5. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/network/graph.py +13 -9
  6. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/network/plot.py +57 -28
  7. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk_network.egg-info/PKG-INFO +1 -1
  8. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/LICENSE +0 -0
  9. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/MANIFEST.in +0 -0
  10. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/README.md +0 -0
  11. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/pyproject.toml +0 -0
  12. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/annotations/__init__.py +0 -0
  13. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/annotations/annotations.py +0 -0
  14. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/constants.py +0 -0
  15. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/log/__init__.py +0 -0
  16. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/log/console.py +0 -0
  17. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/log/params.py +0 -0
  18. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/neighborhoods/__init__.py +0 -0
  19. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/neighborhoods/community.py +0 -0
  20. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/neighborhoods/neighborhoods.py +0 -0
  21. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/network/__init__.py +0 -0
  22. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/network/geometry.py +0 -0
  23. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/network/io.py +0 -0
  24. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/risk.py +0 -0
  25. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/stats/__init__.py +0 -0
  26. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/stats/fisher_exact.py +0 -0
  27. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/stats/hypergeom.py +0 -0
  28. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/stats/permutation/__init__.py +0 -0
  29. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/stats/permutation/permutation.py +0 -0
  30. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/stats/permutation/test_functions.py +0 -0
  31. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk/stats/stats.py +0 -0
  32. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk_network.egg-info/SOURCES.txt +0 -0
  33. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk_network.egg-info/dependency_links.txt +0 -0
  34. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk_network.egg-info/requires.txt +0 -0
  35. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/risk_network.egg-info/top_level.txt +0 -0
  36. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/setup.cfg +0 -0
  37. {risk_network-0.0.6b2 → risk_network-0.0.6b4}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: risk-network
3
- Version: 0.0.6b2
3
+ Version: 0.0.6b4
4
4
  Summary: A Python package for biological network analysis
5
5
  Author: Ira Horecka
6
6
  Author-email: Ira Horecka <ira89@icloud.com>
@@ -7,4 +7,4 @@ RISK: RISK Infers Spatial Kinships
7
7
 
8
8
  from risk.risk import RISK
9
9
 
10
- __version__ = "0.0.6-beta.2"
10
+ __version__ = "0.0.6-beta.4"
@@ -153,6 +153,35 @@ class AnnotationsIO:
153
153
  # Load the annotations into the provided network
154
154
  return load_annotations(network, annotations_input)
155
155
 
156
+ def load_dict_annotation(self, content: Dict[str, Any], network: nx.Graph) -> Dict[str, Any]:
157
+ """Load annotations from a provided dictionary and convert them to a dictionary annotation.
158
+
159
+ Args:
160
+ content (dict): The annotations dictionary to load.
161
+ network (NetworkX graph): The network to which the annotations are related.
162
+
163
+ Returns:
164
+ dict: A dictionary containing ordered nodes, ordered annotations, and the annotations matrix.
165
+ """
166
+ # Ensure the input content is a dictionary
167
+ if not isinstance(content, dict):
168
+ raise TypeError(
169
+ f"Expected 'content' to be a dictionary, but got {type(content).__name__} instead."
170
+ )
171
+
172
+ filetype = "Dictionary"
173
+ # Log the loading of the annotations from the dictionary
174
+ params.log_annotations(filepath="In-memory dictionary", filetype=filetype)
175
+ _log_loading(filetype, "In-memory dictionary")
176
+
177
+ # Load the annotations into the provided network
178
+ annotations_dict = load_annotations(network, content)
179
+ # Ensure the output is a dictionary
180
+ if not isinstance(annotations_dict, dict):
181
+ raise ValueError("Expected output to be a dictionary")
182
+
183
+ return annotations_dict
184
+
156
185
 
157
186
  def _load_matrix_file(
158
187
  filepath: str,
@@ -35,26 +35,31 @@ def define_domains(
35
35
  Returns:
36
36
  pd.DataFrame: DataFrame with the primary domain for each node.
37
37
  """
38
- # Perform hierarchical clustering on the binary enrichment matrix
39
- m = significant_neighborhoods_enrichment[:, top_annotations["top attributes"]].T
40
- best_linkage, best_metric, best_threshold = _optimize_silhouette_across_linkage_and_metrics(
41
- m, linkage_criterion, linkage_method, linkage_metric
42
- )
43
- try:
44
- Z = linkage(m, method=best_linkage, metric=best_metric)
45
- except ValueError as e:
46
- raise ValueError("No significant annotations found.") from e
38
+ # Check if there's more than one column in significant_neighborhoods_enrichment
39
+ if significant_neighborhoods_enrichment.shape[1] == 1:
40
+ print("Single annotation detected. Skipping clustering.")
41
+ top_annotations["domain"] = 1 # Assign a default domain or handle appropriately
42
+ else:
43
+ # Perform hierarchical clustering on the binary enrichment matrix
44
+ m = significant_neighborhoods_enrichment[:, top_annotations["top attributes"]].T
45
+ best_linkage, best_metric, best_threshold = _optimize_silhouette_across_linkage_and_metrics(
46
+ m, linkage_criterion, linkage_method, linkage_metric
47
+ )
48
+ try:
49
+ Z = linkage(m, method=best_linkage, metric=best_metric)
50
+ except ValueError as e:
51
+ raise ValueError("No significant annotations found.") from e
47
52
 
48
- print(
49
- f"Linkage criterion: '{linkage_criterion}'\nLinkage method: '{best_linkage}'\nLinkage metric: '{best_metric}'"
50
- )
51
- print(f"Optimal linkage threshold: {round(best_threshold, 3)}")
53
+ print(
54
+ f"Linkage criterion: '{linkage_criterion}'\nLinkage method: '{best_linkage}'\nLinkage metric: '{best_metric}'"
55
+ )
56
+ print(f"Optimal linkage threshold: {round(best_threshold, 3)}")
52
57
 
53
- max_d_optimal = np.max(Z[:, 2]) * best_threshold
54
- domains = fcluster(Z, max_d_optimal, criterion=linkage_criterion)
55
- # Assign domains to the annotations matrix
56
- top_annotations["domain"] = 0
57
- top_annotations.loc[top_annotations["top attributes"], "domain"] = domains
58
+ max_d_optimal = np.max(Z[:, 2]) * best_threshold
59
+ domains = fcluster(Z, max_d_optimal, criterion=linkage_criterion)
60
+ # Assign domains to the annotations matrix
61
+ top_annotations["domain"] = 0
62
+ top_annotations.loc[top_annotations["top attributes"], "domain"] = domains
58
63
 
59
64
  # Create DataFrames to store domain information
60
65
  node_to_enrichment = pd.DataFrame(
@@ -63,6 +68,7 @@ def define_domains(
63
68
  )
64
69
  node_to_domain = node_to_enrichment.groupby(level="domain", axis=1).sum()
65
70
 
71
+ # Find the maximum enrichment score for each node
66
72
  t_max = node_to_domain.loc[:, 1:].max(axis=1)
67
73
  t_idxmax = node_to_domain.loc[:, 1:].idxmax(axis=1)
68
74
  t_idxmax[t_max == 0] = 0
@@ -42,12 +42,16 @@ class NetworkGraph:
42
42
  node_enrichment_sums (np.ndarray): Array containing the enrichment sums for the nodes.
43
43
  """
44
44
  self.top_annotations = top_annotations
45
- self.domain_to_nodes = self._create_domain_to_nodes_map(domains)
45
+ self.domain_to_nodes_map = self._create_domain_to_nodes_map(domains)
46
46
  self.domains = domains
47
47
  self.trimmed_domain_to_term = self._create_domain_to_term_map(trimmed_domains)
48
48
  self.trimmed_domains = trimmed_domains
49
- self.node_label_to_id_map = node_label_to_id_map
50
49
  self.node_enrichment_sums = node_enrichment_sums
50
+ self.node_id_to_label_map = {v: k for k, v in node_label_to_id_map.items()}
51
+ self.node_label_to_enrichment_map = dict(
52
+ zip(node_label_to_id_map.keys(), node_enrichment_sums)
53
+ )
54
+ self.node_label_to_id_map = node_label_to_id_map
51
55
  # NOTE: self.network and self.node_coordinates are declared in _initialize_network
52
56
  self.network = None
53
57
  self.node_coordinates = None
@@ -63,12 +67,12 @@ class NetworkGraph:
63
67
  dict: A dictionary where keys are domain IDs and values are lists of nodes belonging to each domain.
64
68
  """
65
69
  cleaned_domains_matrix = domains.reset_index()[["index", "primary domain"]]
66
- node_to_domains = cleaned_domains_matrix.set_index("index")["primary domain"].to_dict()
67
- domain_to_nodes = defaultdict(list)
68
- for k, v in node_to_domains.items():
69
- domain_to_nodes[v].append(k)
70
+ node_to_domains_map = cleaned_domains_matrix.set_index("index")["primary domain"].to_dict()
71
+ domain_to_nodes_map = defaultdict(list)
72
+ for k, v in node_to_domains_map.items():
73
+ domain_to_nodes_map[v].append(k)
70
74
 
71
- return domain_to_nodes
75
+ return domain_to_nodes_map
72
76
 
73
77
  def _create_domain_to_term_map(self, trimmed_domains: pd.DataFrame) -> Dict[str, Any]:
74
78
  """Create a mapping from domain IDs to their corresponding terms.
@@ -154,7 +158,7 @@ class NetworkGraph:
154
158
  # Initialize composite colors array with shape (number of nodes, 4) for RGBA
155
159
  composite_colors = np.zeros((num_nodes, 4))
156
160
  # Assign colors to nodes based on domain_colors
157
- for domain_idx, nodes in self.domain_to_nodes.items():
161
+ for domain_idx, nodes in self.domain_to_nodes_map.items():
158
162
  color = domain_colors[domain_idx]
159
163
  for node in nodes:
160
164
  composite_colors[node] = color
@@ -185,7 +189,7 @@ class NetworkGraph:
185
189
  domain_colors = _get_colors(
186
190
  num_colors_to_generate=len(domains), cmap=cmap, color=color, random_seed=random_seed
187
191
  )
188
- return dict(zip(self.domain_to_nodes.keys(), domain_colors))
192
+ return dict(zip(self.domain_to_nodes_map.keys(), domain_colors))
189
193
 
190
194
 
191
195
  def _transform_colors(
@@ -207,6 +207,7 @@ class NetworkPlotter:
207
207
  self,
208
208
  node_size: Union[int, np.ndarray] = 50,
209
209
  node_shape: str = "o",
210
+ node_edgewidth: float = 1.0,
210
211
  edge_width: float = 1.0,
211
212
  node_color: Union[str, List, Tuple, np.ndarray] = "white",
212
213
  node_edgecolor: Union[str, List, Tuple, np.ndarray] = "black",
@@ -214,11 +215,12 @@ class NetworkPlotter:
214
215
  node_alpha: float = 1.0,
215
216
  edge_alpha: float = 1.0,
216
217
  ) -> None:
217
- """Plot the network graph with customizable node colors, sizes, and edge widths.
218
+ """Plot the network graph with customizable node colors, sizes, edge widths, and node edge widths.
218
219
 
219
220
  Args:
220
221
  node_size (int or np.ndarray, optional): Size of the nodes. Can be a single integer or an array of sizes. Defaults to 50.
221
222
  node_shape (str, optional): Shape of the nodes. Defaults to "o".
223
+ node_edgewidth (float, optional): Width of the node edges. Defaults to 1.0.
222
224
  edge_width (float, optional): Width of the edges. Defaults to 1.0.
223
225
  node_color (str, list, tuple, or np.ndarray, optional): Color of the nodes. Can be a single color or an array of colors. Defaults to "white".
224
226
  node_edgecolor (str, list, tuple, or np.ndarray, optional): Color of the node edges. Defaults to "black".
@@ -232,6 +234,7 @@ class NetworkPlotter:
232
234
  "custom" if isinstance(node_size, np.ndarray) else node_size
233
235
  ), # np.ndarray usually indicates custom sizes
234
236
  network_node_shape=node_shape,
237
+ network_node_edgewidth=node_edgewidth,
235
238
  network_edge_width=edge_width,
236
239
  network_node_color=(
237
240
  "custom" if isinstance(node_color, np.ndarray) else node_color
@@ -245,10 +248,7 @@ class NetworkPlotter:
245
248
  # Convert colors to RGBA using the _to_rgba helper function
246
249
  # If node_colors was generated using get_annotated_node_colors, its alpha values will override node_alpha
247
250
  node_color = _to_rgba(node_color, node_alpha, num_repeats=len(self.graph.network.nodes))
248
- # Convert other colors to RGBA using the _to_rgba helper function
249
- node_edgecolor = _to_rgba(
250
- node_edgecolor, 1.0, num_repeats=len(self.graph.network.nodes)
251
- ) # Node edges are usually fully opaque
251
+ node_edgecolor = _to_rgba(node_edgecolor, 1.0, num_repeats=len(self.graph.network.nodes))
252
252
  edge_color = _to_rgba(edge_color, edge_alpha, num_repeats=len(self.graph.network.edges))
253
253
 
254
254
  # Extract node coordinates from the network graph
@@ -262,6 +262,7 @@ class NetworkPlotter:
262
262
  node_shape=node_shape,
263
263
  node_color=node_color,
264
264
  edgecolors=node_edgecolor,
265
+ linewidths=node_edgewidth,
265
266
  ax=self.ax,
266
267
  )
267
268
  # Draw the edges of the graph
@@ -278,6 +279,7 @@ class NetworkPlotter:
278
279
  nodes: List,
279
280
  node_size: Union[int, np.ndarray] = 50,
280
281
  node_shape: str = "o",
282
+ node_edgewidth: float = 1.0, # Added node_edgewidth parameter
281
283
  edge_width: float = 1.0,
282
284
  node_color: Union[str, List, Tuple, np.ndarray] = "white",
283
285
  node_edgecolor: Union[str, List, Tuple, np.ndarray] = "black",
@@ -291,6 +293,7 @@ class NetworkPlotter:
291
293
  nodes (list): List of node labels to include in the subnetwork.
292
294
  node_size (int or np.ndarray, optional): Size of the nodes. Can be a single integer or an array of sizes. Defaults to 50.
293
295
  node_shape (str, optional): Shape of the nodes. Defaults to "o".
296
+ node_edgewidth (float, optional): Width of the node edges. Defaults to 1.0.
294
297
  edge_width (float, optional): Width of the edges. Defaults to 1.0.
295
298
  node_color (str, list, tuple, or np.ndarray, optional): Color of the nodes. Can be a single color or an array of colors. Defaults to "white".
296
299
  node_edgecolor (str, list, tuple, or np.ndarray, optional): Color of the node edges. Defaults to "black".
@@ -301,7 +304,6 @@ class NetworkPlotter:
301
304
  Raises:
302
305
  ValueError: If no valid nodes are found in the network graph.
303
306
  """
304
- # Don't log subnetwork parameters as they are specific to individual annotations
305
307
  # Filter to get node IDs and their coordinates
306
308
  node_ids = [
307
309
  self.graph.node_label_to_id_map.get(node)
@@ -311,10 +313,18 @@ class NetworkPlotter:
311
313
  if not node_ids:
312
314
  raise ValueError("No nodes found in the network graph.")
313
315
 
316
+ # If node_color is an array, match its length to the found node_ids
317
+ if not isinstance(node_color, str):
318
+ node_color = [
319
+ node_color[nodes.index(node)]
320
+ for node in nodes
321
+ if node in self.graph.node_label_to_id_map
322
+ ]
323
+
314
324
  # Convert colors to RGBA using the _to_rgba helper function
315
- node_color = _to_rgba(node_color, node_alpha)
316
- node_edgecolor = _to_rgba(node_edgecolor, 1.0) # Node edges usually fully opaque
317
- edge_color = _to_rgba(edge_color, edge_alpha)
325
+ node_color = _to_rgba(node_color, node_alpha, num_repeats=len(node_ids))
326
+ node_edgecolor = _to_rgba(node_edgecolor, 1.0, num_repeats=len(node_ids))
327
+ edge_color = _to_rgba(edge_color, edge_alpha, num_repeats=len(self.graph.network.edges))
318
328
  # Get the coordinates of the filtered nodes
319
329
  node_coordinates = {node_id: self.graph.node_coordinates[node_id] for node_id in node_ids}
320
330
 
@@ -327,6 +337,7 @@ class NetworkPlotter:
327
337
  node_shape=node_shape,
328
338
  node_color=node_color,
329
339
  edgecolors=node_edgecolor,
340
+ linewidths=node_edgewidth,
330
341
  ax=self.ax,
331
342
  )
332
343
  # Draw the edges between the specified nodes in the subnetwork
@@ -372,11 +383,11 @@ class NetworkPlotter:
372
383
  )
373
384
 
374
385
  # Ensure color is converted to RGBA with repetition matching the number of domains
375
- color = _to_rgba(color, alpha, num_repeats=len(self.graph.domain_to_nodes))
386
+ color = _to_rgba(color, alpha, num_repeats=len(self.graph.domain_to_nodes_map))
376
387
  # Extract node coordinates from the network graph
377
388
  node_coordinates = self.graph.node_coordinates
378
389
  # Draw contours for each domain in the network
379
- for idx, (_, nodes) in enumerate(self.graph.domain_to_nodes.items()):
390
+ for idx, (_, nodes) in enumerate(self.graph.domain_to_nodes_map.items()):
380
391
  if len(nodes) > 1:
381
392
  self._draw_kde_contour(
382
393
  self.ax,
@@ -579,9 +590,9 @@ class NetworkPlotter:
579
590
  )
580
591
 
581
592
  # Convert colors to RGBA using the _to_rgba helper function, applying alpha separately for font and arrow
582
- fontcolor = _to_rgba(fontcolor, fontalpha, num_repeats=len(self.graph.domain_to_nodes))
593
+ fontcolor = _to_rgba(fontcolor, fontalpha, num_repeats=len(self.graph.domain_to_nodes_map))
583
594
  arrow_color = _to_rgba(
584
- arrow_color, arrow_alpha, num_repeats=len(self.graph.domain_to_nodes)
595
+ arrow_color, arrow_alpha, num_repeats=len(self.graph.domain_to_nodes_map)
585
596
  )
586
597
 
587
598
  # Normalize words_to_omit to lowercase
@@ -590,7 +601,7 @@ class NetworkPlotter:
590
601
 
591
602
  # Calculate the center and radius of the network
592
603
  domain_centroids = {}
593
- for domain, nodes in self.graph.domain_to_nodes.items():
604
+ for domain, nodes in self.graph.domain_to_nodes_map.items():
594
605
  if nodes: # Skip if the domain has no nodes
595
606
  domain_centroids[domain] = self._calculate_domain_centroid(nodes)
596
607
 
@@ -810,9 +821,9 @@ class NetworkPlotter:
810
821
  Returns:
811
822
  np.ndarray: Array of node sizes, with enriched nodes larger than non-enriched ones.
812
823
  """
813
- # Merge all enriched nodes from the domain_to_nodes dictionary
824
+ # Merge all enriched nodes from the domain_to_nodes_map dictionary
814
825
  enriched_nodes = set()
815
- for _, nodes in self.graph.domain_to_nodes.items():
826
+ for _, nodes in self.graph.domain_to_nodes_map.items():
816
827
  enriched_nodes.update(nodes)
817
828
 
818
829
  # Initialize all node sizes to the non-enriched size
@@ -927,7 +938,7 @@ class NetworkPlotter:
927
938
  random_seed=random_seed,
928
939
  )
929
940
  annotated_colors = []
930
- for _, nodes in self.graph.domain_to_nodes.items():
941
+ for _, nodes in self.graph.domain_to_nodes_map.items():
931
942
  if len(nodes) > 1:
932
943
  # For multi-node domains, choose the brightest color based on RGB sum
933
944
  domain_colors = np.array([node_colors[node] for node in nodes])
@@ -968,33 +979,51 @@ def _to_rgba(
968
979
  alpha: float = 1.0,
969
980
  num_repeats: Union[int, None] = None,
970
981
  ) -> np.ndarray:
971
- """Convert a color or array of colors to RGBA format, applying or updating the alpha as needed.
982
+ """Convert a color or array of colors to RGBA format, applying alpha only if the color is RGB.
972
983
 
973
984
  Args:
974
985
  color (Union[str, list, tuple, np.ndarray]): The color(s) to convert. Can be a string, list, tuple, or np.ndarray.
975
- alpha (float, optional): Alpha value (transparency) to apply. Defaults to 1.0.
986
+ alpha (float, optional): Alpha value (transparency) to apply if the color is in RGB format. Defaults to 1.0.
976
987
  num_repeats (int or None, optional): If provided, the color will be repeated this many times. Defaults to None.
977
988
 
978
989
  Returns:
979
990
  np.ndarray: The RGBA color or array of RGBA colors.
980
991
  """
981
- # Handle single color case
992
+ # Handle single color case (string, RGB, or RGBA)
982
993
  if isinstance(color, str) or (
983
994
  isinstance(color, (list, tuple, np.ndarray)) and len(color) in [3, 4]
984
995
  ):
985
- rgba_color = np.array(mcolors.to_rgba(color, alpha))
986
- # Repeat the color if repeat argument is provided
996
+ rgba_color = np.array(mcolors.to_rgba(color))
997
+ # Only set alpha if the input is an RGB color or a string (not RGBA)
998
+ if len(rgba_color) == 4 and (
999
+ len(color) == 3 or isinstance(color, str)
1000
+ ): # If it's RGB or a string, set the alpha
1001
+ rgba_color[3] = alpha
1002
+
1003
+ # Repeat the color if num_repeats argument is provided
987
1004
  if num_repeats is not None:
988
1005
  return np.array([rgba_color] * num_repeats)
989
1006
 
990
1007
  return rgba_color
991
1008
 
992
- # Handle array of colors case
993
- elif isinstance(color, (list, tuple, np.ndarray)) and isinstance(
994
- color[0], (list, tuple, np.ndarray)
995
- ):
996
- rgba_colors = [mcolors.to_rgba(c, alpha) if len(c) == 3 else np.array(c) for c in color]
997
- # Repeat the colors if repeat argument is provided
1009
+ # Handle array of colors case (including strings, RGB, and RGBA)
1010
+ elif isinstance(color, (list, tuple, np.ndarray)):
1011
+ rgba_colors = []
1012
+ for c in color:
1013
+ # Ensure each element is either a valid string or a list/tuple of length 3 (RGB) or 4 (RGBA)
1014
+ if isinstance(c, str) or (
1015
+ isinstance(c, (list, tuple, np.ndarray)) and len(c) in [3, 4]
1016
+ ):
1017
+ rgba_c = np.array(mcolors.to_rgba(c))
1018
+ # Apply alpha only to RGB colors (not RGBA) and strings
1019
+ if len(rgba_c) == 4 and (len(c) == 3 or isinstance(c, str)):
1020
+ rgba_c[3] = alpha
1021
+
1022
+ rgba_colors.append(rgba_c)
1023
+ else:
1024
+ raise ValueError(f"Invalid color: {c}. Must be a valid RGB/RGBA or string color.")
1025
+
1026
+ # Repeat the colors if num_repeats argument is provided
998
1027
  if num_repeats is not None and len(rgba_colors) == 1:
999
1028
  return np.array([rgba_colors[0]] * num_repeats)
1000
1029
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: risk-network
3
- Version: 0.0.6b2
3
+ Version: 0.0.6b4
4
4
  Summary: A Python package for biological network analysis
5
5
  Author: Ira Horecka
6
6
  Author-email: Ira Horecka <ira89@icloud.com>
File without changes
File without changes
File without changes
File without changes