risk-network 0.0.7b11__tar.gz → 0.0.8b0__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 (38) hide show
  1. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/PKG-INFO +1 -1
  2. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/__init__.py +1 -1
  3. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/annotations/annotations.py +1 -1
  4. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/annotations/io.py +12 -12
  5. risk_network-0.0.8b0/risk/log/config.py +139 -0
  6. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/neighborhoods/domains.py +2 -2
  7. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/neighborhoods/neighborhoods.py +2 -2
  8. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/network/io.py +12 -12
  9. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/network/plot.py +124 -17
  10. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/risk.py +14 -21
  11. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/stats/permutation/permutation.py +20 -7
  12. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk_network.egg-info/PKG-INFO +1 -1
  13. risk_network-0.0.7b11/risk/log/config.py +0 -48
  14. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/LICENSE +0 -0
  15. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/MANIFEST.in +0 -0
  16. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/README.md +0 -0
  17. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/pyproject.toml +0 -0
  18. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/annotations/__init__.py +0 -0
  19. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/constants.py +0 -0
  20. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/log/__init__.py +0 -0
  21. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/log/params.py +0 -0
  22. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/neighborhoods/__init__.py +0 -0
  23. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/neighborhoods/community.py +0 -0
  24. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/network/__init__.py +0 -0
  25. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/network/geometry.py +0 -0
  26. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/network/graph.py +0 -0
  27. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/stats/__init__.py +0 -0
  28. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/stats/hypergeom.py +0 -0
  29. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/stats/permutation/__init__.py +0 -0
  30. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/stats/permutation/test_functions.py +0 -0
  31. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/stats/poisson.py +0 -0
  32. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk/stats/stats.py +0 -0
  33. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk_network.egg-info/SOURCES.txt +0 -0
  34. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk_network.egg-info/dependency_links.txt +0 -0
  35. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk_network.egg-info/requires.txt +0 -0
  36. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/risk_network.egg-info/top_level.txt +0 -0
  37. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/setup.cfg +0 -0
  38. {risk_network-0.0.7b11 → risk_network-0.0.8b0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: risk-network
3
- Version: 0.0.7b11
3
+ Version: 0.0.8b0
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.7-beta.11"
10
+ __version__ = "0.0.8-beta.0"
@@ -4,7 +4,7 @@ risk/annotations/annotations
4
4
  """
5
5
 
6
6
  from collections import Counter
7
- from itertools import compress, permutations
7
+ from itertools import compress
8
8
  from typing import Any, Dict, List, Set
9
9
 
10
10
  import networkx as nx
@@ -25,12 +25,12 @@ class AnnotationsIO:
25
25
  def __init__(self):
26
26
  pass
27
27
 
28
- def load_json_annotation(self, filepath: str, network: nx.Graph) -> Dict[str, Any]:
28
+ def load_json_annotation(self, network: nx.Graph, filepath: str) -> Dict[str, Any]:
29
29
  """Load annotations from a JSON file and convert them to a DataFrame.
30
30
 
31
31
  Args:
32
- filepath (str): Path to the JSON annotations file.
33
32
  network (NetworkX graph): The network to which the annotations are related.
33
+ filepath (str): Path to the JSON annotations file.
34
34
 
35
35
  Returns:
36
36
  dict: A dictionary containing ordered nodes, ordered annotations, and the annotations matrix.
@@ -49,8 +49,8 @@ class AnnotationsIO:
49
49
 
50
50
  def load_excel_annotation(
51
51
  self,
52
- filepath: str,
53
52
  network: nx.Graph,
53
+ filepath: str,
54
54
  label_colname: str = "label",
55
55
  nodes_colname: str = "nodes",
56
56
  sheet_name: str = "Sheet1",
@@ -59,8 +59,8 @@ class AnnotationsIO:
59
59
  """Load annotations from an Excel file and associate them with the network.
60
60
 
61
61
  Args:
62
- filepath (str): Path to the Excel annotations file.
63
62
  network (nx.Graph): The NetworkX graph to which the annotations are related.
63
+ filepath (str): Path to the Excel annotations file.
64
64
  label_colname (str): Name of the column containing the labels (e.g., GO terms).
65
65
  nodes_colname (str): Name of the column containing the nodes associated with each label.
66
66
  sheet_name (str, optional): The name of the Excel sheet to load (default is 'Sheet1').
@@ -87,8 +87,8 @@ class AnnotationsIO:
87
87
 
88
88
  def load_csv_annotation(
89
89
  self,
90
- filepath: str,
91
90
  network: nx.Graph,
91
+ filepath: str,
92
92
  label_colname: str = "label",
93
93
  nodes_colname: str = "nodes",
94
94
  nodes_delimiter: str = ";",
@@ -96,8 +96,8 @@ class AnnotationsIO:
96
96
  """Load annotations from a CSV file and associate them with the network.
97
97
 
98
98
  Args:
99
- filepath (str): Path to the CSV annotations file.
100
99
  network (nx.Graph): The NetworkX graph to which the annotations are related.
100
+ filepath (str): Path to the CSV annotations file.
101
101
  label_colname (str): Name of the column containing the labels (e.g., GO terms).
102
102
  nodes_colname (str): Name of the column containing the nodes associated with each label.
103
103
  nodes_delimiter (str, optional): Delimiter used to separate multiple nodes within the nodes column (default is ';').
@@ -121,8 +121,8 @@ class AnnotationsIO:
121
121
 
122
122
  def load_tsv_annotation(
123
123
  self,
124
- filepath: str,
125
124
  network: nx.Graph,
125
+ filepath: str,
126
126
  label_colname: str = "label",
127
127
  nodes_colname: str = "nodes",
128
128
  nodes_delimiter: str = ";",
@@ -130,8 +130,8 @@ class AnnotationsIO:
130
130
  """Load annotations from a TSV file and associate them with the network.
131
131
 
132
132
  Args:
133
- filepath (str): Path to the TSV annotations file.
134
133
  network (nx.Graph): The NetworkX graph to which the annotations are related.
134
+ filepath (str): Path to the TSV annotations file.
135
135
  label_colname (str): Name of the column containing the labels (e.g., GO terms).
136
136
  nodes_colname (str): Name of the column containing the nodes associated with each label.
137
137
  nodes_delimiter (str, optional): Delimiter used to separate multiple nodes within the nodes column (default is ';').
@@ -153,12 +153,12 @@ 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]:
156
+ def load_dict_annotation(self, network: nx.Graph, content: Dict[str, Any]) -> Dict[str, Any]:
157
157
  """Load annotations from a provided dictionary and convert them to a dictionary annotation.
158
158
 
159
159
  Args:
160
- content (dict): The annotations dictionary to load.
161
160
  network (NetworkX graph): The network to which the annotations are related.
161
+ content (dict): The annotations dictionary to load.
162
162
 
163
163
  Returns:
164
164
  dict: A dictionary containing ordered nodes, ordered annotations, and the annotations matrix.
@@ -219,6 +219,6 @@ def _log_loading(filetype: str, filepath: str = "") -> None:
219
219
  filepath (str, optional): The path to the file being loaded.
220
220
  """
221
221
  log_header("Loading annotations")
222
- logger.info(f"Filetype: {filetype}")
222
+ logger.debug(f"Filetype: {filetype}")
223
223
  if filepath:
224
- logger.info(f"Filepath: {filepath}")
224
+ logger.debug(f"Filepath: {filepath}")
@@ -0,0 +1,139 @@
1
+ """
2
+ risk/log/config
3
+ ~~~~~~~~~~~~~~~
4
+ """
5
+
6
+ import logging
7
+
8
+
9
+ def in_jupyter():
10
+ """Check if the code is running in a Jupyter notebook environment.
11
+
12
+ Returns:
13
+ bool: True if running in a Jupyter notebook or QtConsole, False otherwise.
14
+ """
15
+ try:
16
+ shell = get_ipython().__class__.__name__
17
+ if shell == "ZMQInteractiveShell": # Jupyter Notebook or QtConsole
18
+ return True
19
+ elif shell == "TerminalInteractiveShell": # Terminal running IPython
20
+ return False
21
+ except NameError:
22
+ return False # Not in Jupyter
23
+
24
+
25
+ # Define the MockLogger class to replicate logging behavior with print statements in Jupyter
26
+ class MockLogger:
27
+ """MockLogger: A lightweight logger replacement using print statements in Jupyter.
28
+
29
+ The MockLogger class replicates the behavior of a standard logger using print statements
30
+ to display messages. This is primarily used in a Jupyter environment to show outputs
31
+ directly in the notebook. The class supports logging levels such as `info`, `debug`,
32
+ `warning`, and `error`, while the `verbose` attribute controls whether to display non-error messages.
33
+ """
34
+
35
+ def __init__(self, verbose: bool = True):
36
+ """Initialize the MockLogger with verbosity settings.
37
+
38
+ Args:
39
+ verbose (bool): If True, display all log messages (info, debug, warning).
40
+ If False, only display error messages. Defaults to True.
41
+ """
42
+ self.verbose = verbose
43
+
44
+ def info(self, message: str) -> None:
45
+ """Display an informational message.
46
+
47
+ Args:
48
+ message (str): The informational message to be printed.
49
+ """
50
+ if self.verbose:
51
+ print(message)
52
+
53
+ def debug(self, message: str) -> None:
54
+ """Display a debug message.
55
+
56
+ Args:
57
+ message (str): The debug message to be printed.
58
+ """
59
+ if self.verbose:
60
+ print(message)
61
+
62
+ def warning(self, message: str) -> None:
63
+ """Display a warning message.
64
+
65
+ Args:
66
+ message (str): The warning message to be printed.
67
+ """
68
+ print(message)
69
+
70
+ def error(self, message: str) -> None:
71
+ """Display an error message.
72
+
73
+ Args:
74
+ message (str): The error message to be printed.
75
+ """
76
+ print(message)
77
+
78
+ def setLevel(self, level: int) -> None:
79
+ """Adjust verbosity based on the logging level.
80
+
81
+ Args:
82
+ level (int): Logging level to control message display.
83
+ - logging.DEBUG sets verbose to True (show all messages).
84
+ - logging.WARNING sets verbose to False (show only warning, error, and critical messages).
85
+ """
86
+ if level == logging.DEBUG:
87
+ self.verbose = True # Show all messages
88
+ elif level == logging.WARNING:
89
+ self.verbose = False # Suppress all except warning, error, and critical messages
90
+
91
+
92
+ # Set up logger based on environment
93
+ if not in_jupyter():
94
+ # Set up logger normally for .py files or terminal environments
95
+ logger = logging.getLogger("risk_logger")
96
+ logger.setLevel(logging.DEBUG)
97
+ console_handler = logging.StreamHandler()
98
+ console_handler.setLevel(logging.DEBUG)
99
+ console_handler.setFormatter(logging.Formatter("%(message)s"))
100
+
101
+ if not logger.hasHandlers():
102
+ logger.addHandler(console_handler)
103
+ else:
104
+ # If in Jupyter, use the MockLogger
105
+ logger = MockLogger()
106
+
107
+
108
+ def set_global_verbosity(verbose):
109
+ """Set the global verbosity level for the logger.
110
+
111
+ Args:
112
+ verbose (bool): Whether to display all log messages (True) or only error messages (False).
113
+
114
+ Returns:
115
+ None
116
+ """
117
+ if not isinstance(logger, MockLogger):
118
+ # For the regular logger, adjust logging levels
119
+ if verbose:
120
+ logger.setLevel(logging.DEBUG) # Show all messages
121
+ console_handler.setLevel(logging.DEBUG)
122
+ else:
123
+ logger.setLevel(logging.WARNING) # Show only warning, error, and critical messages
124
+ console_handler.setLevel(logging.WARNING)
125
+ else:
126
+ # For the MockLogger, set verbosity directly
127
+ logger.setLevel(logging.DEBUG if verbose else logging.WARNING)
128
+
129
+
130
+ def log_header(input_string: str) -> None:
131
+ """Log the input string as a header with a line of dashes above and below it.
132
+
133
+ Args:
134
+ input_string (str): The string to be printed as a header.
135
+ """
136
+ border = "-" * len(input_string)
137
+ logger.info(border)
138
+ logger.info(input_string)
139
+ logger.info(border)
@@ -46,10 +46,10 @@ def define_domains(
46
46
  )
47
47
  # Perform hierarchical clustering
48
48
  Z = linkage(m, method=best_linkage, metric=best_metric)
49
- logger.info(
49
+ logger.warning(
50
50
  f"Linkage criterion: '{linkage_criterion}'\nLinkage method: '{best_linkage}'\nLinkage metric: '{best_metric}'"
51
51
  )
52
- logger.info(f"Optimal linkage threshold: {round(best_threshold, 3)}")
52
+ logger.debug(f"Optimal linkage threshold: {round(best_threshold, 3)}")
53
53
  # Calculate the optimal threshold for clustering
54
54
  max_d_optimal = np.max(Z[:, 2]) * best_threshold
55
55
  # Assign domains to the annotations matrix
@@ -130,7 +130,7 @@ def process_neighborhoods(
130
130
  enrichment_matrix = neighborhoods["enrichment_matrix"]
131
131
  binary_enrichment_matrix = neighborhoods["binary_enrichment_matrix"]
132
132
  significant_enrichment_matrix = neighborhoods["significant_enrichment_matrix"]
133
- logger.info(f"Imputation depth: {impute_depth}")
133
+ logger.debug(f"Imputation depth: {impute_depth}")
134
134
  if impute_depth:
135
135
  (
136
136
  enrichment_matrix,
@@ -143,7 +143,7 @@ def process_neighborhoods(
143
143
  max_depth=impute_depth,
144
144
  )
145
145
 
146
- logger.info(f"Pruning threshold: {prune_threshold}")
146
+ logger.debug(f"Pruning threshold: {prune_threshold}")
147
147
  if prune_threshold:
148
148
  (
149
149
  enrichment_matrix,
@@ -451,10 +451,10 @@ class NetworkIO:
451
451
  # Log the number of nodes and edges before and after cleaning
452
452
  num_final_nodes = G.number_of_nodes()
453
453
  num_final_edges = G.number_of_edges()
454
- logger.info(f"Initial node count: {num_initial_nodes}")
455
- logger.info(f"Final node count: {num_final_nodes}")
456
- logger.info(f"Initial edge count: {num_initial_edges}")
457
- logger.info(f"Final edge count: {num_final_edges}")
454
+ logger.debug(f"Initial node count: {num_initial_nodes}")
455
+ logger.debug(f"Final node count: {num_final_nodes}")
456
+ logger.debug(f"Initial edge count: {num_initial_edges}")
457
+ logger.debug(f"Final edge count: {num_final_edges}")
458
458
 
459
459
  def _assign_edge_weights(self, G: nx.Graph) -> None:
460
460
  """Assign weights to the edges in the graph.
@@ -472,7 +472,7 @@ class NetworkIO:
472
472
  ) # Default to 1.0 if 'weight' not present
473
473
 
474
474
  if self.include_edge_weight and missing_weights:
475
- logger.info(f"Total edges missing weights: {missing_weights}")
475
+ logger.debug(f"Total edges missing weights: {missing_weights}")
476
476
 
477
477
  def _validate_nodes(self, G: nx.Graph) -> None:
478
478
  """Validate the graph structure and attributes.
@@ -511,13 +511,13 @@ class NetworkIO:
511
511
  filepath (str, optional): The path to the file being loaded. Defaults to "".
512
512
  """
513
513
  log_header("Loading network")
514
- logger.info(f"Filetype: {filetype}")
514
+ logger.debug(f"Filetype: {filetype}")
515
515
  if filepath:
516
- logger.info(f"Filepath: {filepath}")
517
- logger.info(f"Edge weight: {'Included' if self.include_edge_weight else 'Excluded'}")
516
+ logger.debug(f"Filepath: {filepath}")
517
+ logger.debug(f"Edge weight: {'Included' if self.include_edge_weight else 'Excluded'}")
518
518
  if self.include_edge_weight:
519
- logger.info(f"Weight label: {self.weight_label}")
520
- logger.info(f"Minimum edges per node: {self.min_edges_per_node}")
521
- logger.info(f"Projection: {'Sphere' if self.compute_sphere else 'Plane'}")
519
+ logger.debug(f"Weight label: {self.weight_label}")
520
+ logger.debug(f"Minimum edges per node: {self.min_edges_per_node}")
521
+ logger.debug(f"Projection: {'Sphere' if self.compute_sphere else 'Plane'}")
522
522
  if self.compute_sphere:
523
- logger.info(f"Surface depth: {self.surface_depth}")
523
+ logger.debug(f"Surface depth: {self.surface_depth}")
@@ -10,10 +10,11 @@ import matplotlib.pyplot as plt
10
10
  import networkx as nx
11
11
  import numpy as np
12
12
  import pandas as pd
13
+ from scipy import linalg
13
14
  from scipy.ndimage import label
14
15
  from scipy.stats import gaussian_kde
15
16
 
16
- from risk.log import params
17
+ from risk.log import params, logger
17
18
  from risk.network.graph import NetworkGraph
18
19
 
19
20
 
@@ -86,6 +87,83 @@ class NetworkPlotter:
86
87
 
87
88
  return ax
88
89
 
90
+ def plot_title(
91
+ self,
92
+ title: Union[str, None] = None,
93
+ subtitle: Union[str, None] = None,
94
+ title_fontsize: int = 20,
95
+ subtitle_fontsize: int = 14,
96
+ font: str = "Arial",
97
+ title_color: str = "black",
98
+ subtitle_color: str = "gray",
99
+ title_y: float = 0.975,
100
+ title_space_offset: float = 0.075,
101
+ subtitle_offset: float = 0.025,
102
+ ) -> None:
103
+ """Plot title and subtitle on the network graph with customizable parameters.
104
+
105
+ Args:
106
+ title (str, optional): Title of the plot. Defaults to None.
107
+ subtitle (str, optional): Subtitle of the plot. Defaults to None.
108
+ title_fontsize (int, optional): Font size for the title. Defaults to 16.
109
+ subtitle_fontsize (int, optional): Font size for the subtitle. Defaults to 12.
110
+ font (str, optional): Font family used for both title and subtitle. Defaults to "Arial".
111
+ title_color (str, optional): Color of the title text. Defaults to "black".
112
+ subtitle_color (str, optional): Color of the subtitle text. Defaults to "gray".
113
+ title_y (float, optional): Y-axis position of the title. Defaults to 0.975.
114
+ title_space_offset (float, optional): Fraction of figure height to leave for the space above the plot. Defaults to 0.075.
115
+ subtitle_offset (float, optional): Offset factor to position the subtitle below the title. Defaults to 0.025.
116
+ """
117
+ # Log the title and subtitle parameters
118
+ params.log_plotter(
119
+ title=title,
120
+ subtitle=subtitle,
121
+ title_fontsize=title_fontsize,
122
+ subtitle_fontsize=subtitle_fontsize,
123
+ title_subtitle_font=font,
124
+ title_color=title_color,
125
+ subtitle_color=subtitle_color,
126
+ subtitle_offset=subtitle_offset,
127
+ title_y=title_y,
128
+ title_space_offset=title_space_offset,
129
+ )
130
+
131
+ # Get the current figure and axis dimensions
132
+ fig = self.ax.figure
133
+ # Use a tight layout to ensure that title and subtitle do not overlap with the original plot
134
+ fig.tight_layout(
135
+ rect=[0, 0, 1, 1 - title_space_offset]
136
+ ) # Leave space above the plot for title
137
+
138
+ # Plot title if provided
139
+ if title:
140
+ # Set the title using figure's suptitle to ensure centering
141
+ self.ax.figure.suptitle(
142
+ title,
143
+ fontsize=title_fontsize,
144
+ color=title_color,
145
+ fontname=font,
146
+ x=0.5, # Center the title horizontally
147
+ ha="center",
148
+ va="top",
149
+ y=title_y,
150
+ )
151
+
152
+ # Plot subtitle if provided
153
+ if subtitle:
154
+ # Calculate the subtitle's y position based on title's position and subtitle_offset
155
+ subtitle_y_position = title_y - subtitle_offset
156
+ self.ax.figure.text(
157
+ 0.5, # Ensure horizontal centering for subtitle
158
+ subtitle_y_position,
159
+ subtitle,
160
+ ha="center",
161
+ va="top",
162
+ fontname=font,
163
+ fontsize=subtitle_fontsize,
164
+ color=subtitle_color,
165
+ )
166
+
89
167
  def plot_circle_perimeter(
90
168
  self,
91
169
  scale: float = 1.0,
@@ -510,26 +588,52 @@ class NetworkPlotter:
510
588
  # Extract the positions of the specified nodes
511
589
  points = np.array([pos[n] for n in nodes])
512
590
  if len(points) <= 1:
513
- return # Not enough points to form a contour
591
+ return None # Not enough points to form a contour
514
592
 
593
+ # Check if the KDE forms a single connected component
515
594
  connected = False
595
+ z = None # Initialize z to None to avoid UnboundLocalError
516
596
  while not connected and bandwidth <= 100.0:
517
- # Perform KDE on the points with the given bandwidth
518
- kde = gaussian_kde(points.T, bw_method=bandwidth)
519
- xmin, ymin = points.min(axis=0) - bandwidth
520
- xmax, ymax = points.max(axis=0) + bandwidth
521
- x, y = np.mgrid[
522
- xmin : xmax : complex(0, grid_size), ymin : ymax : complex(0, grid_size)
523
- ]
524
- z = kde(np.vstack([x.ravel(), y.ravel()])).reshape(x.shape)
525
- # Check if the KDE forms a single connected component
526
- connected = _is_connected(z)
527
- if not connected:
528
- bandwidth += 0.05 # Increase bandwidth slightly and retry
597
+ try:
598
+ # Perform KDE on the points with the given bandwidth
599
+ kde = gaussian_kde(points.T, bw_method=bandwidth)
600
+ xmin, ymin = points.min(axis=0) - bandwidth
601
+ xmax, ymax = points.max(axis=0) + bandwidth
602
+ x, y = np.mgrid[
603
+ xmin : xmax : complex(0, grid_size), ymin : ymax : complex(0, grid_size)
604
+ ]
605
+ z = kde(np.vstack([x.ravel(), y.ravel()])).reshape(x.shape)
606
+ # Check if the KDE forms a single connected component
607
+ connected = _is_connected(z)
608
+ if not connected:
609
+ bandwidth += 0.05 # Increase bandwidth slightly and retry
610
+ except linalg.LinAlgError:
611
+ bandwidth += 0.05 # Increase bandwidth and retry
612
+ except Exception as e:
613
+ # Catch any other exceptions and log them
614
+ logger.error(f"Unexpected error when drawing KDE contour: {e}")
615
+ return None
616
+
617
+ # If z is still None, the KDE computation failed
618
+ if z is None:
619
+ logger.error("Failed to compute KDE. Skipping contour plot for these nodes.")
620
+ return None
529
621
 
530
622
  # Define contour levels based on the density
531
623
  min_density, max_density = z.min(), z.max()
624
+ if min_density == max_density:
625
+ logger.warning(
626
+ "Contour levels could not be created due to lack of variation in density."
627
+ )
628
+ return None
629
+
630
+ # Create contour levels based on the density values
532
631
  contour_levels = np.linspace(min_density, max_density, levels)[1:]
632
+ if len(contour_levels) < 2 or not np.all(np.diff(contour_levels) > 0):
633
+ logger.error("Contour levels must be strictly increasing. Skipping contour plot.")
634
+ return None
635
+
636
+ # Set the contour color and linestyle
533
637
  contour_colors = [color for _ in range(levels - 1)]
534
638
  # Plot the filled contours using fill_alpha for transparency
535
639
  if fill_alpha > 0:
@@ -554,6 +658,7 @@ class NetworkPlotter:
554
658
  linewidths=linewidth,
555
659
  alpha=alpha,
556
660
  )
661
+
557
662
  # Set linewidth for the contour lines to 0 for levels other than the base level
558
663
  for i in range(1, len(contour_levels)):
559
664
  c.collections[i].set_linewidth(0)
@@ -1090,14 +1195,16 @@ class NetworkPlotter:
1090
1195
  return np.array(annotated_colors)
1091
1196
 
1092
1197
  @staticmethod
1093
- def savefig(*args, **kwargs) -> None:
1094
- """Save the current plot to a file.
1198
+ def savefig(*args, pad_inches: float = 0.5, dpi: int = 100, **kwargs) -> None:
1199
+ """Save the current plot to a file with additional export options.
1095
1200
 
1096
1201
  Args:
1097
1202
  *args: Positional arguments passed to `plt.savefig`.
1203
+ pad_inches (float, optional): Padding around the figure when saving. Defaults to 0.5.
1204
+ dpi (int, optional): Dots per inch (DPI) for the exported image. Defaults to 300.
1098
1205
  **kwargs: Keyword arguments passed to `plt.savefig`, such as filename and format.
1099
1206
  """
1100
- plt.savefig(*args, bbox_inches="tight", **kwargs)
1207
+ plt.savefig(*args, bbox_inches="tight", pad_inches=pad_inches, dpi=dpi, **kwargs)
1101
1208
 
1102
1209
  @staticmethod
1103
1210
  def show(*args, **kwargs) -> None:
@@ -33,24 +33,17 @@ class RISK(NetworkIO, AnnotationsIO):
33
33
  and performing network-based statistical analysis, such as neighborhood significance testing.
34
34
  """
35
35
 
36
- def __init__(self, *args, verbose: bool = True, **kwargs):
36
+ def __init__(self, verbose: bool = True):
37
37
  """Initialize the RISK class with configuration settings.
38
38
 
39
39
  Args:
40
40
  verbose (bool): If False, suppresses all log messages to the console. Defaults to True.
41
- *args: Variable length argument list.
42
- **kwargs: Arbitrary keyword arguments.
43
-
44
- Note:
45
- - All *args and **kwargs are passed to NetworkIO's __init__ method.
46
- - AnnotationsIO does not take any arguments and is initialized without them.
47
41
  """
48
42
  # Set global verbosity for logging
49
43
  set_global_verbosity(verbose)
50
44
  # Initialize and log network parameters
51
45
  params.initialize()
52
- # Use super() to call NetworkIO's __init__ with the given arguments and keyword arguments
53
- super().__init__(*args, **kwargs)
46
+ super().__init__()
54
47
 
55
48
  @property
56
49
  def params(self) -> params:
@@ -221,10 +214,10 @@ class RISK(NetworkIO, AnnotationsIO):
221
214
  )
222
215
 
223
216
  # Log and display permutation test settings
224
- logger.info(f"Neighborhood scoring metric: '{score_metric}'")
225
- logger.info(f"Null distribution: '{null_distribution}'")
226
- logger.info(f"Number of permutations: {num_permutations}")
227
- logger.info(f"Maximum workers: {max_workers}")
217
+ logger.debug(f"Neighborhood scoring metric: '{score_metric}'")
218
+ logger.debug(f"Null distribution: '{null_distribution}'")
219
+ logger.debug(f"Number of permutations: {num_permutations}")
220
+ logger.debug(f"Maximum workers: {max_workers}")
228
221
  # Run permutation test to compute neighborhood significance
229
222
  neighborhood_significance = compute_permutation_test(
230
223
  neighborhoods=neighborhoods,
@@ -290,9 +283,9 @@ class RISK(NetworkIO, AnnotationsIO):
290
283
  max_cluster_size=max_cluster_size,
291
284
  )
292
285
 
293
- logger.info(f"p-value cutoff: {pval_cutoff}")
294
- logger.info(f"FDR BH cutoff: {fdr_cutoff}")
295
- logger.info(
286
+ logger.debug(f"p-value cutoff: {pval_cutoff}")
287
+ logger.debug(f"FDR BH cutoff: {fdr_cutoff}")
288
+ logger.debug(
296
289
  f"Significance tail: '{tail}' ({'enrichment' if tail == 'right' else 'depletion' if tail == 'left' else 'both'})"
297
290
  )
298
291
  # Calculate significant neighborhoods based on the provided parameters
@@ -314,8 +307,8 @@ class RISK(NetworkIO, AnnotationsIO):
314
307
  )
315
308
 
316
309
  log_header("Finding top annotations")
317
- logger.info(f"Min cluster size: {min_cluster_size}")
318
- logger.info(f"Max cluster size: {max_cluster_size}")
310
+ logger.debug(f"Min cluster size: {min_cluster_size}")
311
+ logger.debug(f"Max cluster size: {max_cluster_size}")
319
312
  # Define top annotations based on processed neighborhoods
320
313
  top_annotations = self._define_top_annotations(
321
314
  network=network,
@@ -414,9 +407,9 @@ class RISK(NetworkIO, AnnotationsIO):
414
407
  else:
415
408
  for_print_distance_metric = distance_metric
416
409
  # Log and display neighborhood settings
417
- logger.info(f"Distance metric: '{for_print_distance_metric}'")
418
- logger.info(f"Edge length threshold: {edge_length_threshold}")
419
- logger.info(f"Random seed: {random_seed}")
410
+ logger.debug(f"Distance metric: '{for_print_distance_metric}'")
411
+ logger.debug(f"Edge length threshold: {edge_length_threshold}")
412
+ logger.debug(f"Random seed: {random_seed}")
420
413
 
421
414
  # Compute neighborhoods based on the network and distance metric
422
415
  neighborhoods = get_network_neighborhoods(
@@ -133,6 +133,7 @@ def _run_permutation_test(
133
133
  observed_neighborhood_scores,
134
134
  neighborhood_score_func,
135
135
  subset_size + (1 if i < remainder else 0),
136
+ num_permutations,
136
137
  progress_counter,
137
138
  max_workers,
138
139
  rng, # Pass the random number generator to each worker
@@ -144,11 +145,9 @@ def _run_permutation_test(
144
145
  results = pool.starmap_async(_permutation_process_subset, params_list, chunksize=1)
145
146
 
146
147
  # Update progress bar based on progress_counter
147
- # NOTE: Waiting for results to be ready while updating progress bar gives a big improvement
148
- # in performance, especially for large number of permutations and workers
149
148
  while not results.ready():
150
149
  progress.update(progress_counter.value - progress.n)
151
- results.wait(0.05) # Wait for 50ms
150
+ results.wait(0.1) # Wait for 100ms
152
151
  # Ensure progress bar reaches 100%
153
152
  progress.update(total_progress - progress.n)
154
153
 
@@ -167,6 +166,7 @@ def _permutation_process_subset(
167
166
  observed_neighborhood_scores: np.ndarray,
168
167
  neighborhood_score_func: Callable,
169
168
  subset_size: int,
169
+ num_permutations: int,
170
170
  progress_counter: ValueProxy,
171
171
  max_workers: int,
172
172
  rng: np.random.Generator,
@@ -180,6 +180,7 @@ def _permutation_process_subset(
180
180
  observed_neighborhood_scores (np.ndarray): Observed neighborhood scores.
181
181
  neighborhood_score_func (Callable): Function to calculate neighborhood scores.
182
182
  subset_size (int): Number of permutations to run in this subset.
183
+ num_permutations (int): Number of total permutations across all subsets.
183
184
  progress_counter (multiprocessing.managers.ValueProxy): Shared counter for tracking progress.
184
185
  max_workers (int): Number of workers for multiprocessing.
185
186
  rng (np.random.Generator): Random number generator object.
@@ -190,11 +191,15 @@ def _permutation_process_subset(
190
191
  # Initialize local count matrices for this worker
191
192
  local_counts_depletion = np.zeros(observed_neighborhood_scores.shape)
192
193
  local_counts_enrichment = np.zeros(observed_neighborhood_scores.shape)
194
+
193
195
  # NOTE: Limit the number of threads used by NumPy's BLAS implementation to 1 when more than one worker is used.
194
- # This can help prevent oversubscription of CPU resources during multiprocessing, ensuring that each process
195
- # doesn't use more than one CPU core.
196
196
  limits = None if max_workers == 1 else 1
197
197
  with threadpool_limits(limits=limits, user_api="blas"):
198
+ # Initialize a local counter for batched progress updates
199
+ local_progress = 0
200
+ # Calculate the modulo value based on total permutations for 1/100th frequency updates
201
+ modulo_value = max(1, num_permutations // 100)
202
+
198
203
  for _ in range(subset_size):
199
204
  # Permute the annotation matrix using the RNG
200
205
  annotation_matrix_permut = annotation_matrix[rng.permutation(idxs)]
@@ -212,7 +217,15 @@ def _permutation_process_subset(
212
217
  local_counts_enrichment,
213
218
  permuted_neighborhood_scores >= observed_neighborhood_scores,
214
219
  )
215
- # Update the shared progress counter
216
- progress_counter.value += 1
220
+
221
+ # Update local progress counter
222
+ local_progress += 1
223
+ # Update shared progress counter every 1/100th of total permutations
224
+ if local_progress % modulo_value == 0:
225
+ progress_counter.value += modulo_value
226
+
227
+ # Final progress update for any remaining iterations
228
+ if local_progress % modulo_value != 0:
229
+ progress_counter.value += modulo_value
217
230
 
218
231
  return local_counts_depletion, local_counts_enrichment
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: risk-network
3
- Version: 0.0.7b11
3
+ Version: 0.0.8b0
4
4
  Summary: A Python package for biological network analysis
5
5
  Author: Ira Horecka
6
6
  Author-email: Ira Horecka <ira89@icloud.com>
@@ -1,48 +0,0 @@
1
- """
2
- risk/log/config
3
- ~~~~~~~~~~~~~~~
4
- """
5
-
6
- import logging
7
-
8
- # Create and configure the global logger
9
- logger = logging.getLogger("risk_logger")
10
- logger.setLevel(logging.INFO)
11
- # Create and configure the console handler
12
- console_handler = logging.StreamHandler()
13
- console_handler.setLevel(logging.INFO)
14
- # Set the output format for the logger
15
- formatter = logging.Formatter("%(message)s")
16
- console_handler.setFormatter(formatter)
17
- # Add the console handler to the logger if not already attached
18
- if not logger.hasHandlers():
19
- logger.addHandler(console_handler)
20
-
21
-
22
- def set_global_verbosity(verbose):
23
- """Set the global verbosity level for the logger.
24
-
25
- Args:
26
- verbose (bool): Whether to display all log messages (True) or only error messages (False).
27
-
28
- Returns:
29
- None
30
- """
31
- if verbose:
32
- logger.setLevel(logging.INFO) # Show all messages
33
- console_handler.setLevel(logging.INFO)
34
- else:
35
- logger.setLevel(logging.ERROR) # Show only error messages
36
- console_handler.setLevel(logging.ERROR)
37
-
38
-
39
- def log_header(input_string: str) -> None:
40
- """Log the input string as a header with a line of dashes above and below it.
41
-
42
- Args:
43
- input_string (str): The string to be printed as a header.
44
- """
45
- border = "-" * len(input_string)
46
- logger.info(border)
47
- logger.info(input_string)
48
- logger.info(border)
File without changes
File without changes