networkx-temporal


Namenetworkx-temporal JSON
Version 1.0b7 PyPI version JSON
download
home_pagehttps://github.com/nelsonaloysio/networkx-temporal
SummaryPython package to build and manipulate temporal NetworkX graphs.
upload_time2024-03-08 01:41:16
maintainer
docs_urlNone
authorNelson Aloysio Reis de Almeida Passos
requires_python>=3.7
licenseMIT
keywords network graph dynamic network temporal graph
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # networkx-temporal

Python package to build and manipulate temporal NetworkX graphs.

## Requirements

* **Python>=3.7**
* networkx>=2.1
* pandas>=1.1.0

## Install

Package is available to install on [PyPI](https://pypi.org/project/networkx-temporal/):

```bash
pip install networkx-temporal
```

## Usage

The code provided as example below is also available as an interactive [Jupyter notebook](https://github.com/nelsonaloysio/networkx-temporal/blob/main/example/notebook.ipynb) ([open on **Colab**](https://colab.research.google.com/github/nelsonaloysio/networkx-temporal/blob/main/example/notebook.ipynb)).

* [Build temporal graph](#common-metrics): basics on manipulating a `networkx-temporal` graph object;
* [Common metrics](#common-metrics): common metrics available from `networkx`;
* [Convert from static to temporal graph](#convert-from-static-to-temporal-graph): converting `networkx` graphs to `networkx-temporal`;
* [Transform temporal graph](#transform-temporal-graph): converting `networkx-temporal` to other graph formats or representations;
* [Detect temporal communities](#detect-temporal-communities): example of temporal community detection with a `networkx-temporal` object.

### Build temporal graph

The `Temporal{Di,Multi,MultiDi}Graph` class uses NetworkX graphs internally to allow easy manipulation of its data structures:

```python
import networkx_temporal as tx
from networkx_temporal.tests.example import draw_temporal_graph

TG = tx.TemporalGraph(directed=True, multigraph=False, t=4)

TG[0].add_edge("a", "b")
TG[1].add_edge("c", "b")
TG[2].add_edge("d", "c")
TG[2].add_edge("d", "e")
TG[2].add_edge("a", "c")
TG[3].add_edge("f", "e")
TG[3].add_edge("f", "a")
TG[3].add_edge("f", "b")

draw_temporal_graph(TG, figsize=(8, 2))
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_0.png)

### Slice into temporal bins

Once initialized, a specified number of bins can be returned in a new object of the same type using `slice`:

```python
TGS = TG.slice(bins=2)
draw_temporal_graph(TGS, figsize=(4, 2))
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_1.png)

By default, created bins are composed of non-overlapping edges and might have uneven size. To balance them, pass `qcut=True`:

```python
TGS = TG.slice(bins=2, qcut=True)
draw_temporal_graph(TGS, figsize=(4, 2))
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_2.png)

Note that in some cases, `qcut` may not be able to split the graph into the number of bins requested and will instead return the maximum number of bins possible. Other exceptions can be worked around by setting `duplicates=True` to allow duplicate edges in bins, or `rank_first=True` to balance snapshots considering the order in which nodes or edges appear.

### Convert to directed or undirected

We can easily convert the edge directions by calling the same methods available from `networkx`:

```python
TG.to_undirected()
TG.to_directed()
```

___

## Common metrics

All methods implemented by `networkx`, e.g., `degree`, are also available to be executed sequentially on the stored time slices.

A few additional methods that consider all time slices are also implemented for convenience, e.g., `temporal_degree` and `temporal_neighbors`.

### Degree centrality

```python
TG.degree()
# TG.in_degree()
# TG.out_degree()
```


```python
TG.temporal_degree()
# TG.temporal_in_degree()
# TG.temporal_out_degree()
```

Or to obtain the degree of a specific node:

```python
TG[0].degree("a")
# TG[0].in_degree("a")
# TG[0].out_degree("a")
```

```python
TG.temporal_degree("a")
# TG.temporal_in_degree("a")
# TG.temporal_out_degree("a")
```

### Node neighborhoods

```python
TG.neighbors("c")
```

To obtain the temporal neighborhood of a node considering all time steps, use the method `temporal_neighbors`:

```python
TG.temporal_neighbors("c")
```

### Order and size

To get the number of nodes and edges in each time step:

```python
print("Order:", TG.order())
print("Size:", TG.size())
```

#### Temporal order and size

The temporal order and size are respectively defined as the length of `TG.temporal_nodes()`, i.e., unique nodes in all time steps, and the length of `TG.temporal_size()`, i.e., sum of edges or interactions among nodes in all time steps.

```python
print("Temporal nodes:", TG.temporal_order())
print("Temporal edges:", TG.temporal_size())
```

#### Total number of nodes and edges

To get the actual number of nodes and edges across all time steps:

```python
print("Total nodes:", TG.total_nodes())  # TG.total_nodes() != TG.temporal_order()
print("Total edges:", TG.total_edges())  # TG.total_edges() == TG.temporal_size()
```

___

## Convert from static to temporal graph

Static graphs can also carry temporal information either in the node- or edge-level attributes.

Slicing a graph into bins usually result in the same number of edges, but a higher number of nodes, as they may appear in more than one snapshot to preserve edge information.

In the example below, we create a static multigraph in which both nodes and edges are attributed with the time step `t` in which they are observed:

```python
import networkx as nx

G = nx.MultiDiGraph()

G.add_nodes_from([
    ("a", {"t": 0}),
    ("b", {"t": 0}),
    ("c", {"t": 1}),
    ("d", {"t": 2}),
    ("e", {"t": 3}),
    ("f", {"t": 3}),
])

G.add_edges_from([
    ("a", "b", {"t": 0}),
    ("c", "b", {"t": 1}),
    ("d", "c", {"t": 2}),
    ("d", "e", {"t": 2}),
    ("a", "c", {"t": 2}),
    ("f", "e", {"t": 3}),
    ("f", "a", {"t": 3}),
    ("f", "b", {"t": 3}),
])
```

### Node-level time attributes

Converting a static graph with node-level temporal data to a temporal graph object (`node_level` considers the source node's time by default when slicing edges):

```python
TG = tx.from_static(G).slice(attr="t", attr_level="node", node_level="source", bins=None, qcut=None)
draw_temporal_graph(TG, figsize=(8, 2))
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_3.png)

Note that considering node-level attributes resulted in placing the edge `(a, c, 2)` in $t=0$ instead, as the source node `a` attribute is set to `t=0`:

```python
G.nodes(data=True)["a"]
```

### Edge-level time attributes

Converting a static graph with edge-level temporal data to a temporal graph object (edge's time applies to both source and target nodes):

```python
TG = tx.from_static(G).slice(attr="t", attr_level="edge", bins=None, qcut=None)
draw_temporal_graph(TG, figsize=(8, 2))
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_4.png)

In this case, considering edge-level attributes results in placing the edge `(a, c, 2)` in $t=2$, as expected.

___

## Transform temporal graph

Once a temporal graph is instantiated, some methods are implemented that allow converting it or returning snaphots, events or unified temporal graphs.

* `to_static`: returns a single graph with unique nodes, does not support dynamic node attributes;
* `to_unified`: returns a single graph with non-unique nodes, supports dynamic node attributes;
* `to_snapshots`: returns a list of graphs with possibly repeated nodes among snapshots;
* `to_events`: returns a list of edge-level events as 3-tuples or 4-tuples, without attributes.

### Convert to different object type

Temporal graphs may be converted to a different object type by calling `convert_to` or passing `to={package}` to the above methods, provided `package` is locally installed. Supported formats:

| Package | Parameter | Alias |
| --- | :---: | :---: |
| [Deep Graph Library](https://www.dgl.ai/) | `dgl` | -
| [graph-tool](https://graph-tool.skewed.de/) | `graph_tool` | `gt`
| [igraph](https://igraph.org/python/) | `igraph` | `ig`
| [NetworKit](https://networkit.github.io/) | `networkit` | `nk`
| [PyTorch Geometric](https://pytorch-geometric.readthedocs.io) | `torch_geometric` | `pyg`

```python
tx.convert_to(G, "igraph")
```

### Static graph

Builds a static or flattened graph containing all the edges found at each time step:

```python
G = TG.to_static()
draw_temporal_graph(G, suptitle="Static Graph")
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_5.png)

### Snapshot-based temporal graph

The snapshot-based temporal graph (STG) is a list of graphs directly accessible under `data` in the temporal graph object:

```python
STG = TG.to_snapshots()
# STG == TG.data
```

### Unified temporal graph

The unified temporal graph (UTG) is a single graph that contains the original data plus proxy nodes and edge couplings connecting sequential temporal nodes.

```python
UTG = TG.to_unified(add_couplings=True)
```

```python
nodes = sorted(TG.temporal_nodes())
pos = {node: (nodes.index(node.rsplit("_")[0]), -int(node.rsplit("_")[1])) for node in UTG.nodes()}
draw_temporal_graph(UTG, pos=pos, figsize=(4, 4), connectionstyle="arc3,rad=0.25", suptitle="Unified Temporal Graph")
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_6.png)

### Event-based temporal graph

An event-based temporal graph (ETG) is a sequence of 3- or 4-tuple edge-based events.

* 3-tuples: `(u, v, t)`, where elements are the source node, target node, and time step of the observed event (also known as a stream graph);

* 4-tuples: `(u, v, t, e)`, where `e` is either a positive (1) or negative (-1) unity for edge addition and deletion, respectively.

```python
ETG = TG.to_events()  # stream=True (default)
ETG

# [('a', 'b', 0),
#  ('c', 'b', 1),
#  ('a', 'c', 2),
#  ('d', 'c', 2),
#  ('d', 'e', 2),
#  ('f', 'e', 3),
#  ('f', 'a', 3),
#  ('f', 'b', 3)]
```

```python
ETG = TG.to_events(stream=False)
ETG

# [('a', 'b', 0, 1),
#  ('c', 'b', 1, 1),
#  ('a', 'b', 1, -1),
#  ('a', 'c', 2, 1),
#  ('d', 'c', 2, 1),
#  ('d', 'e', 2, 1),
#  ('c', 'b', 2, -1),
#  ('f', 'e', 3, 1),
#  ('f', 'a', 3, 1),
#  ('f', 'b', 3, 1),
#  ('a', 'c', 3, -1),
#  ('d', 'c', 3, -1),
#  ('d', 'e', 3, -1)]
```

### Convert back to TemporalGraph object

Functions to convert a newly created STG, ETG, or UTG back to a temporal graph object are also implemented.

```python
tx.from_snapshots(STG)
```

```python
tx.from_events(ETG, directed=False, multigraph=True)
```

```python
tx.from_unified(UTG)
```

___

## Detect temporal communities

The [leidenalg](https://leidenalg.readthedocs.io) package implements community detection algorithms on snapshot-based temporal graphs.

Depending on the objectives, temporal community detection may bring significant advantages on what comes to descriptive tasks and post-hoc network analysis.

Let's first use the [Stochastic Block Model](https://networkx.org/documentation/stable/reference/generated/networkx.generators.community.stochastic_block_model.html) to construct a temporal graph of 4 snapshots, in which each of the **five clusters** of five nodes each continuously mix together:

```python
snapshots = 4   # Temporal snapshots to creaete.
clusters = 5    # Number of clusters/communities.
order = 5       # Nodes in each cluster.
intra = .9      # High probability of intra-community edges.
inter = .1      # Low initial probability of inter-community edges.
change = .5    # Change in intra- and inter-community edges over time.

# Get probabilities for each snapshot.
probs = [[[(intra if i == j else inter) + (t * (change/snapshots) * (-1 if i == j else 1))
            for j in range(clusters)
        ] for i in range(clusters)
    ] for t in range(snapshots)]

# Create graphs from probabilities.
graphs = {}
for t in range(snapshots):
    graphs[t] = nx.stochastic_block_model(clusters*[order], probs[t], seed=10)
    graphs[t].name = t

# Create temporal graph from snapshots.
TG = tx.from_snapshots(graphs)
```

### Static community detection

#### On the static graph (flattened)

Running the Leiden algorithm on the static graph to obtain the community modules fails to retrieve the five communities in the network:

```python
import leidenalg as la

c = plt.cm.tab10.colors

membership = la.find_partition(
    TG.to_static("igraph"),
    la.ModularityVertexPartition,
    n_iterations=-1,
    seed=0,
)

node_color = [c[m] for m in membership.membership]

draw_temporal_graph(TG.to_static(), figsize=(4, 4), node_color=node_color, suptitle="Static Communities")
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_7.png)

We can plot all four generated snapshots, while keeping the community assignments from the previous run:

```python
draw_temporal_graph(TG, figsize=(12, 4), node_color=node_color, suptitle="Static Communities")
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_8.png)

Note that running the same algorithm on the unified temporal graph also yields no significant advantages in terms of correctly retrieving the five clusters.

#### On the snapshots (individually)

Running the same algorithm on each of the generated snapshots instead retrieves the correct clusters on the first snapshot only.

Although results may seem initially better, we lose the community indices previously assigned to nodes in previous snapshots, represented by their different colors:

```python
temporal_opts = {}

for t in range(len(TG)):
    membership = la.find_partition(
        TG[t:t+1].to_static("igraph"),
        la.ModularityVertexPartition,
        n_iterations=-1,
        seed=0,
    )
    temporal_opts[t] = {
        "node_color": [c[m] for m in membership.membership]
    }

draw_temporal_graph(TG, nrows=1, ncols=4, figsize=(12, 4), suptitle="Snapshot Communities", temporal_opts=temporal_opts)
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_9.png)

### Temporal community detection

Detecting temporal communities instead allows us to correctly retrieve the clusters in all snapshots, while maintaining their indices/colors over time.

The `interslice_weight` among temporal nodes in a sequence of snapshots defaults to `1.0` in unweighted graphs and may be adjusted accordingly:

```python
temporal_membership, improvement = la.find_partition_temporal(
    TG.to_snapshots("igraph"),
    la.ModularityVertexPartition,
    interslice_weight=1.0,
    n_iterations=-1,
    seed=0,
    vertex_id_attr="_nx_name"
)

temporal_opts = {
    t: {"node_color": [c[m] for m in temporal_membership[t]]}
    for t in range(len(TG))
}

draw_temporal_graph(TG, nrows=1, ncols=4, figsize=(12, 4), suptitle="Temporal Communities", temporal_opts=temporal_opts)
```

![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_10.png)

___

### References

* [Deep Graph Library](https://www.dgl.ai/)
* [graph-tool](https://graph-tool.skewed.de/)
* [igraph](https://igraph.org/python/)
* [Leiden](https://leidenalg.readthedocs.io)
* [NetworKit]()
* [NetworkX](https://networkx.github.io)
* [Pandas](https://pandas.pydata.org/)
* [PyTorch Geometric](https://pytorch-geometric.readthedocs.io)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/nelsonaloysio/networkx-temporal",
    "name": "networkx-temporal",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "Network,Graph,Dynamic Network,Temporal Graph",
    "author": "Nelson Aloysio Reis de Almeida Passos",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/13/c3/974c3f9017a1d56ae94fa9b9ae0c0b422c7267f444a7fabe01d7248978af/networkx-temporal-1.0b7.tar.gz",
    "platform": null,
    "description": "# networkx-temporal\n\nPython package to build and manipulate temporal NetworkX graphs.\n\n## Requirements\n\n* **Python>=3.7**\n* networkx>=2.1\n* pandas>=1.1.0\n\n## Install\n\nPackage is available to install on [PyPI](https://pypi.org/project/networkx-temporal/):\n\n```bash\npip install networkx-temporal\n```\n\n## Usage\n\nThe code provided as example below is also available as an interactive [Jupyter notebook](https://github.com/nelsonaloysio/networkx-temporal/blob/main/example/notebook.ipynb) ([open on **Colab**](https://colab.research.google.com/github/nelsonaloysio/networkx-temporal/blob/main/example/notebook.ipynb)).\n\n* [Build temporal graph](#common-metrics): basics on manipulating a `networkx-temporal` graph object;\n* [Common metrics](#common-metrics): common metrics available from `networkx`;\n* [Convert from static to temporal graph](#convert-from-static-to-temporal-graph): converting `networkx` graphs to `networkx-temporal`;\n* [Transform temporal graph](#transform-temporal-graph): converting `networkx-temporal` to other graph formats or representations;\n* [Detect temporal communities](#detect-temporal-communities): example of temporal community detection with a `networkx-temporal` object.\n\n### Build temporal graph\n\nThe `Temporal{Di,Multi,MultiDi}Graph` class uses NetworkX graphs internally to allow easy manipulation of its data structures:\n\n```python\nimport networkx_temporal as tx\nfrom networkx_temporal.tests.example import draw_temporal_graph\n\nTG = tx.TemporalGraph(directed=True, multigraph=False, t=4)\n\nTG[0].add_edge(\"a\", \"b\")\nTG[1].add_edge(\"c\", \"b\")\nTG[2].add_edge(\"d\", \"c\")\nTG[2].add_edge(\"d\", \"e\")\nTG[2].add_edge(\"a\", \"c\")\nTG[3].add_edge(\"f\", \"e\")\nTG[3].add_edge(\"f\", \"a\")\nTG[3].add_edge(\"f\", \"b\")\n\ndraw_temporal_graph(TG, figsize=(8, 2))\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_0.png)\n\n### Slice into temporal bins\n\nOnce initialized, a specified number of bins can be returned in a new object of the same type using `slice`:\n\n```python\nTGS = TG.slice(bins=2)\ndraw_temporal_graph(TGS, figsize=(4, 2))\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_1.png)\n\nBy default, created bins are composed of non-overlapping edges and might have uneven size. To balance them, pass `qcut=True`:\n\n```python\nTGS = TG.slice(bins=2, qcut=True)\ndraw_temporal_graph(TGS, figsize=(4, 2))\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_2.png)\n\nNote that in some cases, `qcut` may not be able to split the graph into the number of bins requested and will instead return the maximum number of bins possible. Other exceptions can be worked around by setting `duplicates=True` to allow duplicate edges in bins, or `rank_first=True` to balance snapshots considering the order in which nodes or edges appear.\n\n### Convert to directed or undirected\n\nWe can easily convert the edge directions by calling the same methods available from `networkx`:\n\n```python\nTG.to_undirected()\nTG.to_directed()\n```\n\n___\n\n## Common metrics\n\nAll methods implemented by `networkx`, e.g., `degree`, are also available to be executed sequentially on the stored time slices.\n\nA few additional methods that consider all time slices are also implemented for convenience, e.g., `temporal_degree` and `temporal_neighbors`.\n\n### Degree centrality\n\n```python\nTG.degree()\n# TG.in_degree()\n# TG.out_degree()\n```\n\n\n```python\nTG.temporal_degree()\n# TG.temporal_in_degree()\n# TG.temporal_out_degree()\n```\n\nOr to obtain the degree of a specific node:\n\n```python\nTG[0].degree(\"a\")\n# TG[0].in_degree(\"a\")\n# TG[0].out_degree(\"a\")\n```\n\n```python\nTG.temporal_degree(\"a\")\n# TG.temporal_in_degree(\"a\")\n# TG.temporal_out_degree(\"a\")\n```\n\n### Node neighborhoods\n\n```python\nTG.neighbors(\"c\")\n```\n\nTo obtain the temporal neighborhood of a node considering all time steps, use the method `temporal_neighbors`:\n\n```python\nTG.temporal_neighbors(\"c\")\n```\n\n### Order and size\n\nTo get the number of nodes and edges in each time step:\n\n```python\nprint(\"Order:\", TG.order())\nprint(\"Size:\", TG.size())\n```\n\n#### Temporal order and size\n\nThe temporal order and size are respectively defined as the length of `TG.temporal_nodes()`, i.e., unique nodes in all time steps, and the length of `TG.temporal_size()`, i.e., sum of edges or interactions among nodes in all time steps.\n\n```python\nprint(\"Temporal nodes:\", TG.temporal_order())\nprint(\"Temporal edges:\", TG.temporal_size())\n```\n\n#### Total number of nodes and edges\n\nTo get the actual number of nodes and edges across all time steps:\n\n```python\nprint(\"Total nodes:\", TG.total_nodes())  # TG.total_nodes() != TG.temporal_order()\nprint(\"Total edges:\", TG.total_edges())  # TG.total_edges() == TG.temporal_size()\n```\n\n___\n\n## Convert from static to temporal graph\n\nStatic graphs can also carry temporal information either in the node- or edge-level attributes.\n\nSlicing a graph into bins usually result in the same number of edges, but a higher number of nodes, as they may appear in more than one snapshot to preserve edge information.\n\nIn the example below, we create a static multigraph in which both nodes and edges are attributed with the time step `t` in which they are observed:\n\n```python\nimport networkx as nx\n\nG = nx.MultiDiGraph()\n\nG.add_nodes_from([\n    (\"a\", {\"t\": 0}),\n    (\"b\", {\"t\": 0}),\n    (\"c\", {\"t\": 1}),\n    (\"d\", {\"t\": 2}),\n    (\"e\", {\"t\": 3}),\n    (\"f\", {\"t\": 3}),\n])\n\nG.add_edges_from([\n    (\"a\", \"b\", {\"t\": 0}),\n    (\"c\", \"b\", {\"t\": 1}),\n    (\"d\", \"c\", {\"t\": 2}),\n    (\"d\", \"e\", {\"t\": 2}),\n    (\"a\", \"c\", {\"t\": 2}),\n    (\"f\", \"e\", {\"t\": 3}),\n    (\"f\", \"a\", {\"t\": 3}),\n    (\"f\", \"b\", {\"t\": 3}),\n])\n```\n\n### Node-level time attributes\n\nConverting a static graph with node-level temporal data to a temporal graph object (`node_level` considers the source node's time by default when slicing edges):\n\n```python\nTG = tx.from_static(G).slice(attr=\"t\", attr_level=\"node\", node_level=\"source\", bins=None, qcut=None)\ndraw_temporal_graph(TG, figsize=(8, 2))\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_3.png)\n\nNote that considering node-level attributes resulted in placing the edge `(a, c, 2)` in $t=0$ instead, as the source node `a` attribute is set to `t=0`:\n\n```python\nG.nodes(data=True)[\"a\"]\n```\n\n### Edge-level time attributes\n\nConverting a static graph with edge-level temporal data to a temporal graph object (edge's time applies to both source and target nodes):\n\n```python\nTG = tx.from_static(G).slice(attr=\"t\", attr_level=\"edge\", bins=None, qcut=None)\ndraw_temporal_graph(TG, figsize=(8, 2))\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_4.png)\n\nIn this case, considering edge-level attributes results in placing the edge `(a, c, 2)` in $t=2$, as expected.\n\n___\n\n## Transform temporal graph\n\nOnce a temporal graph is instantiated, some methods are implemented that allow converting it or returning snaphots, events or unified temporal graphs.\n\n* `to_static`: returns a single graph with unique nodes, does not support dynamic node attributes;\n* `to_unified`: returns a single graph with non-unique nodes, supports dynamic node attributes;\n* `to_snapshots`: returns a list of graphs with possibly repeated nodes among snapshots;\n* `to_events`: returns a list of edge-level events as 3-tuples or 4-tuples, without attributes.\n\n### Convert to different object type\n\nTemporal graphs may be converted to a different object type by calling `convert_to` or passing `to={package}` to the above methods, provided `package` is locally installed. Supported formats:\n\n| Package | Parameter | Alias |\n| --- | :---: | :---: |\n| [Deep Graph Library](https://www.dgl.ai/) | `dgl` | -\n| [graph-tool](https://graph-tool.skewed.de/) | `graph_tool` | `gt`\n| [igraph](https://igraph.org/python/) | `igraph` | `ig`\n| [NetworKit](https://networkit.github.io/) | `networkit` | `nk`\n| [PyTorch Geometric](https://pytorch-geometric.readthedocs.io) | `torch_geometric` | `pyg`\n\n```python\ntx.convert_to(G, \"igraph\")\n```\n\n### Static graph\n\nBuilds a static or flattened graph containing all the edges found at each time step:\n\n```python\nG = TG.to_static()\ndraw_temporal_graph(G, suptitle=\"Static Graph\")\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_5.png)\n\n### Snapshot-based temporal graph\n\nThe snapshot-based temporal graph (STG) is a list of graphs directly accessible under `data` in the temporal graph object:\n\n```python\nSTG = TG.to_snapshots()\n# STG == TG.data\n```\n\n### Unified temporal graph\n\nThe unified temporal graph (UTG) is a single graph that contains the original data plus proxy nodes and edge couplings connecting sequential temporal nodes.\n\n```python\nUTG = TG.to_unified(add_couplings=True)\n```\n\n```python\nnodes = sorted(TG.temporal_nodes())\npos = {node: (nodes.index(node.rsplit(\"_\")[0]), -int(node.rsplit(\"_\")[1])) for node in UTG.nodes()}\ndraw_temporal_graph(UTG, pos=pos, figsize=(4, 4), connectionstyle=\"arc3,rad=0.25\", suptitle=\"Unified Temporal Graph\")\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_6.png)\n\n### Event-based temporal graph\n\nAn event-based temporal graph (ETG) is a sequence of 3- or 4-tuple edge-based events.\n\n* 3-tuples: `(u, v, t)`, where elements are the source node, target node, and time step of the observed event (also known as a stream graph);\n\n* 4-tuples: `(u, v, t, e)`, where `e` is either a positive (1) or negative (-1) unity for edge addition and deletion, respectively.\n\n```python\nETG = TG.to_events()  # stream=True (default)\nETG\n\n# [('a', 'b', 0),\n#  ('c', 'b', 1),\n#  ('a', 'c', 2),\n#  ('d', 'c', 2),\n#  ('d', 'e', 2),\n#  ('f', 'e', 3),\n#  ('f', 'a', 3),\n#  ('f', 'b', 3)]\n```\n\n```python\nETG = TG.to_events(stream=False)\nETG\n\n# [('a', 'b', 0, 1),\n#  ('c', 'b', 1, 1),\n#  ('a', 'b', 1, -1),\n#  ('a', 'c', 2, 1),\n#  ('d', 'c', 2, 1),\n#  ('d', 'e', 2, 1),\n#  ('c', 'b', 2, -1),\n#  ('f', 'e', 3, 1),\n#  ('f', 'a', 3, 1),\n#  ('f', 'b', 3, 1),\n#  ('a', 'c', 3, -1),\n#  ('d', 'c', 3, -1),\n#  ('d', 'e', 3, -1)]\n```\n\n### Convert back to TemporalGraph object\n\nFunctions to convert a newly created STG, ETG, or UTG back to a temporal graph object are also implemented.\n\n```python\ntx.from_snapshots(STG)\n```\n\n```python\ntx.from_events(ETG, directed=False, multigraph=True)\n```\n\n```python\ntx.from_unified(UTG)\n```\n\n___\n\n## Detect temporal communities\n\nThe [leidenalg](https://leidenalg.readthedocs.io) package implements community detection algorithms on snapshot-based temporal graphs.\n\nDepending on the objectives, temporal community detection may bring significant advantages on what comes to descriptive tasks and post-hoc network analysis.\n\nLet's first use the [Stochastic Block Model](https://networkx.org/documentation/stable/reference/generated/networkx.generators.community.stochastic_block_model.html) to construct a temporal graph of 4 snapshots, in which each of the **five clusters** of five nodes each continuously mix together:\n\n```python\nsnapshots = 4   # Temporal snapshots to creaete.\nclusters = 5    # Number of clusters/communities.\norder = 5       # Nodes in each cluster.\nintra = .9      # High probability of intra-community edges.\ninter = .1      # Low initial probability of inter-community edges.\nchange = .5    # Change in intra- and inter-community edges over time.\n\n# Get probabilities for each snapshot.\nprobs = [[[(intra if i == j else inter) + (t * (change/snapshots) * (-1 if i == j else 1))\n            for j in range(clusters)\n        ] for i in range(clusters)\n    ] for t in range(snapshots)]\n\n# Create graphs from probabilities.\ngraphs = {}\nfor t in range(snapshots):\n    graphs[t] = nx.stochastic_block_model(clusters*[order], probs[t], seed=10)\n    graphs[t].name = t\n\n# Create temporal graph from snapshots.\nTG = tx.from_snapshots(graphs)\n```\n\n### Static community detection\n\n#### On the static graph (flattened)\n\nRunning the Leiden algorithm on the static graph to obtain the community modules fails to retrieve the five communities in the network:\n\n```python\nimport leidenalg as la\n\nc = plt.cm.tab10.colors\n\nmembership = la.find_partition(\n    TG.to_static(\"igraph\"),\n    la.ModularityVertexPartition,\n    n_iterations=-1,\n    seed=0,\n)\n\nnode_color = [c[m] for m in membership.membership]\n\ndraw_temporal_graph(TG.to_static(), figsize=(4, 4), node_color=node_color, suptitle=\"Static Communities\")\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_7.png)\n\nWe can plot all four generated snapshots, while keeping the community assignments from the previous run:\n\n```python\ndraw_temporal_graph(TG, figsize=(12, 4), node_color=node_color, suptitle=\"Static Communities\")\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_8.png)\n\nNote that running the same algorithm on the unified temporal graph also yields no significant advantages in terms of correctly retrieving the five clusters.\n\n#### On the snapshots (individually)\n\nRunning the same algorithm on each of the generated snapshots instead retrieves the correct clusters on the first snapshot only.\n\nAlthough results may seem initially better, we lose the community indices previously assigned to nodes in previous snapshots, represented by their different colors:\n\n```python\ntemporal_opts = {}\n\nfor t in range(len(TG)):\n    membership = la.find_partition(\n        TG[t:t+1].to_static(\"igraph\"),\n        la.ModularityVertexPartition,\n        n_iterations=-1,\n        seed=0,\n    )\n    temporal_opts[t] = {\n        \"node_color\": [c[m] for m in membership.membership]\n    }\n\ndraw_temporal_graph(TG, nrows=1, ncols=4, figsize=(12, 4), suptitle=\"Snapshot Communities\", temporal_opts=temporal_opts)\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_9.png)\n\n### Temporal community detection\n\nDetecting temporal communities instead allows us to correctly retrieve the clusters in all snapshots, while maintaining their indices/colors over time.\n\nThe `interslice_weight` among temporal nodes in a sequence of snapshots defaults to `1.0` in unweighted graphs and may be adjusted accordingly:\n\n```python\ntemporal_membership, improvement = la.find_partition_temporal(\n    TG.to_snapshots(\"igraph\"),\n    la.ModularityVertexPartition,\n    interslice_weight=1.0,\n    n_iterations=-1,\n    seed=0,\n    vertex_id_attr=\"_nx_name\"\n)\n\ntemporal_opts = {\n    t: {\"node_color\": [c[m] for m in temporal_membership[t]]}\n    for t in range(len(TG))\n}\n\ndraw_temporal_graph(TG, nrows=1, ncols=4, figsize=(12, 4), suptitle=\"Temporal Communities\", temporal_opts=temporal_opts)\n```\n\n![png](https://github.com/nelsonaloysio/networkx-temporal/raw/main/example/fig/fig_10.png)\n\n___\n\n### References\n\n* [Deep Graph Library](https://www.dgl.ai/)\n* [graph-tool](https://graph-tool.skewed.de/)\n* [igraph](https://igraph.org/python/)\n* [Leiden](https://leidenalg.readthedocs.io)\n* [NetworKit]()\n* [NetworkX](https://networkx.github.io)\n* [Pandas](https://pandas.pydata.org/)\n* [PyTorch Geometric](https://pytorch-geometric.readthedocs.io)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Python package to build and manipulate temporal NetworkX graphs.",
    "version": "1.0b7",
    "project_urls": {
        "Homepage": "https://github.com/nelsonaloysio/networkx-temporal",
        "Source": "https://github.com/nelsonaloysio/networkx-temporal",
        "Tracker": "https://github.com/nelsonaloysio/networkx-temporal/issues"
    },
    "split_keywords": [
        "network",
        "graph",
        "dynamic network",
        "temporal graph"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5d46f7304e5a6468fc22cd9be1fd388980fbffa5a78cf0c9a4838d343dca3fe8",
                "md5": "74a2d4820d16165cc9c1d6670630bd42",
                "sha256": "bd9a4287d1f2e15464ddf84b4cad0c39abe61eadade55fc3595072710f3c4185"
            },
            "downloads": -1,
            "filename": "networkx_temporal-1.0b7-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "74a2d4820d16165cc9c1d6670630bd42",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 19821,
            "upload_time": "2024-03-08T01:41:14",
            "upload_time_iso_8601": "2024-03-08T01:41:14.577088Z",
            "url": "https://files.pythonhosted.org/packages/5d/46/f7304e5a6468fc22cd9be1fd388980fbffa5a78cf0c9a4838d343dca3fe8/networkx_temporal-1.0b7-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "13c3974c3f9017a1d56ae94fa9b9ae0c0b422c7267f444a7fabe01d7248978af",
                "md5": "8675effb23a5e0f78ac51e90397d347b",
                "sha256": "ecd9c90477e879562693f19abe671d2ee329e4ef8251cf9bd9c8f250fd0ae0f5"
            },
            "downloads": -1,
            "filename": "networkx-temporal-1.0b7.tar.gz",
            "has_sig": false,
            "md5_digest": "8675effb23a5e0f78ac51e90397d347b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 22655,
            "upload_time": "2024-03-08T01:41:16",
            "upload_time_iso_8601": "2024-03-08T01:41:16.517189Z",
            "url": "https://files.pythonhosted.org/packages/13/c3/974c3f9017a1d56ae94fa9b9ae0c0b422c7267f444a7fabe01d7248978af/networkx-temporal-1.0b7.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-08 01:41:16",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "nelsonaloysio",
    "github_project": "networkx-temporal",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "networkx-temporal"
}
        
Elapsed time: 0.20496s