# Source code for networkx.algorithms.assortativity.neighbor_degree

import networkx as nx

__all__ = ["average_neighbor_degree"]

[docs] @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