# Bio Applied Ppi Networks

> Build and analyze protein-protein interaction (PPI) networks from STRING DB with NetworkX: compute degree/betweenness/closeness/eigenvector centrality, classify hub and bottleneck genes, test scale-free topology, and detect network communities/modules (Louvain, greedy modularity). Use when asked to find hub genes, identify drug targets from a network, query the STRING API, build a gene interaction graph, or cluster a PPI network into functional modules.

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

---


# PPI Network Construction and Analysis

## When to Use

- Querying STRING DB for a gene/protein list and building an interaction network from the results.
- Ranking genes by centrality to nominate hub genes, essential proteins, or bottleneck drug targets.
- Testing whether a network follows the scale-free (power-law) topology expected of biological PPI networks.
- Detecting functional modules/communities in a PPI or co-expression network (Louvain, greedy modularity, WGCNA-style).
- Visualizing a gene network colored by function, module, or centrality.

## Version Compatibility

- Python ≥ 3.10, `networkx` ≥ 3.2, `pandas` ≥ 2.0, `requests` ≥ 2.31
- Optional: `python-louvain` (`community` package) for true Louvain partitioning — `networkx.community.greedy_modularity_communities` works without it.
- STRING DB REST API v12 (`https://string-db.org/api`)

## Prerequisites

- `pip install networkx pandas requests matplotlib python-louvain`
- Familiarity with graph concepts (nodes/edges, connected components) and basic pandas.

**Goal:** Fetch confidence-scored protein interactions for a gene list from STRING DB.

**Approach:** POST/GET gene identifiers to the STRING `network` endpoint, filter by `combined_score`, and load into a `pandas.DataFrame`.

```python
import requests
import pandas as pd


def query_string_network(genes: list[str], species: int = 9606,
                           required_score: int = 400,
                           caller_identity: str = "my_app") -> pd.DataFrame:
    """Query STRING DB for interactions among a list of gene symbols.

    Args:
        genes: gene symbols, e.g. ["TP53", "MDM2", "BRCA1", "EGFR"]
        species: NCBI taxonomy ID (9606 = human)
        required_score: STRING combined_score threshold (0-999)
        caller_identity: required by STRING API to identify the calling app

    Returns:
        DataFrame with columns preferredName_A, preferredName_B, combined_score, ...
    """
    url = "https://string-db.org/api/json/network"
    params = {
        "identifiers": "%0d".join(genes),
        "species": species,
        "required_score": required_score,
        "caller_identity": caller_identity,
    }
    response = requests.get(url, params=params, timeout=30)
    response.raise_for_status()
    return pd.DataFrame(response.json())


# df = query_string_network(["TP53", "MDM2", "BRCA1", "EGFR", "MYC"])
```

**STRING score channels:** neighborhood, gene fusion, co-occurrence, coexpression, experimental, database, text mining.
`combined_score = 1 - prod(1 - individual_scores)`.

| Score range | Confidence |
|---|---|
| 0–150 | Low (noise) |
| 400–700 | Medium (common threshold) |
| 700–900 | High |
| 900–999 | Highest (multiple independent evidence sources) |

**Goal:** Build a NetworkX graph from a STRING edge list and compute centrality metrics to find hubs and bottlenecks.

**Approach:** Filter by confidence, restrict to the largest connected component, then compute degree/betweenness/closeness/eigenvector centrality and classify each gene's role.

```python
import networkx as nx
import pandas as pd


def build_ppi_graph(df_edges: pd.DataFrame, score_col: str = "combined_score",
                     threshold: int = 400) -> nx.Graph:
    """Build an undirected PPI graph from a STRING-style edge DataFrame.

    Args:
        df_edges: columns preferredName_A/protein1, preferredName_B/protein2, score_col
        threshold: minimum combined_score to keep an edge (400 = medium confidence)

    Returns:
        Graph restricted to the largest connected component.
    """
    src, dst = df_edges.columns[0], df_edges.columns[1]
    df_filtered = df_edges[df_edges[score_col] >= threshold].copy()
    G = nx.from_pandas_edgelist(df_filtered, source=src, target=dst, edge_attr=score_col)
    largest_cc = max(nx.connected_components(G), key=len)
    return G.subgraph(largest_cc).copy()


def centrality_table(G: nx.Graph) -> pd.DataFrame:
    """Compute centrality metrics and classify each node as hub/bottleneck/peripheral."""
    degree = dict(G.degree())
    betweenness = nx.betweenness_centrality(G, normalized=True)
    closeness = nx.closeness_centrality(G)
    eigenvector = nx.eigenvector_centrality(G, max_iter=500, tol=1e-6)

    df = pd.DataFrame({
        "Gene": list(G.nodes()),
        "Degree": [degree[g] for g in G.nodes()],
        "Betweenness": [betweenness[g] for g in G.nodes()],
        "Closeness": [closeness[g] for g in G.nodes()],
        "Eigenvector": [eigenvector[g] for g in G.nodes()],
    }).sort_values("Degree", ascending=False)

    deg_thresh = df["Degree"].median()
    bet_thresh = df["Betweenness"].median()

    def classify(row):
        is_hub = row.Degree > deg_thresh
        is_bottleneck = row.Betweenness > bet_thresh
        if is_hub and is_bottleneck:
            return "hub+bottleneck"
        if is_hub:
            return "hub"
        if is_bottleneck:
            return "bottleneck"
        return "peripheral"

    df["role"] = df.apply(classify, axis=1)
    return df
```

### Hub/bottleneck classification

```text
             High betweenness    Low betweenness
High degree  Hub + bottleneck    Hub only
Low degree   Bottleneck only     Peripheral
```

| Metric | Definition | Biological use |
|---|---|---|
| Degree | Direct neighbor count | Hub genes; essential proteins |
| Betweenness | Fraction of shortest paths through node | Bottleneck drug targets |
| Closeness | Inverse avg shortest path to all nodes | Signal propagation speed |
| Eigenvector | Importance weighted by neighbor importance | Quality over quantity of connections |
| Clustering coeff | Fraction of neighbors that are connected | Module membership |

PPI networks are expected to be scale-free, with degree distribution P(k) ~ k^-γ: most nodes have few connections while a few hubs (e.g. TP53, EGFR, MYC) have many. Verify by plotting the degree distribution on log-log axes — it should be roughly linear.

**Goal:** Partition a PPI network into functional modules/communities.

**Approach:** Use Louvain (`python-louvain`) if available, else fall back to NetworkX's greedy modularity maximization; report the modularity score Q.

```python
import networkx as nx


def detect_communities(G: nx.Graph) -> tuple[dict, float]:
    """Partition graph nodes into communities and compute modularity Q.

    Returns:
        (partition, modularity) where partition maps node -> community id.
        Q > 0.3 indicates meaningful community structure; Q > 0.5 is strong.
    """
    try:
        import community as community_louvain
        partition = community_louvain.best_partition(G, random_state=42)
    except ImportError:
        communities = nx.community.greedy_modularity_communities(G)
        partition = {node: i for i, comm in enumerate(communities) for node in comm}

    n_communities = len(set(partition.values()))
    comm_sets = [{n for n, c in partition.items() if c == i} for i in range(n_communities)]
    modularity = nx.community.modularity(G, comm_sets)
    return partition, modularity
```

## Pitfalls

- **Always filter by confidence score** before building the graph — unfiltered STRING data includes many low-confidence edges that inflate connectivity.
- **Use the largest connected component** for centrality metrics; isolated nodes and small components distort global statistics and can make `eigenvector_centrality` fail to converge.
- **Directed vs undirected:** use `nx.DiGraph` for regulatory networks (TF → target), `nx.Graph` for PPI/co-expression networks.
- **Betweenness is O(V·E)** — expensive on large graphs; use `nx.betweenness_centrality(G, k=500)` (sampling) for approximation on graphs with thousands of nodes.
- **Modularity Q depends on resolution** — communities found by Louvain are non-deterministic without `random_state`; always fix a seed for reproducibility.

## See Also

- `bio-applied-network-modules` — deeper community detection (Louvain/Leiden, WGCNA eigengenes)
- `bio-applied-gene-regulatory-networks` — directed TF-target inference (GENIE3, ARACNE)
- `network-biology` — broader network biology overview including Cytoscape export and GO enrichment per module

