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 networkx. igraph::eigen_centrality scales to a maximum of 1 instead, so the two agree up to one scalar -- the ratio is constant across nodes to 1e-14 on Zachary's karate club (tests/reference_parity/test_network_parity.py). Rankings and relative magnitudes are identical; absolute values are not. 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).

Directed data may carry both (i, j) and (j, i). They are two dyads that share both members, and like any pair sharing a member they enter the variance once.

.. versionchanged:: 1.28.0 Pairs of dyads were weighted by the number of members they share rather than by whether they share one. The two coincide when each unordered pair appears once, so undirected results are unchanged; on directed data the reciprocal cross terms were double-counted, moving the standard errors by 1.8% on a 20-node design. Now matches the R package dyadRobust to 2e-15 on both designs. Rows with i == j now raise instead of entering with an inconsistent weight.

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).

The Pucci family, an isolate in Padgett's marriage data, is omitted. ergm's flomarriage keeps it (16 nodes), so graph-level quantities that depend on n -- density, component count, mean degree -- differ from that version even though the 20 marriage ties are identical edge for edge.

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