Network objects

Every network, downloaded or generated, converts and writes the same way: to_networkx(), to_infomap(), and to_pajek(directory) / to_link_list(directory).

class netgamut.Network(dataset, file, archive_path, member=None, source='', edge_file=None, columns=Columns(weight=None, time=None, layer=None, names=()))[source]

A downloaded network on local disk.

dataset: DatasetLike

Full metadata for the dataset this file belongs to.

file: DataFile

The specific artifact that was downloaded.

archive_path: Path

The downloaded artifact exactly as the source serves it, compression included.

member: str | None = None

For sources that ship a bundle, the member holding the edge list – Netzschleuder’s csv.zip contains edges.csv alongside node and graph properties. None when the download is a single file.

source: str = ''

Which netgamut.<source> produced this handle. nodes() uses it to reach the reader that understands this source’s metadata layout, since no two sources keep node attributes in the same place.

edge_file: Path | None = None

For sources whose download needs rewriting before any tool can read it, the rewritten edge list. KONECT’s files open with a % header Infomap rejects and number the two sides of a bipartite network independently, so netgamut.konect writes a clean copy and keeps the archive beside it.

columns: Columns = Columns(weight=None, time=None, layer=None, names=())

What the fields beyond source and target hold – weight, time, layer – as the source declares it from its metadata. Everything that reads links follows it, so a timestamp or a sign is never mistaken for a weight. Replace it with dataclasses.replace() to read a column differently.

Whether links carry a weight column Infomap accepts.

property is_bundle: bool

Whether the download is a multi-file archive on disk.

property is_archive: bool

Whether reading needs extract() rather than path.

False for a bundle with a designated member: that member is the network, so reading works directly even though the download is an archive. Use is_bundle to ask about the file itself.

property path: Path

A path you can hand to any tool that reads a text file.

Gzipped files are decompressed once, next to the archive. A designated member is extracted from its bundle the same way. For a bundle with no member there is no single text file, so this returns the archive; use extract() instead.

extract(destination=None)[source]

Unpack a .tar.gz or .zip bundle and return the directory.

Works even when a member has been designated as the network, so a Netzschleuder bundle’s node and graph properties stay reachable.

Extraction lands in a scratch directory and is published with a rename, so a crashed or interrupted run cannot leave a partial directory that a later call would mistake for the real thing.

open()[source]

Open the network as a text stream, decompressing on the fly.

property header: tuple[str, ...]

The leading comment lines, which SNAP uses to describe the file.

property delimiter: str | None

The field separator this file uses, or None for whitespace.

rows(*, delimiter=None)[source]

Yield every non-comment line, split into its fields.

Use this for the datasets that carry more than two columns, such as timestamps in the temporal category or signs in the signed category.

The separator is detected from the first data line, since a few SNAP files are CSV rather than whitespace-separated. Pass delimiter to override that.

nodes()[source]

Read this network’s per-node metadata, in the shape every source uses.

Where the attributes live differs completely by source – a separate SNAP artifact, a member of a Netzschleuder bundle, OGB’s raw/ and mapping/ folders, a materialised PyG table – so the reading is delegated to the source. What comes back is always a NodeTable:

nodes = netgamut.load("ogbn-arxiv").nodes()
nodes.columns                       # ('arxiv_category', 'year', 'paper')
nodes["arxiv_category"]             # {node: 'arxiv cs na', ...}
nodes.partition("arxiv_category")   # {node: 0..k-1}, for AMI/NMI/D

Sources that keep metadata in a separate artifact download it here, on the same cache-once terms as load().

Node metadata is not ground truth for community structure: a partition that disagrees with an attribute is not thereby wrong. See netgamut._core.nodes.

Raises:

LookupError – The source publishes no per-node metadata, or this dataset ships none.

communities(*, cast=None)[source]

Stream communities from a SNAP ground-truth file.

Each line of a *.cmty.txt file is one community, listed as tab-separated node ids. Communities overlap, so a node may appear in several. Use this as the reference partition when scoring a community-detection run.

edges(*, cast=None)[source]

Stream (source, target) pairs.

Values are strings unless you pass cast; cast=int is usual, since most SNAP files use integer node ids. Streaming means memory stays flat no matter how large the file is.

to_networkx(*, directed=None, cast=<class 'int'>)[source]

Build a networkx graph.

Direction defaults to what SNAP reports for the dataset. Requires the networkx extra:

pip install "netgamut[networkx]"

Be deliberate here: a networkx graph holds every node and edge in memory, so check dataset.edges before calling this on a large one.

property partition: None

a downloaded network has no planted partition.

Present so code written for generated networks runs on downloaded ones too. Per-node metadata, which is not ground truth, is nodes().

Type:

Always None

to_pajek(directory, *, name=None, directed=None, names=None)[source]

Write <directory>/<name>.net in Pajek format and return its path.

name defaults to the dataset’s name and directed to the direction the source reports. Integer node ids are kept; any other labels are numbered 1..N and kept as *Vertices names. The file is streamed in two passes – nodes, then links – so memory holds the node set but never the links.

names maps a node id to the name shown on its *Vertices line. By default it is the source’s own names, where they come in the download already made – Netzschleuder’s nodes.csv, KONECT’s ent.* files; see NodeTable.labels(). A node without one shows its id. Pass {} to label every node by its id.

Write <directory>/<name>.txt as a link list and return its path.

Streams in one pass. A # directed or # undirected comment records directed, which defaults to what the source reports.

Raises:

ValueError – A node id is not a non-negative integer; use to_pajek(), which keeps labels as names.

to_infomap(*, directed=None, **kwargs)[source]

Build an infomap.Infomap instance with this network loaded.

Direction defaults to what SNAP reports. Extra keyword arguments are passed to the Infomap constructor. Requires the infomap extra:

pip install "netgamut[infomap]"
property times: Any[source]

The time of every link, as a numpy array in file order.

One entry per link row, aligned with edges(). Integers stay int64 (Unix timestamps usually), fractional times are float64 and ISO dates datetime64[s]. Read once and kept, so look at it before choosing how to bin:

t = net.times
t.min(), t.max()
numpy.histogram(t, bins="auto")

Needs numpy.

Raises:

LookupError – The source declares no time column for this file.

time_bins(bins=10, range=None)[source]

The bin edges snapshots() would use, from times.

bins and range mean what they mean to numpy.histogram_bin_edges(): a number of equal-width bins, a sequence of edges, or an estimator such as "auto" or "fd".

snapshots(bins=10, range=None, *, directed=None)[source]

Yield one first-order network per time bin, built only when reached.

Bins follow time_bins(); bin i holds the links with edges[i] <= t < edges[i + 1] (the last bin also t == edges[-1]), and links outside every bin are left out. Every bin yields, empty ones included, so the position in the iteration is the bin. A link’s weight is how many times it occurs in the bin – the sum of its weights if the file has a weight column. Each snapshot’s info holds its bin, start, end and the number of events.

The file is read once, on the first next(); each snapshot is assembled from its rows when it is reached.

Raises:

LookupError – The source declares no time column for this file.

to_multilayer(*, bins=None, range=None, layer=None, directed=None)[source]

Build a MultilayerNetwork from a layer or time column.

With bins, the layers are time bins, exactly the snapshots(): layer i is bin i, empty ones kept so layer ids stay time order, and info["bin_edges"] records the edges. Without, the layers come from the source’s layer column, or from the column named layer – any categorical field, e.g. a type of interaction. Link weights are counts of occurrences, summed weights where the file has a weight column.

There are no inter-layer links: give Infomap a relax rate, and for time layers a relax limit so flow moves only between neighbouring bins:

ml.to_infomap(multilayer_relax_rate=0.15, multilayer_relax_limit=1)
Raises:
  • LookupError – No time column (with bins), or no layer column.

  • ValueError – Both bins and layer given.

class netgamut.InMemoryNetwork(name, nodes, links, directed=False, weighted=False, partition=None, info=<factory>)[source]

A first-order network in memory: generated, or a time slice of a download.

Instances are frozen; the containers are the generator’s result and must not be mutated. Convert with to_networkx() for anything that edits.

name: str

Used as the file name by to_pajek() and to_link_list().

nodes: tuple[Hashable, ...]

Every node, isolated ones included, in the generator’s order.

(source, target) -> weight; weight 1.0 throughout when unweighted.

weighted: bool = False

Whether links carries weights worth writing.

partition: dict[Hashable, Any] | None = None

The planted ground truth of a generated network, {node: module}; a module is an int, a path tuple for a hierarchical partition, or a tuple of memberships for an overlapping cover. None for real data.

info: dict[str, Any]

For a generated network, "generator" and "parameters" and the generator’s diagnostics; for a snapshot, its bin and time span.

classmethod from_networkx(graph, *, name, partition=None, info=None)[source]

Capture a networkx graph; "weight" is the only edge data kept.

edges()[source]

Yield (source, target) pairs, as Network.edges() does.

to_networkx(*, partition_attribute='module')[source]

Build a networkx graph, with the planted partition as a node attribute.

Each node’s module lands under partition_attribute ("module" by default; None leaves it out), so networkx.get_node_attributes(G, "module") gives back partition. Weights are set only for a weighted network.

to_infomap(**kwargs)[source]

Build an infomap.Infomap instance with this network loaded.

Direction follows the network; extra keyword arguments go to the Infomap constructor. Node ids are the labels when those are non-negative integers, as every generator here produces; otherwise 1..N with the labels as names.

to_pajek(directory, *, name=None)[source]

Write <directory>/<name>.net in Pajek format and return its path.

name defaults to name. Generator names include the parameters that matter, but not all of them: pass name when writing a sweep that varies something else into one directory.

Write <directory>/<name>.txt as a link list and return its path.

A link list holds links only, so isolated nodes are not in the file; to_pajek() keeps them.

Raises:

ValueError – A node label is not a non-negative integer; use to_pajek(), which keeps labels as names.

class netgamut.StateNetwork(order, physical_nodes, states, links, num_paths=0, num_short_paths=0, name='', partition=None, info=<factory>)[source]

A memory network in Infomap’s state format.

Build one from paths with from_paths() – or sample one with netgamut.generate.memory_benchmark() – then write it for the Infomap CLI with to_pajek() or hand it to the Python API with to_infomap(). Instances are frozen; the dicts are the construction result and must not be mutated.

order: int

how many physical nodes one state remembers.

Type:

Markov order

physical_nodes: dict[int, str]

Physical node id -> label, ids assigned from 1 in first-seen order.

states: dict[int, tuple[int, ...]]

State id -> window of physical ids; the current node is the last entry.

(source_state_id, target_state_id) -> accumulated weight.

num_paths: int = 0

How many paths were consumed, including the too-short ones.

num_short_paths: int = 0

they contain no state transition and contribute nothing. Nonzero here is not an error, but a large fraction means the chosen order overreaches the data – or, if the paths were cut to a node subset, that cutting fragmented them.

Type:

Paths shorter than order + 1 nodes

name: str = ''

Used as the file name by to_pajek(); the dataset’s name when built from a path dataset.

partition: dict[int, Any] | None = None

The planted ground truth of a sampled state network, {state_id: module}.

info: dict[str, Any]

For a sampled network, "generator", "parameters" and the sampler’s diagnostics.

classmethod from_paths(paths, *, order=2, name='')[source]

Accumulate paths into a state network of the given Markov order.

Each item is a sequence of node labels, or a (sequence, weight) pair – itineraries flown by many passengers count that many times. A sliding window turns every (order + 1)-gram into one state link: with the default order 2, i -> j -> k adds weight to the link (i, j) -> (j, k). Repeated transitions accumulate.

Labels can be any hashable value; they are compared as given and rendered with str in output. Ids are assigned in first-seen order, so the same paths in the same order produce identical output files.

Raises:

ValueError – order is smaller than 1.

state_name(state_id)[source]

A readable name for one state: its memory, e.g. "LAX -> JFK".

write(target)[source]

Write the Infomap states format: *Vertices, *States, *Links.

The file works directly as CLI input:

infomap network_states.net output/ --directed

Output is sorted by id, so equal networks produce equal files.

to_pajek(directory, *, name=None)[source]

Write <directory>/<name>.net and return its path.

The states format (see write()) for order 2 and up; at order 1, where states and physical nodes coincide, a plain directed Pajek file, matching what to_infomap() loads. name defaults to name.

Write <directory>/<name>.txt at order 1 and return its path.

Raises:

TypeError – Order 2 and up; a link list cannot say which physical node a state belongs to, so use to_pajek().

to_networkx(*, partition_attribute='module')[source]

Build a networkx.DiGraph over state ids.

Each node carries physical (its physical id) and name (its memory, e.g. "LAX -> JFK"), plus the planted module under partition_attribute when there is one. Edges carry weight.

to_infomap(**kwargs)[source]

Build an infomap.Infomap instance with this state network loaded.

Directed flow is the default – transitions are inherently directed. Pass flow_model="rawdir" to use the accumulated counts as raw flow instead of smoothing them with the default PageRank iteration; which is right depends on whether the paths sample a stationary process. Requires the infomap extra:

pip install "netgamut[infomap]"
class netgamut.MultilayerNetwork(name, intra, inter=<factory>, directed=False, weighted=False, partition=None, names=<factory>, info=<factory>)[source]

Intra-layer links per layer, optional inter-layer links, planted partition.

Instances are frozen; the containers are the generator’s result and must not be mutated.

name: str

Used as the file name by to_pajek().

intra: dict[Hashable, dict[tuple[Hashable, Hashable], float]]

{layer: {(u, v): weight}}, weight 1.0 throughout when unweighted.

inter: dict[tuple[tuple[Hashable, Hashable], tuple[Hashable, Hashable]], float]

{((node, layer), (node, layer)): weight}, both ends on the same physical node – Infomap’s *Inter links, which it spreads to the node’s neighbours in the target layer. Empty for a multiplex whose layers are coupled by the relax rate rather than by links.

weighted: bool = False

Whether link weights are worth writing; multilayer files always carry a weight column, so this only affects to_networkx().

partition: dict[tuple[Hashable, Hashable], Any] | None = None

The planted ground truth, {(node, layer): module}.

names: dict[Hashable, str]

Display names for physical nodes, where they differ from the labels.

info: dict[str, Any]

"generator" and "parameters" say how this was made; the rest is the generator’s own diagnostics.

property layers: list[Hashable]

Every layer, integers in numeric order.

state_nodes()[source]

Every (node, layer): link endpoints plus partitioned isolates.

to_networkx(*, layer=None, partition_attribute='module')[source]

Build a networkx graph of the whole network or of one layer.

With layer None, the supra-graph: nodes are (node, layer) tuples carrying node and layer attributes, intra-layer edges join state nodes of one layer and inter-layer edges are marked interlayer=True. With a layer, that layer alone over physical nodes – what {layer: graph} used to hold. Either way each node’s planted module lands under partition_attribute (None to skip).

to_infomap(**kwargs)[source]

Build an infomap.Infomap instance with this network loaded.

Pass multilayer_relax_rate (and friends) to control how Infomap couples layers that have no inter-layer links. Ids follow netgamut._core.formats.node_ids(): kept when they are non-negative integers, renumbered otherwise.

to_pajek(directory, *, name=None)[source]

Write <directory>/<name>.net in Infomap’s multilayer format.

*Vertices, then *Intra, and *Inter when there are inter-layer links. Returns the path.

Not available: a link list cannot say which layer a link is in.

Raises:

TypeError – Always; use to_pajek(), whose *Intra/*Inter sections are the multilayer equivalent.

edges()[source]

Yield every link as a ((node, layer), (node, layer)) pair.

class netgamut.Hypergraph(dataset, file, archive_path, source='', revision=None)[source]

A downloaded higher-order dataset on local disk.

dataset: DatasetLike

Full metadata for the dataset this file belongs to.

file: DataFile

The specific artifact that was downloaded.

archive_path: Path

The downloaded artifact exactly as the source serves it, gzip included.

source: str = ''

Which netgamut.<source> produced this handle, for nodes().

revision: int | None = None

Which published revision this file is, when the source pins them.

property path: Path

The file on disk, still compressed if that is how it arrived.

open()[source]

Open the dataset as a text stream, decompressing on the fly.

property metadata: dict[str, Any]

The dataset-level JSON object on the first line, exactly as written.

Keys are the file’s own – _format-version on every currently published file. Use format_version, file_revision and num_networks to read the reserved ones without caring which spelling this file used.

property format_version: str

Which revision of the exchange format wrote this file.

property file_revision: int | None

The revision number the file itself records, or None.

Format 0.1 wrote no revision at all, so older published revisions have none to report. revision – the one that was asked for – is always known.

property num_networks: int

How many constituent networks the file holds; 1 unless it says otherwise.

interactions(*, cast=None, network=None)[source]

Stream (nodes, attributes) for every interaction.

Members come back as strings unless you pass cast; cast=int is usual. Streaming means memory stays flat however large the file is:

for members, attributes in hypergraph.interactions(cast=int):
    ...   # ((2, 12, 20), {'category': 'lie-algebras'})

In a multi-network file, pass network to take only the constituent network with that id; without it every interaction is streamed and the marker attributes are the way to tell them apart.

node_records(*, cast=None)[source]

Stream (node, attributes) for every explicitly declared node.

Most datasets declare no nodes at all and let the interactions imply them, so an empty stream is normal rather than an error.

networks()[source]

The marker objects of a multi-network file, in file order.

Each is that constituent network’s own metadata – MANTRA-2-manifolds gives 43,214 of them, one per triangulated manifold, carrying Betti numbers, orientability and genus. Empty for an ordinary file.

interaction_labels(attribute, *, network=None)[source]

The value of attribute on each interaction, in file order.

This is the reference labelling for a hyperedge-classification dataset: interaction_labels("category") on algebra-questions gives the question category each co-answering group belongs to. Interactions missing the attribute contribute None rather than being dropped, so the list stays aligned with interactions().

nodes()[source]

Read per-node metadata into the shape every source uses.

Delegates to the source, as netgamut.Network.nodes() does. Node metadata is not ground truth for community structure; see netgamut._core.nodes.

Raises:

LookupError – The source publishes no per-node metadata, or this dataset declares no nodes explicitly.

to_networkx(*, projection='clique', cast=<class 'int'>, weighted=True)[source]

Build a networkx graph by projecting the interactions.

projection="clique" connects every pair that shares an interaction, weighting each edge by how many interactions they share. "star" keeps the interactions as nodes – labelled e0, e1, … and marked bipartite=1 – joined to their members, which loses nothing.

Requires the networkx extra:

pip install "netgamut[networkx]"
Raises:

ValueError – Unknown projection.

to_infomap(**kwargs)[source]

Build an infomap.Infomap instance holding the bipartite projection.

Infomap reads bipartite networks natively, so the interactions stay in the model rather than being flattened away: physical nodes take ids 0..n-1, each interaction takes an id at or above bipartite_start_id, and the modules Infomap reports span both. That is the whole reason for keeping a hypergraph handle at all – the clique projection would have thrown the interaction sizes away before Infomap ever saw them.

The file is read twice: once to number the nodes in first-seen order, once to add the links. Extra keyword arguments go to the Infomap constructor. Requires the infomap extra:

pip install "netgamut[infomap]"
to_network(*, projection='clique', cast=None, force=False)[source]

Flatten to a dyadic edge list and return a Network over it.

The projected edge list is written next to the cached archive and reused on later calls, so this costs one pass the first time and nothing after. Use it to feed a hypergraph to anything that wants an ordinary graph – netgamut.generate, an external tool reading a two-column file.

What comes back describes the projection, not the dataset: no source tag, so per-node metadata stays here rather than on the result, and no sizes, because an interaction count is not an edge count.

The star projection labels its interaction nodes e0, e1, … , so the file mixes those with the source’s own identifiers and cannot be read with cast=int. Prefer to_infomap(), which builds the bipartite network directly and keeps the two kinds of node properly separated.

Raises:

ValueError – Unknown projection.

class netgamut.Columns(weight=None, time=None, layer=None, names=())[source]

Which field of each row holds what. Field 0 is the source, 1 the target.

None means the file has no such field. The default – no weight, no time, no layer – reads every file as an unweighted edge list, which is the safe reading of a column nobody has vouched for.

weight: int | None = None

Link weight, read as a number.

time: int | None = None

Timestamp of the link, for Network.times and time snapshots.

layer: int | None = None

Layer of the link, for Network.to_multilayer().

names: tuple[str, ...] = ()

Every field’s name, where the source publishes them.

find(name)[source]

The index of the field called name.

Raises:

LookupError – No field has that name.