Files
graphes-l3/Seance_2_Representations.ipynb
T

23 KiB

🧮 Séance 2 — Représenter un graphe : matrice, liste d'adjacence, incidence

Objectifs pédagogiques

À la fin de cette séance, vous serez capables de :

  • représenter un graphe à l'aide d'une matrice d'adjacence et d'une liste d'adjacence ;
  • représenter un graphe à l'aide d'une matrice/liste d'incidence (sommets × arêtes) ;
  • choisir la représentation la plus adaptée selon la taille et la densité d'un réseau ;
  • interpréter les liens sociaux à partir de ces représentations ;
  • comprendre la symétrie, le degré et les liens indirects dans un réseau ;
  • construire et explorer ces représentations avec Python.

Introduction

En sociologie, on cherche souvent à représenter les relations entre individus : qui connaît qui, qui travaille avec qui, qui échange le plus, etc.

Un graphe permet de modéliser ces interactions :

  • nœuds (ou sommets) → les individus
  • arêtes (ou liens) → les relations entre eux

Mais il existe plusieurs manières, plus mathématiques, de représenter ces relations : la matrice d'adjacence, la liste d'adjacence, et la matrice/liste d'incidence. Chacune a ses avantages selon la taille du réseau et ce qu'on veut en faire.

Définition

Pour un graphe G = (V, E) à n sommets, la matrice d'adjacence A est une matrice carrée n × n où :

A_{ij} = \begin{cases}1 & \text{si une arête relie le sommet } i \text{ au sommet } j \\0 & \text{sinon}\end{cases}
  • Dans un graphe non dirigé, A est symétrique : A_{ij} = A_{ji}
  • Dans un graphe dirigé, A peut être asymétrique : A_{ij} = 1 ne signifie pas forcément A_{ji} = 1

Partie 1 — Représentation papier

1. Graphe de départ

Voici un petit réseau d'amitié entre cinq personnes :

Alice — Bob — Claire
  │        │
 David     Emma

Consigne :

  1. Listez les sommets : Alice, Bob, Claire, David, Emma.
  2. Complétez la matrice d'adjacence correspondante :
Alice Bob Claire David Emma
Alice 0
Bob 0
Claire 0
David 0
Emma 0

(Remplissez avec des 1 là où il existe un lien.)

2. Questions d'analyse

  1. Quel est le degré de chaque individu (combien de liens possède-t-il) ?
  2. Quel est l'individu le plus central dans ce réseau ?
  3. Si on ajoute un lien entre David et Emma, comment la matrice change-t-elle ?
  4. Que signifie le fait que la matrice soit symétrique ?
  5. En sociologie, que représenterait une matrice non symétrique ? (donnez un exemple concret)

Explication :

  • Une matrice symétrique signifie que les relations sont réciproques : si Alice est amie avec Bob, Bob l'est aussi avec Alice. Cela correspond à des relations mutuelles (amitié, collaboration, parenté, etc.).
  • Une matrice non symétrique représente des relations non réciproques : par exemple, A suit B sur Twitter, A cite B dans un article scientifique, ou A supervise B dans une organisation.

Partie 2 — Exploration Python

In [ ]:
import networkx as nx
import numpy as np
import matplotlib.pyplot as plt

1. Créer et afficher la matrice

In [ ]:
G = nx.Graph()
G.add_edges_from([
    ("Alice","Bob"),
    ("Bob","Claire"),
    ("Alice","David"),
    ("Bob","Emma")
])

# Obtenir la matrice d'adjacence
A = nx.to_numpy_array(G, nodelist=G.nodes())
print(list(G.nodes()))
print(A.astype(int))

2. Visualiser le graphe

In [ ]:
plt.figure()
nx.draw(G, with_labels=True, node_color="lightblue", node_size=1000)
plt.show()

3. Explorer la structure du réseau

In [ ]:
# Degré de chaque sommet
print("Degré de chaque individu :", dict(G.degree()))

# Densité du réseau (rapport entre liens existants et liens possibles)
print("Densité du réseau :", round(nx.density(G), 2))

4. Liens indirects : le carré de la matrice

In [ ]:
A2 = np.linalg.matrix_power(A, 2)
print("A² =\n", A2.astype(int))

Interprétation :

  • Si A²[i][j] > 0, cela signifie qu'il existe un ami commun entre les nœuds i et j.
  • En sociologie : cela mesure les liens indirects, c'est-à-dire les "amis d'amis".

Pourquoi ne met-on pas les éléments eux-mêmes au carré ?

Quand on écrit A², cela signifie A × A, c'est-à-dire qu'on multiplie la matrice par elle-même selon les règles de la multiplication matricielle.

On ne calcule pas (A_{ij})^2, car cela n'aurait aucun intérêt :

  • 0^2 = 0 et 1^2 = 1, donc la matrice ne changerait pas.
  • Le but du carré matriciel est de compter les chemins de longueur 2 entre les nœuds.

Formellement :

A^2_{ij} = \sum_k A_{ik} \times A_{kj}

Cela signifie que A^2_{ij} indique combien de façons il existe d'aller du nœud i au nœud j en passant par exactement un autre nœud.

Exemple : Si A =


\begin{bmatrix}
0 & 1 & 0 \\
1 & 0 & 1 \\
0 & 1 & 0
\end{bmatrix}

alors A^2 =


\begin{bmatrix}
1 & 0 & 1 \\
0 & 2 & 0 \\
1 & 0 & 1
\end{bmatrix}
  • A^2_{13} = 1 : il existe un chemin de longueur 2 entre 1 et 3 (en passant par 2).
  • A^2_{22} = 2 : le sommet 2 a deux chemins de longueur 2 qui reviennent à lui-même.

Résumé :

Calcul Signification Utilité
(A_{ij})^2 Élève chaque case au carré Inutile (0 et 1 inchangés)
A^2 = A × A Produit matriciel Donne les liens indirects (chemins de longueur 2)

Partie 3 — Liste d'adjacence

La matrice d'adjacence a un défaut : pour un réseau de 500 personnes, elle contient 500 × 500 = 250 000 cases, même si chacune n'a que 10 amis en moyenne (la plupart des cases valent alors 0). Or les réseaux sociaux réels sont presque toujours creux (sparse) : chaque individu ne connaît qu'une petite fraction du groupe.

La liste d'adjacence répond à ce problème : au lieu d'une grille, on associe à chaque sommet la liste de ses voisins. On ne stocke que les liens qui existent réellement.

\text{Alice} \rightarrow [\text{Bob}, \text{David}] \text{Bob} \rightarrow [\text{Alice}, \text{Claire}, \text{Emma}]

1. Construire la liste d'adjacence

In [ ]:
# NetworkX construit directement la liste d'adjacence à partir du graphe G
liste_adjacence = nx.to_dict_of_lists(G)

for personne, amis in liste_adjacence.items():
    print(f"{personne} → {amis}")

2. Comparer matrice et liste d'adjacence

Questions :

  1. Combien de "cases" contient la matrice d'adjacence de ce petit réseau à 5 personnes ? Combien de liens contient la liste d'adjacence ?
  2. Pour un réseau de 10 000 personnes où chacune a en moyenne 50 ami·es, quelle représentation prendrait le moins de mémoire ?
  3. Pour répondre rapidement à la question "Alice et Emma sont-elles amies ?", quelle représentation est la plus directe ?
  4. Pour répondre à "quels sont les ami·es de Bob ?", quelle représentation est la plus directe ?

En résumé : la matrice d'adjacence est pratique pour les calculs matriciels (comme A^2) et les réseaux denses. La liste d'adjacence est plus économe en mémoire et plus rapide à parcourir pour les réseaux creux — c'est-à-dire la grande majorité des réseaux sociaux réels.

Partie 4 — Matrice et liste d'incidence

Les représentations précédentes croisent des sommets avec des sommets (qui est relié à qui). La matrice d'incidence change de point de vue : elle croise des sommets avec des arêtes (quel individu est concerné par quelle relation).

Pour un graphe à n sommets et m arêtes, la matrice d'incidence B est une matrice n × m où :

B_{ie} = \begin{cases}1 & \text{si le sommet } i \text{ est une extrémité de l'arête } e \\0 & \text{sinon}\end{cases}

Chaque colonne représente donc une relation (une arête), avec exactement deux 1 : ses deux extrémités.

Cette idée rejoint celle du graphe biparti vue en Introduction (COURS.md — individus ↔ opinions, individus ↔ événements) : on peut voir la matrice d'incidence comme "qui participe à quelle relation", exactement comme on notait "qui participe à quel événement".

1. Construire la matrice d'incidence

In [ ]:
# Construction manuelle de la matrice d'incidence avec numpy
# (une colonne par arête, deux 1 par colonne : les deux extrémités)
liste_aretes = list(G.edges())
sommets = list(G.nodes())

B = np.zeros((len(sommets), len(liste_aretes)), dtype=int)
for colonne, (u, v) in enumerate(liste_aretes):
    B[sommets.index(u), colonne] = 1
    B[sommets.index(v), colonne] = 1

print("Arêtes (colonnes) :", liste_aretes)
print(sommets)
print(B)

Interprétation : chaque colonne de B contient exactement deux 1, ceux des deux personnes impliquées dans cette relation. Une ligne "pleine de 0" signifierait un individu isolé, qui n'apparaît dans aucune relation.

2. Liste d'incidence

De la même façon qu'on a allégé la matrice d'adjacence en liste d'adjacence, on peut alléger la matrice d'incidence : au lieu d'une grille sommets × arêtes, on liste simplement, pour chaque arête, ses deux extrémités. C'est en fait exactement ce que renvoie G.edges() :

In [ ]:
# La liste d'incidence : pour chaque arête, ses deux extrémités
for i, (u, v) in enumerate(G.edges()):
    print(f"Arête {i} : {u} — {v}")

Application sociologique : cette matrice sommets × arêtes met en avant les relations elles-mêmes plutôt que les individus. En pratique, en sociologie, elle reste peu utilisée : elle contient exactement la même information que la matrice d'adjacence, sous une forme plus encombrante.

⚠️ Attention à un piège de vocabulaire. En analyse des réseaux sociaux, on appelle aussi « matrice d'incidence » un objet différent : une matrice individus × événements. Ce n'est pas la même chose que la matrice sommets × arêtes ci-dessus, et c'est celle-là qui est vraiment utile au sociologue. C'est l'objet de la partie 5.

Partie 5 — L'incidence utile au sociologue : individus × événements

1. Pourquoi changer d'objet

Dans la partie 4, chaque colonne représentait une arête et contenait donc exactement deux 1 : les deux extrémités de la relation.

Mais en sociologie, on n'observe presque jamais les liens directement. On n'a pas la liste des amitiés : on a des listes de présence. Qui siège dans quel conseil d'administration, qui signe quelle pétition, qui assiste à quelle réunion, qui adhère à quelle association.

On construit alors une matrice B de taille individus × événements :

B_{ie} = \begin{cases}1 & \text{si l'individu } i \text{ participe à l'événement } e \\ 0 & \text{sinon}\end{cases}

La différence est structurelle : une colonne n'est plus une arête mais un événement, et elle contient autant de 1 qu'il y a de participants. Une réunion à cinq personnes donne une colonne à cinq 1.

C'est ce que Breiger (1974) appelle la dualité des personnes et des groupes : les individus sont reliés par les événements qu'ils partagent, et les événements sont reliés par les individus qu'ils rassemblent.

2. Construire la matrice d'affiliation

Reprenons nos cinq personnes, mais en observant cette fois leurs participations à quatre événements associatifs, et non plus leurs amitiés.

In [ ]:
personnes = ["Alice", "Bob", "Claire", "David", "Emma"]
evenements = ["Réunion A", "Atelier B", "Sortie C", "Conseil D"]

participations = {
    "Alice":  ["Réunion A", "Atelier B"],
    "Bob":    ["Réunion A", "Conseil D"],
    "Claire": ["Atelier B", "Sortie C", "Conseil D"],
    "David":  ["Atelier B", "Sortie C", "Conseil D"],
    "Emma":   ["Sortie C"],
}

B = np.zeros((len(personnes), len(evenements)), dtype=int)
for i, personne in enumerate(personnes):
    for evenement in participations[personne]:
        B[i, evenements.index(evenement)] = 1

print("Colonnes :", evenements)
print(personnes)
print(B)
print()
print("Nombre de 1 par colonne :", B.sum(axis=0))

Observez la dernière ligne : les colonnes somment à 2, 3, 3 et 3, et non à 2 partout. C'est la preuve concrète qu'il ne s'agit pas de la matrice sommets × arêtes de la partie 4.

3. La projection : transformer des co-présences en réseau

Voici l'opération qui rend cette matrice utile. Deux produits matriciels suffisent :

  • C = B \, B^{T} est une matrice individus × individus : C_{ij} compte les événements que i et j ont en commun ;
  • D = B^{T} B est une matrice événements × événements : D_{ef} compte les personnes présentes à la fois en e et en f.

Autrement dit, on part de données d'affiliation, qui sont ce qu'on collecte réellement, et on en déduit un réseau interpersonnel, qui est ce qu'on veut analyser.

In [ ]:
C = B @ B.T   # individus × individus
D = B.T @ B   # événements × événements

print("C = B @ B.T  (individus × individus)")
print(personnes)
print(C)
print()

print("Nombre d'événements par personne (diagonale de C) :")
for i, personne in enumerate(personnes):
    print(f"  {personne} : {C[i, i]}")
print()

print("Co-participations (hors diagonale) :")
for i in range(len(personnes)):
    for j in range(i + 1, len(personnes)):
        if C[i, j] > 0:
            print(f"  {personnes[i]} et {personnes[j]} : {C[i, j]} événement(s) en commun")

4. Lecture sociologique

Deux choses se lisent directement dans C :

  • la diagonale donne le nombre d'événements de chacun, c'est-à-dire son degré d'engagement dans la vie collective. Emma en a 1, Claire et David en ont 3 ;
  • les valeurs hors diagonale mesurent la force du lien indirect. Claire et David partagent 3 événements : c'est le lien le plus fort du réseau.

Or, si vous remontez au graphe d'amitié du début de la séance, Claire et David ne sont reliés par aucune arête. Leur proximité est totalement invisible dans la matrice d'adjacence, et elle saute aux yeux dans la projection.

C'est exactement ce que le sociologue cherche : des liens que les acteurs ne déclarent pas, mais que leurs pratiques révèlent.

Attention à l'interprétation. Partager trois réunions ne prouve pas qu'on se connaisse, encore moins qu'on s'apprécie. La projection produit une hypothèse de lien, à confronter à d'autres sources. Deux personnes peuvent siéger dans le même conseil sans s'être jamais adressé la parole.

In [ ]:
print("D = B.T @ B  (événements × événements)")
print(evenements)
print(D)
print()
print("Nombre de participants par événement (diagonale de D) :")
for e, evenement in enumerate(evenements):
    print(f"  {evenement} : {D[e, e]}")

La matrice D se lit symétriquement : deux événements qui partagent beaucoup de participants relèvent probablement du même milieu. C'est ainsi qu'on repère, par exemple, que certaines instances recrutent dans le même vivier.

Synthèse

Concept mathématique Interprétation sociologique
1 dans la matrice d'adjacence lien direct entre deux individus
0 dans la matrice d'adjacence absence de lien
somme d'une ligne (matrice) / longueur d'une liste (liste d'adjacence) degré (popularité / nombre de connexions)
symétrie réciprocité des relations
A^2 existence de relations indirectes
densité cohésion du groupe
liste d'adjacence représentation compacte, adaptée aux réseaux creux (la plupart des réseaux sociaux réels)
matrice/liste d'incidence (sommets × arêtes) met en avant les relations elles-mêmes ; même information que l'adjacence, sous une forme plus lourde
matrice d'affiliation (individus × événements) données réellement collectées : qui participe à quoi
B B^{T} co-participations : force du lien indirect entre deux individus
B^{T} B proximité entre événements : recrutent-ils dans le même milieu ?
Représentation Taille Adaptée à
Matrice d'adjacence n × n petits réseaux, réseaux denses, calculs matriciels
Liste d'adjacence proportionnelle au nombre de liens grands réseaux creux (cas le plus fréquent)
Matrice/liste d'incidence n × m (sommets × arêtes) surtout théorique ; peu utilisée en sociologie
Matrice d'affiliation n × p (individus × événements) réseaux à deux modes : présences, adhésions, signatures

Questions de réflexion

  1. En quoi la matrice d'adjacence permet-elle de quantifier un réseau social ?
  2. Si une matrice est très dense, que peut-on en conclure sur le type de groupe étudié ?
  3. Comment pourriez-vous pondérer les liens dans cette matrice pour représenter l'intensité des relations ?
  4. Vous devez analyser le réseau de tou·tes les utilisateur·rices d'un réseau social (des millions de personnes, chacune avec quelques centaines d'ami·es en moyenne). Justifiez votre choix entre matrice et liste d'adjacence.
  5. Donnez un exemple d'enquête où vous disposeriez d'une matrice d'affiliation (individus × événements) plutôt que d'une matrice d'adjacence. Qu'apporterait la projection B B^{T} dans ce cas ?
  6. Claire et David ne sont reliés par aucune arête dans le graphe d'amitié, mais partagent trois événements. Comment interpréteriez-vous cela ? Quelles précautions prendre avant d'en conclure qu'elles et ils se connaissent ?