bio-data-visualization-network-visualization

$npx mdskill add GPTomics/bioSkills/bio-data-visualization-network-visualization

Generate publication-ready biological network diagrams from interaction data.

  • Creates static, interactive, and publication-quality graphs from gene or protein data.
  • Depends on NetworkX, PyVis, matplotlib, numpy, and Cytoscape automation.
  • Selects output format based on user request for static, interactive, or publication quality.
  • Delivers rendered figures or programmatic control scripts via Python execution.
SKILL.md
.github/skills/bio-data-visualization-network-visualizationView on GitHub ↗
---
name: bio-data-visualization-network-visualization
description: Visualize biological networks including gene regulatory networks, protein interaction networks, and co-expression modules using NetworkX, PyVis, and Cytoscape automation. Produces interactive and publication-quality network figures. Use when creating network diagrams from interaction data, GRN results, or co-expression modules.
tool_type: python
primary_tool: NetworkX
---

## Version Compatibility

Reference examples tested with: matplotlib 3.8+, numpy 1.26+

Before using code patterns, verify installed versions match. If versions differ:
- Python: `pip show <package>` then `help(module.function)` to check signatures

If code throws ImportError, AttributeError, or TypeError, introspect the installed
package and adapt the example to match the actual API rather than retrying.

# Network Visualization

**"Visualize a biological network"** → Display protein-protein interaction, gene regulatory, or pathway networks as node-edge graphs.
- Python: `networkx` + `matplotlib`, `pyvis.network.Network()` (interactive)
- CLI: Cytoscape with `py4cytoscape` for programmatic control

Visualize biological networks with static (matplotlib), interactive (PyVis), and publication-quality (Cytoscape) approaches.

## NetworkX + Matplotlib

### Basic Network Plot

```python
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np

G = nx.karate_club_graph()

fig, ax = plt.subplots(figsize=(10, 8))
pos = nx.spring_layout(G, seed=42)
nx.draw_networkx(G, pos, node_size=300, node_color='#4DBBD5', edge_color='gray',
                 font_size=8, width=0.8, ax=ax)
ax.set_title('Network')
ax.axis('off')
plt.tight_layout()
plt.savefig('network.png', dpi=300, bbox_inches='tight')
```

### Layout Algorithms

| Layout | Function | Best For |
|--------|----------|----------|
| Spring | `nx.spring_layout(G, k=1.5, seed=42)` | General-purpose, force-directed |
| Kamada-Kawai | `nx.kamada_kawai_layout(G)` | Small-medium networks, clean separation |
| Circular | `nx.circular_layout(G)` | Showing all connections, small networks |
| Shell | `nx.shell_layout(G, nlist=[core, periphery])` | Hub-spoke topology |
| Spectral | `nx.spectral_layout(G)` | Revealing clusters |

The spring layout `k` parameter controls node spacing: increase for sparse layouts, decrease for compact. Always set `seed` for reproducibility.

### Degree-Based Node Sizing

```python
def draw_network(G, pos=None, title='', ax=None, node_cmap='YlOrRd'):
    '''Draw network with degree-proportional node sizes and colored by degree'''
    if pos is None:
        pos = nx.spring_layout(G, seed=42, k=1.5)
    if ax is None:
        fig, ax = plt.subplots(figsize=(10, 8))

    degrees = dict(G.degree())
    node_sizes = [100 + degrees[n] * 150 for n in G.nodes()]
    node_colors = [degrees[n] for n in G.nodes()]

    nx.draw_networkx_edges(G, pos, alpha=0.3, edge_color='gray', width=0.8, ax=ax)
    nodes = nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors,
                                   cmap=plt.cm.get_cmap(node_cmap), edgecolors='black',
                                   linewidths=0.5, ax=ax)
    nx.draw_networkx_labels(G, pos, font_size=7, ax=ax)

    plt.colorbar(nodes, ax=ax, label='Degree', shrink=0.8)
    ax.set_title(title)
    ax.axis('off')
    return ax
```

### Edge Weight Visualization

```python
def draw_weighted_network(G, pos=None, weight_key='weight'):
    '''Draw network with edge width proportional to weight'''
    if pos is None:
        pos = nx.spring_layout(G, seed=42, k=1.5)

    fig, ax = plt.subplots(figsize=(10, 8))
    weights = [G[u][v].get(weight_key, 1.0) for u, v in G.edges()]

    # Normalize weights to [0.5, 4] for visible edge widths
    min_w, max_w = min(weights), max(weights)
    if max_w > min_w:
        scaled = [0.5 + 3.5 * (w - min_w) / (max_w - min_w) for w in weights]
    else:
        scaled = [1.0] * len(weights)

    nx.draw_networkx_edges(G, pos, width=scaled, alpha=0.5, edge_color='gray', ax=ax)
    nx.draw_networkx_nodes(G, pos, node_size=400, node_color='#4DBBD5', edgecolors='black', linewidths=0.5, ax=ax)
    nx.draw_networkx_labels(G, pos, font_size=8, ax=ax)
    ax.axis('off')
    plt.tight_layout()
    return fig, ax
```

### Community Detection Coloring

**Goal:** Color network nodes by community membership to reveal modular structure.

**Approach:** Run greedy modularity community detection, map each node to its community index, and render with a qualitative colormap where each community gets a distinct color.

```python
from networkx.algorithms.community import greedy_modularity_communities

def draw_communities(G, pos=None):
    '''Color nodes by community membership'''
    if pos is None:
        pos = nx.spring_layout(G, seed=42, k=1.5)

    communities = list(greedy_modularity_communities(G))
    node_to_community = {}
    for i, comm in enumerate(communities):
        for node in comm:
            node_to_community[node] = i
    node_colors = [node_to_community[n] for n in G.nodes()]

    palette = plt.cm.get_cmap('Set2', len(communities))

    fig, ax = plt.subplots(figsize=(10, 8))
    nx.draw_networkx_edges(G, pos, alpha=0.2, edge_color='gray', ax=ax)
    nx.draw_networkx_nodes(G, pos, node_size=400, node_color=node_colors, cmap=palette,
                           edgecolors='black', linewidths=0.5, ax=ax)
    nx.draw_networkx_labels(G, pos, font_size=7, ax=ax)

    for i, comm in enumerate(communities):
        ax.scatter([], [], c=[palette(i)], label=f'Community {i+1} ({len(comm)} nodes)', s=80)
    ax.legend(loc='upper left', fontsize=8, framealpha=0.9)
    ax.set_title(f'Network Communities (modularity, {len(communities)} groups)')
    ax.axis('off')
    plt.tight_layout()
    return fig, ax
```

### Highlight Subnetwork

```python
def highlight_genes(G, highlight_nodes, pos=None, highlight_color='#E64B35', default_color='#cccccc'):
    '''Highlight specific nodes (e.g., DE genes) in a network'''
    if pos is None:
        pos = nx.spring_layout(G, seed=42, k=1.5)

    colors = [highlight_color if n in highlight_nodes else default_color for n in G.nodes()]
    sizes = [600 if n in highlight_nodes else 200 for n in G.nodes()]
    font_sizes = {n: 9 if n in highlight_nodes else 0 for n in G.nodes()}

    fig, ax = plt.subplots(figsize=(10, 8))
    nx.draw_networkx_edges(G, pos, alpha=0.15, edge_color='gray', ax=ax)
    nx.draw_networkx_nodes(G, pos, node_size=sizes, node_color=colors, edgecolors='black', linewidths=0.5, ax=ax)
    labels = {n: n for n in highlight_nodes if n in G.nodes()}
    nx.draw_networkx_labels(G, pos, labels=labels, font_size=9, font_weight='bold', ax=ax)
    ax.axis('off')
    plt.tight_layout()
    return fig, ax
```

## PyVis Interactive Networks

### Basic Interactive Plot

```python
from pyvis.network import Network

def interactive_network(G, output='network.html', height='700px', width='100%'):
    '''Create interactive HTML network with PyVis'''
    net = Network(height=height, width=width, bgcolor='white', font_color='black')
    net.from_nx(G)
    net.toggle_physics(True)
    net.show_buttons(filter_=['physics'])
    net.save_graph(output)
    return output
```

### Styled Interactive Network

```python
def styled_interactive_network(G, output='styled_network.html', community_colors=None):
    '''Interactive network with degree-based sizing and community colors'''
    net = Network(height='700px', width='100%', bgcolor='white', font_color='black')

    degrees = dict(G.degree())
    palette = ['#E64B35', '#4DBBD5', '#00A087', '#3C5488', '#F39B7F', '#8491B4', '#91D1C2', '#DC0000']

    for node in G.nodes():
        size = 10 + degrees[node] * 5
        if community_colors and node in community_colors:
            color = palette[community_colors[node] % len(palette)]
        else:
            color = '#4DBBD5'
        net.add_node(node, size=size, color=color, title=f'{node}\nDegree: {degrees[node]}')

    for u, v, data in G.edges(data=True):
        weight = data.get('weight', 1.0)
        net.add_edge(u, v, width=weight * 2, title=f'Weight: {weight:.2f}')

    net.toggle_physics(True)
    net.set_options('{"physics": {"forceAtlas2Based": {"gravitationalConstant": -50}}}')
    net.save_graph(output)
    return output
```

### PyVis Configuration Options

| Option | Values | Effect |
|--------|--------|--------|
| `toggle_physics(True)` | True/False | Enable force-directed layout |
| `show_buttons()` | filter list | UI controls for layout tuning |
| `barnes_hut()` | - | Fast layout for large networks |
| `force_atlas_2based()` | - | Good cluster separation |
| `repulsion()` | - | Uniform spacing |

## Cytoscape Automation

### py4cytoscape Basics

```python
import py4cytoscape as p4c

def send_to_cytoscape(G, title='Network', layout='force-directed'):
    '''Send NetworkX graph to Cytoscape (must be running)'''
    p4c.create_network_from_networkx(G, title=title)
    p4c.layout_network(layout)
    return p4c.get_network_suid()

def style_by_degree(network_suid=None):
    '''Apply degree-proportional node sizing in Cytoscape'''
    style_name = 'DegreeStyle'
    p4c.create_visual_style(style_name)

    p4c.set_node_size_mapping('degree', [1, 5, 20], [30, 60, 120],
                              mapping_type='c', style_name=style_name)
    p4c.set_node_color_mapping('degree', [1, 10, 20], ['#FFFFCC', '#FD8D3C', '#BD0026'],
                               mapping_type='c', style_name=style_name)
    p4c.set_visual_style(style_name)
```

### Export from Cytoscape

```python
def export_cytoscape_figure(filename='network.pdf', resolution=300):
    '''Export current Cytoscape view as publication figure'''
    if filename.endswith('.pdf'):
        p4c.export_image(filename, type='PDF')
    else:
        p4c.export_image(filename, type='PNG', resolution=resolution)
```

### Cytoscape Layout Options

| Layout | Use Case |
|--------|----------|
| `force-directed` | General-purpose, good default |
| `circular` | Small networks, all connections visible |
| `hierarchical` | Signaling cascades, directed networks |
| `grid` | Uniform placement for comparison |
| `degree-circle` | Hub nodes in center |

## Bioinformatics Styling Patterns

### PPI Network Styling

```python
def style_ppi_network(G, pos=None, score_key='score'):
    '''Style for protein-protein interaction networks'''
    if pos is None:
        pos = nx.kamada_kawai_layout(G)

    fig, ax = plt.subplots(figsize=(12, 10))
    degrees = dict(G.degree())
    node_sizes = [200 + degrees[n] * 100 for n in G.nodes()]

    edges = G.edges(data=True)
    edge_colors = ['#E64B35' if d.get(score_key, 0) >= 900
                   else '#F39B7F' if d.get(score_key, 0) >= 700
                   else '#cccccc' for _, _, d in edges]

    nx.draw_networkx_edges(G, pos, edge_color=edge_colors, width=1.2, alpha=0.6, ax=ax)
    nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='#4DBBD5',
                           edgecolors='black', linewidths=0.5, ax=ax)
    labels = {n: n for n in G.nodes() if degrees[n] >= 3}
    nx.draw_networkx_labels(G, pos, labels=labels, font_size=8, font_weight='bold', ax=ax)
    ax.axis('off')
    plt.tight_layout()
    return fig, ax
```

### GRN Directed Network

**Goal:** Visualize a gene regulatory network with distinct styling for TFs vs targets and activating vs repressing edges.

**Approach:** Separate edges by regulation type, draw activating edges as solid green arrows and repressing edges as dashed red arrows, style TF nodes as diamonds and target nodes as circles.

```python
def draw_grn(G, pos=None):
    '''Draw gene regulatory network with directed edges and TF highlighting'''
    if pos is None:
        pos = nx.spring_layout(G, seed=42, k=2.0)

    fig, ax = plt.subplots(figsize=(12, 10))

    activating = [(u, v) for u, v, d in G.edges(data=True) if d.get('regulation') == 'activation']
    repressing = [(u, v) for u, v, d in G.edges(data=True) if d.get('regulation') == 'repression']

    nx.draw_networkx_edges(G, pos, edgelist=activating, edge_color='#00A087',
                           arrows=True, arrowsize=15, width=1.5, alpha=0.7, ax=ax)
    nx.draw_networkx_edges(G, pos, edgelist=repressing, edge_color='#E64B35',
                           arrows=True, arrowsize=15, width=1.5, alpha=0.7,
                           style='dashed', ax=ax)

    tfs = [n for n in G.nodes() if G.out_degree(n) > 0]
    targets = [n for n in G.nodes() if n not in tfs]

    nx.draw_networkx_nodes(G, pos, nodelist=tfs, node_size=600, node_color='#3C5488',
                           node_shape='D', edgecolors='black', linewidths=0.5, ax=ax)
    nx.draw_networkx_nodes(G, pos, nodelist=targets, node_size=300, node_color='#F39B7F',
                           edgecolors='black', linewidths=0.5, ax=ax)
    nx.draw_networkx_labels(G, pos, font_size=7, ax=ax)

    ax.scatter([], [], c='#00A087', marker='>', s=60, label='Activation')
    ax.scatter([], [], c='#E64B35', marker='>', s=60, label='Repression')
    ax.scatter([], [], c='#3C5488', marker='D', s=60, label='TF')
    ax.scatter([], [], c='#F39B7F', marker='o', s=60, label='Target')
    ax.legend(loc='upper left', fontsize=9, framealpha=0.9)
    ax.axis('off')
    plt.tight_layout()
    return fig, ax
```

### Multi-Panel Network Comparison

```python
def compare_networks(graphs, titles, ncols=2):
    '''Side-by-side network comparison'''
    nrows = (len(graphs) + ncols - 1) // ncols
    fig, axes = plt.subplots(nrows, ncols, figsize=(7 * ncols, 6 * nrows))
    axes = axes.flatten() if hasattr(axes, 'flatten') else [axes]

    for ax, G, title in zip(axes, graphs, titles):
        pos = nx.spring_layout(G, seed=42, k=1.5)
        degrees = dict(G.degree())
        sizes = [100 + degrees[n] * 100 for n in G.nodes()]
        nx.draw_networkx_edges(G, pos, alpha=0.2, edge_color='gray', ax=ax)
        nx.draw_networkx_nodes(G, pos, node_size=sizes, node_color='#4DBBD5',
                               edgecolors='black', linewidths=0.5, ax=ax)
        nx.draw_networkx_labels(G, pos, font_size=6, ax=ax)
        ax.set_title(f'{title}\n({G.number_of_nodes()} nodes, {G.number_of_edges()} edges)', fontsize=10)
        ax.axis('off')

    for ax in axes[len(graphs):]:
        ax.set_visible(False)
    plt.tight_layout()
    return fig
```

## Export Formats

| Format | Method | Use Case |
|--------|--------|----------|
| PNG | `plt.savefig('net.png', dpi=300)` | Presentations, web |
| PDF | `plt.savefig('net.pdf')` | Publication figures |
| SVG | `plt.savefig('net.svg')` | Editable vector graphics |
| HTML | `net.save_graph('net.html')` | Interactive sharing (PyVis) |
| GraphML | `nx.write_graphml(G, 'net.graphml')` | Import to Cytoscape |
| GML | `nx.write_gml(G, 'net.gml')` | Import to Gephi |

## Related Skills

- data-visualization/multipanel-figures - Combine network panels with other plots
- gene-regulatory-networks/coexpression-networks - Build co-expression networks to visualize
- database-access/interaction-databases - Fetch PPI data for network construction
More from GPTomics/bioSkills