polarity-network-motifs 0.2.0
A Python package for detecting and analyzing polarity network motifs - i.e., signed/typed networks
Requires Python: >=3.10
Keywords: network motifs, graph analysis, polarity networks, signed networks, network science

# Network Motifs Detection with Polarity Support

[![Unit Tests](https://github.com/deangeckt/network_motifs/actions/workflows/python-app.yml/badge.svg)](https://github.com/deangeckt/network_motifs/actions/workflows/python-app.yml)

A Python package for detecting and analyzing network motifs in directed, signed/typed networks with support for **arbitrary polarity attributes** and **large motif patterns (K > 3)**.

Traditional motif detection treats all edges equally. But what if your network has **polarities** — excitatory vs inhibitory synapses, positive vs negative social interactions, or any custom edge types? And what if you need to find **larger patterns** beyond the typical 3-node motifs?

**Visualization of K=3 motifs with polarity - a specific triplet pattern:**

<img width="1467" alt="K=3 Binary Polarity Motifs" src="https://github.com/user-attachments/assets/b2de815f-2fc9-40b7-8d85-a60421c6a892" />

**Polarity variants of a specific K=5 motif:**

<img width="1594" alt="K=5 Polarity Variants" src="https://github.com/user-attachments/assets/c906d58f-73de-4fa9-9b72-6c74eb52eaae" />

*The package automatically discovers and statistically tests each unique excitatory/inhibitory combination for any motif size.*

## Key Features

### 🎯 Edge-Level Polarity Support
- **Arbitrary polarity types**: Binary (+/-), ternary (+/-/complex), or any custom edge attributes
- **Edge-level polarity**: Allows a single node to have different types of connections (e.g., one excitatory and one inhibitory) - more flexible than node-level attributes
- **Polarity-aware statistics**: Each polarity variant receives its own Z-score and significance testing

### 📊 Large Motif Detection (K > 3)
- **Support for K=4, K=5, K=6 and beyond**: Detect larger structural patterns in networks
- **Automatic isomorphic mapping**: Uses precomputed mappings when available (K≤4 for binary polarity)
- **Fallback mode**: Seamlessly handles large K values without mappings via on-the-fly isomorphism checking

### 🧩 Extensible Architecture
- **Plug-and-Play Design**: Easily integrate custom algorithms, random network generators, and data loaders to suit your specific research needs.
- **Comprehensive Examples**: See how to extend the package in our [Extending the Package](#extending-the-package) section below.
- **Flexible Input**: Native support for NetworkX, SciPy sparse matrices, txt and csv.
- **Post-Motif Analysis Tools**: A set of utilities for analyzing results including node role analysis, multi-network comparison, and subgraph visualization. See [Post-Motif Analysis Tools](#post-motif-analysis-tools).
- **Programmatic API**: Designed for seamless integration into existing data science pipelines.

## Installation

```bash
pip install polarity-network-motifs
```

For development:
```bash
git clone https://github.com/deangeckt/network_motifs.git
cd network_motifs
pip install -e ".[dev]"
```

## Quick Start

### Example 1: Binary Polarity Network (K=3)

```python
from polarity_network_motifs import MotifSearch, from_edgelist, MarkovChainSwitching, NetsciWrapper
from polarity_network_motifs.analysis.plots import plot_Z_nice

# Load network with binary polarity (+/-)
network = from_edgelist('network.txt', polarity_col=3, polarity_values=['+', '-'])

# Run motif detection
search = MotifSearch(
    network=network,
    k=3,
    algorithm=NetsciWrapper,
    randomizer=MarkovChainSwitching,
    num_random_networks=100,
    random_seed=42
)

results = search.run()

# Plot Z-scores
plot_Z_nice(results.motifs.values())
```

**Visualization of K=3 motifs without polarity**

<img width="1475" height="720" alt="image" src="https://github.com/user-attachments/assets/b392f9b4-91af-4cbc-950e-c06ec804c626" />

---

### Example 2: Large Motifs (K=5)

```python
from polarity_network_motifs import MotifSearch, from_edgelist, MFinderInduced, MarkovChainSwitching
from polarity_network_motifs.analysis.plot_large import plot_motifs_z

# Load network
network = from_edgelist('network.txt', polarity_col=3, polarity_values=['+', '-'])

# Detect K=5 motifs (no precomputed mapping needed)
search = MotifSearch(
    network=network,
    k=5,  # Large motif size
    algorithm=MFinderInduced,
    randomizer=MarkovChainSwitching,
    num_random_networks=100,
    use_isomorphic_mapping=False,  # Automatic for K>4
    random_seed=42
)

results = search.run()

# Plot K=5 motifs sorted by Z-score
plot_motifs_z(results.motifs.values(), title="K=5 Motifs")
```

**Visualization of K=5 motifs:**

<img width="1549" alt="K=5 Motifs" src="https://github.com/user-attachments/assets/adf125ae-55c4-4920-b037-dd7137d7496a" />

---

## Algorithms

### For K=3 Motifs
- `NetsciWrapper` - Wrapper for [netsci package](https://github.com/gialdetti/netsci) (Gal et al. 2019 & Louzoun et al. 2019). **Best for full analysis** - retrieves the complete participant list of nodes and subgraphs per motif.
- `TriadicCensus` - (Batagelj & Mrvar 2001). **Fast implementation for statistics** - efficient for getting z-scores of triplets without listing specific subgraphs/participants.

### For Larger Motifs (K > 3)
- `MFinderInduced` - Induced subgraph enumeration (Milo et al. 2002).
- `FanmodESU` - FANMOD algorithm (Wernicke 2006). Alternative enumeration strategy.
- `MFinderNoneInduced` - Non-induced version of MFinder.

**Random network generators:**

| Property | Markov Chain | Configuration Model | Erdős-Rényi | Barabási-Albert |
|----------|--------------|---------------------|-------------|-----------------|
| **Per-node in/out-degree** | ✅ Exact | ✅ Exact | ❌ Random | ❌ Random |
| **Per-node, per-polarity out-degree** | ✅ **Preserved** | ❌ Random | ❌ Random | ❌ Random |
| **Overall polarity ratio** | ✅ Exact | ✅ Exact | ✅ Approximate | ✅ Approximate |
| **Complexity** | O(E × switch_factor) | O(E) | O(E) | O(E) |
| **Speed** | Slow (~10k swaps) | **Fast** | **Fast** | **Fast** |
| **Null Model Strength** | **Strongest** | Strong | Weak | Weak |
| **Best For** | Rigorous analysis | Quick exploration | Baseline | Scale-free |

**Recommendation:** Use `MarkovChainSwitching` (default) for rigorous motif analysis when per-node polarity patterns matter. Use `ConfigurationModel` for rapid exploration or when generating many networks.

See [polarity_network_motifs/random_networks/README.md](polarity_network_motifs/random_networks/README.md) for more details and [speed_check.py](polarity_network_motifs/random_networks/speed_check.py) for benchmarks.

## Loaders

The package supports multiple ways to load networks:
- **Edge list files** (`.txt`, `.csv`): Use `from_edgelist()` for text files with source, target, and optional polarity columns.
- **NetworkX graphs**: Use `from_networkx()` to convert existing NetworkX graphs.
- **Scipy sparse matrices**: Use `from_scipy_sparse()` for adjacency matrices.

For more advanced loading options (e.g., custom Excel formats, biological datasets), please refer to the [`examples/`](./examples) directory, where you can find custom loader implementations for specific datasets.

## Usage Examples

We create a big examples repository with all sorts of use cases such as large K motifs, with/without polarity, and various input formats. Please check the [`examples/`](./examples) directory for detailed code samples.

- **[simple/](./examples/simple)** - Basic usage with NetworkX and scipy sparse matrices.
- **[flyvis/](./examples/flyvis)** 🪰 - Drosophila visual system cell type connectivity (Lappalainen et al., 2024).
- **[fenyves/](./examples/fenyves)** 🪱 - C. elegans chemical synapse with polarity (Fenyves et al., 2020).
- **[durbin/](./examples/durbin)** 🪱 - C. elegans connectome (Durbin, 1986).
- **[cook/](./examples/cook)** 🪱 - C. elegans Whole-Animal Connectome (Cook et al., 2019).
- **[polarity_and_large_examples/](./examples/polarity_and_large_examples)** - K=3 to K=6 motifs with various polarity types, and usage of different algorithms.
- **[multiple_network/](./examples/multiple_network)** - Comparison of multiple networks example: comparing motif profiles across different network topologies.
- **[distance_dependent/](./examples/distance_dependent)** - Custom distance-dependent randomizer example.


## Post-Motif Analysis Tools

The package includes some basic tools for analyzing motif detection results:

### Node Role Analysis
```python
from polarity_network_motifs.analysis.tools import populate_node_data
from polarity_network_motifs.analysis.plots import plot_motif_roles

# Load results and network
results = import_results("results_k3.h5")
network = from_networkx(G, polarity_attr='polarity', node_names=neuron_names)
motif = results['motifs'][78]  # Bi-Mutual motif

# Populate node data (required for analysis)
populate_node_data(motif, network.neuron_names)

# Visualize which nodes appear most in each role
plot_motif_roles(motif, top=10, roles_to_plot=['a', 'b', 'c'])
```

**Frequency of nodes in each role for a specific motif:**

<img width="744" height="296" alt="image" src="https://github.com/user-attachments/assets/230e4812-4b76-4ab6-929b-761ad8985b79" />


See **[flyvis/analysis.ipynb](./examples/flyvis/analysis.ipynb)** for the complete example.


### Finding Co-occurring Nodes
```python
from polarity_network_motifs.analysis.tools import get_node_neighbors_in_motif

# Find all nodes that co-occur with 'Mi1' when it plays role 'a' in the motif
node_name = 'Mi1'
neighbors = get_node_neighbors_in_motif(node_name, motif, network.neuron_names, role='a')
print(f"Co-occurring nodes: {neighbors}")
```

### Subgraph Visualization
```python
from polarity_network_motifs.analysis.plots import draw_sub_graph

# Visualize induced subgraph around a node and its neighbors
nodes_to_visualize = neighbors[:5] + [node_name]
draw_sub_graph(network.graph, nodes_to_visualize, neuron_names=network.neuron_names, center=node_name)
```

**Induced subgraph showing a specific node's participation in a motif:**

<img width="640" height="636" alt="image" src="https://github.com/user-attachments/assets/49e06c02-be18-4ea8-a356-d7efcd9a2084" />


See **[flyvis/analysis.ipynb](./examples/flyvis/analysis.ipynb)** for the complete example.

### Multi-Network Comparison
```python
from polarity_network_motifs.analysis.tools import compare_networks_z_scores

# Compare motif Z-scores across multiple networks
result_files = ['hierarchical_k3.h5', 'feedback_k3.h5', 'ffl_k3.h5']
network_names = ['Hierarchical', 'Feedback', 'FFL-Rich']

compare_networks_z_scores(result_files, network_names, motif_size=3, mode='norm')
```

**Comparing motif Z-scores across multiple networks:**

<img width="694" height="342" alt="image" src="https://github.com/user-attachments/assets/d1f1fac7-cb7d-4c23-a62a-fd2b1aa691b9" />


See **[multiple_network/analysis.ipynb](./examples/multiple_network/analysis.ipynb)** for the complete example.

### CSV Export
```python
from polarity_network_motifs.analysis.tools import get_motif_roles_freq_csv, get_motif_subgraphs_csv

# Export node role frequencies to CSV
get_motif_roles_freq_csv(motif, neuron_names=network.neuron_names)
# Creates: motif_{id}_roles_freq.csv
# Format: Each row is a node, columns are role frequencies (e.g., '38_a', '38_b', '38_c')

# Export all subgraph instances to CSV
get_motif_subgraphs_csv(motif)
# Creates: motif_{id}_subgraphs.csv
# Format: Each row is a subgraph instance with role-to-node mappings (e.g., columns 'a', 'b', 'c')
```

See **[flyvis/analysis.ipynb](./examples/flyvis/analysis.ipynb)** and **[multiple_network/](./examples/multiple_network)** for complete examples.

## Extending the Package

This package is designed to be extensible. Users can expand the package to their own needs with their own implementation of algorithms or randomizers.

For a complete working example of a custom randomizer, check out the **[Distance Dependent Randomizer Example](./examples/distance_dependent)**.

### Custom Algorithms

```python
from polarity_network_motifs import SubGraphsABC

class MyCustomEnumerationAlgorithm(SubGraphsABC):
    def search_sub_graphs(self, k, allow_self_loops):
        # Your implementation
        pass

# Use it
search = MotifSearch(network, k=3, algorithm=MyCustomEnumerationAlgorithm)
```

### Custom Randomizers

```python
from polarity_network_motifs import NetworkRandomizer

class MyCustomRandomizer(NetworkRandomizer):
    def generate(self, amount):
        # Your implementation
        return [random_graph1, random_graph2, ...]

# Use it
search = MotifSearch(network, k=3, randomizer=MyCustomRandomizer)
```

## Polarity Support Details

### Edge-Level vs Node-Level Polarity

This package implements **edge-level polarity**, which is more general than node-level attributes:

- **Edge-level polarity** (this package): Each edge has its own polarity attribute
  - Example: Node A → B with polarity `+`, Node A → C with polarity `-`
  - More flexible for modeling diverse biological and social networks
  
- **Node-level polarity**: All outgoing edges from a node share the same polarity
  - Special case of edge-level polarity
  - Can be easily represented by assigning the same polarity to all edges from a node

### Supported Polarity Types

- **Binary**: `['+', '-']` (excitatory/inhibitory)
- **Ternary**: `['+', '-', 'complex']`
- **Custom**: Any list of string values, e.g., `['type1', 'type2', 'type3']`


### Isomorphic Mapping Availability

The package uses precomputed isomorphic mappings for efficiency when available:

| K | No Polarity | Binary Polarity | Complex Polarity |
|---|-------------|-----------------|------------------|
| 3 | ✅ | ✅ | ✅ |
| 4 | ✅ | ✅ | ❌ |
| 5+ | ❌ | ❌ | ❌ |

When mappings are unavailable, the package automatically falls back to on-the-fly isomorphism checking with `use_isomorphic_mapping=False`. 

For more details about isomorphic mappings, polarity symbol requirements and large motifs, see [polarity_network_motifs/isomorphic/README.md](polarity_network_motifs/isomorphic/README.md).



## Citation

If you use the Polarity Network Motifs in your research, please cite it as:

```bibtex
@software{polarity_network_motifs,
  author = {Dean Geckt},
  title = {detecting network motifs in directed, signed networks with support for arbitrary polarity attributes and large motif patterns},
  year = {2026},
  url = {https://github.com/deangeckt/polarity-network-motifs},
}
```


## Contributing

Contributions welcome! Please open an issue or submit a pull request.