Quando si parla di igraph Python, si entra nel cuore dell’analisi dei network complessi. Se il tuo obiettivo è studiare relazioni, flussi, strutture e dinamiche in sistemi complessi, igraph è una libreria Python di riferimento che ti offre strumenti potenti, efficienti e altamente performanti. Non lasciarti ingannare dal prefisso i: sotto il cofano, igraph è scritto in C, garantendo velocità notevoli, essenziali quando si lavora con grafi di grandi dimensioni.
Questo articolo esplorerà come iniziare a utilizzare igraph in Python, dalla creazione dei grafi all’analisi delle loro proprietà e alla loro visualizzazione, con esempi pratici e gradualmente più complessi.
1. Installazione: Il Primo Passo
Per iniziare a lavorare con igraph, devi prima installarlo. Il modo più semplice è tramite pip:
pip install python-igraph
Potrebbe essere necessario installare anche matplotlib se intendi visualizzare i grafi direttamente all’interno di Python, dato che igraph si integra bene con esso per il plotting.
pip install matplotlib
Se usate Python 3, si può scrivere equivalentemente:
pip3 install python-igraph
pip3 install matplotlib
2. Concetti Fondamentali di igraph
Prima di immergerci nel codice, è utile ripassare alcuni concetti chiave che igraph modella:
- Grafo (Graph): La struttura dati principale, composta da vertici (nodi) e archi (collegamenti). A sua volta il grafo può essere:
- Diretto (Directed): I archi hanno una direzione (A -> B). Utile per relazioni di precedenza, dipendenza.
- Non Diretto (Undirected): I archi non hanno una direzione (A – B). Utile per amicizie, connessioni fisiche.
- Misto (con archi sia orientati che non orientati)
- Nodo / Vertice (Vertex): Gli elementi individuali nel grafo. In
igraph, sono indicizzati a partire da 0. - Collegamento / Arco (Edge): Le connessioni tra i vertici.
- Attributi (Attributes): Proprietà personalizzate che puoi assegnare a vertici e lati (es. nome di una persona per un vertice, peso per un lato, colore, etc.).
3. Creazione di Grafi
igraph offre diversi modi per costruire un grafo, a seconda dei dati che hai a disposizione.
a) Grafo Vuoto
Puoi iniziare con un grafo vuoto e aggiungere vertici e lati in seguito.
b) Aggiunta di Vertici e Lati
Dopo aver creato un grafo, potrai aggiungere elementi. Questo esempio crea il seguente grafo in formato JPG e PDF.
import igraph as ig
import matplotlib.pyplot as plt
# Costruisci un grafo con 5 vertici (persone)
n_vertices = 5
# Definiamo i lati (le relazioni tra le persone)
# Es: (0, 1) significa che la persona 0 è connessa alla persona 1
edges = [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (3, 4)]
g = ig.Graph(n_vertices, edges)
# Imposta attributi per il grafo, i nodi (persone) e i lati (relazioni)
g["titolo"] = "Piccola Rete Sociale Italiana" # Titolo del grafo
g.vs["nome"] = ["Mario Rossi", "Anna Ferrari", "Luca Bianchi", "Giulia Verdi", "Paolo Russo"] # Nomi delle persone
g.vs["genere"] = ["M", "F", "M", "F", "M"] # Genere M (Maschio) o F (Femmina)
g.es["sposati"] = [False, False, False, False, False, False, False, True] # Se la relazione è di matrimonio (tra 3 e 4)
# Imposta attributi individuali per specifiche persone o relazioni
g.vs[1]["nome"] = "Anna Rossi" # Anna Ferrari ha cambiato cognome, è ora Anna Rossi
g.es[0]["sposati"] = True # La relazione tra Mario (0) e Anna (1) è un matrimonio
# Plot in matplotlib
# Nota: gli attributi possono essere impostati globalmente (es. vertex_size),
# o impostati individualmente usando array (es. vertex_color)
fig, ax = plt.subplots(figsize=(7,7)) # Aumentiamo leggermente la dimensione per una migliore leggibilità
ig.plot(
g,
target=ax,
layout="circle", # Dispone i nodi in un layout circolare
vertex_size=30, # Dimensione dei nodi
# Colora i nodi: azzurro ("steelblue") per maschi, salmone ("salmon") per femmine
vertex_color=["steelblue" if genere == "M" else "salmon" for genere in g.vs["genere"]],
vertex_frame_width=4.0, # Larghezza del bordo del nodo
vertex_frame_color="white", # Colore del bordo del nodo
vertex_label=g.vs["nome"], # Etichette dei nodi (i nomi delle persone)
vertex_label_size=8.0, # Dimensione del font delle etichette
# Larghezza dei lati: più spessa (2) se 'sposati' è True, altrimenti normale (1)
edge_width=[3 if sposati else 1 for sposati in g.es["sposati"]], # Rendo la linea del matrimonio più evidente
# Colore dei lati: viola scuro per relazioni di matrimonio, grigio per le altre
edge_color=["#8A2BE2" if sposati else "#AAA" for sposati in g.es["sposati"]], # Viola per il matrimonio
edge_curved=0.2 # Rendi i lati leggermente curvi per maggiore chiarezza
)
plt.title(g["titolo"], fontsize=12) # Aggiungi il titolo del grafo
plt.show()
# Salva il grafo come file immagine
fig.savefig('rete_sociale_italiana.png')
fig.savefig('rete_sociale_italiana.jpg')
fig.savefig('rete_sociale_italiana.pdf')
print("\nGrafo della rete sociale italiana generato e salvato.")
# Esporta e importa un grafo come file GML (Graph Modeling Language)
# GML è un formato testuale comune per la descrizione dei grafi
g.save("rete_sociale_italiana.gml")
g_caricato = ig.load("rete_sociale_italiana.gml")
print(f"\nGrafo salvato come 'rete_sociale_italiana.gml' e ricaricato. Vertici: {g_caricato.vcount()}, Lati: {g_caricato.ecount()}")
c) Da Lista di Lati (Graph.TupleList())
Questo è un modo molto comune per creare grafi quando hai una lista di connessioni. Puoi anche includere attributi per i lati.
d) Da Matrice di Adiacenza (Graph.Adjacency())
Certamente! Procediamo con la spiegazione del metodo Graph.Adjacency() di igraph, iniziando con una breve introduzione su cosa sia una matrice di adiacenza.
Cos’è una Matrice di Adiacenza?
Una matrice di adiacenza è una rappresentazione matematica di un grafo. È una matrice quadrata (ovvero, ha lo stesso numero di righe e colonne) dove sia le righe che le colonne corrispondono ai vertici (nodi) del grafo.
- Se il grafo ha vertici, la matrice di adiacenza sarà una matrice .
- L’elemento (situato all’intersezione della riga e della colonna ) della matrice indica se esiste un lato (o arco) che connette il vertice al vertice .
Per i grafi non diretti:
- se esiste un lato tra il vertice e il vertice .
- se non esiste un lato tra il vertice e il vertice .
- La matrice è simmetrica (), poiché se c’è un lato , c’è anche un lato .
Per i grafi diretti:
- se esiste un lato che va dal vertice al vertice .
- se non esiste un lato in quella direzione.
- La matrice non è necessariamente simmetrica.
Matrici Pesate:
- Per i grafi pesati, può contenere il peso del lato anziché semplicemente o . Un valore di indica assenza di lato.
Creazione di un Grafo da Matrice di Adiacenza con Graph.Adjacency()
Il metodo Graph.Adjacency() di igraph ti permette di costruire un grafo a partire da una matrice di adiacenza fornita come una lista di liste (o un array NumPy).
Sintassi:
Python
Graph.Adjacency(adj_matrix, mode="undirected" | "directed" | "max" | "min" | "plus" | "lower" | "upper", attr=None, mapping=True)
Parametri chiave:
adj_matrix: La matrice di adiacenza, tipicamente una lista di liste di numeri interi (0 o 1 per grafi non pesati, pesi per grafi pesati).mode: Questo parametro è fondamentale per specificare comeigraphdeve interpretare la matrice, specialmente per i grafi non diretti o quando la matrice non è perfettamente simmetrica:"undirected"(default): Crea un grafo non diretto. Se la matrice non è simmetrica,igraphaggiungerà un lato se o è diverso da zero."directed": Crea un grafo diretto. Un lato esiste se è diverso da zero."max","min","plus": Utilizzati per grafi non diretti pesati. Determinano come combinare i pesi e se la matrice non è simmetrica (es.maxprende il massimo tra i due)."lower","upper": Utilizzati per grafi non diretti. Usano solo la parte inferiore o superiore della matrice (utile se la matrice è già stata pensata come simmetrica e si vuole risparmiare sul tempo di elaborazione).
attr(opzionale): Se la matrice contiene pesi, questo parametro specifica il nome dell’attributo del lato in cui verranno memorizzati i pesi (es.attr="weight").mapping: SeTrue(default),igraphcrea vertici con ID da a . SeFalse, usa direttamente i valori non zero come ID, il che è raro e va usato con cautela.
Esempio: Creazione di un Grafo Sociale da Matrice di Adiacenza
Per collegarci al tuo esempio precedente, useremo una matrice di adiacenza per rappresentare le stesse connessioni, ma senza nomi diretti nella matrice stessa (i nomi verranno aggiunti come attributi in seguito).
Matrice di Adiacenza per il tuo Grafo (5 vertici):
Consideriamo i tuoi vertici come:
- 0: Mario Rossi
- 1: Anna Rossi
- 2: Luca Bianchi
- 3: Giulia Verdi
- 4: Paolo Russo
Le tue connessioni originali erano: [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (3, 4)]
Quindi, la matrice di adiacenza (non diretta, simmetrica) sarà:
0 1 2 3 4
0: [0,1,1,1,1] (0 è connesso con 1,2,3,4)
1: [1,0,1,1,1] (1 è connesso con 0,2,3,4)
2: [1,1,0,0,0] (2 è connesso con 0,1)
3: [1,1,0,0,1] (3 è connesso con 0,1,4)
4: [1,1,0,1,0] (4 è connesso con 0,1,3)
Nota: ho basato la matrice sugli edges forniti nel tuo primo esempio.
[(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (3, 4)]
Convertiamo questa logica in una matrice:
- (0,1): 0-1, 1-0
- (0,2): 0-2, 2-0
- (0,3): 0-3, 3-0
- (0,4): 0-4, 4-0
- (1,2): 1-2, 2-1
- (1,3): 1-3, 3-1
- (1,4): 1-4, 4-1
- (3,4): 3-4, 4-3
Matrice corretta per i tuoi edges:
Indices: 0 1 2 3 4
—————-
0: [0, 1, 1, 1, 1]
1: [1, 0, 1, 1, 1]
2: [1, 1, 0, 0, 0]
3: [1, 1, 0, 0, 1]
4: [1, 1, 0, 1, 0]
Python
import igraph as ig
import matplotlib.pyplot as plt
# Definizione della matrice di adiacenza
# I valori '1' indicano una connessione, '0' l'assenza
adj_matrix = [
[0, 1, 1, 1, 1], # Connessioni di Mario (0)
[1, 0, 1, 1, 1], # Connessioni di Anna (1)
[1, 1, 0, 0, 0], # Connessioni di Luca (2)
[1, 1, 0, 0, 1], # Connessioni di Giulia (3)
[1, 1, 0, 1, 0] # Connessioni di Paolo (4)
]
# Creiamo il grafo dalla matrice di adiacenza
# Usiamo mode="undirected" perché le relazioni sociali sono tipicamente bidirezionali
g = ig.Graph.Adjacency(adj_matrix, mode="undirected")
# Impostiamo gli attributi dei vertici (persone)
g["titolo"] = "Piccola Rete Sociale Italiana (da Matrice di Adiacenza)"
g.vs["nome"] = ["Mario Rossi", "Anna Ferrari", "Luca Bianchi", "Giulia Verdi", "Paolo Russo"]
g.vs["genere"] = ["M", "F", "M", "F", "M"]
# Impostiamo gli attributi dei lati (relazioni)
# Dobbiamo sapere quali lati sono "sposati" in base alla loro posizione
# Ricordiamo che igraph assegna ID agli edge nell'ordine in cui vengono "trovati"
# per prima il lato (0,1), poi (0,2), (0,3), (0,4), (1,2), (1,3), (1,4), (3,4)
# Nel tuo esempio originale, il matrimonio era (3,4) e (0,1).
# Qui le dobbiamo gestire manualmente, dato che la matrice non ha questo attributo
married_edges = []
for edge in g.es:
# Controlliamo se la coppia di nomi corrisponde a quelle sposate
source_name = g.vs[edge.source]["nome"]
target_name = g.vs[edge.target]["nome"]
# Anna e Mario (0,1)
if (source_name == "Mario Rossi" and target_name == "Anna Ferrari") or \
(source_name == "Anna Ferrari" and target_name == "Mario Rossi"):
married_edges.append(True)
# Giulia e Paolo (3,4)
elif (source_name == "Giulia Verdi" and target_name == "Paolo Russo") or \
(source_name == "Paolo Russo" and target_name == "Giulia Verdi"):
married_edges.append(True)
else:
married_edges.append(False)
g.es["sposati"] = married_edges
# Plot in matplotlib
fig, ax = plt.subplots(figsize=(7,7))
ig.plot(
g,
target=ax,
layout="circle",
vertex_size=30,
vertex_color=["steelblue" if genere == "M" else "salmon" for genere in g.vs["genere"]],
vertex_frame_width=4.0,
vertex_frame_color="white",
vertex_label=g.vs["nome"],
vertex_label_size=8.0,
edge_width=[3 if sposati else 1 for sposati in g.es["sposati"]],
edge_color=["#8A2BE2" if sposati else "#AAA" for sposati in g.es["sposati"]],
edge_curved=0.2
)
plt.title(g["titolo"], fontsize=12)
plt.show()
# Salva il grafo come file immagine
fig.savefig('rete_sociale_italiana_adj_matrix.png')
print("\nGrafo della rete sociale italiana (da Matrice di Adiacenza) generato e salvato.")
# Esporta e importa un grafo come file GML
g.save("rete_sociale_italiana_adj_matrix.gml")
g_caricato = ig.load("rete_sociale_italiana_adj_matrix.gml")
print(f"\nGrafo salvato come 'rete_sociale_italiana_adj_matrix.gml' e ricaricato. Vertici: {g_caricato.vcount()}, Lati: {g_caricato.ecount()}")
Spiegazione delle Modifiche e Considerazioni
-
Definizione della Matrice di Adiacenza:
- Abbiamo creato una lista di liste
adj_matrixche rappresenta esplicitamente le connessioni tra i 5 vertici. L’indice di riga/colonna corrisponde all’ID del vertice (da 0 a 4). - I valori
1indicano la presenza di un lato,0l’assenza. Data la natura non diretta del grafo, la matrice è simmetrica.
- Abbiamo creato una lista di liste
-
g = ig.Graph.Adjacency(adj_matrix, mode="undirected"):- Questo è il cuore della creazione.
igraphlegge la matrice e crea i vertici (ID da 0 a 4) e i lati corrispondenti. - Il
mode="undirected"è cruciale per indicare che stiamo costruendo un grafo non diretto.
- Questo è il cuore della creazione.
-
Attribuzione degli Attributi:
- A differenza di
TupleList, la matrice di adiacenza di base non veicola direttamente nomi o attributi dei lati. Quindi, dobbiamo assegnareg.vs["nome"]eg.vs["genere"]separatamente, una volta che il grafo e i suoi vertici numerici sono stati creati. - Per l’attributo
g.es["sposati"], è necessario iterare sui lati esistenti del grafo (cheigraphha creato dalla matrice) e determinare quali di essi corrispondono alle relazioni di matrimonio, basandosi sui nomi dei vertici coinvolti. Questo è un po’ più laborioso qui perché la matrice di adiacenza “pura” non ha un concetto di “tipo di relazione” o “peso” a meno che non si usino valori diversi da 0 e 1 e l’argomentoattr.
- A differenza di
Vantaggi di Graph.Adjacency():
- Input diretto: Ideale se hai già i tuoi dati in formato matrice di adiacenza (es. da calcoli scientifici o da altri sistemi).
- Controllo esplicito: Puoi vedere esattamente quali connessioni stai definendo a livello di riga/colonna.
Svantaggi (rispetto a TupleList per alcuni casi):
- Meno intuitivo per grafi sparsi: Per grafi con pochi lati rispetto al numero di vertici (grafi sparsi), la maggior parte della matrice sarà piena di zeri, rendendola inefficiente in termini di spazio e meno immediata da leggere per gli esseri umani.
- Gestione attributi: I nomi dei vertici e gli attributi dei lati (come “sposati” o “tipo_relazione”) non sono inclusi direttamente nella matrice (a meno che non sia una matrice pesata e tu usi l’argomento
attrper un singolo attributo numerico). Richiedono un passaggio successivo per l’assegnazione.
Questo esempio mostra chiaramente come puoi costruire un grafo igraph partendo dalla sua rappresentazione matriciale, e poi arricchirlo con gli attributi necessari per l’analisi e la visualizzazione.
