Source code for xgi.algorithms.assortativity

"""Algorithms for finding the degree assortativity of a hypergraph."""

import random
from itertools import combinations, permutations

import numpy as np

from ..exception import XGIError
from .properties import is_uniform, unique_edge_sizes

__all__ = ["dynamical_assortativity", "degree_assortativity"]


[docs]def dynamical_assortativity(H): """Computes the dynamical assortativity of a uniform hypergraph. Parameters ---------- H : xgi.Hypergraph Hypergraph of interest Returns ------- float The dynamical assortativity See Also -------- degree_assortativity Raises ------ XGIError If the hypergraph is not uniform, or if there are no nodes or no edges References ---------- Nicholas Landry and Juan G. Restrepo, Hypergraph assortativity: A dynamical systems perspective, Chaos 2022. DOI: 10.1063/5.0086905 """ if H.num_nodes == 0: raise XGIError("Hypergraph must contain nodes") elif H.num_edges == 0: raise XGIError("Hypergraph must contain edges!") if not is_uniform(H): raise XGIError("Hypergraph must be uniform!") if 1 in unique_edge_sizes(H): raise XGIError("No singleton edges!") d = H.nodes.degree members = H.edges.members(dtype=dict) k = d.asdict() k1 = d.mean() k2 = d.moment(2) kk1 = np.mean( [k[n1] * k[n2] for e in H.edges for n1, n2 in combinations(members[e], 2)] ) return kk1 * k1**2 / k2**2 - 1
[docs]def degree_assortativity(H, kind="uniform", exact=False, num_samples=1000): """Computes the degree assortativity of a hypergraph Parameters ---------- H : Hypergraph The hypergraph of interest kind : str, optional the type of degree assortativity. valid choices are "uniform", "top-2", and "top-bottom". By default, "uniform". exact : bool, optional whether to compute over all edges or sample randomly from the set of edges. By default, False. num_samples : int, optional if not exact, specify the number of samples for the computation. By default, 1000. Returns ------- float the degree assortativity Raises ------ XGIError If there are no nodes or no edges See Also -------- dynamical_assortativity References ---------- Phil Chodrow, Configuration models of random hypergraphs, Journal of Complex Networks 2020. DOI: 10.1093/comnet/cnaa018 """ if H.num_nodes == 0: raise XGIError("Hypergraph must contain nodes") elif H.num_edges == 0: raise XGIError("Hypergraph must contain edges!") k = H.degree() members = H.edges.members(dtype=dict) if exact: if kind == "uniform": k1k2 = [ [k[n1], k[n2]] for e in H.edges for n1, n2 in permutations(members[e], 2) if n1 != n2 and len(members[e]) > 1 # permutations is so that k1 and k2 have the same variance ] elif kind == "top-2": k1k2 = [ d for e in H.edges if len(members[e]) > 1 for d in permutations(_choose_degrees(members[e], k, "top-2"), 2) # permutations is so that k1 and k2 have the same variance ] elif kind == "top-bottom": k1k2 = [ d for e in H.edges if len(members[e]) > 1 for d in permutations(_choose_degrees(members[e], k, "top-bottom"), 2) # permutations is so that k1 and k2 have the same variance ] else: raise XGIError("Invalid type of degree assortativity!") else: edges = [e for e in H.edges if len(H.edges.members(e)) > 1] k1k2 = [ np.random.permutation( _choose_degrees(members[random.choice(edges)], k, kind) ) for _ in range(num_samples) ] rho = np.corrcoef(np.array(k1k2).T)[0, 1] if np.isnan(rho): return 0 return rho
def _choose_degrees(e, k, kind="uniform"): """Choose the degrees of two nodes in a hyperedge. Parameters ---------- e : iterable the members in a hyperedge k : dict the degrees where keys are node IDs and values are degrees kind : str, optional the type of degree assortativity, options are "uniform", "top-2", and "top-bottom". By default, "uniform". Returns ------- tuple two degrees selected from the edge Raises ------ XGIError if invalid assortativity function chosen See Also -------- degree_assortativity References ---------- Phil Chodrow, Configuration models of random hypergraphs, Journal of Complex Networks 2020. DOI: 10.1093/comnet/cnaa018 """ e = list(e) if len(e) > 1: if kind == "uniform": i = np.random.randint(len(e)) j = i while i == j: j = np.random.randint(len(e)) return (k[e[i]], k[e[j]]) elif kind == "top-2": return sorted([k[i] for i in e])[-2:] elif kind == "top-bottom": # this selects the largest and smallest degrees in one line return sorted([k[i] for i in e])[:: len(e) - 1] else: raise XGIError("Invalid type of degree assortativity!") else: raise XGIError("Edge must have more than one member!")