aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/__init__.py5
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/connectivity.py122
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/correlation.py302
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/mixing.py255
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/neighbor_degree.py160
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/pairs.py127
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py81
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py143
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py123
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py176
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py108
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py87
13 files changed, 1689 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/__init__.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/__init__.py
new file mode 100644
index 00000000..4d988860
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/__init__.py
@@ -0,0 +1,5 @@
+from networkx.algorithms.assortativity.connectivity import *
+from networkx.algorithms.assortativity.correlation import *
+from networkx.algorithms.assortativity.mixing import *
+from networkx.algorithms.assortativity.neighbor_degree import *
+from networkx.algorithms.assortativity.pairs import *
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/connectivity.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/connectivity.py
new file mode 100644
index 00000000..c3fde0da
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/connectivity.py
@@ -0,0 +1,122 @@
+from collections import defaultdict
+
+import networkx as nx
+
+__all__ = ["average_degree_connectivity"]
+
+
+@nx._dispatchable(edge_attrs="weight")
+def average_degree_connectivity(
+ G, source="in+out", target="in+out", nodes=None, weight=None
+):
+ r"""Compute the average degree connectivity of graph.
+
+ The average degree connectivity is the average nearest neighbor degree of
+ nodes with degree k. For weighted graphs, an analogous measure can
+ be computed using the weighted average neighbors degree defined in
+ [1]_, for a node `i`, as
+
+ .. math::
+
+ k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j
+
+ where `s_i` is the weighted degree of node `i`,
+ `w_{ij}` is the weight of the edge that links `i` and `j`,
+ and `N(i)` are the neighbors of node `i`.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : "in"|"out"|"in+out" (default:"in+out")
+ Directed graphs only. Use "in"- or "out"-degree for source node.
+
+ target : "in"|"out"|"in+out" (default:"in+out"
+ Directed graphs only. Use "in"- or "out"-degree for target node.
+
+ nodes : list or iterable (optional)
+ Compute neighbor connectivity for these nodes. The default is all
+ nodes.
+
+ weight : string or None, optional (default=None)
+ The edge attribute that holds the numerical value used as a weight.
+ If None, then each edge has weight 1.
+
+ Returns
+ -------
+ d : dict
+ A dictionary keyed by degree k with the value of average connectivity.
+
+ Raises
+ ------
+ NetworkXError
+ If either `source` or `target` are not one of 'in',
+ 'out', or 'in+out'.
+ If either `source` or `target` is passed for an undirected graph.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(4)
+ >>> G.edges[1, 2]["weight"] = 3
+ >>> nx.average_degree_connectivity(G)
+ {1: 2.0, 2: 1.5}
+ >>> nx.average_degree_connectivity(G, weight="weight")
+ {1: 2.0, 2: 1.75}
+
+ See Also
+ --------
+ average_neighbor_degree
+
+ References
+ ----------
+ .. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani,
+ "The architecture of complex weighted networks".
+ PNAS 101 (11): 3747–3752 (2004).
+ """
+ # First, determine the type of neighbors and the type of degree to use.
+ if G.is_directed():
+ if source not in ("in", "out", "in+out"):
+ raise nx.NetworkXError('source must be one of "in", "out", or "in+out"')
+ if target not in ("in", "out", "in+out"):
+ raise nx.NetworkXError('target must be one of "in", "out", or "in+out"')
+ direction = {"out": G.out_degree, "in": G.in_degree, "in+out": G.degree}
+ neighbor_funcs = {
+ "out": G.successors,
+ "in": G.predecessors,
+ "in+out": G.neighbors,
+ }
+ source_degree = direction[source]
+ target_degree = direction[target]
+ neighbors = neighbor_funcs[source]
+ # `reverse` indicates whether to look at the in-edge when
+ # computing the weight of an edge.
+ reverse = source == "in"
+ else:
+ if source != "in+out" or target != "in+out":
+ raise nx.NetworkXError(
+ f"source and target arguments are only supported for directed graphs"
+ )
+ source_degree = G.degree
+ target_degree = G.degree
+ neighbors = G.neighbors
+ reverse = False
+ dsum = defaultdict(int)
+ dnorm = defaultdict(int)
+ # Check if `source_nodes` is actually a single node in the graph.
+ source_nodes = source_degree(nodes)
+ if nodes in G:
+ source_nodes = [(nodes, source_degree(nodes))]
+ for n, k in source_nodes:
+ nbrdeg = target_degree(neighbors(n))
+ if weight is None:
+ s = sum(d for n, d in nbrdeg)
+ else: # weight nbr degree by weight of (n,nbr) edge
+ if reverse:
+ s = sum(G[nbr][n].get(weight, 1) * d for nbr, d in nbrdeg)
+ else:
+ s = sum(G[n][nbr].get(weight, 1) * d for nbr, d in nbrdeg)
+ dnorm[k] += source_degree(n, weight=weight)
+ dsum[k] += s
+
+ # normalize
+ return {k: avg if dnorm[k] == 0 else avg / dnorm[k] for k, avg in dsum.items()}
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/correlation.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/correlation.py
new file mode 100644
index 00000000..52ae7a12
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/correlation.py
@@ -0,0 +1,302 @@
+"""Node assortativity coefficients and correlation measures."""
+
+import networkx as nx
+from networkx.algorithms.assortativity.mixing import (
+ attribute_mixing_matrix,
+ degree_mixing_matrix,
+)
+from networkx.algorithms.assortativity.pairs import node_degree_xy
+
+__all__ = [
+ "degree_pearson_correlation_coefficient",
+ "degree_assortativity_coefficient",
+ "attribute_assortativity_coefficient",
+ "numeric_assortativity_coefficient",
+]
+
+
+@nx._dispatchable(edge_attrs="weight")
+def degree_assortativity_coefficient(G, x="out", y="in", weight=None, nodes=None):
+ """Compute degree assortativity of graph.
+
+ Assortativity measures the similarity of connections
+ in the graph with respect to the node degree.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ x: string ('in','out')
+ The degree type for source node (directed graphs only).
+
+ y: string ('in','out')
+ The degree type for target node (directed graphs only).
+
+ weight: string or None, optional (default=None)
+ The edge attribute that holds the numerical value used
+ as a weight. If None, then each edge has weight 1.
+ The degree is the sum of the edge weights adjacent to the node.
+
+ nodes: list or iterable (optional)
+ Compute degree assortativity only for nodes in container.
+ The default is all nodes.
+
+ Returns
+ -------
+ r : float
+ Assortativity of graph by degree.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(4)
+ >>> r = nx.degree_assortativity_coefficient(G)
+ >>> print(f"{r:3.1f}")
+ -0.5
+
+ See Also
+ --------
+ attribute_assortativity_coefficient
+ numeric_assortativity_coefficient
+ degree_mixing_dict
+ degree_mixing_matrix
+
+ Notes
+ -----
+ This computes Eq. (21) in Ref. [1]_ , where e is the joint
+ probability distribution (mixing matrix) of the degrees. If G is
+ directed than the matrix e is the joint probability of the
+ user-specified degree type for the source and target.
+
+ References
+ ----------
+ .. [1] M. E. J. Newman, Mixing patterns in networks,
+ Physical Review E, 67 026126, 2003
+ .. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M.
+ Edge direction and the structure of networks, PNAS 107, 10815-20 (2010).
+ """
+ if nodes is None:
+ nodes = G.nodes
+
+ degrees = None
+
+ if G.is_directed():
+ indeg = (
+ {d for _, d in G.in_degree(nodes, weight=weight)}
+ if "in" in (x, y)
+ else set()
+ )
+ outdeg = (
+ {d for _, d in G.out_degree(nodes, weight=weight)}
+ if "out" in (x, y)
+ else set()
+ )
+ degrees = set.union(indeg, outdeg)
+ else:
+ degrees = {d for _, d in G.degree(nodes, weight=weight)}
+
+ mapping = {d: i for i, d in enumerate(degrees)}
+ M = degree_mixing_matrix(G, x=x, y=y, nodes=nodes, weight=weight, mapping=mapping)
+
+ return _numeric_ac(M, mapping=mapping)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def degree_pearson_correlation_coefficient(G, x="out", y="in", weight=None, nodes=None):
+ """Compute degree assortativity of graph.
+
+ Assortativity measures the similarity of connections
+ in the graph with respect to the node degree.
+
+ This is the same as degree_assortativity_coefficient but uses the
+ potentially faster scipy.stats.pearsonr function.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ x: string ('in','out')
+ The degree type for source node (directed graphs only).
+
+ y: string ('in','out')
+ The degree type for target node (directed graphs only).
+
+ weight: string or None, optional (default=None)
+ The edge attribute that holds the numerical value used
+ as a weight. If None, then each edge has weight 1.
+ The degree is the sum of the edge weights adjacent to the node.
+
+ nodes: list or iterable (optional)
+ Compute pearson correlation of degrees only for specified nodes.
+ The default is all nodes.
+
+ Returns
+ -------
+ r : float
+ Assortativity of graph by degree.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(4)
+ >>> r = nx.degree_pearson_correlation_coefficient(G)
+ >>> print(f"{r:3.1f}")
+ -0.5
+
+ Notes
+ -----
+ This calls scipy.stats.pearsonr.
+
+ References
+ ----------
+ .. [1] M. E. J. Newman, Mixing patterns in networks
+ Physical Review E, 67 026126, 2003
+ .. [2] Foster, J.G., Foster, D.V., Grassberger, P. & Paczuski, M.
+ Edge direction and the structure of networks, PNAS 107, 10815-20 (2010).
+ """
+ import scipy as sp
+
+ xy = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight)
+ x, y = zip(*xy)
+ return float(sp.stats.pearsonr(x, y)[0])
+
+
+@nx._dispatchable(node_attrs="attribute")
+def attribute_assortativity_coefficient(G, attribute, nodes=None):
+ """Compute assortativity for node attributes.
+
+ Assortativity measures the similarity of connections
+ in the graph with respect to the given attribute.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ attribute : string
+ Node attribute key
+
+ nodes: list or iterable (optional)
+ Compute attribute assortativity for nodes in container.
+ The default is all nodes.
+
+ Returns
+ -------
+ r: float
+ Assortativity of graph for given attribute
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> G.add_nodes_from([0, 1], color="red")
+ >>> G.add_nodes_from([2, 3], color="blue")
+ >>> G.add_edges_from([(0, 1), (2, 3)])
+ >>> print(nx.attribute_assortativity_coefficient(G, "color"))
+ 1.0
+
+ Notes
+ -----
+ This computes Eq. (2) in Ref. [1]_ , (trace(M)-sum(M^2))/(1-sum(M^2)),
+ where M is the joint probability distribution (mixing matrix)
+ of the specified attribute.
+
+ References
+ ----------
+ .. [1] M. E. J. Newman, Mixing patterns in networks,
+ Physical Review E, 67 026126, 2003
+ """
+ M = attribute_mixing_matrix(G, attribute, nodes)
+ return attribute_ac(M)
+
+
+@nx._dispatchable(node_attrs="attribute")
+def numeric_assortativity_coefficient(G, attribute, nodes=None):
+ """Compute assortativity for numerical node attributes.
+
+ Assortativity measures the similarity of connections
+ in the graph with respect to the given numeric attribute.
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ attribute : string
+ Node attribute key.
+
+ nodes: list or iterable (optional)
+ Compute numeric assortativity only for attributes of nodes in
+ container. The default is all nodes.
+
+ Returns
+ -------
+ r: float
+ Assortativity of graph for given attribute
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> G.add_nodes_from([0, 1], size=2)
+ >>> G.add_nodes_from([2, 3], size=3)
+ >>> G.add_edges_from([(0, 1), (2, 3)])
+ >>> print(nx.numeric_assortativity_coefficient(G, "size"))
+ 1.0
+
+ Notes
+ -----
+ This computes Eq. (21) in Ref. [1]_ , which is the Pearson correlation
+ coefficient of the specified (scalar valued) attribute across edges.
+
+ References
+ ----------
+ .. [1] M. E. J. Newman, Mixing patterns in networks
+ Physical Review E, 67 026126, 2003
+ """
+ if nodes is None:
+ nodes = G.nodes
+ vals = {G.nodes[n][attribute] for n in nodes}
+ mapping = {d: i for i, d in enumerate(vals)}
+ M = attribute_mixing_matrix(G, attribute, nodes, mapping)
+ return _numeric_ac(M, mapping)
+
+
+def attribute_ac(M):
+ """Compute assortativity for attribute matrix M.
+
+ Parameters
+ ----------
+ M : numpy.ndarray
+ 2D ndarray representing the attribute mixing matrix.
+
+ Notes
+ -----
+ This computes Eq. (2) in Ref. [1]_ , (trace(e)-sum(e^2))/(1-sum(e^2)),
+ where e is the joint probability distribution (mixing matrix)
+ of the specified attribute.
+
+ References
+ ----------
+ .. [1] M. E. J. Newman, Mixing patterns in networks,
+ Physical Review E, 67 026126, 2003
+ """
+ if M.sum() != 1.0:
+ M = M / M.sum()
+ s = (M @ M).sum()
+ t = M.trace()
+ r = (t - s) / (1 - s)
+ return float(r)
+
+
+def _numeric_ac(M, mapping):
+ # M is a 2D numpy array
+ # numeric assortativity coefficient, pearsonr
+ import numpy as np
+
+ if M.sum() != 1.0:
+ M = M / M.sum()
+ x = np.array(list(mapping.keys()))
+ y = x # x and y have the same support
+ idx = list(mapping.values())
+ a = M.sum(axis=0)
+ b = M.sum(axis=1)
+ vara = (a[idx] * x**2).sum() - ((a[idx] * x).sum()) ** 2
+ varb = (b[idx] * y**2).sum() - ((b[idx] * y).sum()) ** 2
+ xy = np.outer(x, y)
+ ab = np.outer(a[idx], b[idx])
+ return float((xy * (M - ab)).sum() / np.sqrt(vara * varb))
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/mixing.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/mixing.py
new file mode 100644
index 00000000..1762d4e5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/mixing.py
@@ -0,0 +1,255 @@
+"""
+Mixing matrices for node attributes and degree.
+"""
+
+import networkx as nx
+from networkx.algorithms.assortativity.pairs import node_attribute_xy, node_degree_xy
+from networkx.utils import dict_to_numpy_array
+
+__all__ = [
+ "attribute_mixing_matrix",
+ "attribute_mixing_dict",
+ "degree_mixing_matrix",
+ "degree_mixing_dict",
+ "mixing_dict",
+]
+
+
+@nx._dispatchable(node_attrs="attribute")
+def attribute_mixing_dict(G, attribute, nodes=None, normalized=False):
+ """Returns dictionary representation of mixing matrix for attribute.
+
+ Parameters
+ ----------
+ G : graph
+ NetworkX graph object.
+
+ attribute : string
+ Node attribute key.
+
+ nodes: list or iterable (optional)
+ Unse nodes in container to build the dict. The default is all nodes.
+
+ normalized : bool (default=False)
+ Return counts if False or probabilities if True.
+
+ Examples
+ --------
+ >>> G = nx.Graph()
+ >>> G.add_nodes_from([0, 1], color="red")
+ >>> G.add_nodes_from([2, 3], color="blue")
+ >>> G.add_edge(1, 3)
+ >>> d = nx.attribute_mixing_dict(G, "color")
+ >>> print(d["red"]["blue"])
+ 1
+ >>> print(d["blue"]["red"]) # d symmetric for undirected graphs
+ 1
+
+ Returns
+ -------
+ d : dictionary
+ Counts or joint probability of occurrence of attribute pairs.
+ """
+ xy_iter = node_attribute_xy(G, attribute, nodes)
+ return mixing_dict(xy_iter, normalized=normalized)
+
+
+@nx._dispatchable(node_attrs="attribute")
+def attribute_mixing_matrix(G, attribute, nodes=None, mapping=None, normalized=True):
+ """Returns mixing matrix for attribute.
+
+ Parameters
+ ----------
+ G : graph
+ NetworkX graph object.
+
+ attribute : string
+ Node attribute key.
+
+ nodes: list or iterable (optional)
+ Use only nodes in container to build the matrix. The default is
+ all nodes.
+
+ mapping : dictionary, optional
+ Mapping from node attribute to integer index in matrix.
+ If not specified, an arbitrary ordering will be used.
+
+ normalized : bool (default=True)
+ Return counts if False or probabilities if True.
+
+ Returns
+ -------
+ m: numpy array
+ Counts or joint probability of occurrence of attribute pairs.
+
+ Notes
+ -----
+ If each node has a unique attribute value, the unnormalized mixing matrix
+ will be equal to the adjacency matrix. To get a denser mixing matrix,
+ the rounding can be performed to form groups of nodes with equal values.
+ For example, the exact height of persons in cm (180.79155222, 163.9080892,
+ 163.30095355, 167.99016217, 168.21590163, ...) can be rounded to (180, 163,
+ 163, 168, 168, ...).
+
+ Definitions of attribute mixing matrix vary on whether the matrix
+ should include rows for attribute values that don't arise. Here we
+ do not include such empty-rows. But you can force them to appear
+ by inputting a `mapping` that includes those values.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(3)
+ >>> gender = {0: "male", 1: "female", 2: "female"}
+ >>> nx.set_node_attributes(G, gender, "gender")
+ >>> mapping = {"male": 0, "female": 1}
+ >>> mix_mat = nx.attribute_mixing_matrix(G, "gender", mapping=mapping)
+ >>> mix_mat
+ array([[0. , 0.25],
+ [0.25, 0.5 ]])
+ """
+ d = attribute_mixing_dict(G, attribute, nodes)
+ a = dict_to_numpy_array(d, mapping=mapping)
+ if normalized:
+ a = a / a.sum()
+ return a
+
+
+@nx._dispatchable(edge_attrs="weight")
+def degree_mixing_dict(G, x="out", y="in", weight=None, nodes=None, normalized=False):
+ """Returns dictionary representation of mixing matrix for degree.
+
+ Parameters
+ ----------
+ G : graph
+ NetworkX graph object.
+
+ x: string ('in','out')
+ The degree type for source node (directed graphs only).
+
+ y: string ('in','out')
+ The degree type for target node (directed graphs only).
+
+ weight: string or None, optional (default=None)
+ The edge attribute that holds the numerical value used
+ as a weight. If None, then each edge has weight 1.
+ The degree is the sum of the edge weights adjacent to the node.
+
+ normalized : bool (default=False)
+ Return counts if False or probabilities if True.
+
+ Returns
+ -------
+ d: dictionary
+ Counts or joint probability of occurrence of degree pairs.
+ """
+ xy_iter = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight)
+ return mixing_dict(xy_iter, normalized=normalized)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def degree_mixing_matrix(
+ G, x="out", y="in", weight=None, nodes=None, normalized=True, mapping=None
+):
+ """Returns mixing matrix for attribute.
+
+ Parameters
+ ----------
+ G : graph
+ NetworkX graph object.
+
+ x: string ('in','out')
+ The degree type for source node (directed graphs only).
+
+ y: string ('in','out')
+ The degree type for target node (directed graphs only).
+
+ nodes: list or iterable (optional)
+ Build the matrix using only nodes in container.
+ The default is all nodes.
+
+ weight: string or None, optional (default=None)
+ The edge attribute that holds the numerical value used
+ as a weight. If None, then each edge has weight 1.
+ The degree is the sum of the edge weights adjacent to the node.
+
+ normalized : bool (default=True)
+ Return counts if False or probabilities if True.
+
+ mapping : dictionary, optional
+ Mapping from node degree to integer index in matrix.
+ If not specified, an arbitrary ordering will be used.
+
+ Returns
+ -------
+ m: numpy array
+ Counts, or joint probability, of occurrence of node degree.
+
+ Notes
+ -----
+ Definitions of degree mixing matrix vary on whether the matrix
+ should include rows for degree values that don't arise. Here we
+ do not include such empty-rows. But you can force them to appear
+ by inputting a `mapping` that includes those values. See examples.
+
+ Examples
+ --------
+ >>> G = nx.star_graph(3)
+ >>> mix_mat = nx.degree_mixing_matrix(G)
+ >>> mix_mat
+ array([[0. , 0.5],
+ [0.5, 0. ]])
+
+ If you want every possible degree to appear as a row, even if no nodes
+ have that degree, use `mapping` as follows,
+
+ >>> max_degree = max(deg for n, deg in G.degree)
+ >>> mapping = {x: x for x in range(max_degree + 1)} # identity mapping
+ >>> mix_mat = nx.degree_mixing_matrix(G, mapping=mapping)
+ >>> mix_mat
+ array([[0. , 0. , 0. , 0. ],
+ [0. , 0. , 0. , 0.5],
+ [0. , 0. , 0. , 0. ],
+ [0. , 0.5, 0. , 0. ]])
+ """
+ d = degree_mixing_dict(G, x=x, y=y, nodes=nodes, weight=weight)
+ a = dict_to_numpy_array(d, mapping=mapping)
+ if normalized:
+ a = a / a.sum()
+ return a
+
+
+def mixing_dict(xy, normalized=False):
+ """Returns a dictionary representation of mixing matrix.
+
+ Parameters
+ ----------
+ xy : list or container of two-tuples
+ Pairs of (x,y) items.
+
+ attribute : string
+ Node attribute key
+
+ normalized : bool (default=False)
+ Return counts if False or probabilities if True.
+
+ Returns
+ -------
+ d: dictionary
+ Counts or Joint probability of occurrence of values in xy.
+ """
+ d = {}
+ psum = 0.0
+ for x, y in xy:
+ if x not in d:
+ d[x] = {}
+ if y not in d:
+ d[y] = {}
+ v = d[x].get(y, 0)
+ d[x][y] = v + 1
+ psum += 1
+
+ if normalized:
+ for _, jdict in d.items():
+ for j in jdict:
+ jdict[j] /= psum
+ return d
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/neighbor_degree.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/neighbor_degree.py
new file mode 100644
index 00000000..6488d041
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/neighbor_degree.py
@@ -0,0 +1,160 @@
+import networkx as nx
+
+__all__ = ["average_neighbor_degree"]
+
+
+@nx._dispatchable(edge_attrs="weight")
+def average_neighbor_degree(G, source="out", target="out", nodes=None, weight=None):
+ r"""Returns the average degree of the neighborhood of each node.
+
+ In an undirected graph, the neighborhood `N(i)` of node `i` contains the
+ nodes that are connected to `i` by an edge.
+
+ For directed graphs, `N(i)` is defined according to the parameter `source`:
+
+ - if source is 'in', then `N(i)` consists of predecessors of node `i`.
+ - if source is 'out', then `N(i)` consists of successors of node `i`.
+ - if source is 'in+out', then `N(i)` is both predecessors and successors.
+
+ The average neighborhood degree of a node `i` is
+
+ .. math::
+
+ k_{nn,i} = \frac{1}{|N(i)|} \sum_{j \in N(i)} k_j
+
+ where `N(i)` are the neighbors of node `i` and `k_j` is
+ the degree of node `j` which belongs to `N(i)`. For weighted
+ graphs, an analogous measure can be defined [1]_,
+
+ .. math::
+
+ k_{nn,i}^{w} = \frac{1}{s_i} \sum_{j \in N(i)} w_{ij} k_j
+
+ where `s_i` is the weighted degree of node `i`, `w_{ij}`
+ is the weight of the edge that links `i` and `j` and
+ `N(i)` are the neighbors of node `i`.
+
+
+ Parameters
+ ----------
+ G : NetworkX graph
+
+ source : string ("in"|"out"|"in+out"), optional (default="out")
+ Directed graphs only.
+ Use "in"- or "out"-neighbors of source node.
+
+ target : string ("in"|"out"|"in+out"), optional (default="out")
+ Directed graphs only.
+ Use "in"- or "out"-degree for target node.
+
+ nodes : list or iterable, optional (default=G.nodes)
+ Compute neighbor degree only for specified nodes.
+
+ weight : string or None, optional (default=None)
+ The edge attribute that holds the numerical value used as a weight.
+ If None, then each edge has weight 1.
+
+ Returns
+ -------
+ d: dict
+ A dictionary keyed by node to the average degree of its neighbors.
+
+ Raises
+ ------
+ NetworkXError
+ If either `source` or `target` are not one of 'in', 'out', or 'in+out'.
+ If either `source` or `target` is passed for an undirected graph.
+
+ Examples
+ --------
+ >>> G = nx.path_graph(4)
+ >>> G.edges[0, 1]["weight"] = 5
+ >>> G.edges[2, 3]["weight"] = 3
+
+ >>> nx.average_neighbor_degree(G)
+ {0: 2.0, 1: 1.5, 2: 1.5, 3: 2.0}
+ >>> nx.average_neighbor_degree(G, weight="weight")
+ {0: 2.0, 1: 1.1666666666666667, 2: 1.25, 3: 2.0}
+
+ >>> G = nx.DiGraph()
+ >>> nx.add_path(G, [0, 1, 2, 3])
+ >>> nx.average_neighbor_degree(G, source="in", target="in")
+ {0: 0.0, 1: 0.0, 2: 1.0, 3: 1.0}
+
+ >>> nx.average_neighbor_degree(G, source="out", target="out")
+ {0: 1.0, 1: 1.0, 2: 0.0, 3: 0.0}
+
+ See Also
+ --------
+ average_degree_connectivity
+
+ References
+ ----------
+ .. [1] A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani,
+ "The architecture of complex weighted networks".
+ PNAS 101 (11): 3747–3752 (2004).
+ """
+ if G.is_directed():
+ if source == "in":
+ source_degree = G.in_degree
+ elif source == "out":
+ source_degree = G.out_degree
+ elif source == "in+out":
+ source_degree = G.degree
+ else:
+ raise nx.NetworkXError(
+ f"source argument {source} must be 'in', 'out' or 'in+out'"
+ )
+
+ if target == "in":
+ target_degree = G.in_degree
+ elif target == "out":
+ target_degree = G.out_degree
+ elif target == "in+out":
+ target_degree = G.degree
+ else:
+ raise nx.NetworkXError(
+ f"target argument {target} must be 'in', 'out' or 'in+out'"
+ )
+ else:
+ if source != "out" or target != "out":
+ raise nx.NetworkXError(
+ f"source and target arguments are only supported for directed graphs"
+ )
+ source_degree = target_degree = G.degree
+
+ # precompute target degrees -- should *not* be weighted degree
+ t_deg = dict(target_degree())
+
+ # Set up both predecessor and successor neighbor dicts leaving empty if not needed
+ G_P = G_S = {n: {} for n in G}
+ if G.is_directed():
+ # "in" or "in+out" cases: G_P contains predecessors
+ if "in" in source:
+ G_P = G.pred
+ # "out" or "in+out" cases: G_S contains successors
+ if "out" in source:
+ G_S = G.succ
+ else:
+ # undirected leave G_P empty but G_S is the adjacency
+ G_S = G.adj
+
+ # Main loop: Compute average degree of neighbors
+ avg = {}
+ for n, deg in source_degree(nodes, weight=weight):
+ # handle degree zero average
+ if deg == 0:
+ avg[n] = 0.0
+ continue
+
+ # we sum over both G_P and G_S, but one of the two is usually empty.
+ if weight is None:
+ avg[n] = (
+ sum(t_deg[nbr] for nbr in G_S[n]) + sum(t_deg[nbr] for nbr in G_P[n])
+ ) / deg
+ else:
+ avg[n] = (
+ sum(dd.get(weight, 1) * t_deg[nbr] for nbr, dd in G_S[n].items())
+ + sum(dd.get(weight, 1) * t_deg[nbr] for nbr, dd in G_P[n].items())
+ ) / deg
+ return avg
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/pairs.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/pairs.py
new file mode 100644
index 00000000..ea5fd287
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/pairs.py
@@ -0,0 +1,127 @@
+"""Generators of x-y pairs of node data."""
+
+import networkx as nx
+
+__all__ = ["node_attribute_xy", "node_degree_xy"]
+
+
+@nx._dispatchable(node_attrs="attribute")
+def node_attribute_xy(G, attribute, nodes=None):
+ """Yields 2-tuples of node attribute values for all edges in `G`.
+
+ This generator yields, for each edge in `G` incident to a node in `nodes`,
+ a 2-tuple of form ``(attribute value, attribute value)`` for the parameter
+ specified node-attribute.
+
+ Parameters
+ ----------
+ G: NetworkX graph
+
+ attribute: key
+ The node attribute key.
+
+ nodes: list or iterable (optional)
+ Use only edges that are incident to specified nodes.
+ The default is all nodes.
+
+ Yields
+ ------
+ (x, y): 2-tuple
+ Generates 2-tuple of (attribute, attribute) values.
+
+ Examples
+ --------
+ >>> G = nx.DiGraph()
+ >>> G.add_node(1, color="red")
+ >>> G.add_node(2, color="blue")
+ >>> G.add_node(3, color="green")
+ >>> G.add_edge(1, 2)
+ >>> list(nx.node_attribute_xy(G, "color"))
+ [('red', 'blue')]
+
+ Notes
+ -----
+ For undirected graphs, each edge is produced twice, once for each edge
+ representation (u, v) and (v, u), with the exception of self-loop edges
+ which only appear once.
+ """
+ if nodes is None:
+ nodes = set(G)
+ else:
+ nodes = set(nodes)
+ Gnodes = G.nodes
+ for u, nbrsdict in G.adjacency():
+ if u not in nodes:
+ continue
+ uattr = Gnodes[u].get(attribute, None)
+ if G.is_multigraph():
+ for v, keys in nbrsdict.items():
+ vattr = Gnodes[v].get(attribute, None)
+ for _ in keys:
+ yield (uattr, vattr)
+ else:
+ for v in nbrsdict:
+ vattr = Gnodes[v].get(attribute, None)
+ yield (uattr, vattr)
+
+
+@nx._dispatchable(edge_attrs="weight")
+def node_degree_xy(G, x="out", y="in", weight=None, nodes=None):
+ """Yields 2-tuples of ``(degree, degree)`` values for edges in `G`.
+
+ This generator yields, for each edge in `G` incident to a node in `nodes`,
+ a 2-tuple of form ``(degree, degree)``. The node degrees are weighted
+ when a `weight` attribute is specified.
+
+ Parameters
+ ----------
+ G: NetworkX graph
+
+ x: string ('in','out')
+ The degree type for source node (directed graphs only).
+
+ y: string ('in','out')
+ The degree type for target node (directed graphs only).
+
+ weight: string or None, optional (default=None)
+ The edge attribute that holds the numerical value used
+ as a weight. If None, then each edge has weight 1.
+ The degree is the sum of the edge weights adjacent to the node.
+
+ nodes: list or iterable (optional)
+ Use only edges that are adjacency to specified nodes.
+ The default is all nodes.
+
+ Yields
+ ------
+ (x, y): 2-tuple
+ Generates 2-tuple of (degree, degree) values.
+
+ Examples
+ --------
+ >>> G = nx.DiGraph()
+ >>> G.add_edge(1, 2)
+ >>> list(nx.node_degree_xy(G, x="out", y="in"))
+ [(1, 1)]
+ >>> list(nx.node_degree_xy(G, x="in", y="out"))
+ [(0, 0)]
+
+ Notes
+ -----
+ For undirected graphs, each edge is produced twice, once for each edge
+ representation (u, v) and (v, u), with the exception of self-loop edges
+ which only appear once.
+ """
+ nodes = set(G) if nodes is None else set(nodes)
+ if G.is_directed():
+ direction = {"out": G.out_degree, "in": G.in_degree}
+ xdeg = direction[x]
+ ydeg = direction[y]
+ else:
+ xdeg = ydeg = G.degree
+
+ for u, degu in xdeg(nodes, weight=weight):
+ # use G.edges to treat multigraphs correctly
+ neighbors = (nbr for _, nbr in G.edges(u) if nbr in nodes)
+ for _, degv in ydeg(neighbors, weight=weight):
+ yield degu, degv
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py
new file mode 100644
index 00000000..46d63006
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py
@@ -0,0 +1,81 @@
+import networkx as nx
+
+
+class BaseTestAttributeMixing:
+ @classmethod
+ def setup_class(cls):
+ G = nx.Graph()
+ G.add_nodes_from([0, 1], fish="one")
+ G.add_nodes_from([2, 3], fish="two")
+ G.add_nodes_from([4], fish="red")
+ G.add_nodes_from([5], fish="blue")
+ G.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)])
+ cls.G = G
+
+ D = nx.DiGraph()
+ D.add_nodes_from([0, 1], fish="one")
+ D.add_nodes_from([2, 3], fish="two")
+ D.add_nodes_from([4], fish="red")
+ D.add_nodes_from([5], fish="blue")
+ D.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)])
+ cls.D = D
+
+ M = nx.MultiGraph()
+ M.add_nodes_from([0, 1], fish="one")
+ M.add_nodes_from([2, 3], fish="two")
+ M.add_nodes_from([4], fish="red")
+ M.add_nodes_from([5], fish="blue")
+ M.add_edges_from([(0, 1), (0, 1), (2, 3)])
+ cls.M = M
+
+ S = nx.Graph()
+ S.add_nodes_from([0, 1], fish="one")
+ S.add_nodes_from([2, 3], fish="two")
+ S.add_nodes_from([4], fish="red")
+ S.add_nodes_from([5], fish="blue")
+ S.add_edge(0, 0)
+ S.add_edge(2, 2)
+ cls.S = S
+
+ N = nx.Graph()
+ N.add_nodes_from([0, 1], margin=-2)
+ N.add_nodes_from([2, 3], margin=-2)
+ N.add_nodes_from([4], margin=-3)
+ N.add_nodes_from([5], margin=-4)
+ N.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)])
+ cls.N = N
+
+ F = nx.Graph()
+ F.add_edges_from([(0, 3), (1, 3), (2, 3)], weight=0.5)
+ F.add_edge(0, 2, weight=1)
+ nx.set_node_attributes(F, dict(F.degree(weight="weight")), "margin")
+ cls.F = F
+
+ K = nx.Graph()
+ K.add_nodes_from([1, 2], margin=-1)
+ K.add_nodes_from([3], margin=1)
+ K.add_nodes_from([4], margin=2)
+ K.add_edges_from([(3, 4), (1, 2), (1, 3)])
+ cls.K = K
+
+
+class BaseTestDegreeMixing:
+ @classmethod
+ def setup_class(cls):
+ cls.P4 = nx.path_graph(4)
+ cls.D = nx.DiGraph()
+ cls.D.add_edges_from([(0, 2), (0, 3), (1, 3), (2, 3)])
+ cls.D2 = nx.DiGraph()
+ cls.D2.add_edges_from([(0, 3), (1, 0), (1, 2), (2, 4), (4, 1), (4, 3), (4, 2)])
+ cls.M = nx.MultiGraph()
+ nx.add_path(cls.M, range(4))
+ cls.M.add_edge(0, 1)
+ cls.S = nx.Graph()
+ cls.S.add_edges_from([(0, 0), (1, 1)])
+ cls.W = nx.Graph()
+ cls.W.add_edges_from([(0, 3), (1, 3), (2, 3)], weight=0.5)
+ cls.W.add_edge(0, 2, weight=1)
+ S1 = nx.star_graph(4)
+ S2 = nx.star_graph(4)
+ cls.DS = nx.disjoint_union(S1, S2)
+ cls.DS.add_edge(4, 5)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py
new file mode 100644
index 00000000..21c6287b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py
@@ -0,0 +1,143 @@
+from itertools import permutations
+
+import pytest
+
+import networkx as nx
+
+
+class TestNeighborConnectivity:
+ def test_degree_p4(self):
+ G = nx.path_graph(4)
+ answer = {1: 2.0, 2: 1.5}
+ nd = nx.average_degree_connectivity(G)
+ assert nd == answer
+
+ D = G.to_directed()
+ answer = {2: 2.0, 4: 1.5}
+ nd = nx.average_degree_connectivity(D)
+ assert nd == answer
+
+ answer = {1: 2.0, 2: 1.5}
+ D = G.to_directed()
+ nd = nx.average_degree_connectivity(D, source="in", target="in")
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_degree_connectivity(D, source="in", target="in")
+ assert nd == answer
+
+ def test_degree_p4_weighted(self):
+ G = nx.path_graph(4)
+ G[1][2]["weight"] = 4
+ answer = {1: 2.0, 2: 1.8}
+ nd = nx.average_degree_connectivity(G, weight="weight")
+ assert nd == answer
+ answer = {1: 2.0, 2: 1.5}
+ nd = nx.average_degree_connectivity(G)
+ assert nd == answer
+
+ D = G.to_directed()
+ answer = {2: 2.0, 4: 1.8}
+ nd = nx.average_degree_connectivity(D, weight="weight")
+ assert nd == answer
+
+ answer = {1: 2.0, 2: 1.8}
+ D = G.to_directed()
+ nd = nx.average_degree_connectivity(
+ D, weight="weight", source="in", target="in"
+ )
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_degree_connectivity(
+ D, source="in", target="out", weight="weight"
+ )
+ assert nd == answer
+
+ def test_weight_keyword(self):
+ G = nx.path_graph(4)
+ G[1][2]["other"] = 4
+ answer = {1: 2.0, 2: 1.8}
+ nd = nx.average_degree_connectivity(G, weight="other")
+ assert nd == answer
+ answer = {1: 2.0, 2: 1.5}
+ nd = nx.average_degree_connectivity(G, weight=None)
+ assert nd == answer
+
+ D = G.to_directed()
+ answer = {2: 2.0, 4: 1.8}
+ nd = nx.average_degree_connectivity(D, weight="other")
+ assert nd == answer
+
+ answer = {1: 2.0, 2: 1.8}
+ D = G.to_directed()
+ nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in")
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in")
+ assert nd == answer
+
+ def test_degree_barrat(self):
+ G = nx.star_graph(5)
+ G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)])
+ G[0][5]["weight"] = 5
+ nd = nx.average_degree_connectivity(G)[5]
+ assert nd == 1.8
+ nd = nx.average_degree_connectivity(G, weight="weight")[5]
+ assert nd == pytest.approx(3.222222, abs=1e-5)
+
+ def test_zero_deg(self):
+ G = nx.DiGraph()
+ G.add_edge(1, 2)
+ G.add_edge(1, 3)
+ G.add_edge(1, 4)
+ c = nx.average_degree_connectivity(G)
+ assert c == {1: 0, 3: 1}
+ c = nx.average_degree_connectivity(G, source="in", target="in")
+ assert c == {0: 0, 1: 0}
+ c = nx.average_degree_connectivity(G, source="in", target="out")
+ assert c == {0: 0, 1: 3}
+ c = nx.average_degree_connectivity(G, source="in", target="in+out")
+ assert c == {0: 0, 1: 3}
+ c = nx.average_degree_connectivity(G, source="out", target="out")
+ assert c == {0: 0, 3: 0}
+ c = nx.average_degree_connectivity(G, source="out", target="in")
+ assert c == {0: 0, 3: 1}
+ c = nx.average_degree_connectivity(G, source="out", target="in+out")
+ assert c == {0: 0, 3: 1}
+
+ def test_in_out_weight(self):
+ G = nx.DiGraph()
+ G.add_edge(1, 2, weight=1)
+ G.add_edge(1, 3, weight=1)
+ G.add_edge(3, 1, weight=1)
+ for s, t in permutations(["in", "out", "in+out"], 2):
+ c = nx.average_degree_connectivity(G, source=s, target=t)
+ cw = nx.average_degree_connectivity(G, source=s, target=t, weight="weight")
+ assert c == cw
+
+ def test_invalid_source(self):
+ with pytest.raises(nx.NetworkXError):
+ G = nx.DiGraph()
+ nx.average_degree_connectivity(G, source="bogus")
+
+ def test_invalid_target(self):
+ with pytest.raises(nx.NetworkXError):
+ G = nx.DiGraph()
+ nx.average_degree_connectivity(G, target="bogus")
+
+ def test_invalid_undirected_graph(self):
+ G = nx.Graph()
+ with pytest.raises(nx.NetworkXError):
+ nx.average_degree_connectivity(G, target="bogus")
+ with pytest.raises(nx.NetworkXError):
+ nx.average_degree_connectivity(G, source="bogus")
+
+ def test_single_node(self):
+ # TODO Is this really the intended behavior for providing a
+ # single node as the argument `nodes`? Shouldn't the function
+ # just return the connectivity value itself?
+ G = nx.trivial_graph()
+ conn = nx.average_degree_connectivity(G, nodes=0)
+ assert conn == {0: 0}
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py
new file mode 100644
index 00000000..5203f944
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py
@@ -0,0 +1,123 @@
+import pytest
+
+np = pytest.importorskip("numpy")
+pytest.importorskip("scipy")
+
+
+import networkx as nx
+from networkx.algorithms.assortativity.correlation import attribute_ac
+
+from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing
+
+
+class TestDegreeMixingCorrelation(BaseTestDegreeMixing):
+ def test_degree_assortativity_undirected(self):
+ r = nx.degree_assortativity_coefficient(self.P4)
+ np.testing.assert_almost_equal(r, -1.0 / 2, decimal=4)
+
+ def test_degree_assortativity_node_kwargs(self):
+ G = nx.Graph()
+ edges = [(0, 1), (0, 3), (1, 2), (1, 3), (1, 4), (5, 9), (9, 0)]
+ G.add_edges_from(edges)
+ r = nx.degree_assortativity_coefficient(G, nodes=[1, 2, 4])
+ np.testing.assert_almost_equal(r, -1.0, decimal=4)
+
+ def test_degree_assortativity_directed(self):
+ r = nx.degree_assortativity_coefficient(self.D)
+ np.testing.assert_almost_equal(r, -0.57735, decimal=4)
+
+ def test_degree_assortativity_directed2(self):
+ """Test degree assortativity for a directed graph where the set of
+ in/out degree does not equal the total degree."""
+ r = nx.degree_assortativity_coefficient(self.D2)
+ np.testing.assert_almost_equal(r, 0.14852, decimal=4)
+
+ def test_degree_assortativity_multigraph(self):
+ r = nx.degree_assortativity_coefficient(self.M)
+ np.testing.assert_almost_equal(r, -1.0 / 7.0, decimal=4)
+
+ def test_degree_pearson_assortativity_undirected(self):
+ r = nx.degree_pearson_correlation_coefficient(self.P4)
+ np.testing.assert_almost_equal(r, -1.0 / 2, decimal=4)
+
+ def test_degree_pearson_assortativity_directed(self):
+ r = nx.degree_pearson_correlation_coefficient(self.D)
+ np.testing.assert_almost_equal(r, -0.57735, decimal=4)
+
+ def test_degree_pearson_assortativity_directed2(self):
+ """Test degree assortativity with Pearson for a directed graph where
+ the set of in/out degree does not equal the total degree."""
+ r = nx.degree_pearson_correlation_coefficient(self.D2)
+ np.testing.assert_almost_equal(r, 0.14852, decimal=4)
+
+ def test_degree_pearson_assortativity_multigraph(self):
+ r = nx.degree_pearson_correlation_coefficient(self.M)
+ np.testing.assert_almost_equal(r, -1.0 / 7.0, decimal=4)
+
+ def test_degree_assortativity_weighted(self):
+ r = nx.degree_assortativity_coefficient(self.W, weight="weight")
+ np.testing.assert_almost_equal(r, -0.1429, decimal=4)
+
+ def test_degree_assortativity_double_star(self):
+ r = nx.degree_assortativity_coefficient(self.DS)
+ np.testing.assert_almost_equal(r, -0.9339, decimal=4)
+
+
+class TestAttributeMixingCorrelation(BaseTestAttributeMixing):
+ def test_attribute_assortativity_undirected(self):
+ r = nx.attribute_assortativity_coefficient(self.G, "fish")
+ assert r == 6.0 / 22.0
+
+ def test_attribute_assortativity_directed(self):
+ r = nx.attribute_assortativity_coefficient(self.D, "fish")
+ assert r == 1.0 / 3.0
+
+ def test_attribute_assortativity_multigraph(self):
+ r = nx.attribute_assortativity_coefficient(self.M, "fish")
+ assert r == 1.0
+
+ def test_attribute_assortativity_coefficient(self):
+ # from "Mixing patterns in networks"
+ # fmt: off
+ a = np.array([[0.258, 0.016, 0.035, 0.013],
+ [0.012, 0.157, 0.058, 0.019],
+ [0.013, 0.023, 0.306, 0.035],
+ [0.005, 0.007, 0.024, 0.016]])
+ # fmt: on
+ r = attribute_ac(a)
+ np.testing.assert_almost_equal(r, 0.623, decimal=3)
+
+ def test_attribute_assortativity_coefficient2(self):
+ # fmt: off
+ a = np.array([[0.18, 0.02, 0.01, 0.03],
+ [0.02, 0.20, 0.03, 0.02],
+ [0.01, 0.03, 0.16, 0.01],
+ [0.03, 0.02, 0.01, 0.22]])
+ # fmt: on
+ r = attribute_ac(a)
+ np.testing.assert_almost_equal(r, 0.68, decimal=2)
+
+ def test_attribute_assortativity(self):
+ a = np.array([[50, 50, 0], [50, 50, 0], [0, 0, 2]])
+ r = attribute_ac(a)
+ np.testing.assert_almost_equal(r, 0.029, decimal=3)
+
+ def test_attribute_assortativity_negative(self):
+ r = nx.numeric_assortativity_coefficient(self.N, "margin")
+ np.testing.assert_almost_equal(r, -0.2903, decimal=4)
+
+ def test_assortativity_node_kwargs(self):
+ G = nx.Graph()
+ G.add_nodes_from([0, 1], size=2)
+ G.add_nodes_from([2, 3], size=3)
+ G.add_edges_from([(0, 1), (2, 3)])
+ r = nx.numeric_assortativity_coefficient(G, "size", nodes=[0, 3])
+ np.testing.assert_almost_equal(r, 1.0, decimal=4)
+
+ def test_attribute_assortativity_float(self):
+ r = nx.numeric_assortativity_coefficient(self.F, "margin")
+ np.testing.assert_almost_equal(r, -0.1429, decimal=4)
+
+ def test_attribute_assortativity_mixed(self):
+ r = nx.numeric_assortativity_coefficient(self.K, "margin")
+ np.testing.assert_almost_equal(r, 0.4340, decimal=4)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py
new file mode 100644
index 00000000..9af09867
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py
@@ -0,0 +1,176 @@
+import pytest
+
+np = pytest.importorskip("numpy")
+
+
+import networkx as nx
+
+from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing
+
+
+class TestDegreeMixingDict(BaseTestDegreeMixing):
+ def test_degree_mixing_dict_undirected(self):
+ d = nx.degree_mixing_dict(self.P4)
+ d_result = {1: {2: 2}, 2: {1: 2, 2: 2}}
+ assert d == d_result
+
+ def test_degree_mixing_dict_undirected_normalized(self):
+ d = nx.degree_mixing_dict(self.P4, normalized=True)
+ d_result = {1: {2: 1.0 / 3}, 2: {1: 1.0 / 3, 2: 1.0 / 3}}
+ assert d == d_result
+
+ def test_degree_mixing_dict_directed(self):
+ d = nx.degree_mixing_dict(self.D)
+ print(d)
+ d_result = {1: {3: 2}, 2: {1: 1, 3: 1}, 3: {}}
+ assert d == d_result
+
+ def test_degree_mixing_dict_multigraph(self):
+ d = nx.degree_mixing_dict(self.M)
+ d_result = {1: {2: 1}, 2: {1: 1, 3: 3}, 3: {2: 3}}
+ assert d == d_result
+
+ def test_degree_mixing_dict_weighted(self):
+ d = nx.degree_mixing_dict(self.W, weight="weight")
+ d_result = {0.5: {1.5: 1}, 1.5: {1.5: 6, 0.5: 1}}
+ assert d == d_result
+
+
+class TestDegreeMixingMatrix(BaseTestDegreeMixing):
+ def test_degree_mixing_matrix_undirected(self):
+ # fmt: off
+ a_result = np.array([[0, 2],
+ [2, 2]]
+ )
+ # fmt: on
+ a = nx.degree_mixing_matrix(self.P4, normalized=False)
+ np.testing.assert_equal(a, a_result)
+ a = nx.degree_mixing_matrix(self.P4)
+ np.testing.assert_equal(a, a_result / a_result.sum())
+
+ def test_degree_mixing_matrix_directed(self):
+ # fmt: off
+ a_result = np.array([[0, 0, 2],
+ [1, 0, 1],
+ [0, 0, 0]]
+ )
+ # fmt: on
+ a = nx.degree_mixing_matrix(self.D, normalized=False)
+ np.testing.assert_equal(a, a_result)
+ a = nx.degree_mixing_matrix(self.D)
+ np.testing.assert_equal(a, a_result / a_result.sum())
+
+ def test_degree_mixing_matrix_multigraph(self):
+ # fmt: off
+ a_result = np.array([[0, 1, 0],
+ [1, 0, 3],
+ [0, 3, 0]]
+ )
+ # fmt: on
+ a = nx.degree_mixing_matrix(self.M, normalized=False)
+ np.testing.assert_equal(a, a_result)
+ a = nx.degree_mixing_matrix(self.M)
+ np.testing.assert_equal(a, a_result / a_result.sum())
+
+ def test_degree_mixing_matrix_selfloop(self):
+ # fmt: off
+ a_result = np.array([[2]])
+ # fmt: on
+ a = nx.degree_mixing_matrix(self.S, normalized=False)
+ np.testing.assert_equal(a, a_result)
+ a = nx.degree_mixing_matrix(self.S)
+ np.testing.assert_equal(a, a_result / a_result.sum())
+
+ def test_degree_mixing_matrix_weighted(self):
+ a_result = np.array([[0.0, 1.0], [1.0, 6.0]])
+ a = nx.degree_mixing_matrix(self.W, weight="weight", normalized=False)
+ np.testing.assert_equal(a, a_result)
+ a = nx.degree_mixing_matrix(self.W, weight="weight")
+ np.testing.assert_equal(a, a_result / float(a_result.sum()))
+
+ def test_degree_mixing_matrix_mapping(self):
+ a_result = np.array([[6.0, 1.0], [1.0, 0.0]])
+ mapping = {0.5: 1, 1.5: 0}
+ a = nx.degree_mixing_matrix(
+ self.W, weight="weight", normalized=False, mapping=mapping
+ )
+ np.testing.assert_equal(a, a_result)
+
+
+class TestAttributeMixingDict(BaseTestAttributeMixing):
+ def test_attribute_mixing_dict_undirected(self):
+ d = nx.attribute_mixing_dict(self.G, "fish")
+ d_result = {
+ "one": {"one": 2, "red": 1},
+ "two": {"two": 2, "blue": 1},
+ "red": {"one": 1},
+ "blue": {"two": 1},
+ }
+ assert d == d_result
+
+ def test_attribute_mixing_dict_directed(self):
+ d = nx.attribute_mixing_dict(self.D, "fish")
+ d_result = {
+ "one": {"one": 1, "red": 1},
+ "two": {"two": 1, "blue": 1},
+ "red": {},
+ "blue": {},
+ }
+ assert d == d_result
+
+ def test_attribute_mixing_dict_multigraph(self):
+ d = nx.attribute_mixing_dict(self.M, "fish")
+ d_result = {"one": {"one": 4}, "two": {"two": 2}}
+ assert d == d_result
+
+
+class TestAttributeMixingMatrix(BaseTestAttributeMixing):
+ def test_attribute_mixing_matrix_undirected(self):
+ mapping = {"one": 0, "two": 1, "red": 2, "blue": 3}
+ a_result = np.array([[2, 0, 1, 0], [0, 2, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]])
+ a = nx.attribute_mixing_matrix(
+ self.G, "fish", mapping=mapping, normalized=False
+ )
+ np.testing.assert_equal(a, a_result)
+ a = nx.attribute_mixing_matrix(self.G, "fish", mapping=mapping)
+ np.testing.assert_equal(a, a_result / a_result.sum())
+
+ def test_attribute_mixing_matrix_directed(self):
+ mapping = {"one": 0, "two": 1, "red": 2, "blue": 3}
+ a_result = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0]])
+ a = nx.attribute_mixing_matrix(
+ self.D, "fish", mapping=mapping, normalized=False
+ )
+ np.testing.assert_equal(a, a_result)
+ a = nx.attribute_mixing_matrix(self.D, "fish", mapping=mapping)
+ np.testing.assert_equal(a, a_result / a_result.sum())
+
+ def test_attribute_mixing_matrix_multigraph(self):
+ mapping = {"one": 0, "two": 1, "red": 2, "blue": 3}
+ a_result = np.array([[4, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
+ a = nx.attribute_mixing_matrix(
+ self.M, "fish", mapping=mapping, normalized=False
+ )
+ np.testing.assert_equal(a, a_result)
+ a = nx.attribute_mixing_matrix(self.M, "fish", mapping=mapping)
+ np.testing.assert_equal(a, a_result / a_result.sum())
+
+ def test_attribute_mixing_matrix_negative(self):
+ mapping = {-2: 0, -3: 1, -4: 2}
+ a_result = np.array([[4.0, 1.0, 1.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
+ a = nx.attribute_mixing_matrix(
+ self.N, "margin", mapping=mapping, normalized=False
+ )
+ np.testing.assert_equal(a, a_result)
+ a = nx.attribute_mixing_matrix(self.N, "margin", mapping=mapping)
+ np.testing.assert_equal(a, a_result / float(a_result.sum()))
+
+ def test_attribute_mixing_matrix_float(self):
+ mapping = {0.5: 1, 1.5: 0}
+ a_result = np.array([[6.0, 1.0], [1.0, 0.0]])
+ a = nx.attribute_mixing_matrix(
+ self.F, "margin", mapping=mapping, normalized=False
+ )
+ np.testing.assert_equal(a, a_result)
+ a = nx.attribute_mixing_matrix(self.F, "margin", mapping=mapping)
+ np.testing.assert_equal(a, a_result / a_result.sum())
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py
new file mode 100644
index 00000000..bf1252d5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py
@@ -0,0 +1,108 @@
+import pytest
+
+import networkx as nx
+
+
+class TestAverageNeighbor:
+ def test_degree_p4(self):
+ G = nx.path_graph(4)
+ answer = {0: 2, 1: 1.5, 2: 1.5, 3: 2}
+ nd = nx.average_neighbor_degree(G)
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_neighbor_degree(D)
+ assert nd == answer
+
+ D = nx.DiGraph(G.edges(data=True))
+ nd = nx.average_neighbor_degree(D)
+ assert nd == {0: 1, 1: 1, 2: 0, 3: 0}
+ nd = nx.average_neighbor_degree(D, "in", "out")
+ assert nd == {0: 0, 1: 1, 2: 1, 3: 1}
+ nd = nx.average_neighbor_degree(D, "out", "in")
+ assert nd == {0: 1, 1: 1, 2: 1, 3: 0}
+ nd = nx.average_neighbor_degree(D, "in", "in")
+ assert nd == {0: 0, 1: 0, 2: 1, 3: 1}
+
+ def test_degree_p4_weighted(self):
+ G = nx.path_graph(4)
+ G[1][2]["weight"] = 4
+ answer = {0: 2, 1: 1.8, 2: 1.8, 3: 2}
+ nd = nx.average_neighbor_degree(G, weight="weight")
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_neighbor_degree(D, weight="weight")
+ assert nd == answer
+
+ D = nx.DiGraph(G.edges(data=True))
+ print(D.edges(data=True))
+ nd = nx.average_neighbor_degree(D, weight="weight")
+ assert nd == {0: 1, 1: 1, 2: 0, 3: 0}
+ nd = nx.average_neighbor_degree(D, "out", "out", weight="weight")
+ assert nd == {0: 1, 1: 1, 2: 0, 3: 0}
+ nd = nx.average_neighbor_degree(D, "in", "in", weight="weight")
+ assert nd == {0: 0, 1: 0, 2: 1, 3: 1}
+ nd = nx.average_neighbor_degree(D, "in", "out", weight="weight")
+ assert nd == {0: 0, 1: 1, 2: 1, 3: 1}
+ nd = nx.average_neighbor_degree(D, "out", "in", weight="weight")
+ assert nd == {0: 1, 1: 1, 2: 1, 3: 0}
+ nd = nx.average_neighbor_degree(D, source="in+out", weight="weight")
+ assert nd == {0: 1.0, 1: 1.0, 2: 0.8, 3: 1.0}
+ nd = nx.average_neighbor_degree(D, target="in+out", weight="weight")
+ assert nd == {0: 2.0, 1: 2.0, 2: 1.0, 3: 0.0}
+
+ D = G.to_directed()
+ nd = nx.average_neighbor_degree(D, weight="weight")
+ assert nd == answer
+ nd = nx.average_neighbor_degree(D, source="out", target="out", weight="weight")
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_neighbor_degree(D, source="in", target="in", weight="weight")
+ assert nd == answer
+
+ def test_degree_k4(self):
+ G = nx.complete_graph(4)
+ answer = {0: 3, 1: 3, 2: 3, 3: 3}
+ nd = nx.average_neighbor_degree(G)
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_neighbor_degree(D)
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_neighbor_degree(D)
+ assert nd == answer
+
+ D = G.to_directed()
+ nd = nx.average_neighbor_degree(D, source="in", target="in")
+ assert nd == answer
+
+ def test_degree_k4_nodes(self):
+ G = nx.complete_graph(4)
+ answer = {1: 3.0, 2: 3.0}
+ nd = nx.average_neighbor_degree(G, nodes=[1, 2])
+ assert nd == answer
+
+ def test_degree_barrat(self):
+ G = nx.star_graph(5)
+ G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)])
+ G[0][5]["weight"] = 5
+ nd = nx.average_neighbor_degree(G)[5]
+ assert nd == 1.8
+ nd = nx.average_neighbor_degree(G, weight="weight")[5]
+ assert nd == pytest.approx(3.222222, abs=1e-5)
+
+ def test_error_invalid_source_target(self):
+ G = nx.path_graph(4)
+ with pytest.raises(nx.NetworkXError):
+ nx.average_neighbor_degree(G, "error")
+ with pytest.raises(nx.NetworkXError):
+ nx.average_neighbor_degree(G, "in", "error")
+ G = G.to_directed()
+ with pytest.raises(nx.NetworkXError):
+ nx.average_neighbor_degree(G, "error")
+ with pytest.raises(nx.NetworkXError):
+ nx.average_neighbor_degree(G, "in", "error")
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py
new file mode 100644
index 00000000..3984292b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py
@@ -0,0 +1,87 @@
+import networkx as nx
+
+from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing
+
+
+class TestAttributeMixingXY(BaseTestAttributeMixing):
+ def test_node_attribute_xy_undirected(self):
+ attrxy = sorted(nx.node_attribute_xy(self.G, "fish"))
+ attrxy_result = sorted(
+ [
+ ("one", "one"),
+ ("one", "one"),
+ ("two", "two"),
+ ("two", "two"),
+ ("one", "red"),
+ ("red", "one"),
+ ("blue", "two"),
+ ("two", "blue"),
+ ]
+ )
+ assert attrxy == attrxy_result
+
+ def test_node_attribute_xy_undirected_nodes(self):
+ attrxy = sorted(nx.node_attribute_xy(self.G, "fish", nodes=["one", "yellow"]))
+ attrxy_result = sorted([])
+ assert attrxy == attrxy_result
+
+ def test_node_attribute_xy_directed(self):
+ attrxy = sorted(nx.node_attribute_xy(self.D, "fish"))
+ attrxy_result = sorted(
+ [("one", "one"), ("two", "two"), ("one", "red"), ("two", "blue")]
+ )
+ assert attrxy == attrxy_result
+
+ def test_node_attribute_xy_multigraph(self):
+ attrxy = sorted(nx.node_attribute_xy(self.M, "fish"))
+ attrxy_result = [
+ ("one", "one"),
+ ("one", "one"),
+ ("one", "one"),
+ ("one", "one"),
+ ("two", "two"),
+ ("two", "two"),
+ ]
+ assert attrxy == attrxy_result
+
+ def test_node_attribute_xy_selfloop(self):
+ attrxy = sorted(nx.node_attribute_xy(self.S, "fish"))
+ attrxy_result = [("one", "one"), ("two", "two")]
+ assert attrxy == attrxy_result
+
+
+class TestDegreeMixingXY(BaseTestDegreeMixing):
+ def test_node_degree_xy_undirected(self):
+ xy = sorted(nx.node_degree_xy(self.P4))
+ xy_result = sorted([(1, 2), (2, 1), (2, 2), (2, 2), (1, 2), (2, 1)])
+ assert xy == xy_result
+
+ def test_node_degree_xy_undirected_nodes(self):
+ xy = sorted(nx.node_degree_xy(self.P4, nodes=[0, 1, -1]))
+ xy_result = sorted([(1, 2), (2, 1)])
+ assert xy == xy_result
+
+ def test_node_degree_xy_directed(self):
+ xy = sorted(nx.node_degree_xy(self.D))
+ xy_result = sorted([(2, 1), (2, 3), (1, 3), (1, 3)])
+ assert xy == xy_result
+
+ def test_node_degree_xy_multigraph(self):
+ xy = sorted(nx.node_degree_xy(self.M))
+ xy_result = sorted(
+ [(2, 3), (2, 3), (3, 2), (3, 2), (2, 3), (3, 2), (1, 2), (2, 1)]
+ )
+ assert xy == xy_result
+
+ def test_node_degree_xy_selfloop(self):
+ xy = sorted(nx.node_degree_xy(self.S))
+ xy_result = sorted([(2, 2), (2, 2)])
+ assert xy == xy_result
+
+ def test_node_degree_xy_weighted(self):
+ G = nx.Graph()
+ G.add_edge(1, 2, weight=7)
+ G.add_edge(2, 3, weight=10)
+ xy = sorted(nx.node_degree_xy(G, weight="weight"))
+ xy_result = sorted([(7, 17), (17, 10), (17, 7), (10, 17)])
+ assert xy == xy_result