Le reti di Twitter

Autori: Lorenzo Di Giulio Cesare, Nadiya Lesiv, Saverio Del Magno, Maristella Martinello, Sara Miozzi, Roberta Serra.

Lanciato nel 2006, Twitter è tra i più importanti social network e un formidabile laboratorio di analisi semantica e sociale, grazie alla possibilità di accedere a milioni di contenuti generati ogni giorno da più di 300 milioni di persone in tutto il mondo.

Metodi e strumenti

La fonte dati primaria è l’Internet Archive, in particolare un sottoinsieme dei tweet prodotti a settembre 2019 (stima del volume: circa 4 mila tweet al minuto). I dati sono organizzati in collezioni di file jsonl compressi (bzip2).

Uno script Python prodotto legge tutti i file .bz2 nella cartella di input ed estrae i dati di frequenza e co-occorrenza di hashtag e utenti (autori dei tweet, dei retweet e menzioni). Due hashtag sono collegati quando compaiono insieme nello stesso tweet. Due utenti sono collegati quando sono menzionati insieme nello stesso tweet e quando un autore menziona un altro utente nel proprio tweet. Sia i nodi (hashtag e utenti) che gli archi (hashtag-hashtag e utente-utente) sono pesati in base alla frequenza.

Nodi e archi per hashtag e utenti sono salvati separatamente in 4 file. Importandoli in Gephi si ricostruiscono le reti semantiche (hashtag) e quelle sociali (utenti) relative all’intervallo temporale selezionato. Per generare le immagini che seguono sono stati applicati opportuni filtri sia sui nodi (degree range), sia sugli archi (edge weight) per semplificare le reti. La dimensione dei nodi è proporzionale alla loro frequenza, così come lo spessore degli archi. Gli algoritmi di layout ForceAtlas2 e Fruchterman Reingold con una scelta ad hoc dei parametri hanno permesso una visualizzazione più chiara delle reti, in particolare delle componenti connesse. In alcuni casi sono state calcolate esplicitamente mediante l’applicazione dell’algoritmo di analisi Connected Components e l’applicazione di una colorazione opportuna (Appearance > Nodes > Color > Partition > Component ID). Gli orari indicati si riferiscono al fuso della costa est degli Stati Uniti.

Risultati

Roberta

Questo network rappresenta parte della distribuzione degli hashtag (nodi) e le connessioni tra essi (archi) del 30 settembre 2019 tra le 15:15 e le 15:45. Proprio in quei giorni si stavano svolgendo i Meus Prêmios Nick 2019, spesso abbreviati come #MPN, la versione brasiliana dei Nickelodeon Kids’ Choice Awards. Tra le connessioni più forti troviamo sia #Anitta, vincitrice del premio Artista preferito e gli #Uniters (fan del gruppo Now United), vincitori del premio Fandom dell’anno. Facilita, invece, risulterà essere la hit nazionale favorita. I #BTS, sempre collegati al network più forte, hanno vinto il premio per artista internazionale preferito insieme al premio per la hit internazionale preferita Boy With Luv. Altri hashtag collegati fanno riferimento ad artisti che hanno partecipato alla competizione come Ariana Grande, Pablo Vittar e Luan Santana. Il gruppo BTS risulta collegato anche all’hashtag #PCAs, acronimo di People Choice Awards.

Maristella

Lorenzo

Data la comunità semantica riportata in blu di un campione di riferimento riguardante la connessione di hashtags avvenute tra le ore 20:00 e le ore 20:30 del 30 Settembre 2019 si può evincere che si è svolto in quel periodo il festival del gruppo musicale sudcoreano GOT7, formatosi a Seul nel 2014. Il GOT7 Fan Festival “Seven Secrets” si è svolto a Bangkok nel 2019 in occasione dell’uscita degli EP I Won’t Let You Go, Spinning Top e Love Loop. Oltre a quelle già citate, tra le connessioni più significative troviamo l’hashtag #JYP che rappresenta un’etichetta discografica K-pop sudcoreana che lanciò i Got7 nel 2014 e annunciò la seconda sotto unità dei Got7 nel 2019, i Jus2, composta da JB e Yugyeom. Come si può notare, collegamenti minori sono rappresentati dai vari membri del gruppo musicale come Jin-young, Mark Tuan e Yugyeom.

Sara

La presente rappresentazione grafica mostra gli hashtag maggiormente ricorrenti nella giornata della vigilia di Natale 2018, più precisamente alle ore 19 del 24 dicembre 2018. Tra gli hashtag più evidenti possiamo notare #MerryChristmas, #VideoMTV18del18, #BLACKPINK, #JIMIN.

Saverio

In questo network sono rappresentati gli hashtag avvenuti il 25 febbraio del 2019 da 00:00 al 01:00 (fuso orario di Greenwich). Il periodo di riferimento corrisponde alla Notte degli Oscar americana. Infatti si evince un nodo di dimensione superiore corrispondente a #Oscar cui sono connessi alcuni tra i migliori film dell’Academy Awards: #GreenBook, #BohemienRhapsody, #BlackPanther. Tra i migliori attori protagonisti prevale Rami Malek, legato all’hashtags #Arab, interconnesso a vari nodi in lingua araba. Si evince, infatti, una forte componente di vincitori come l’attore Marshall Ali, migliore attore non protagonista per il film Green Book.

Nadiya

Da questa connessione di hashtags si deduce che il giorno 5 settembre, ogni anni, in India si festeggia il ‘Teacher’s Day’ per onorare gli insegnanti. Nello stesso giorno viene celebrato l’anniversario di nascita di Dr. Sarvepalli Radhakrishnan, il primo Vicepresidente ed il secondo Presidente dell’India post indipendenza, nonché noto studioso e professore.

Codice sorgente e dati

L’applicazione è scritta in Python 3, il codice sorgente è rilasciato con licenza di pubblico dominio (CC0) ed è scaricabile da Google Drive.

Uso: python main.py /path/to/bz2/directory/.

I file dati in formato compresso .bz2 sono scaricabili da Internet Archive (ecco un esempio). In output il programma genera 4 file .csv da importare in Gephi: hashtags_nodes.csv, hashtags_edges.csv, users_nodes.csv, users_edges.csv.

# Twitter hashtags and users counter

import sys
import bz2
import json
from pathlib import Path

input_directory = Path(sys.argv[1])

hashtags_nodes = {}
hashtags_edges = {}

users_nodes = {}
users_edges = {}

for input_filename in input_directory.glob("**/*.bz2"):

    with bz2.open(input_filename, "rb") as f:

        for line in f:

            tweet = json.loads(line)
            
            if "delete" in tweet:
                continue
            
            if "extended_tweet" in tweet:
                tweet_hashtags = tweet["extended_tweet"]["entities"]["hashtags"]
                tweet_mentions = tweet["extended_tweet"]["entities"]["user_mentions"]
            else:
                tweet_hashtags = tweet["entities"]["hashtags"]
                tweet_mentions = tweet["entities"]["user_mentions"]
            
            # Calcolo le occorrenze degli hashtag
            for hashtag in tweet_hashtags:
                hashtag_text = hashtag["text"]
                if hashtag_text not in hashtags_nodes:
                    hashtags_nodes[hashtag_text] = 1
                else:
                    hashtags_nodes[hashtag_text] += 1

            # Calcolo le occorrenze delle menzioni
            for mention in tweet_mentions:
                screen_name = mention["screen_name"]
                if screen_name not in users_nodes:
                    users_nodes[screen_name] = 1
                else:
                    users_nodes[screen_name] += 1

            # Calcolo le occorrenze dell'autore del tweet
            screen_name = tweet["user"]["screen_name"]
            if screen_name not in users_nodes:
                users_nodes[screen_name] = 1
            else:
                users_nodes[screen_name] += 1

            # Calcolo le occorrenze dell'autore del tweet retwittato
            if "retweeted_status" in tweet:
                screen_name = tweet["retweeted_status"]["user"]["screen_name"]
                if screen_name not in users_nodes:
                    users_nodes[screen_name] = 1
                else:
                    users_nodes[screen_name] += 1

            # Calcolo le co-occorrenze degli hashtag
            for h1 in tweet_hashtags: # Ciclo sugli hashtag: A, B, C

                for h2 in tweet_hashtags: # Ciclo sugli hashtag: A, B, C

                    if h2["text"] != h1["text"]: # Considero solo gli hashtag diversi

                        # Ordino la coppia di hashtag in ordine alfabetico
                        if h2["text"] > h1["text"]:
                            hashtags_edge_key = h1["text"] + "," + h2["text"]
                        else:
                            hashtags_edge_key = h2["text"] + "," + h1["text"]

                        if hashtags_edge_key not in hashtags_edges:
                            hashtags_edges[hashtags_edge_key] = 1
                        else:
                            hashtags_edges[hashtags_edge_key] += 1

            # Calcolo le co-occorrenze delle menzioni
            for u1 in tweet_mentions: # Ciclo sulle menzioni: A, B, C

                # Ordino la coppia autore / menzione in ordine alfabetico
                if tweet["user"]["screen_name"] > u1["screen_name"]:
                    mentions_edge_key = u1["screen_name"] + "," + tweet["user"]["screen_name"]
                else:
                    mentions_edge_key = tweet["user"]["screen_name"] + "," + u1["screen_name"]

                if mentions_edge_key not in users_edges:
                    users_edges[mentions_edge_key] = 2
                else:
                    users_edges[mentions_edge_key] += 2

                for u2 in tweet_mentions: # Ciclo sulle menzioni: A, B, C

                    if u2["screen_name"] != u1["screen_name"]: # Considero solo le menzioni diverse

                        # Ordino la coppia di menzioni in ordine alfabetico
                        if u2["screen_name"] > u1["screen_name"]:
                            mentions_edge_key = u1["screen_name"] + "," + u2["screen_name"]
                        else:
                            mentions_edge_key = u2["screen_name"] + "," + u1["screen_name"]

                        if mentions_edge_key not in users_edges:
                            users_edges[mentions_edge_key] = 1
                        else:
                            users_edges[mentions_edge_key] += 1

with open("hashtags_nodes.csv", "w") as f:
    f.write("Id,Label,Weight\n")
    for key in hashtags_nodes:
        if hashtags_nodes[key] > 1:
            try:
                f.write(key + "," + key + "," + str(hashtags_nodes[key]) + "\n")
            except:
                continue

with open("hashtags_edges.csv", "w") as f:
    f.write("Source,Target,Weight\n")
    for key in hashtags_edges:
        if hashtags_edges[key]//2 > 1:
            try:
                f.write(key + "," + str(hashtags_edges[key]//2) + "\n")
            except:
                continue

with open("users_nodes.csv", "w") as f:
    f.write("Id,Label,Weight\n")
    for key in users_nodes:
        if users_nodes[key] > 1:
            try:
                f.write(key + "," + key + "," + str(users_nodes[key]) + "\n")
            except:
                continue

with open("users_edges.csv", "w") as f:
    f.write("Source,Target,Weight\n")
    for key in users_edges:
        if users_edges[key]//2 > 1:
            try:
                f.write(key + "," + str(users_edges[key]//2) + "\n")
            except:
                continue