Skip to main content

Graphoskop

Analysis of networks and of vectors defined on their nodes.

Install

pip install graphoskop

Usage

import graphoskop as gsk
net = gsk.load_network("edges.tsv", "nodes.tsv")
net = gsk.load_network(edges_df, nodes_df)          # or from pandas DataFrames
edges_df, nodes_df = net.to_pandas_dataframes()     # and back
gsk.save_network(net, "out/net")                    # out/net_edges.tsv, out/net_nodes.tsv (and _interlayer.tsv)
gsk.save_network(net, "out/net", pickle = True)     # out/net.pt, read with gsk.load_pickled_network

Directed networks can be symmetrized, combining the attributes of the two directions:

und = gsk.to_undirected(net, combine = "sum")       # or "mean", "max"; sign: disagree = "zero", "positive", "negative"
core = gsk.remove_isolates(net)                     # drop nodes without edges
clean = gsk.remove_self_loops(net)                  # drop edges from a node to itself
simple = gsk.remove_parallel_edges(net)             # merge parallel edges (same direction, same layer)
ego = gsk.subgraph(net, ["alice", "bob"])           # induced subgraph
scc = gsk.to_strongly_connected(net)                # directed: split into strongly connected components

Degree and clustering:

gsk.connected_components_count(net, type = "strong"), gsk.connected_components_nodes(net)
gsk.degree(net, "alice"), gsk.degree(net, distribution = "ccdf")
gsk.degree(net, "alice", weight = "weight")         # weighted degree (strength)
gsk.degree_power_law(net)                           # exponent, k_min and cutoff, after likelihood ratio tests (Clauset et al.)
gsk.degree_power_law(net, ccdf = True)["ccdf"]     # empirical and fitted ccdf, to plot
gsk.degree_power_law(net)["formula"]                # best fit written out, e.g. "P(k) = 0.27 * k^(-2.37) / 0.026, for k >= 12"
gsk.degree_centralization(net)["centralization"]    # degree entropy vs random networks of the same size
gsk.average_degree(net)                             # 2m / n (undirected), m / n (directed)
gsk.density(net)                                    # also directed, bipartite, multilayer (per layer)
gsk.reciprocity(net), gsk.reciprocity(net, normalized = True)   # directed networks
gsk.average_shortest_path_length(net)               # same paths as the shortest_paths module
gsk.shortest_path_length_distribution(net)          # number of node pairs at each distance (edges)
gsk.diameter(net)                                   # longest shortest path
net.is_planar()                                     # drawable without edge crossings
gsk.maximally_filtered_graph(net, "weight")         # planar backbone (PMFG), a new network
gsk.triangulated_filtered_graph(net, "weight")      # faster planar backbone (TMFG), on the GPU
gsk.bfs(net, "alice", max_depth = 2), gsk.dfs(net, "alice")   # exploration: node, depth, parent in visit order

Random walks: gsk.stationary_distribution(net), gsk.effective_resistance(net), and hitting times H = gsk.hitting_time(net) (H[i, j]: expected steps from i to reach j; alpha for teleportation).

Centralities (closeness, harmonic, betweenness of nodes and edges) can share precomputed shortest path lengths:

P = gsk.all_pairs_shortest_path_lengths(net)
b = gsk.betweenness_centrality(net, lengths = P)    # also closeness_centrality, harmonic_centrality
eb = gsk.edge_betweenness_centrality(net, lengths = P)   # per stored edge: net.set_edge_attribute("eb", eb)
c = gsk.closeness_centrality(net, component = None, lin = True)   # all components, comparable
f = gsk.flow_centrality(net)                        # random walk betweenness (Newman, 2005)
gsk.eigenvector_centrality(net, weight = "weight")   # power iteration on the device
gsk.pagerank(net, component = None, personalization = {"alice": 1})   # personalized PageRank
hubs, authorities = gsk.hits(net)                   # HITS
gsk.k_core_centrality(net)                          # core number of every node
gsk.centralization(net, gsk.betweenness_centrality)   # Freeman centralization of any centrality
gsk.structural_equivalence(net, "alice", "bob")   # Jaccard coefficient of their neighbors
gsk.automorphic_equivalence(net, "alice", "bob")  # 1 if some automorphism swaps them
gsk.pairwise_structural_equivalence(net), gsk.pairwise_automorphic_equivalence(net)   # all pairs, n x n
gsk.pairwise_regular_equivalence(dnet)              # REGE: directed or valued networks
gsk.pairwise_path_regular_equivalence(net, alpha = 0.97)   # Leicht, Holme and Newman similarity
gsk.katz_centrality(net, alpha = 0.05)              # alpha below 1 / largest eigenvalue
gsk.alpha_centrality(net, alpha = 0.5, exo = "wealth")   # Bonacich alpha centrality, any alpha
gsk.reach_centrality(dnet)                          # directed networks: fraction of nodes reached
gsk.global_reach_centrality(dnet)                   # hierarchy of a directed network
gsk.modularity(net, "party")                        # partition from a node attribute, a dict or a list
gsk.maximal_cliques(net)                            # node sets of maximal cliques with 3+ nodes
gsk.graph_coloring(net), gsk.maximum_independent_set(net)   # exact when proven within time_limit
gsk.local_clustering(net, "alice")                  # or a vector over a component
gsk.global_clustering(net), gsk.average_clustering(net)
gsk.assortativity(net), gsk.assortativity(net, "age"), gsk.assortativity(net, "city")   # degree, numeric, qualitative

Numeric node attributes go to the chosen device (e.g. "cuda"); qualitative ones (strings, categories) always stay in CPU memory.

Bipartite networks need a bipartite_type column in the node table, with two values:

net = gsk.load_network("edges.tsv", "nodes.tsv", bipartite = True)
users = gsk.simple_count_projection(net, "user")   # projection onto the "user" nodes
users = gsk.hyperbolic_projection(net, "user")     # also hist_intersection_projection
items = gsk.resource_allocation_projection(net, "item", lambd = 1)   # ProbS; 0 HeatS, 0.5 hybrid
items = gsk.random_walk_projection(net, "item")    # YCN random walks

Multilayer networks need a layer column in the edge table; nodes become copies (entity, layer):

net = gsk.load_network("edges.tsv", "nodes.tsv", multilayer = True, coupling = "all")   # or "ordinal", "temporal"
gsk.laplacian(net)                                  # supra-Laplacian
gsk.degree(net, ("alice", "work")), gsk.degree(net, "alice")   # in one layer, summed over layers
work = gsk.layer_network(net, "work")               # one layer as an ordinary network
flat = gsk.aggregate_network(net)                   # all layers flattened

Signed networks carry a +1 / -1 sign edge attribute; build them from a positive and a negative layer, or by thresholding an edge weight:

signed = gsk.signed_network(net, positive = "ally", negative = "enemy")   # multilayer net
signed = gsk.signed_network(net, weight = "score", threshold = 0)         # >= 0 positive
A = gsk.signed_adjacency(signed)                    # sparse, per component
gsk.structural_balance(signed)                      # Tr(A^3) / Tr(|A|^3)
gsk.structural_balance(signed, beta = 0.5)          # Tr(e^(beta A)) / Tr(e^(beta |A|)), all closed walks
gsk.signed_triangle_counts(signed)                  # per sign pattern, vs. shuffled signs (exact)

Synthetic networks: every generator takes its parameters, or an existing network to randomize (same nodes and attributes, new random edges, edge attributes shuffled over them):

g = gsk.gnm_random_graph(1000, 5000, seed = 1)       # random: 1000 nodes, 5000 random edges
g = gsk.gnp_random_graph(1000, 0.01, seed = 1)       # random: each pair an edge with probability 0.01
b = gsk.gnm_random_graph((100, 300), 1000)           # bipartite
c = gsk.caveman_graph(10, 5, connected = True)       # clustered: 10 cliques of 5 in a ring; node attribute "cave"
w = gsk.watts_strogatz_graph(1000, 6, 0.1)           # clustered: small world; rewire = "target", "both" or "add"
ba = gsk.barabasi_albert_graph(10000, 6)             # broad degree: preferential attachment, average degree 6 (m = 3)
ls = gsk.link_selection_graph(10000, 6)              # broad degree: new nodes link to an end of random links
cp = gsk.copying_graph(10000, 6)                     # broad degree: new nodes copy a link of random nodes
hk = gsk.holme_kim_graph(10000, 6, 0.8)              # broad degree with clustering: triangles closed with probability 0.8
rnd = gsk.gnm_random_graph(net = net)                # randomization of net, same n and m
rnd = gsk.gnp_random_graph(net = net)                # same n, p = density of net
rnd = gsk.caveman_graph(net = net)                   # nodes in random caves, cave size from the average degree
rnd = gsk.watts_strogatz_graph(net = net, p = 0.1)   # nodes on a ring, same n and m, then rewired
rnd = gsk.barabasi_albert_graph(net = net)           # same n and m, nodes arrive in random order
rnd = gsk.link_selection_graph(net = net)            # same n and m, nodes arrive in random order
rnd = gsk.copying_graph(net = net)                   # same n and m, nodes arrive in random order
rnd = gsk.holme_kim_graph(net = net)                 # same n, m and average clustering (p inferred)

Metadata

Release files for graphoskop 0.4.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for graphoskop 0.4.8
File Size Uploaded Provenance
graphoskop-0.4.8.tar.gz 164.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for graphoskop 0.4.8
File Interpreter ABI Platform
graphoskop-0.4.8-py3-none-any.whl Python 3 none any Details

Total release size: 340.6 kB

Release files / graphoskop-0.4.8.tar.gz

Download URL graphoskop-0.4.8.tar.gz
Size 164.9 kB
Tags Source
SHA-256 checksum
How to use checksums
72ba858f76bda94958526ef55604ed9883438ef9856f46e6b0db49bdf581046e
BLAKE2b-256 checksum
How to use checksums
ef06f91c6eb212c3c84255402f702ffb63bf004c79fa05ce42f058ac1d876aeb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / graphoskop-0.4.8-py3-none-any.whl

Download URL graphoskop-0.4.8-py3-none-any.whl
Size 175.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
054ef9dbb5d815e3351d7594634353ef0f0d8a663dd037bc933cd5ee1d4494da
BLAKE2b-256 checksum
How to use checksums
01c3bbb0ab61dc080a8d07e821711ed935c7e2f9840bcfbb1f4848e7bef5338b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Security for graphoskop 0.4.8

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page