Skip to content

Commit acd585f

Browse files
authored
Merge pull request #107 from UCD-BDLab/Testing_updated_for_v1.3.0
All tests up to date with changes for v1.3.0. Passed all CI / Build tests
2 parents 737ed72 + 6870d9b commit acd585f

46 files changed

Lines changed: 1398 additions & 1291 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.pre-commit-config.yaml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ repos:
2222
hooks:
2323
- id: mypy
2424
name: Static type checking with MyPy
25-
args: [--ignore-missing-imports]
25+
exclude: ^bioneuralnet/network/pysmccnet/
26+
args: [
27+
--ignore-missing-imports,
28+
--follow-imports=silent,
29+
]
2630

2731
- repo: local
2832
hooks:

‎CHANGELOG.md‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/).
77

8+
## [1.3.0] - 2026-04-01
9+
10+
### Network Module (`bioneuralnet.network`)
11+
- **New dedicated module**: Network construction and analysis moved from `bioneuralnet.utils` to `bioneuralnet.network`.
12+
- **Renamed construction functions**: `gen_similarity_graph` -> `similarity_network`, `gen_correlation_graph` -> `correlation_network`, `gen_threshold_graph` -> `threshold_network`, `gen_gaussian_knn_graph` -> `gaussian_knn_network`.
13+
- **`NetworkAnalyzer`**: Moved to `bioneuralnet.network`; GPU-accelerated via PyTorch; added `hub_analysis`, `cross_omics_analysis`, `edge_weight_analysis`, `find_strongest_edges`, `degree_distribution`, `clustering_coefficient_gpu`, `connected_components`.
14+
- **`auto_pysmccnet`**: Phenotype-driven network construction via SmCCNet 2.0; supports CCA and PLS modes, now fully implemented in native Python, simplifying user experience and removing the R dependency.
15+
16+
### Utils Module
17+
- **`impute_omics` / `impute_omics_knn` renamed**: Now `impute_simple` and `impute_knn`.
18+
- **`normalize_omics` renamed**: Now `normalize`; supports `"standard"`, `"minmax"`, `"log2"`.
19+
- **`beta_to_m` renamed**: Now `m_transform`.
20+
- **New `feature_selection` submodule**: `laplacian_score`, `mad_filter`, `pca_loadings`, `correlation_filter`, `importance_rf`, `variance_threshold`, `top_anova_f_features`.
21+
- **New `data` functions**: `data_stats`, `sparse_filter`, `nan_summary`, `zero_summary`.
22+
- **`clean_internal`**: New cleaning function with configurable NaN threshold.
23+
24+
### DPMON Enhancements
25+
- **`tune_trials`**: Already introduced in 1.2.2; now fully documented.
26+
- **`ae_architecture`**: New parameter; supports `"original"` and `"dynamic"` autoencoder architectures.
27+
- **`correlation_mode`**: New parameter; supports `"abs_pearson"` (default) and `"adaptive"` node feature computation.
28+
- **Inner CV tuning**: Ray Tune now performs epoch-synchronized inner k-fold cross-validation across all trials.
29+
30+
### Datasets
31+
- **PAAD removed** from built-in datasets.
32+
- **Dataset size reduction**: BRCA, LGG, and KIPAN datasets significantly reduced from ~4,000 omics features per dataset to 700 (400 methylation, 200 mRNA, 100 miRNA) using Laplacian Score filtering, replacing the previous ANOVA-F & Random Forest intersection strategy. This standardization was necessary to stay within the PyPI 100 MB package size limit (v1.2.2 reached 97.9 MB) and results in substantially faster installs and downloads for users.
33+
34+
### Documentation
35+
- **Data Decision Framework**: New comprehensive stage-by-stage parameter reference (`quick_start/data_framework.rst`).
36+
- **Quick Start notebooks**: New home for end-to-end `Quick_Start.ipynb` and `quick_start_bio.rst`.
37+
- **Subgraph page**: Updated case studies from KIPAN to TCGA-LGG and ROSMAP with full algorithm documentation.
38+
- **`network.rst`**: New dedicated page for the network module.
39+
- **`utils.rst`**, **`datasets.rst`**, **`index.rst`**, **`subgraph.rst`**: Major updates throughout.
40+
- **README**: GitHub readme updated to reflect all API changes, new images, and corrected function names.
41+
42+
### Removed
43+
- `gen_similarity_graph`, `gen_correlation_graph`, `gen_threshold_graph`, `gen_gaussian_knn_graph` from `bioneuralnet.utils`.
44+
- `graph_analysis`, `repair_graph_connectivity`, `find_optimal_graph` from `bioneuralnet.utils` (superseded by `NetworkAnalyzer` and `network_search`).
45+
- `impute_omics`, `impute_omics_knn`, `normalize_omics`, `beta_to_m` (renamed, see above).
46+
47+
### Testing
48+
- Test suite updated to align with new `network` module and renamed utils functions.
49+
850
## [1.2.2] - 2025-12-29
951

1052
### Documentation

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![Documentation](https://img.shields.io/badge/docs-read%20the%20docs-blue.svg)](https://bioneuralnet.readthedocs.io/en/latest/)
99
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.17503083.svg)](https://doi.org/10.5281/zenodo.17503083)
1010

11-
## Welcome to BioNeuralNet 1.2.2
11+
## Welcome to BioNeuralNet 1.3.0
1212

1313
![BioNeuralNet Logo](assets/logo_update.png)
1414

@@ -386,4 +386,4 @@ For your convenience, you can use the following BibTeX entry:
386386
url={https://arxiv.org/abs/2507.20440},
387387
}
388388
```
389-
</details>
389+
</details>

‎assets/logo_update.png‎

-3.31 MB
Loading

‎assets/logo_update2.png‎

3.37 MB
Loading

‎bioneuralnet/__init__.py‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
1414
"""
1515

16-
__version__ = "1.2.2"
16+
__version__ = "1.3.0"
1717

1818
# submodules to enable direct imports such as `from bioneuralnet import utils`
1919
from . import utils
@@ -51,24 +51,25 @@
5151

5252
__all__ = [
5353
"__version__",
54-
54+
5555
"utils",
5656
"metrics",
5757
"datasets",
5858
"clustering",
5959
"network_embedding",
6060
"downstream_task",
6161
"network",
62+
"external_tools",
6263

6364
"GNNEmbedding",
6465
"SubjectRepresentation",
6566
"auto_pysmccnet",
6667
"DPMON",
67-
68+
6869
"DatasetLoader",
6970
"CorrelatedPageRank",
7071
"CorrelatedLouvain",
71-
72+
7273
"HybridLouvain",
7374

7475
"load_example",

‎bioneuralnet/clustering/__init__.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
r"""Network Clustering and Subgraph Detection.
22
3-
This module implements hybrid algorithms for identifying phenotype-associated
4-
subgraphs in multi-omics networks. It combines global modularity optimization
3+
This module implements hybrid algorithms for identifying phenotype-associated
4+
subgraphs in multi-omics networks. It combines global modularity optimization
55
with local random-walk refinement, weighted by phenotypic correlation.
66
77
Classes:
8-
HybridLouvain: The primary pipeline. Iteratively alternates between global partitioning
9-
(Louvain) and local refinement (PageRank) to find the most significant
8+
HybridLouvain: The primary pipeline. Iteratively alternates between global partitioning
9+
(Louvain) and local refinement (PageRank) to find the most significant
1010
subgraph associated with a phenotype.
1111
CorrelatedLouvain: Extends standard Louvain by optimizing a hybrid objective:
1212
Q_hybrid = k_L * Modularity + (1 - k_L) * Correlation.
13-
CorrelatedPageRank: Performs a biased random walk (PageRank) followed by a sweep cut to
13+
CorrelatedPageRank: Performs a biased random walk (PageRank) followed by a sweep cut to
1414
minimize a hybrid conductance objective:
1515
Phi_hybrid = k_P * Conductance + (1 - k_P) * Correlation.
1616
Louvain: Standard Louvain community detection (based on modularity maximization).
@@ -27,4 +27,4 @@
2727
"CorrelatedLouvain",
2828
"HybridLouvain",
2929
"Louvain"
30-
]
30+
]

‎bioneuralnet/clustering/correlated_louvain.py‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,41 @@
11
r"""
22
Correlated Louvain Community Detection.
33
4-
This module extends the standard Louvain algorithm by incorporating an
5-
absolute phenotype-correlation objective into the modularity maximization
4+
This module extends the standard Louvain algorithm by incorporating an
5+
absolute phenotype-correlation objective into the modularity maximization
66
process.
77
88
References:
9-
Abdel-Hafiz et al. (2022), "Significant Subgraph Detection in
10-
Multi-omics Networks for Disease Pathway Identification,"
9+
Abdel-Hafiz et al. (2022), "Significant Subgraph Detection in
10+
Multi-omics Networks for Disease Pathway Identification,"
1111
Frontiers in Big Data.
1212
1313
Notes:
1414
**Hybrid Modularity Objective**
15-
The algorithm optimizes connectivity and phenotype correlation
15+
The algorithm optimizes connectivity and phenotype correlation
1616
simultaneously using the following weighted objective function:
1717
1818
.. math::
1919
Q_{hybrid} = k_L Q + (1 - k_L) \rho
2020
2121
Where:
2222
* :math:`Q`: Standard modularity (internal connectivity).
23-
* :math:`\\rho`: Absolute Pearson correlation of the community's
23+
* :math:`\\rho`: Absolute Pearson correlation of the community's
2424
first principal component (PC1) with phenotype :math:`Y`.
2525
* :math:`k_L`: User-defined weight on modularity (Suggested: 0.2).
2626
2727
Algorithm:
28-
The hierarchical loop and Phase 2 (network aggregation) remain
29-
identical to the standard Louvain method. The modification occurs
28+
The hierarchical loop and Phase 2 (network aggregation) remain
29+
identical to the standard Louvain method. The modification occurs
3030
exclusively in **Phase 1 (Local Optimization)**.
3131
32-
When evaluating the movement of node :math:`v` from community :math:`D`
32+
When evaluating the movement of node :math:`v` from community :math:`D`
3333
to community :math:`C`, the gain is calculated as:
3434
3535
.. math::
3636
\Delta_{hybrid} = k_L \Delta Q + (1 - k_L) \Delta \\rho
3737
38-
The correlation gain :math:`\Delta \\rho` is defined as the change in
38+
The correlation gain :math:`\Delta \\rho` is defined as the change in
3939
total correlation across affected communities:
4040
4141
.. math::
@@ -86,10 +86,10 @@ def __init__(
8686
raise ValueError(f"k_L must be in [0, 1], got {k_L}")
8787

8888
super().__init__(
89-
G=G,
90-
weight=weight,
89+
G=G,
90+
weight=weight,
9191
max_passes=max_passes,
92-
min_delta=min_delta,
92+
min_delta=min_delta,
9393
seed=seed,
9494
)
9595

@@ -196,7 +196,7 @@ def _collect_orig(
196196
s: Set[int] = set()
197197
for idx in np.where(community == comm_id)[0]:
198198
s.update(n2o[int(idx)])
199-
199+
200200
return frozenset(s)
201201

202202
def _correlated_phase1(
@@ -227,7 +227,7 @@ def _correlated_phase1(
227227
for node in order:
228228
cur_comm = int(community[node])
229229
nbr_idx = np.nonzero(A[node])[0]
230-
230+
231231
if len(nbr_idx) == 0:
232232
continue
233233

@@ -383,6 +383,6 @@ def get_top_communities(self, n: int = 1) -> List[Tuple[int, float, List[Any]]]:
383383
continue
384384
idx_set = frozenset(self.node_to_idx[nd] for nd in nds)
385385
ranked.append((cid, self._pc1_correlation(idx_set), nds))
386-
386+
387387
ranked.sort(key=lambda x: x[1], reverse=True)
388-
return ranked[:n]
388+
return ranked[:n]

‎bioneuralnet/clustering/correlated_pagerank.py‎

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
r"""
22
Correlated PageRank Clustering.
33
4-
This module implements a personalized PageRank algorithm combined with a
4+
This module implements a personalized PageRank algorithm combined with a
55
phenotype-aware sweep cut to detect significant subgraphs.
66
77
References:
8-
Abdel-Hafiz et al. (2022), "Significant Subgraph Detection in
9-
Multi-omics Networks for Disease Pathway Identification,"
8+
Abdel-Hafiz et al. (2022), "Significant Subgraph Detection in
9+
Multi-omics Networks for Disease Pathway Identification,"
1010
Frontiers in Big Data.
1111
1212
Algorithm:
1313
The PageRank vector is computed as the stationary distribution of:
14-
14+
1515
.. math::
1616
pr_{\\alpha}(s) = \\alpha s + (1 - \\alpha) pr_{\\alpha}(s) W
1717
@@ -21,13 +21,13 @@
2121
* :math:`W`: Transition matrix.
2222
2323
.. important::
24-
The `networkx.pagerank` implementation uses a `alpha` parameter
25-
representing the **damping factor** (link-following probability).
24+
The `networkx.pagerank` implementation uses a `alpha` parameter
25+
representing the **damping factor** (link-following probability).
2626
Therefore, :math:`\\text{nx_alpha} = 1 - \\alpha_{theoretical}`.
2727
2828
Notes:
2929
**Sweep Cut Optimization**
30-
Nodes are sorted by PageRank-per-degree in descending order. For each
30+
Nodes are sorted by PageRank-per-degree in descending order. For each
3131
prefix set :math:`S_i`, the algorithm minimizes the **Hybrid Conductance**:
3232
3333
.. math::
@@ -39,13 +39,13 @@
3939
* :math:`k_P`: Trade-off weight (Default: ~0.5).
4040
4141
**Personalization Vector (Seed Weighting)**
42-
Teleportation probabilities for seeds are weighted by their marginal
42+
Teleportation probabilities for seeds are weighted by their marginal
4343
contribution to correlation:
4444
4545
.. math::
4646
\\alpha_i = \\frac{\\rho_i}{\\max(\\rho_{seeds})} \\cdot \\alpha_{max}
4747
48-
Where :math:`\\rho_i = |\\rho(S)| - |\\rho(S \setminus \{i\})|`.
48+
Where :math:`\\rho_i = |\\rho(S)| - |\\rho(S \setminus \{i\})|`.
4949
Values where :math:`\\rho_i < 0` are clamped to 0.
5050
"""
5151

@@ -103,13 +103,13 @@ def __init__(
103103

104104
if not 0.0 <= teleport_prob <= 1.0:
105105
raise ValueError(f"teleport_prob must be in [0, 1], got {teleport_prob}")
106-
106+
107107
self.teleport_prob = teleport_prob
108108
self._nx_alpha = 1.0 - teleport_prob
109109

110110
if not 0.0 <= k_P <= 1.0:
111111
raise ValueError(f"k_P must be in [0, 1], got {k_P}")
112-
112+
113113
self.k_P = k_P
114114
self.max_iter = max_iter
115115
self.tol = tol
@@ -135,14 +135,14 @@ def _validate_inputs(self):
135135
"""
136136
if not isinstance(self.G, nx.Graph):
137137
raise TypeError("graph must be a networkx.Graph")
138-
138+
139139
if not isinstance(self.B, pd.DataFrame):
140140
raise TypeError("omics_data must be a pandas DataFrame")
141-
141+
142142
graph_nodes = set(str(n) for n in self.G.nodes())
143143
omics_cols = set(str(c) for c in self.B.columns)
144144
missing = graph_nodes - omics_cols
145-
145+
146146
if missing:
147147
logger.warning(
148148
f"{len(missing)} graph nodes missing from omics columns "
@@ -174,7 +174,7 @@ def phen_omics_corr(self, nodes: List[Any]) -> Tuple[float, float]:
174174
return 0.0, 1.0
175175

176176
B_sub = self.B[valid_cols]
177-
177+
178178
if B_sub.shape[0] < 2:
179179
return 0.0, 1.0
180180

@@ -199,7 +199,7 @@ def phen_omics_corr(self, nodes: List[Any]) -> Tuple[float, float]:
199199
pc1, y_vals = pc1[:n_limit], y_vals[:n_limit]
200200

201201
corr, pvalue = pearsonr(pc1, y_vals)
202-
202+
203203
return (float(corr), float(pvalue)) if np.isfinite(corr) else (0.0, 1.0)
204204

205205
except Exception as e:
@@ -255,7 +255,7 @@ def sweep_cut(self, pr_scores: Dict[Any, float]) -> Dict[str, Any]:
255255

256256
vol_S = sum(d for _, d in self.G.degree(current_cluster, weight="weight"))
257257
vol_T = sum(d for _, d in self.G.degree(complement, weight="weight"))
258-
258+
259259
if min(vol_S, vol_T) == 0:
260260
continue
261261

@@ -310,7 +310,7 @@ def generate_weighted_personalization(
310310
if not nodes_excl:
311311
contributions.append(0.0)
312312
continue
313-
313+
314314
corr_excl, _ = self.phen_omics_corr(nodes_excl)
315315
rho_i = abs_total - abs(corr_excl)
316316
contributions.append(rho_i)
@@ -347,12 +347,12 @@ def run(self, seed_nodes: List[Any]) -> Dict[str, Any]:
347347

348348
graph_nodes = set(self.G.nodes())
349349
missing = set(seed_nodes) - graph_nodes
350-
350+
351351
if missing:
352352
raise ValueError(f"Seed nodes not in graph: {missing}")
353353

354354
personalization = self.generate_weighted_personalization(seed_nodes)
355-
355+
356356
logger.info(
357357
f"Personalization: {len(personalization)} nodes, "
358358
f"max_weight={max(personalization.values()):.4f}, "
@@ -392,4 +392,4 @@ def run(self, seed_nodes: List[Any]) -> Dict[str, Any]:
392392
else:
393393
logger.warning("Sweep cut found no valid cluster.")
394394

395-
return results
395+
return results

0 commit comments

Comments
 (0)