Skip to content

statspai.network

network

Social network analysis for StatsPAI (sp.network).

A numpy/scipy-native SNA toolkit aligned with R's igraph / sna / statnet and Stata's nwcommands, covering the layers an applied network analyst needs:

Construction :func:network_graph (factory) and the :class:Graph object — build from a dense/sparse adjacency, an edge list, or a tidy DataFrame.

Descriptives :func:network_summary, :func:transitivity, :func:clustering, :func:reciprocity, :func:assortativity, :func:network_components.

Centrality :func:centrality dispatcher plus :func:degree_centrality, :func:closeness_centrality, :func:betweenness_centrality, :func:eigenvector_centrality, :func:katz_centrality, :func:pagerank, :func:bonacich_power, :func:hits.

Community detection :func:community_detection (Louvain / greedy / label propagation) and :func:network_modularity.

Network regression :func:netlm / :func:netlogit (QAP / MRQAP) and :func:dyadic_regression (dyadic-cluster-robust SEs).

Network formation :func:ergm (exponential random graph models via MPLE).

Data & plots :func:karate_club, :func:florentine_families, :func:network_plot.

Roadmap

Sparse-CSR storage for very large graphs; full MCMC-MLE ERGM estimation (the current :func:ergm uses maximum pseudo-likelihood); SAOM / RSiena stochastic actor-oriented models for network dynamics; temporal/multiplex networks. These are tracked rather than silently stubbed.

Graph

A social network as a dense adjacency matrix.

Parameters:

Name Type Description Default
adjacency ndarray or sparse or W - like

Square (n, n) adjacency. Entry A[i, j] is the weight of the tie from i to j (1 for an unweighted tie).

required
directed bool

If False the graph is treated as undirected and stored symmetrically.

False
node_labels sequence of str

Human-readable node names (defaults to "0".."n-1").

None
weighted bool

Whether to treat off-diagonal entries as weights. If None (default) it is inferred: the graph is weighted when any entry is not in {0, 1}.

None
allow_self_loops bool

Keep the diagonal of adjacency. Off by default (SNA convention).

False

Attributes:

Name Type Description
n_nodes int
n_edges int

Number of (directed) arcs; for an undirected graph this is the number of edges (each unordered pair counted once).

is_directed bool
is_weighted bool

Examples:

>>> import statspai as sp
>>> import numpy as np
>>> A = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]], float)  # path 0-1-2
>>> g = sp.network_graph(A)
>>> g.n_nodes, g.n_edges
(3, 2)
>>> g.degree().tolist()
[1.0, 2.0, 1.0]

density property

density: float

Fraction of possible ties present.

from_edgelist classmethod

from_edgelist(edges: Sequence[Tuple[Any, Any]], directed: bool = False, weights: Optional[Sequence[float]] = None, nodes: Optional[Sequence[Any]] = None) -> 'Graph'

Build a :class:Graph from an iterable of (u, v) pairs.

Parameters:

Name Type Description Default
edges sequence of (hashable, hashable)
required
directed bool
False
weights sequence of float

Per-edge weights (default 1 for every edge).

None
nodes sequence

Explicit node ordering / inclusion of isolates. If omitted, the node set is the sorted union of endpoints.

None

from_pandas_edgelist classmethod

from_pandas_edgelist(df: Any, source: str, target: str, weight: Optional[str] = None, directed: bool = False, nodes: Optional[Sequence[Any]] = None) -> 'Graph'

Build a :class:Graph from a tidy edge-list DataFrame.

adjacency_matrix

adjacency_matrix(binary: bool = False) -> ndarray

Return a copy of the adjacency matrix (optionally binarised).

binary

binary() -> ndarray

0/1 adjacency (ties present), self-loops already removed.

degree

degree(mode: str = 'all') -> ndarray

Unweighted degree.

Parameters:

Name Type Description Default
mode ('all', 'in', 'out')

For undirected graphs all three coincide. For directed graphs, "out" counts A[i, :] and "in" counts A[:, i]; "all" is their sum.

"all"

strength

strength(mode: str = 'all') -> ndarray

Weighted degree (sum of incident tie weights).

subgraph

subgraph(nodes: Sequence[Any]) -> 'Graph'

Induced subgraph on nodes (given as labels).

NetworkSummaryResult dataclass

Bases: ResultProtocolMixin

Structural summary of a network (the sp.network_summary output).

Attributes:

Name Type Description
n_nodes, n_edges int
directed, weighted bool
density float
n_components int
largest_component_frac float
is_connected bool
diameter float

Longest shortest path within the largest component (inf only for an empty graph).

average_path_length float

Mean shortest-path length over reachable ordered pairs.

mean_degree float
transitivity float

Global clustering coefficient.

average_clustering float

Mean local clustering coefficient.

reciprocity float

Directed graphs only; nan for undirected.

assortativity float

Newman degree-assortativity coefficient.

Examples:

>>> import statspai as sp
>>> res = sp.network_summary(sp.karate_club())
>>> res.n_nodes, res.n_edges
(34, 78)
>>> round(res.density, 4)
0.139

ComponentsResult dataclass

Bases: ResultProtocolMixin

Connected-component decomposition of a graph.

Attributes:

Name Type Description
n_components int
membership Series

Component id per node (indexed by node label).

sizes list of int

Component sizes, descending.

largest_size int
connection str

"weak" or "strong" (directed graphs only).

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (2, 3)], node_labels=[0, 1, 2, 3])
>>> sp.network_components(g).sizes
[2, 2]

CentralityResult dataclass

Bases: ResultProtocolMixin

Per-node centrality table (the sp.centrality output).

Attributes:

Name Type Description
scores DataFrame

One column per requested measure, indexed by node label.

measures list of str
most_central dict

measure -> node label of the top-scoring node.

Examples:

>>> import statspai as sp
>>> res = sp.centrality(sp.karate_club(), kind=["degree", "betweenness"])
>>> list(res.scores.columns)
['degree', 'betweenness']

top

top(measure: str, k: int = 5) -> Series

The k highest-scoring nodes on measure.

CommunityResult dataclass

Bases: ResultProtocolMixin

Community-detection partition (the sp.community_detection output).

Attributes:

Name Type Description
membership Series

Community id per node, indexed by node label.

n_communities int
modularity float

Newman modularity Q of the partition.

method str
sizes list of int

Community sizes, descending.

Examples:

>>> import statspai as sp
>>> res = sp.community_detection(sp.karate_club(), method="louvain")
>>> res.n_communities >= 2
True
>>> res.modularity > 0.38
True

QAPResult dataclass

Bases: ResultProtocolMixin

QAP / MRQAP network-regression result (sp.netlm / sp.netlogit).

Attributes:

Name Type Description
coefficients DataFrame

Columns variable, coef, se, z/t, p_qap.

p_qap dict

Permutation p-value per coefficient (the headline inference).

r_squared float

OLS R^2 (netlm) or McFadden pseudo-R^2 (netlogit).

n_dyads int
nperm int
method str

"netlm" or "netlogit"; permutation records "dsp" or "y-permutation".

Examples:

>>> import statspai as sp
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> n = 25
>>> X = (rng.random((n, n)) < 0.3).astype(float); np.fill_diagonal(X, 0)
>>> noise = rng.normal(0, 0.1, (n, n))
>>> Y = 2.0 * X + noise
>>> res = sp.netlm(Y, X, nperm=200, seed=1)
>>> bool(1.5 < res.coefficients.loc[1, "coef"] < 2.5)
True

DyadicRegressionResult dataclass

Bases: ResultProtocolMixin

Dyadic OLS with dyadic-cluster-robust standard errors.

Attributes:

Name Type Description
coefficients DataFrame

Columns variable, coef, se_dyadic, se_classical, z, p, ci_low, ci_high.

n_dyads, n_nodes int
r_squared float

Examples:

>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> rows = []
>>> for i in range(20):
...     for j in range(i + 1, 20):
...         x = rng.normal()
...         rows.append((i, j, x, 1.0 + 0.5 * x + rng.normal(0, 0.5)))
>>> df = pd.DataFrame(rows, columns=["i", "j", "x", "y"])
>>> res = sp.dyadic_regression(df, y="y", covariates=["x"], i="i", j="j")
>>> res.coefficients.loc[1, "variable"]
'x'

ERGMResult dataclass

Bases: ResultProtocolMixin

ERGM fit by maximum pseudo-likelihood (the sp.ergm output).

Attributes:

Name Type Description
coefficients DataFrame

Columns term, estimate, se, z, p.

terms list of str
log_pseudolikelihood float
n_dyads int
directed bool
dyad_independent bool

Whether the model contains only dyad-independent terms (so MPLE = MLE).

method str

"MPLE".

Examples:

>>> import statspai as sp
>>> g = sp.florentine_families()
>>> res = sp.ergm(g, terms=["edges"])
>>> # edges-only MPLE recovers the log-odds of the density:
>>> import numpy as np
>>> dens = sp.network_summary(g).density
>>> bool(abs(res.coefficients.loc[0, "estimate"]
...          - np.log(dens / (1 - dens))) < 1e-6)
True

as_graph

as_graph(obj: GraphLike, directed: Optional[bool] = None) -> Graph

Coerce any adjacency-like input into a :class:Graph.

The universal adapter used by every public sp.network function so that callers may pass a :class:Graph, a dense/sparse adjacency matrix, or a libpysal W object interchangeably.

Parameters:

Name Type Description Default
obj Graph or ndarray or sparse or W - like
required
directed bool

Override the directedness. Ignored when obj is already a :class:Graph unless it differs, in which case the graph is re-interpreted.

None

Returns:

Type Description
Graph

shortest_path_lengths

shortest_path_lengths(g: Graph, weighted: Optional[bool] = None) -> ndarray

All-pairs shortest-path length matrix.

Uses :func:scipy.sparse.csgraph.shortest_path. Unreachable pairs are inf. Weighted distances use the tie weights as edge lengths when the graph is weighted (or weighted=True); otherwise every edge has length one.

Parameters:

Name Type Description Default
g Graph
required
weighted bool

Force weighted/unweighted distances. Defaults to g.is_weighted.

None

Returns:

Type Description
ndarray of shape (n, n)

network_summary

network_summary(graph: Any) -> NetworkSummaryResult

Compute the structural summary of a network.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

A :class:~statspai.network.Graph, a dense/sparse adjacency matrix, or a libpysal W object.

required

Returns:

Type Description
NetworkSummaryResult

Examples:

>>> import statspai as sp
>>> res = sp.network_summary(sp.karate_club())
>>> res.diameter
5.0
>>> res.n_components
1

transitivity

transitivity(graph: Any) -> float

Global clustering coefficient (transitivity).

The fraction of connected triples of nodes that are closed into a triangle: 3 * (#triangles) / (#connected triples).

Parameters:

Name Type Description Default
graph Graph or adjacency - like
required

Returns:

Type Description
float

Examples:

>>> import statspai as sp
>>> g = sp.karate_club()
>>> round(sp.transitivity(g), 4)
0.2557

clustering

clustering(graph: Any) -> Series

Per-node local clustering coefficient (Watts-Strogatz).

c_i = 2 e_i / (k_i (k_i - 1)) where e_i is the number of ties among the k_i neighbours of i. Nodes with degree < 2 receive 0.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Network whose local clustering coefficients should be computed.

required

Returns:

Type Description
Series

Indexed by node label.

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (2, 0)])
>>> sp.clustering(g).tolist()
[1.0, 1.0, 1.0]

reciprocity

reciprocity(graph: Any) -> float

Directed reciprocity: share of arcs that are reciprocated.

sum_{i!=j} A_ij A_ji / sum_{i!=j} A_ij on the binarised graph. An undirected graph returns 1.0 (every tie is mutual by construction).

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Directed network. Undirected inputs return 1.0 by construction.

required

Returns:

Type Description
float

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 0), (1, 2)], directed=True)
>>> round(sp.reciprocity(g), 3)
0.667

assortativity

assortativity(graph: Any) -> float

Newman degree-assortativity coefficient.

The Pearson correlation of the degrees at the two ends of an edge. Positive values mean high-degree nodes tend to attach to high-degree nodes (assortative mixing); negative values indicate disassortativity (hub-and-spoke), as in many social and technological networks.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Network whose endpoint-degree correlation should be computed.

required

Returns:

Type Description
float
References

newman2002assortative

Examples:

>>> import statspai as sp
>>> val = sp.assortativity(sp.karate_club())
>>> bool(val < 0)
True

network_components

network_components(graph: Any, connection: str = 'weak') -> ComponentsResult

Connected-component decomposition.

Parameters:

Name Type Description Default
graph Graph or adjacency - like
required
connection ('weak', 'strong')

For directed graphs, whether to use weak or strong connectivity. Ignored for undirected graphs.

"weak"

Returns:

Type Description
ComponentsResult

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (2, 3)], node_labels=[0, 1, 2, 3])
>>> sp.network_components(g).n_components
2

degree_centrality

degree_centrality(graph: Any, mode: str = 'all', normalized: bool = True) -> Series

Degree centrality.

Parameters:

Name Type Description Default
graph Graph or adjacency - like
required
mode ('all', 'in', 'out')
"all"
normalized bool

Divide by n - 1 (the maximum possible degree).

True

Returns:

Type Description
Series

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (1, 3)])
>>> float(sp.degree_centrality(g, normalized=False).loc[1])
3.0

closeness_centrality

closeness_centrality(graph: Any, weighted: Optional[bool] = None) -> Series

Closeness centrality with the Wasserman-Faust disconnected correction.

For node i reaching r others with total distance T: C(i) = (r / T) * (r / (n - 1)). On a connected graph this reduces to (n - 1) / T.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Network to score.

required
weighted bool

Use tie weights as distances. Defaults to the graph's weighted flag.

None

Returns:

Type Description
Series

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (2, 3)])
>>> int(sp.closeness_centrality(g).idxmax())
1

betweenness_centrality

betweenness_centrality(graph: Any, normalized: bool = True, weighted: Optional[bool] = None) -> Series

Shortest-path betweenness centrality (Brandes 2001).

Parameters:

Name Type Description Default
graph Graph or adjacency - like
required
normalized bool

Scale by 1/((n-1)(n-2)) (directed) or 2/((n-1)(n-2)) (undirected), so scores lie in [0, 1].

True
weighted bool

Use tie weights as path lengths (Dijkstra). Defaults to the graph's own weighted flag.

None

Returns:

Type Description
Series
References

brandes2001faster

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (2, 3)])
>>> float(sp.betweenness_centrality(g, normalized=False).loc[1])
2.0

eigenvector_centrality

eigenvector_centrality(graph: Any, weighted: Optional[bool] = None, max_iter: int = 1000, tol: float = 1e-09) -> Series

Eigenvector centrality (leading eigenvector of the adjacency matrix).

Computed by power iteration and L2-normalised, matching the igraph / networkx convention. For directed graphs the right eigenvector is used (centrality flows along out-ties' reverse).

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Network to score.

required
weighted bool

Use tie weights; defaults to the graph's weighted flag.

None
max_iter int

Maximum power-iteration steps.

1000
tol float

Convergence tolerance on the score vector.

1e-9

Returns:

Type Description
Series

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (1, 3)])
>>> int(sp.eigenvector_centrality(g).idxmax())
1

katz_centrality

katz_centrality(graph: Any, alpha: float = 0.1, beta: float = 1.0, normalized: bool = True, weighted: Optional[bool] = None) -> Series

Katz centrality.

x = beta (I - alpha A^T)^{-1} 1 — every node gets a base score beta plus alpha times the centrality of nodes pointing to it. Requires alpha < 1 / lambda_max(A) for convergence.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Network to score.

required
alpha float

Attenuation parameter. Must be below 1 / lambda_max(A).

0.1
beta float

Baseline score for each node.

1.0
normalized bool

L2-normalise the returned vector.

True
weighted bool

Use tie weights; defaults to the graph's weighted flag.

None

Returns:

Type Description
Series
References

katz1953new

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (1, 3)])
>>> int(sp.katz_centrality(g, alpha=0.05).idxmax())
1

pagerank

pagerank(graph: Any, alpha: float = 0.85, max_iter: int = 1000, tol: float = 1e-12, weighted: Optional[bool] = None) -> Series

Google PageRank (Brin & Page 1998).

The stationary distribution of a random surfer who follows out-ties with probability alpha and teleports uniformly with probability 1 - alpha. Dangling nodes redistribute their mass uniformly.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Directed or undirected network to score.

required
alpha float

Probability of following a tie rather than teleporting.

0.85
max_iter int

Maximum power-iteration steps.

1000
tol float

L1 convergence tolerance.

1e-12
weighted bool

Use tie weights; defaults to the graph's weighted flag.

None

Returns:

Type Description
Series

Sums to 1.

References

brin1998anatomy

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (1, 3)])
>>> round(float(sp.pagerank(g).sum()), 6)
1.0

bonacich_power

bonacich_power(graph: Any, beta: float = 0.1, weighted: Optional[bool] = None) -> Series

Bonacich (1987) power centrality.

c = (I - beta A)^{-1} A 1, rescaled so sum(c^2) = n (the sna bonpow normalisation). beta > 0 rewards being connected to powerful others; beta < 0 (bargaining contexts) rewards being connected to weak others; beta = 0 reduces to degree.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Network to score.

required
beta float

Bonacich attenuation parameter. Positive values reward ties to powerful nodes; negative values reward ties to weak nodes.

0.1
weighted bool

Use tie weights; defaults to the graph's weighted flag.

None

Returns:

Type Description
Series
References

bonacich1987power

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (1, 3)])
>>> int(sp.bonacich_power(g, beta=0).idxmax())
1

hits

hits(graph: Any, max_iter: int = 1000, tol: float = 1e-12) -> DataFrame

Kleinberg HITS hub and authority scores.

Authorities are nodes pointed to by good hubs; hubs point to good authorities. Scores are L1-normalised (sum to 1). On an undirected graph hub and authority coincide with the eigenvector centrality.

Parameters:

Name Type Description Default
graph Graph or adjacency - like

Directed or undirected network to score.

required
max_iter int

Maximum power-iteration steps.

1000
tol float

L1 convergence tolerance for hub and authority vectors.

1e-12

Returns:

Type Description
DataFrame

Columns hub and authority, indexed by node label.

References

kleinberg1999authoritative

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (0, 2)], directed=True)
>>> sorted(sp.hits(g).columns)
['authority', 'hub']

community_detection

community_detection(graph: Any, method: str = 'louvain', resolution: float = 1.0, seed: Optional[int] = None) -> CommunityResult

Partition a network into communities (family dispatcher).

Parameters:

Name Type Description Default
graph Graph or adjacency - like
required
method ('louvain', 'greedy', 'label_prop')

"louvain" — multi-level modularity optimisation (Blondel 2008); "greedy" — agglomerative modularity (Clauset-Newman-Moore 2004); "label_prop" — label propagation (Raghavan 2007).

"louvain"
resolution float

Modularity resolution gamma (Louvain / greedy). Higher values yield more, smaller communities.

1.0
seed int

Random seed. Louvain visits nodes in fixed order when seed is None (deterministic); label propagation always needs a seed for its random tie-breaking (defaults to 0).

None

Returns:

Type Description
CommunityResult

Examples:

>>> import statspai as sp
>>> g = sp.karate_club()
>>> sp.community_detection(g, method="greedy").n_communities
3

network_modularity

network_modularity(graph: Any, membership: Any, resolution: float = 1.0) -> float

Newman-Girvan modularity Q of a partition.

Q = (1/2m) sum_ij (A_ij - gamma k_i k_j / 2m) delta(c_i, c_j) with weighted degrees k and total edge weight m. Q rises as ties concentrate within communities relative to a degree-preserving null model; resolution (gamma) tunes community size.

Parameters:

Name Type Description Default
graph Graph or adjacency - like
required
membership Series, sequence, or mapping

Community label per node (any hashable labels).

required
resolution float
1.0

Returns:

Type Description
float
References

newman2006modularity

Examples:

>>> import statspai as sp
>>> g = sp.karate_club()
>>> # Zachary's observed factional split:
>>> faction = [0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,0,1,0,1,1,
...            1,1,1,1,1,1,1,1,1,1]
>>> round(sp.network_modularity(g, faction), 4)  # Newman's Q for the split
0.3715

netlm

netlm(y: Any, predictors: Union[Any, Sequence, Mapping], directed: Optional[bool] = None, nperm: int = 1000, method: str = 'dsp', seed: Optional[int] = None, intercept: bool = True) -> QAPResult

MRQAP linear network regression (sna::netlm analogue).

Regress a dependent network matrix y on one or more predictor network matrices, with permutation inference that respects the dyadic dependence structure. For multiple predictors the default method="dsp" is Dekker-Krackhardt-Snijders double-semi-partialling, which is robust to collinearity and network autocorrelation.

Parameters:

Name Type Description Default
y Graph or (n, n) array
required
predictors (n, n) array, sequence of arrays, or ``{name: array}``
required
directed bool

Whether dyads (i, j) and (j, i) are distinct. Inferred from the symmetry of y when None.

None
nperm int

Number of QAP permutations.

1000
method ('dsp', 'y')

"dsp" = Dekker double-semi-partialling (recommended); "y" = permute the dependent matrix (classic Krackhardt QAP).

"dsp"
seed int
None
intercept bool
True

Returns:

Type Description
QAPResult
References

dekker2007sensitivity

Examples:

>>> import statspai as sp
>>> import numpy as np
>>> X = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]], float)
>>> Y = 1.0 + 2.0 * X
>>> res = sp.netlm(Y, X, nperm=10, seed=0)
>>> res.method
'netlm'

netlogit

netlogit(y: Any, predictors: Union[Any, Sequence, Mapping], directed: Optional[bool] = None, nperm: int = 1000, seed: Optional[int] = None, intercept: bool = True, max_iter: int = 100) -> QAPResult

QAP logistic network regression for a binary dependent network.

Fits a logistic regression of the binarised dependent matrix on predictor matrices, with QAP (dependent-matrix-permutation) inference. Analogue of sna::netlogit.

Parameters:

Name Type Description Default
y Graph or (n, n) array

Binary (0/1) dependent network (non-zero entries are treated as 1).

required
predictors Union[Any, Sequence, Mapping]

As in :func:netlm.

required
directed Union[Any, Sequence, Mapping]

As in :func:netlm.

required
nperm Union[Any, Sequence, Mapping]

As in :func:netlm.

required
seed Union[Any, Sequence, Mapping]

As in :func:netlm.

required
intercept Union[Any, Sequence, Mapping]

As in :func:netlm.

required
max_iter int

IRLS iterations for the logistic fit.

100

Returns:

Type Description
QAPResult
References

krackhardt1988predicting

Examples:

>>> import statspai as sp
>>> import numpy as np
>>> X = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]], float)
>>> Y = (X > 0).astype(float)
>>> res = sp.netlogit(Y, X, nperm=5, seed=0)
>>> res.method
'netlogit'

dyadic_regression

dyadic_regression(data: DataFrame, y: str, covariates: Sequence[str], i: str, j: str, intercept: bool = True, alpha: float = 0.05) -> DyadicRegressionResult

OLS on dyadic data with dyadic-cluster-robust standard errors.

Estimates y_ij = x_ij' beta + e_ij by OLS and reports the Aronow-Samii-Assenova (2015) dyadic-cluster-robust variance, which allows arbitrary correlation between any two dyads that share a node — the dependence structure that invalidates classical / one-way clustered SEs in network data (Fafchamps-Gubert 2007).

Parameters:

Name Type Description Default
data DataFrame

One row per dyad.

required
y str

Outcome column.

required
covariates sequence of str

Dyadic regressor columns.

required
i str

Columns identifying the two nodes of each dyad.

required
j str

Columns identifying the two nodes of each dyad.

required
intercept bool
True
alpha float
0.05

Returns:

Type Description
DyadicRegressionResult
References

aronow2015cluster

Examples:

>>> import statspai as sp
>>> import pandas as pd
>>> df = pd.DataFrame({
...     "i": [0, 0, 1, 1],
...     "j": [1, 2, 2, 3],
...     "x": [0.0, 1.0, 0.5, 1.5],
...     "y": [1.0, 2.0, 1.5, 2.5],
... })
>>> res = sp.dyadic_regression(df, y="y", covariates=["x"], i="i", j="j")
>>> res.n_dyads
4

karate_club

karate_club() -> Graph

Zachary's karate club friendship network (undirected, 34 nodes).

Returns:

Type Description
Graph

Binary undirected graph with 34 nodes and 78 edges.

Examples:

>>> import statspai as sp
>>> g = sp.karate_club()
>>> g.n_nodes, g.n_edges
(34, 78)
References

zachary1977information

florentine_families

florentine_families() -> Graph

Padgett's Florentine marriage network (undirected, 15 families).

Returns:

Type Description
Graph

Binary undirected graph; nodes are labelled by family name. The Medici sit at the structural centre (highest betweenness).

Examples:

>>> import statspai as sp
>>> g = sp.florentine_families()
>>> g.n_nodes, g.n_edges
(15, 20)
References

padgett1993robust

network_plot

network_plot(graph: Any, layout: str = 'spring', node_color: Optional[Sequence] = None, node_size: Optional[Sequence] = None, labels: bool = False, ax: Any = None, seed: Optional[int] = 0, cmap: str = 'tab10', edge_alpha: float = 0.35, title: Optional[str] = None) -> Any

Draw a network as a node-link diagram.

Parameters:

Name Type Description Default
graph Graph or adjacency - like
required
layout ('spring', 'circular')
"spring"
node_color sequence

Per-node values (e.g. a community membership :class:pandas.Series) mapped through cmap.

None
node_size sequence

Per-node sizes (e.g. a centrality score); rescaled to a sensible point range.

None
labels bool

Annotate nodes with their labels.

False
ax matplotlib Axes
None
seed int

Layout seed (spring layout).

0
cmap str
"tab10"
edge_alpha float
0.35
title str
None

Returns:

Type Description
Axes

Examples:

>>> import statspai as sp
>>> g = sp.karate_club()
>>> com = sp.community_detection(g)
>>> sp.network_plot(g, node_color=com.membership)

spring_layout

spring_layout(A: ndarray, iterations: int = 100, seed: Optional[int] = 0, k: Optional[float] = None) -> ndarray

Fruchterman-Reingold force-directed layout (numpy).

Parameters:

Name Type Description Default
A (n, n) ndarray

Adjacency (symmetrised internally).

required
iterations int
100
seed int
0
k float

Ideal edge length; defaults to 1/sqrt(n).

None

Returns:

Type Description
(n, 2) ndarray of positions in roughly ``[-1, 1]^2``.

circular_layout

circular_layout(n: int) -> ndarray

Evenly spaced positions on the unit circle.

network_graph

network_graph(adjacency: Any = None, edges: Optional[Sequence] = None, directed: bool = False, node_labels: Optional[Sequence] = None, weights: Optional[Sequence] = None, weighted: Optional[bool] = None) -> Graph

Construct a :class:Graph from an adjacency matrix or an edge list.

This is the single agent-friendly entry point for building a network. Provide either adjacency (a dense/sparse matrix or libpysal W) or edges (an iterable of (u, v) pairs).

Parameters:

Name Type Description Default
adjacency ndarray or sparse or W - like

Square adjacency matrix.

None
edges sequence of (hashable, hashable)

Edge list (mutually exclusive with adjacency).

None
directed bool
False
node_labels sequence

Node names (adjacency input) or explicit node ordering (edge input).

None
weights sequence of float

Per-edge weights (edge-list input only).

None
weighted bool

Force weighted/unweighted interpretation (adjacency input).

None

Returns:

Type Description
Graph

Examples:

>>> import statspai as sp
>>> g = sp.network_graph(edges=[(0, 1), (1, 2), (2, 0)])
>>> g.n_nodes, g.n_edges
(3, 3)
>>> import numpy as np
>>> A = np.array([[0, 1], [1, 0]], float)
>>> sp.network_graph(A).density
1.0