NetworkX¶
NetworkX fournit une analyse de graphes en mémoire sans nécessiter de base de données externe. Idéal pour le prototypage, l'analyse locale et l'intégration avec les outils de science des données Python.
Dépendances¶
NetworkX est un extra optionnel :
pip install "ddigraph[networkx]"
Pour la visualisation :
pip install matplotlib # graphiques de base
pip install pyvis # visualisation HTML interactive
Utilisation de base¶
import os
from ddigraph.backends.networkx import to_networkx
G = to_networkx(os.environ["FIXTURE"])
print(f"Graphe : {G.number_of_nodes()} nœuds, {G.number_of_edges()} arêtes")
La source peut être du DDI XML de n'importe quelle variante, ou un export
RDF comme survey.ttl. Vous pouvez aussi passer des blocs déjà obtenus avec
ddigraph.iter_graph().
Contenu du graphe¶
G est un MultiDiGraph. Il est orienté, car les liens DDI le sont. Il est
multiple, car deux nœuds peuvent être reliés par plusieurs sortes de liens.
- Les identifiants de nœud ont la forme
"Variable:v1": le type du nœud, puis sa clé. Le type fait partie de l'identifiant, car une clé n'est unique qu'au sein de son type. Un codebook peut utiliserc1pour une catégorie et pour un concept. - Les attributs de nœud sont
node_type,node_key, les champs d'identité et chaque propriété qui a une valeur. Le type s'appellenode_typeet nonlabel, car la plupart des enregistrements DDI ont leur proprelabel: le libellé lisible. - Les arêtes portent
relationship, leur type. La clé de l'arête est aussi le type : charger deux fois le même fichier ne double aucune arête.
import os
from collections import Counter
from ddigraph.backends.networkx import to_networkx
G = to_networkx(os.environ["FIXTURE"])
types = Counter(data["node_type"] for _, data in G.nodes(data=True))
for node_type, count in types.most_common():
print(f"{node_type}: {count}")
Plusieurs fichiers, un graphe¶
Passez graph= pour compléter un graphe existant. Les nœuds communs aux
fichiers fusionnent :
G = to_networkx("wave1.xml")
G = to_networkx("wave2.xml", graph=G)
Analyse de graphes¶
Statistiques de base¶
# Informations sur le graphe
print(f"{G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
# Composantes connexes (pour la vue non orientée)
undirected = G.to_undirected()
components = list(nx.connected_components(undirected))
print(f"Composantes connexes : {len(components)}")
# Densité
print(f"Densité : {nx.density(G):.4f}")
Métriques de centralité¶
# Centralité de degré
degree_cent = nx.degree_centrality(G)
# Centralité d'intermédiation
betweenness = nx.betweenness_centrality(G)
# PageRank
pagerank = nx.pagerank(G)
# Trouver les noeuds les plus importants
important = sorted(pagerank.items(), key=lambda x: x[1], reverse=True)[:10]
for node_id, score in important:
print(f"{G.nodes[node_id]['node_type']}: {score:.4f}")
Analyse de chemins¶
# Trouver tous les chemins entre les noeuds
instrument_nodes = [n for n, d in G.nodes(data=True) if d.get("node_type") == "Instrument"]
question_nodes = [n for n, d in G.nodes(data=True) if d.get("node_type") == "QuestionItem"]
if instrument_nodes and question_nodes:
paths = list(nx.all_simple_paths(G, instrument_nodes[0], question_nodes[0], cutoff=10))
print(f"{len(paths)} chemins trouvés")
# Plus court chemin
if nx.has_path(G, instrument_nodes[0], question_nodes[0]):
path = nx.shortest_path(G, instrument_nodes[0], question_nodes[0])
print(f"Plus court chemin : {' -> '.join(path)}")
Extraction de sous-graphes¶
# Extraire un sous-graphe par type de noeud
question_items = [n for n, d in G.nodes(data=True) if d.get("node_type") == "QuestionItem"]
code_lists = [n for n, d in G.nodes(data=True) if d.get("node_type") == "CodeList"]
categories = [n for n, d in G.nodes(data=True) if d.get("node_type") == "Category"]
subgraph_nodes = set(question_items + code_lists + categories)
subgraph = G.subgraph(subgraph_nodes)
print(f"Sous-graphe : {subgraph.number_of_nodes()} noeuds")
# Extraire le réseau ego (voisins d'un noeud)
ego = nx.ego_graph(G, instrument_nodes[0], radius=2)
print(f"Réseau ego : {ego.number_of_nodes()} noeuds")
Visualisation¶
Matplotlib¶
import matplotlib.pyplot as plt
# Disposition simple
pos = nx.spring_layout(G, k=2, iterations=50)
# Couleur par type de noeud
color_map = {
"Instrument": "red",
"Sequence": "blue",
"QuestionConstruct": "green",
"QuestionItem": "orange",
"CodeList": "purple",
"Category": "yellow",
}
colors = [color_map.get(G.nodes[n].get("node_type", ""), "gray") for n in G.nodes()]
plt.figure(figsize=(16, 12))
nx.draw(G, pos, node_color=colors, node_size=50, with_labels=False, alpha=0.7)
plt.savefig("ddi_graph.png", dpi=150)
plt.show()
PyVis (HTML interactif)¶
from pyvis.network import Network
net = Network(height="800px", width="100%", directed=True)
# Ajouter les noeuds avec des couleurs
for node_id, data in G.nodes(data=True):
node_type = data.get("node_type", "Unknown")
label = data.get("label", node_id)[:30]
color = color_map.get(node_type, "gray")
net.add_node(node_id, label=label, color=color, title=f"{node_type}: {label}")
# Ajouter les arêtes
for source, target, data in G.edges(data=True):
rel = data.get("relationship", "")
net.add_edge(source, target, title=rel)
net.show("ddi_interactive.html")
Formats d'export¶
GraphML et GEXF ne savent pas stocker de listes, et certaines propriétés DDI
en sont. Construisez le graphe avec flatten_lists=True pour les joindre
d'abord avec | :
import os
import networkx as nx
from ddigraph.backends.networkx import to_networkx
G = to_networkx(os.environ["FIXTURE"], flatten_lists=True)
nx.write_graphml(G, "survey.graphml")
nx.write_gexf(G, "survey.gexf") # pour Gephi
Le JSON node-link conserve les listes telles quelles :
import json
with open("graph.json", "w") as f:
json.dump(nx.node_link_data(G, edges="edges"), f, indent=2)
Sous forme de tableaux¶
Pour des tableaux, utilisez le backend pandas. Il lit le même fichier dans un DataFrame de nœuds et un DataFrame de relations.
Mémoire¶
Tout le graphe tient en mémoire, attributs compris. Si seule la structure vous intéresse, retirez les attributs inutiles :
for _, data in G.nodes(data=True):
for key in [k for k in data if k not in ("node_type", "node_key")]:
del data[key]
Voir aussi¶
- pandas - Le même graphe, en deux tableaux
- Gremlin - Le même graphe, dans une base de données
- Documentation NetworkX