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:
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.
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.
import igraph as ig
import matplotlib.pyplot as plt
# Definiamo le relazioni direttamente con i nomi delle persone,
# e aggiungiamo un attributo ‘tipo_relazione’ per ogni lato.
# Ogni tupla è (sorgente, destinazione, tipo_relazione)
relazioni_con_nomi = [
(“Mario Rossi”, “Anna Rossi”, “coniugi”),
(“Mario Rossi”, “Luca Bianchi”, “amici”),
(“Mario Rossi”, “Giulia Verdi”, “colleghi”),
(“Mario Rossi”, “Paolo Russo”, “vicini”),
(“Anna Rossi”, “Luca Bianchi”, “amici”),
(“Anna Rossi”, “Giulia Verdi”, “colleghi”),
(“Anna Rossi”, “Paolo Russo”, “conoscenti”),
(“Giulia Verdi”, “Paolo Russo”, “sposati”) # L’ultimo matrimonio è tra Giulia e Paolo
]
# Creiamo il grafo utilizzando Graph.TupleList()
# “vertex_name_attr=’nome’” indica che i nomi dei vertici saranno memorizzati nell’attributo ‘nome’
# “edge_attrs=[‘tipo_relazione’]” indica che l’ultimo elemento di ogni tupla sarà l’attributo ‘tipo_relazione’ del lato
g = ig.Graph.TupleList(relazioni_con_nomi, directed=False,
vertex_name_attr=’nome’, edge_attrs=[‘tipo_relazione’])
# Impostiamo il titolo del grafo
g[“titolo”] = “Piccola Rete Sociale Italiana (da TupleList)”
# Aggiungiamo attributi ai vertici creati automaticamente
# igraph ha già assegnato ID numerici e memorizzato i nomi
# Recuperiamo i nomi per assegnare il genere
nomi_vertici = g.vs[“nome”]
generi = []
for nome in nomi_vertici:
if”Mario”innomeor”Luca”innomeor”Paolo”innome:
generi.append(“M”)
else:
generi.append(“F”)
g.vs[“genere”] = generi
# Aggiorniamo l’attributo ‘sposati’ per i lati
# Utilizziamo l’attributo ‘tipo_relazione’ appena creato
# Rimuoviamo il vecchio attributo ‘sposati’ per ricrearlo in base al nuovo criterio
if “sposati” in g.es.attributes():
delg.es[“sposati”]
g.es[“sposati”] = [True if tipo == “sposati” or tipo == “coniugi” else False for tipo in g.es[“tipo_relazione”]]
# Plot in matplotlib
fig, ax = plt.subplots(figsize=(7,7))
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”ifgenere==”M”else”salmon”forgenereing.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 (3) se ‘sposati’ è True, altrimenti normale (1)
edge_width=[3ifsposatielse1forsposatiing.es[“sposati”]],
# Colore dei lati: viola scuro per relazioni di matrimonio, grigio per le altre
edge_color=[“#8A2BE2″ifsposatielse”#AAA”forsposatiing.es[“sposati”]],
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_tuplelist.png’)
fig.savefig(‘rete_sociale_italiana_tuplelist.jpg’)
fig.savefig(‘rete_sociale_italiana_tuplelist.pdf’)
print(“\nGrafo della rete sociale italiana (creato con TupleList) generato e salvato.”)
# Esporta e importa un grafo come file GML (Graph Modeling Language)
g.save(“rete_sociale_italiana_tuplelist.gml”)
g_caricato = ig.load(“rete_sociale_italiana_tuplelist.gml”)
print(f”\nGrafo salvato come ‘rete_sociale_italiana_tuplelist.gml’ e ricaricato. Vertici: {g_caricato.vcount()}, Lati: {g_caricato.ecount()}”)
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 N vertici, la matrice di adiacenza sarà una matrice N×N.
- L’elemento Mij (situato all’intersezione della riga i e della colonna j) della matrice indica se esiste un lato (o arco) che connette il vertice i al vertice j.
Per i grafi non diretti:
- Mij=1 se esiste un lato tra il vertice i e il vertice j.
- Mij=0 se non esiste un lato tra il vertice i e il vertice j.
- La matrice è simmetrica (Mij=Mji), poiché se c’è un lato i−j, c’è anche un lato j−i.
Per i grafi diretti:
- Mij=1 se esiste un lato che va dal vertice i al vertice j.
- Mij=0 se non esiste un lato in quella direzione.
- La matrice non è necessariamente simmetrica.
Matrici Pesate:
- Per i grafi pesati, Mij può contenere il peso del lato anziché semplicemente 1 o 0. Un valore di 0 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:
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 come igraph deve 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, igraph aggiungerà un lato se Mij o Mji è diverso da zero.
"directed": Crea un grafo diretto. Un lato (i,j) esiste se Mij è diverso da zero.
"max", "min", "plus": Utilizzati per grafi non diretti pesati. Determinano come combinare i pesi Mij e Mji se la matrice non è simmetrica (es. max prende 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: Se True (default), igraph crea vertici con ID da 0 a N−1. Se False, 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à:
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]
Spiegazione delle Modifiche e Considerazioni
-
Definizione della Matrice di Adiacenza:
- Abbiamo creato una lista di liste
adj_matrix che rappresenta esplicitamente le connessioni tra i 5 vertici. L’indice di riga/colonna corrisponde all’ID del vertice (da 0 a 4).
- I valori
1 indicano la presenza di un lato, 0 l’assenza. Data la natura non diretta del grafo, la matrice è simmetrica.
-
g = ig.Graph.Adjacency(adj_matrix, mode="undirected"):
- Questo è il cuore della creazione.
igraph legge 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.
-
Attribuzione degli Attributi:
- A differenza di
TupleList, la matrice di adiacenza di base non veicola direttamente nomi o attributi dei lati. Quindi, dobbiamo assegnare g.vs["nome"] e g.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 (che igraph ha 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’argomento attr.
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
attr per 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.