Centralities, assortativity, communities, and motifs
A network recorded with its centralities, degree mixing, communities, and triangle z-score.
- Name:
network_structure_analysis - Field:
mathematics/networks - Formalism: agent-based model
- References: Newman 2010; Milo et al. 2002; Blondel et al. 2008; Bertin 2021
Factory
@register_model(
field='mathematics/networks',
tags=('network', 'centrality', 'communities', 'motifs', 'assortativity'),
references=('Newman 2010', 'Milo et al. 2002', 'Blondel et al. 2008', 'Bertin 2021'),
defaults={'until': 1, 'dt': 1},
observables=_structure_observables,
)
def network_structure_analysis(
graph: nx.Graph | None = None,
*,
reference: str | None = 'block',
null_samples: int = 10,
random_state: int | None = 0,
) -> System:
"""Return a network recorded with its centralities, degree mixing, communities, and triangle z-score.
Every node is an agent that carries its degree, clustering, betweenness, and the community found by Louvain
modularity maximization. The observables report the clustering, the mean path length of the largest component, the
degree assortativity, the modularity, the agreement of the communities with a reference partition, and the z-score
of triangles against degree-preserving edge switches.
Args:
graph:
The undirected NetworkX graph, or `None` for a stochastic block model of 4 blocks of 100 nodes with mean
in-block degree 16 and out-block degree 2.
reference:
The node attribute that holds the reference partition, such as `'block'` of a stochastic block model or
`'club'` of Zachary's karate club, or `None`. The agreement is reported only when every node has it.
null_samples:
The number of edge-switched graphs of the triangle z-score, which is reported only when positive.
random_state:
The seed of the block model, the community detection, and the null graphs.
Returns:
system:
The model, with the node group `nodes` and the network measures `measures`.
Examples:
>>> import networkx as nx
>>> from skcomplex.models.mathematics.networks import network_structure_analysis
>>> system = network_structure_analysis(nx.karate_club_graph(), reference='club', null_samples=0)
>>> system.elements[0].check().prepare().size_
34
"""
network = _planted_partition(random_state) if graph is None else graph
found = (
NetworkAnalysis('communities', reference=_reference(network, reference), random_state=random_state)
.check()
.prepare(network)
.run()
)
summary = NetworkAnalysis('summary').check().prepare(network).run()
clustering = nx.clustering(network)
betweenness = NetworkAnalysis('centrality', measure='betweenness').check().prepare(network).run().centrality_[0]
nodes = list(network.nodes())
group = GroupElement(
'nodes',
element=NetworkNodeElement,
size=len(nodes),
values={
'degree': np.asarray([network.degree(node) for node in nodes], dtype=float),
'clustering': np.asarray([clustering[node] for node in nodes], dtype=float),
'betweenness': np.asarray([betweenness[node] for node in nodes], dtype=float),
'community': found.labels_[0].astype(float),
},
location=nodes,
)
zscore = 0.0
if null_samples:
motifs = MotifAnalysis('z-scores', samples=null_samples, random_state=random_state)
zscore = motifs.check().prepare(network).run().z_scores_[0]['triangle']
measures = NetworkMeasuresElement(
'measures',
path_length=float(summary.path_length_[0]),
assortativity=float(NetworkAnalysis('assortativity').check().prepare(network).run().assortativity_[0]),
modularity=float(found.modularity_[0]),
agreement=0.0 if found.agreement_ is None else float(found.agreement_[0]),
triangle_zscore=zscore,
)
return System(elements=[group, measures], space=NetworkSpace(network))
Components
class NetworkNodeElement(Element):
"""Node of a network that carries its degree, clustering, betweenness, and community.
Args:
label:
The label of the agent or group.
visual:
How the element is drawn, or `None` for `default_visual_`.
degree:
The degree of the node.
clustering:
The clustering coefficient of the node.
betweenness:
The normalized betweenness centrality of the node.
community:
The index of the community of the node.
Examples:
>>> from skcomplex.core.elements import GroupElement
>>> from skcomplex.models.mathematics.networks import NetworkNodeElement
>>> group = GroupElement('nodes', element=NetworkNodeElement, size=3)
>>> group.label, group.element.__name__, group.size
('nodes', 'NetworkNodeElement', 3)
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = VisualSpec('agents', {'color_by': 'community'}),
*,
degree: Annotated[float, StateSpec(domain=NN)] = 0.0,
clustering: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
betweenness: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
community: Annotated[float, StateSpec(domain=NN)] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.degree = degree
self.clustering = clustering
self.betweenness = betweenness
self.community = community
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the rule that keeps the node in place.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
return [RuleLaw(self.stay)]
def stay(self, agent: AgentView, model: ModelView) -> None:
"""Keep the node and its measures unchanged.
Args:
agent:
The node.
model:
The model.
"""
class NetworkMeasuresElement(Element):
"""Measures of a whole network, held as parameters that the observables of the model report.
Args:
label:
The label of the element.
visual:
How the element is drawn, or `None` for `default_visual_`.
path_length:
The mean shortest path length of the largest component.
assortativity:
The degree assortativity.
modularity:
The modularity of the communities found.
agreement:
The agreement of the communities found with a reference partition.
triangle_zscore:
The z-score of the number of triangles against degree-preserving edge switches.
Examples:
>>> from skcomplex.models.mathematics.networks import NetworkMeasuresElement
>>> NetworkMeasuresElement('measures', modularity=0.4).modularity
0.4
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
path_length: Annotated[float, ParameterSpec()] = 0.0,
assortativity: Annotated[float, ParameterSpec()] = 0.0,
modularity: Annotated[float, ParameterSpec()] = 0.0,
agreement: Annotated[float, ParameterSpec()] = 0.0,
triangle_zscore: Annotated[float, ParameterSpec()] = 0.0,
) -> None:
super().__init__(label, visual=visual)
self.path_length = path_length
self.assortativity = assortativity
self.modularity = modularity
self.agreement = agreement
self.triangle_zscore = triangle_zscore