# Network Biology

> Build PPI networks from STRING with NetworkX, find hub genes via centrality, detect Louvain modules, infer GRNs with GENIE3. Use for protein interaction networks, hub/bottleneck genes, network communities, GRN inference.

- Skill: `pavel-kravchenko/network-biology` (Agent Skill)
- Install (CLI): `npx skillmds@latest add pavel-kravchenko/network-biology`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pavel-kravchenko/network-biology/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: pavel-kravchenko (https://skillmd.com/u/pavel-kravchenko)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pavel-kravchenko/network-biology

---


# Network Biology

## When to Use

Use this skill when:
- Building protein-protein interaction (PPI) networks from STRING/BioGRID
- Computing network topology metrics (degree, betweenness, closeness, clustering) to find hub genes or bottlenecks
- Detecting functional modules/communities in biological networks (Louvain) and running GO enrichment per module
- Inferring gene regulatory networks (GRNs) from expression data with GENIE3
- Integrating network modules with differential expression results, or exporting a network to Cytoscape

## Version Compatibility

- Python ≥3.10, `networkx` ≥3.2, `pandas` ≥2.0, `requests` ≥2.31
- `python-louvain` (`community` package) ≥0.16 for modularity-based community detection
- `gseapy` ≥1.1 for Enrichr-based GO enrichment
- R ≥4.3, `GENIE3` (Bioconductor) ≥1.24 for GRN inference
- STRING REST API v11/v12 (`string-db.org/api`)

## Prerequisites

```bash
pip install networkx pandas requests python-louvain gseapy scipy matplotlib
# R: BiocManager::install("GENIE3")
```
Assumes familiarity with basic graph concepts (nodes/edges, degree) and a gene list or expression matrix to start from.

## Quick Reference

| Task | Tool | Key Method |
|------|------|-----------|
| Retrieve PPI data | STRING REST API | `requests.post(string_api, data=params)` |
| Build graph | NetworkX | `nx.from_pandas_edgelist(df)` |
| Degree centrality | NetworkX | `dict(G.degree())` |
| Betweenness | NetworkX | `nx.betweenness_centrality(G)` |
| Community detection | python-louvain | `best_partition(G)` |
| GO enrichment | gseapy | `gp.enrichr(gene_list, gene_sets='GO_BP')` |
| Export to Cytoscape | NetworkX | `nx.write_graphml(G, 'net.graphml')` |
| GRN inference | GENIE3 (R) | `GENIE3(exprMatrix, regulators=tfs)` |

## Key Patterns

**Pattern 1: STRING PPI network**
```python
import requests
import pandas as pd
import networkx as nx

genes = ['TP53', 'BRCA1', 'EGFR', 'MYC', 'KRAS']
string_api = 'https://string-db.org/api/json/network'
params = {
    'identifiers': '%0d'.join(genes),
    'species': 9606,
    'required_score': 700  # high confidence
}
resp = requests.post(string_api, data=params)
interactions = pd.DataFrame(resp.json())

G = nx.from_pandas_edgelist(
    interactions,
    source='preferredName_A',
    target='preferredName_B',
    edge_attr='score'
)
```

**Pattern 2: Network metrics**
```python
degree = dict(G.degree())
betweenness = nx.betweenness_centrality(G)
closeness = nx.closeness_centrality(G)
clustering = nx.clustering(G)

metrics = pd.DataFrame({
    'degree': degree,
    'betweenness': betweenness,
    'closeness': closeness,
    'clustering': clustering
})
# Hub genes: top 10 by degree
hubs = metrics[metrics['degree'] >= metrics['degree'].quantile(0.9)]
```

**Pattern 3: Louvain community detection**
```python
from community import best_partition  # pip install python-louvain

partition = best_partition(G, random_state=42)
# Map nodes to communities
modules = {}
for node, mod_id in partition.items():
    modules.setdefault(mod_id, []).append(node)
print(f'{len(modules)} modules detected')
```

**Pattern 4: GO enrichment per module**
```python
import gseapy as gp

for mod_id, genes in modules.items():
    if len(genes) < 5:
        continue
    enr = gp.enrichr(
        gene_list=genes,
        gene_sets='GO_Biological_Process_2021',
        organism='Human',
        outdir=None
    )
    top = enr.results.head(5)[['Term', 'Adjusted P-value']]
    print(f'Module {mod_id} ({len(genes)} genes): {top.to_string()}')
```

**Pattern 5: GENIE3 GRN inference (R)**
```r
library(GENIE3)

# exprMatrix: rows = genes, columns = samples (numeric matrix, not a data.frame)
tfs <- c('TP53', 'MYC', 'E2F1', 'FOXM1')
weight_matrix <- GENIE3(exprMatrix=expr_matrix, regulators=tfs,
                         nTrees=1000, nCores=8)
link_list <- getLinkList(weight_matrix, reportMax=20000)
# link_list columns: regulatoryGene, targetGene, weight (higher weight = more confident regulatory link)
```

## Biological Network Properties

| Property | Random Network | Scale-Free (Biological) |
|----------|----------------|------------------------|
| Degree distribution | Poisson | Power law (P(k) ~ k^-γ) |
| Hubs | Absent | Present (oncogenes, TFs) |
| Robustness | Uniform | Robust to random, vulnerable to hub removal |
| Clustering | Low | High (modular) |
| Path length | √N | log(N) |

## Pitfalls

- **Literature bias** — STRING PPI data is biased toward well-studied genes; hub genes may reflect research attention, not true biology
- **Directionality** — PPI networks are undirected (physical); GRNs are directed (regulatory); do not mix
- **Community detection randomness** — Louvain is stochastic; use `random_state` and run multiple times
- **Score threshold** — STRING combined_score > 700 = high confidence; lower thresholds inflate network with false positives
- **GRN validation** — GENIE3 weights are correlational; validate against known TF binding data (JASPAR, TRRUST, ENCODE ChIP-seq)

## Code Templates

### Degree Distribution Plot
```python
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
from collections import Counter

def plot_degree_distribution(G, title='Degree Distribution'):
    degrees = [d for _, d in G.degree()]
    counts = Counter(degrees)
    k = sorted(counts.keys())
    pk = [counts[ki] / len(degrees) for ki in k]

    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
    ax1.bar(k, pk, color='steelblue', alpha=0.7)
    ax1.set(xlabel='Degree k', ylabel='P(k)', title='Linear scale')

    ax2.loglog(k, pk, 'o', color='steelblue', alpha=0.7)
    ax2.set(xlabel='Degree k', ylabel='P(k)', title='Log-log (power law check)')

    plt.suptitle(title)
    plt.tight_layout()
    return degrees

degs = plot_degree_distribution(G, title=f'Network ({G.number_of_nodes()} nodes)')
```

### Identify Hub Genes and Bottlenecks
```python
import networkx as nx
import pandas as pd

def find_network_key_nodes(G, top_n=20):
    metrics = pd.DataFrame({
        'degree':      dict(G.degree()),
        'betweenness': nx.betweenness_centrality(G, normalized=True),
        'closeness':   nx.closeness_centrality(G),
        'pagerank':    nx.pagerank(G),
    })
    metrics['composite'] = (
        metrics['degree'] / metrics['degree'].max() +
        metrics['betweenness'] / metrics['betweenness'].max() +
        metrics['pagerank'] / metrics['pagerank'].max()
    ) / 3
    return metrics.sort_values('composite', ascending=False).head(top_n)

key_nodes = find_network_key_nodes(G)
print(key_nodes[['degree', 'betweenness', 'pagerank']].head(10))
```

### Overlap Network Modules with DEGs
```python
import pandas as pd

def module_deg_overlap(modules, degs, background_size=20000):
    """Fisher's exact test for overlap between modules and DEG list."""
    from scipy.stats import fisher_exact
    results = []
    deg_set = set(degs)
    for mod_id, genes in modules.items():
        module_set = set(genes)
        a = len(module_set & deg_set)          # overlap
        b = len(module_set) - a                # in module, not DEG
        c = len(deg_set) - a                   # DEG, not in module
        d = background_size - a - b - c        # neither
        odds, pval = fisher_exact([[a, b], [c, d]], alternative='greater')
        results.append({'module': mod_id, 'size': len(module_set),
                        'overlap': a, 'odds_ratio': odds, 'pvalue': pval})
    df = pd.DataFrame(results).sort_values('pvalue')
    df['padj'] = df['pvalue'] * len(df)  # Bonferroni
    return df

overlap = module_deg_overlap(modules, degs=significant_genes)
print(overlap[overlap['padj'] < 0.05])
```

### Export Network to GraphML (Cytoscape)
```python
import networkx as nx

def export_for_cytoscape(G, metrics_df, output_path):
    """Add node attributes from metrics and save as GraphML."""
    for node, row in metrics_df.iterrows():
        if node in G:
            G.nodes[node]['degree'] = int(row['degree'])
            G.nodes[node]['betweenness'] = float(row['betweenness'])
            G.nodes[node]['pagerank'] = float(row['pagerank'])
    nx.write_graphml(G, output_path)
    print(f"Saved {G.number_of_nodes()} nodes, {G.number_of_edges()} edges to {output_path}")

export_for_cytoscape(G, key_nodes, 'network.graphml')
```

## See Also
- `bio-applied-ppi-networks` — additional PPI network construction and analysis patterns
- `bio-applied-gene-regulatory-networks` — GRN inference beyond GENIE3 (SCENIC, ARACNe)
- `bio-applied-network-modules` — module/community detection deep dive
- `bio-applied-data-harmonization` — integrating network modules with multi-omics expression data

