MODERN
MOdule DEtection and Refinement in signed Networks (MODERN) detects community structure in signed networks — networks with both positive (attractive) and negative (repulsive) edges. It extends classical community detection algorithms (Louvain, Leiden) to leverage negative edge information, which is critical for accurate module detection in biological networks such as gene co-expression networks.
Why signed networks?
Standard community detection methods use only positive edges and ignore negative correlations between genes. When inter-community positive edges are abundant (e.g., in correlation-based networks), unsigned methods fail to identify the correct community structure because they cannot distinguish "noise" positive edges from true intra-community edges.
Signed methods use negative edges as repulsive forces to push nodes apart, recovering the correct community structure even when the positive graph alone is ambiguous.
Signed adjacency matrix sorted by detected communities. As inter-community positive edges increase (top to bottom), unsigned modularity collapses (ARI=0.07) while signed methods maintain near-perfect recovery (ARI>0.93).
Methods
MODERN provides three community detection methods for signed networks:
| Method | Algorithm | Objective | Best for |
|---|---|---|---|
louvain |
LouvainSigned | α·Q⁺ − (1−α)·Q⁻ | Compatibility with existing Louvain workflows |
leiden-mod-alpha |
LeidenSigned (modularity) | α·Q⁺ − (1−α)·Q⁻ | General signed networks |
leiden-cpm-single |
LeidenSigned (CPM) | Σ(w_ij − γ) on signed graph | Correlation-based networks (co-expression) |
When to use CPM over modularity: Modularity's null model assumes a configuration-model random graph. In correlation-based networks (e.g., gene co-expression), the positive graph is inherently dense, distorting the null model. CPM uses an absolute density threshold (γ) instead and is not affected by this issue.
Installation
pip install modernsn
Dependencies
- Python ≥ 3.9
- numpy
- igraph (python-igraph)
- leidenalg
- networkx
- matplotlib (for plotting outputs)
- scipy (for MAT format support)
Quick start
# Basic run with Leiden signed modularity
modern --pos positive_edges.tsv --neg negative_edges.tsv \
--method leiden-mod-alpha --alpha 0.6 --resolution 1.0 \
--out-prefix results/my_analysis
# Leiden signed CPM (recommended for co-expression networks)
modern --pos positive_edges.tsv --neg negative_edges.tsv \
--method leiden-cpm-single --gamma 0.05 --lambda-neg 1.0 \
--out-prefix results/my_analysis
# Louvain signed
modern --pos positive_edges.tsv --neg negative_edges.tsv \
--method louvain --alpha 0.6 --resolution 1.0 \
--out-prefix results/my_analysis
# Multi-seed negative-edge leverage screen
modern --pos positive_edges.tsv --neg negative_edges.tsv \
--method leiden-mod-alpha --negative-edge-leverage \
--leverage-seeds 1 2 3 10 42 \
--out-prefix results/my_analysis
# Signed-network diagnostic and analysis recommendations
modern --pos positive_edges.tsv --neg negative_edges.tsv \
--method leiden-mod-alpha --check-signed-network \
--leverage-seeds 1 2 3 10 42 \
--out-prefix results/my_analysis
Input formats
TSV (default)
Tab-separated file with five columns (no header):
gene_id1 gene_id2 gene_name1 gene_name2 weight
ENSG00001 ENSG00002 GeneA GeneB 15.3
ENSG00001 ENSG00003 GeneA GeneC 12.1
Provide separate files for positive and negative edges via --pos and --neg. Use --thre-pos and --thre-neg to filter edges by weight.
MAT (MATLAB sparse matrix)
modern --format mat --mat data/network.mat \
--pos-key pos --neg-key neg \
--method leiden-mod-alpha --out-prefix results/mat_run
The MAT file should contain two sparse matrices (positive and negative adjacency).
Output files
When --out-prefix is specified, MODERN generates the following outputs:
| File | Description |
|---|---|
<prefix>_partition.tsv |
Community assignment for each gene (gene_id, gene_name, community) |
<prefix>_summary.txt |
Parameters, graph statistics, community size distribution, entropy |
<prefix>_communities.gmt |
GMT format for direct use with GSEA, Enrichr, clusterProfiler |
<prefix>_community_sizes.pdf |
Rank-size plot and histogram of community sizes |
<prefix>_inter_community.pdf |
Heatmap of inter-community edge density (positive / negative / signed) |
<prefix>_module_<id>.pdf |
Subnetwork visualization for top modules (positive=red, negative=blue) |
With --negative-edge-leverage, MODERN instead writes one row per seed to
<prefix>_negative_edge_leverage.tsv and a multi-seed summary to
<prefix>_negative_edge_leverage_summary.txt.
With --check-signed-network, MODERN reports network size, low- and
high-resolution partition summaries, metanode enrichment, multi-seed
negative-edge leverage, and analysis recommendations to standard output. The
same readable report is saved to <prefix>_signed_network_check.txt.
Negative-edge leverage
Negative-edge leverage asks whether positive-only reintegration would merge module boundaries supported by negative edges. MODERN first generates a high-resolution positive-only Leiden partition, then performs reintegration without applying the negative-edge veto. The score is
(negative edges newly placed within modules) / (negative edges evaluated)
The defaults reproduce the manuscript screen: resolution 4, positive-coupling robust-z threshold 1, minimum module size 10, and seeds 1, 2, 3, 10, and 42. The empirical candidate threshold of 0.02 is reported as a screen rather than a general significance cutoff.
Disabling outputs
--no-plot # Skip all PDF plots
--no-gmt # Skip GMT output
--quiet # Suppress progress messages
Controlling module plots
--top-modules 10 # Number of top modules to plot (default: 10)
--max-nodes-per-module 200 # Max nodes per module plot (default: 200)
Parameters
Method-specific parameters
Modularity-based methods (louvain, leiden-mod-alpha):
| Parameter | Description | Default |
|---|---|---|
--alpha |
Balance between positive and negative modularity (0–1). Higher α emphasizes positive edges. | 0.5 |
--resolution |
Resolution parameter. Higher values produce more, smaller communities. | 1.0 |
CPM method (leiden-cpm-single):
| Parameter | Description | Default |
|---|---|---|
--gamma |
CPM resolution. Minimum edge density within communities. | 0.5 |
--lambda-neg |
Weight multiplier for negative edges. Controls how strongly negative edges repel. | 0.0 |
--neg-weight-mode |
How to transform negative weights: absolute (−λ·|w|) or signed (λ·w). |
absolute |
Common parameters
| Parameter | Description | Default |
|---|---|---|
--seed |
Random seed for reproducibility. | None |
--out-prefix |
Output file prefix. If omitted, only prints summary to stdout. | None |
Python API
MODERN can also be used as a Python library:
import modernsn.network_module as nr
import modernsn.LeidenSigned as les
# Load graphs
G_pos = nr.load_graph_from_TSV_igraph("positive.tsv", threshold=10)
G_neg = nr.load_graph_from_TSV_igraph("negative.tsv", threshold=5)
# Leiden signed modularity
partition = les.find_partition_signed_modularity_alpha(
G_pos, G_neg, alpha=0.6, resolution=1.0, seed=42
)
# Leiden signed CPM (single signed graph)
G_signed = nr.load_signed_graph_from_two_TSV_igraph(
"positive.tsv", "negative.tsv",
pos_threshold=10, neg_threshold=5, lambda_neg=1.0
)
partition = les.find_partition_signed_CPM_single_graph(
G_signed, gamma=0.05, seed=42
)
# Inspect results
print(partition.membership)
nr.display_communities_by_name(G_pos, partition)
nr.count_nodes_in_communities(partition)
# Multi-seed negative-edge leverage
leverage = nr.calculate_negative_edge_leverage_multiseed(
G_pos, G_neg, seeds=[1, 2, 3, 10, 42]
)
print(leverage["negative_edge_leverage_median"])
print(leverage["classification"])
# Combined signed-network diagnostic
diagnostic = nr.check_signed_network(G_pos, G_neg)
print(diagnostic["network"])
print(diagnostic["recommendations"])
Visualization
# Visualize a specific module (positive=red, negative=blue)
nr.visualize_module_signed(G_pos, G_neg, partition, community_id=0)
# Visualize the module containing a specific gene
nr.visualize_module_of_gene_signed(G_pos, G_neg, partition, "TP53")
# Top-degree nodes within a module
nr.visualize_module_of_gene_top_degree_nodes_signed(
G_pos, G_neg, partition, "TP53", top_n=30
)
Benchmarking
MODERN includes a simulation framework for evaluating community detection methods on signed networks.
# Quick test (no modernsn package required)
python sim3_benchmark.py test
# Full benchmark with all methods and parameter grids
python sim3_benchmark.py run sim3_results/
# Generate example correlation matrix visualizations
python sim3_benchmark.py plot sim3_results/
Simulation types
| Type | Model | Tests |
|---|---|---|
| Type A | Correlation-based co-expression | Modularity null model distortion on dense positive graphs |
| Type B | Planted partition (stochastic block model) | Control — balanced positive/negative structure |
| Type C | scRNA-seq exclusive expression | Multipartite negative graph where "enemy of enemy ≠ friend" |
Example visualizations
# Generate concrete examples showing signed vs unsigned differences
python sim3_examples.py sim3_examples/
This produces network graphs and adjacency matrix heatmaps at three difficulty levels, clearly demonstrating when signed methods outperform unsigned methods.
How it works
Signed modularity (multiplex optimization)
The positive and negative graphs are treated as two layers of a multiplex network. The objective function is:
Q_signed = α · Q_modularity(G⁺) − (1 − α) · Q_modularity(G⁻)
This is optimized using leidenalg.optimise_partition_multiplex, which simultaneously considers both layers with different weights.
Signed CPM (single graph)
Positive and negative edges are combined into a single graph with signed weights:
w_signed(i,j) = w_pos(i,j) − λ_neg · w_neg(i,j)
Standard CPM is then applied, where the objective function naturally penalizes negative edges within the same community:
H_CPM = Σ_{i,j in same community} (w_signed(i,j) − γ)
Choosing a method
Is your network correlation-based (co-expression, etc.)?
├── Yes → Use leiden-cpm-single
│ Start with --gamma 0.05 --lambda-neg 1.0
│ Increase gamma for smaller communities
│
└── No (e.g., social network, citation network)
├── Need reproducibility/stability? → Use leiden-mod-alpha
│ Start with --alpha 0.6 --resolution 1.0
│
└── Compatibility with existing Louvain pipeline? → Use louvain
Start with --alpha 0.6 --resolution 1.0
Citation
If you use MODERN in your research, please cite:
[Citation information to be added]
Release files for modernsn 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| modernsn-0.1.1.tar.gz | 67.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| modernsn-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 130.7 kB
Release files / modernsn-0.1.1.tar.gz
| Download URL | modernsn-0.1.1.tar.gz |
|---|---|
| Size | 67.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4bf5dea4dc7dcc69690ededb81615841f42ecaec191294b073b014dc86636b19
|
|
BLAKE2b-256 checksum How to use checksums |
61c8541b99cd3466fcadd6ed84881aa3e6a1f18592fabcf2eb60675586e0a4a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.13
|
Release files / modernsn-0.1.1-py3-none-any.whl
| Download URL | modernsn-0.1.1-py3-none-any.whl |
|---|---|
| Size | 63.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd33d3f0dc983711a52fadef9fe0f4d7627525ca3c39648338d1103bd0def322
|
|
BLAKE2b-256 checksum How to use checksums |
4f4f7591a7785b9598899bb28c94b97bff5df9d9b01534ac97fb41bbb1ad62b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.13
|