[Pubblichiamo una versione italiana dell’articolo Analyzing Cultural Domains with Python scritto dall’autore di Data Science con Python. La prima parte dell’articolo è apparsa lo scorso 18 aprile; la seconda, il 16 maggio.]
Nei due articoli precedenti a questo si è lavorato per realizzare un modello analizzabile del nostro dominio culturale scelto come campione: la community che su LiveJournal commenta la serie televisiva americana The Good Wife. Con Python e librerie assortite abbiamo eliminato doppioni, risolto ambiguità lessicali e semantiche e preparato una struttura dati di tipo DataFrame (come la chiama Pandas) sulla quale concentrare oggi il seguito del lavoro.
Nasce un network
La prossima fase nell’analisi di questo dominio culturale richiede la messa a punto di un network di termini: un grafo i cui nodi rappresentano termini e i segmenti (pesati) le rispettive similitudini tra essi. Potrei includere nel grafo tutti i 12.437 termini scoperti, ma alcuni di essi vengono nominati una volta o due al massimo (ce lo aspettavamo, per via della Legge di Zipf). Invece di chiedermi perché le parole meno usate siano meno usate delle parole più usate, preferisco eliminare tutte le righe con meno di dieci occorrenze, sia pure lasciando per il futuro una opzione di modifica della soglia indicata da MIN_SUPPORT. Qui sono contrariato dal fatto che Python non possieda costanti di prima classe, ma nondimeno scrivo MIN_SUPPORT tutto in maiuscolo. Il DataFrame che risulta è una versione tronca del dominio, con sole 331 righe.
MIN_SUPPORT = 10 sums = domain.sum(axis=1) limited = domain.ix[sums[sums >= MIN_SUPPORT].index]
Per definire le similitudini tra le righe (o le colonne) esistono vari modi:
- La distanza di Chebyshev;
- La correlazione di Pearson;
- Il Coseno di similitudine;
- La similitudine generalizzata.
Il primo modo fornisce solitamente risultati a grana molto grossa; l’ultimo è impegnativo da calcolare, specie con dominî molto grandi. La correlazione di Pearson (anche detta semplicemente correlazione) è ben conosciuta nonché supportata dai DataFrame. Nello sceglierla, traspongo la matrice per ottenere correlazioni tra le righe (non tra colonne) e sottraggo una matrice di identità per liberarmi delle autocorrelazioni.
words = limited.T.corr() words -= np.eye(len(words))
Il risultato contiene sia elementi positivi che negativi e mi tocca un’altra decisione dolorosa: quali convertire in segmenti pesati del grafo? L’analisi delle reti in genere ammette segmenti con pesi negativi, ma la gran parte degli algoritmi si trova molto disagio davanti a loro. Eliminerò tutti i segmenti collegano nodi più o meno dissimili. Questa impostazione del valore di soglia SLICING controlla la densità del network risultante: se il valore di soglia è troppo alto, la rete si disfa in minuscoli frammenti disgiunti; se è troppo basso, la rete diventa una palla pelosa priva di una struttura analizzabile.
SLICING = 0.25 words = words[words >= SLICING].fillna(0)
Qui entra in ballo NetworkX: un toolbox per costruire, analizzare, esportare, importare e visualizzare reti complesse. NetworkX dispone di una elegante interfaccia verso Pandas che mi consente di convertire un DataFrame in un network con una singola chiamata di funzione. Il nuovo oggetto network, words_network, ha nodi privi di etichette, che assegno usando quelle delle colonne del DataFrame (avrei potuto usare indifferentemente le etichette delle righe).
words_network = nx.from_numpy_matrix(words.values) nx.relabel_nodes(words_network, dict(enumerate(words.columns)), copy=False)
Qui arrivano i primi risultati realmente interessanti, che salvo senza esitare in un file GraphML.
if not os.path.isdir("results"): os.mkdir("results") with open("results/" + GROUP + ".graphml", "wb") as ofile: nx.write_graphml(words_network, ofile)
Se non avessi accesso a moduli Python esterni ad Anaconda, a questo punto eseguirei l’analisi del network su Pajek o Gephi. Ma ho community e nessuna paura di usarlo. Il modulo community usa il metodo di Louvain per estrarre comunità di termini dal network: gruppi di nodi connessi più strettamente del previsto tra essi che con il resto del network. In un prossimo articolo mostrerà i risultati ottenuti e tirerò le conclusioni del lavoro svolto attorno a questo script.