1
Fork 0
mirror of https://github.com/Steffo99/unimore-bda-5.git synced 2024-11-24 00:54:25 +00:00
Quinta attività di Big Data Analytics
Find a file
2023-05-09 16:40:40 +02:00
scripts Scrivi Leiden 2023-05-09 16:40:40 +02:00
README.md Scrivi Leiden 2023-05-09 16:40:40 +02:00

 Stefano Pigozzi | Grafo "creato da zero" | Tema Graph Analytics | Big Data Analytics | A.A. 2022/2023 | Unimore 

Analisi su grafo Neo4J relativo alle dipendenze delle crates del linguaggio Rust

Graph analytics

Obiettivo dellattività è analizzare il data graph di una Sandbox di Neo4j (esclusa quella vista a lezione) attraverso la definizione di almeno due research question che possano essere risolte attraverso le tecniche di graph analytics viste a lezione.

Lattività consisterà nello studio delle research question attraverso la progettazione, limplementazione e lesecuzione di almeno 4 tecniche distinte e una loro interpretazione nel contesto della Sandbox scelta.

Alcune precisazioni riguardo lattività richiesta:

  • Le Sandbox di Neo4J che possono essere usate a questo scopo sono quelle che hanno installato la Graph Data Science (GDS) Library.

  • Lattività di progettazione consisterà

    1. nella definizione delle proiezioni che saranno memorizzate in named graph, Almeno una proiezione dovrà essere una Cypher Projection;
    2. nella scelta degli algoritmi. In questa fase, si farà uso delle funzioni di memory estimation.
  • Le tecniche potranno essere implementate sia usando gli algoritmi di GDS messi a disposizione da Neo4J sia attraverso lesecuzione di query Cypher. Le tecniche implementate non dovranno essere già presenti nella Sandbox.

Il risultato dellattività sarà un documento contenente

  • una breve descrizione della Sandbox scelta, dello schema del grafo analizzato e delle principali caratteristiche;
  • una descrizione delle research question e della soluzione proposta inclusa la progettazione delle tecniche proposte che dovrà essere adeguatamente giustificata;
  • il codice delle query eseguite sulla Sandbox, i risultati ottenuti e linterpretazione dei risultati ottenuti che rappresenteranno le risposte alle research question.

Le attività verranno valutate sulla base dei seguenti criteri:

  • storytelling: la Sandbox è ben descritta? le research question proposte sono adeguate alle caratteristiche del grafo analizzato?
  • progettazione della graph analytics e analisi dei risultati:
  • Le proiezioni e gli algoritmi individuati sono adeguati in termini di correttezza e completezza a rispondere alle research question?
  • Linterpretazione dei risultati risponde alle research quesion?
  • complessità dellimplementazione

Corrispondenza

[...] Ho installato la Graph Data Science library sul grafo che ho realizzato per la precedente attività, ed adesso sarei interessato a svolgere l'attività di Graph Analytics su di esso.

È un'opzione prevista dalla consegna, oppure non è consentita?

Si è consentita ma la dimensione del grafo deve essere sufficiente per fare Graph Analytics. 

Sinossi

Si sono effettuate ricerche di Graph Analytics sul database a grafo dell'indice Crates.io, realizzato per il progetto a tema Neo4J, determinando le crate più importanti all'ecosistema attraverso gli algoritmi di Degree Centrality, Betweenness Centrality, e PageRank, e ricercando community di tag per migliorare la classificazione delle crate nell'indice attraverso gli algoritmi di Louvain, Label Propagation, e Leiden.

Introduzione

Per informazioni su cosa è una crate in Rust, come è formata, o come è stato costruito il dataset utilizzato, si veda l'introduzione della relazione del progetto a tema Neo4J.

All'interno di questa relazione si esplorano due diverse research questions, marcate rispettivamente con i simboli 1 e 2.

1 Quali sono le crates più importanti dell'ecosistema Rust?

Un'informazione utile da sapere per gli sviluppatori del linguaggio Rust e per i manutentori dell'indice Crates.io sono i nomi delle crate più importanti nell'indice.

Alcuni esempi di casi in cui il dato di importanza delle crate potrebbe essere utile sono:

  • selezionare anticipatamente le crate su cui effettuare caching più aggressivo
  • determinare le crate più a rischio di supply chain attack
  • prioritizzare determinate crate nell'esecuzione di esperimenti con crater

Lo scopo di questa ricerca è quello di determinare, attraverso indagini sulla rete di dipendenze, un valore di importanza per ciascuna crate, e una classifica delle 10 crate più importanti dell'indice.

2 Quali potrebbero essere altre categories utilizzabili per classificare crate?

Affinchè le crate pubblicate possano essere utilizzate, non è sufficiente che esse vengano indicizzate: è necessario anche che gli sviluppatori che potrebbero farne uso vengano al corrente della loro esistenza.

Nasce così il problema della discoverability, ovvero di rendere più facile possibile per gli sviluppatori le migliori crate con le funzionalità a loro necessarie.

A tale fine, Crates.io permette agli autori di ciascuna crate di specificare fino a 5 keyword (brevi stringhe arbitrarie alfanumeriche, come logging o serialization) per essa, attraverso le quali è possibile trovare la crate tramite funzionalità di ricerca del sito, e fino a 5 category (chiavi predefinite in un apposito thesaurus, come Aerospace :: Unmanned aerial vehicles), che inseriscono la crate in raccolte tematiche sfogliabili.

Lo scopo di questa ricerca è quello di determinare, attraverso indagini sulle keyword, nuove possibili category da eventualmente introdurre nell'indice, ed eventualmente sperimentare un metodo innovativo per effettuare classificazione automatica delle crate.

Struttura del progetto

Il progetto è organizzato nelle seguenti directory:

  • README.md: questo stesso file
  • scripts/: le query presenti in questa relazione come file separati, per una più facile esecuzione

Prerequisiti

Si è scelto di utilizzare un clone del DBMS Neo4J gestito da Neo4J Desktop del progetto precedente.

Neo4J Desktop (1.5.7)

Per effettuare il clone del DBMS, è stato sufficiente aprire il menu ··· del DBMS originale e cliccare l'opzione Clone presente al suo interno.

Graph Data Science Library (2.3.3)

Per installare la Graph Data Science Library, si è cliccato sul nome del database clonato, si ha selezionato la scheda Plugins, aperto la sezione Graph Data Science Library, e infine premuto su Install.

Concetti

Graph Catalog

La Graph Data Science Library non è in grado di operare direttamente sul grafo, ma opera su delle proiezioni di parti di esso immagazzinate effimeramente all'interno di uno storage denominato Graph Catalog, al fine di permettere agli algoritmi di operare con maggiore efficienza su un sottoinsieme mirato di elementi del grafo.

Esistono vari modi per creare nuove proiezioni, ma all'interno di questa relazione ci si concentra su due di essi, ovvero le funzioni Cypher:

  • gds.graph.project.cypher (anche detta Cypher projection), che crea una proiezione a partire da due query Cypher, suggerita per il solo utilizzo in fase di sviluppo in quanto relativamente lenta
  • gds.graph.project (anche detta native projection), che crea una proiezione a partire dai label di nodi ed archi, operando direttamente sui dati grezzi del DBMS, ottenendo così un'efficienza significativamente maggiore e offrendo alcune funzionalità aggiuntive

Il Graph Catalog viene svuotato ad ogni nuovo avvio del DBMS Neo4J; si richiede pertanto di fare attenzione a non interrompere il processo del DBMS tra la creazione di una proiezione e l'esecuzione di un algoritmo su di essa.

Modalità d'uso

Analisi

1 Realizzazione della Graph Projection Cypher

Si utilizza un approccio bottom-up per la costruzione della graph projection delle crate e delle loro dipendenze.

Determinazione dei nodi partecipanti

Si usa la seguente query triviale per determinare i codici identificativi dei nodi che partecipano all'algoritmo:

// Trova tutti gli id dei nodi con il label :Crate
MATCH (a:Crate)
RETURN id(a) AS id
id
0
1
2

Determinazione degli archi partecipanti

Si costruisce invece una query più avanzata per interconnettere all'interno della proiezione i nodi in base alle dipendenze della loro versione più recente:

// Trova tutte le versioni delle crate
MATCH (a:Crate)-[:HAS_VERSION]->(v:Version)
// Metti in ordine le versioni utilizzando l'ordine lessicografico inverso, che corrisponde all'ordine del versionamento semantico (semver) dalla versione più recente alla più vecchia
WITH a, v ORDER BY v.name DESC
// Per ogni crate, crea una lista ordinata contenente tutti i nomi delle relative versioni, ed estraine il primo, ottenendo così il nome della versione più recente
WITH a, collect(v.name)[0] AS vn
// Utilizzando il nome trovato, determina il nodo :Version corrispondente ad essa, e le crate che la contengono
MATCH (a:Crate)-[:HAS_VERSION]->(v:Version {name: vn})-[:DEPENDS_ON]->(c:Crate)
// Restituisci gli id dei nodi sorgente e destinazione
RETURN id(a) AS source, id(c) AS target
source target
98825 21067
98825 16957
22273 21318

Creazione della Graph Projection

Si combinano le due precedenti query in una chiamata a gds.graph.project.cypher:

CALL gds.graph.project.cypher(
	"deps",
	"MATCH (a:Crate) RETURN id(a) AS id",
	"MATCH (a:Crate)-[:HAS_VERSION]->(v:Version) WITH a, v ORDER BY v.name DESC WITH a, collect(v.name)[0] AS vn MATCH (a:Crate)-[:HAS_VERSION]->(v:Version {name: vn})-[:DEPENDS_ON]->(c:Crate) RETURN id(a) AS source, id(c) AS target"
) YIELD
	graphName,
	nodeCount,
	relationshipCount,
	projectMillis
graphName nodeCount relationshipCount projectMillis
"deps" 105287 537154 8272

1 Degree Centrality

Come misura di importanza più basilare, si decide di analizzare la Degree Centrality, ovvero il numero di archi entranti che ciascun nodo possiede, utilizzando la funzione gds.degree in modalità Stream per semplicità di operazione.

Prima di eseguire l'algoritmo, si stimano le risorse computazionali richieste:

CALL gds.degree.stream.estimate(
	"deps",
	{
		// Di default l'algoritmo conteggia gli archi uscenti di ciascun nodo; con questo parametro, il comportamento si inverte
		orientation: "REVERSE"
	}
) YIELD
	nodeCount, 
	relationshipCount, 
	bytesMin, 
	bytesMax, 
	requiredMemory
nodeCount relationshipCount bytesMin bytesMax requiredMemory
105287 537154 56 56 "56 Bytes"

Dato che la memoria richiesta stimata per l'esecuzione dell'algoritmo è pochissima, si procede immediatamente con l'esecuzione, e con il recupero delle 10 crate con più dipendenze entranti:

CALL gds.degree.stream(
	"deps",
	{
		// Di default l'algoritmo conteggia gli archi uscenti di ciascun nodo; con questo parametro, il comportamento si inverte
		orientation: "REVERSE"
	}
) YIELD
	nodeId,
	score
MATCH (n)
WHERE ID(n) = nodeId
RETURN n.name AS name, score, n.description AS description
ORDER BY score DESC
LIMIT 10
name score description
serde 24612.0 "A generic serialization/deserialization framework"
serde_json 16365.0 "A JSON serialization file format"
log 12134.0 "A lightweight logging facade for Rust"
tokio 11298.0 "An event-driven, non-blocking I/O platform for writing asynchronous I/Obacked applications."
clap 10066.0 "A simple to use, efficient, and full-featured Command Line Argument Parser"
rand 9993.0 "Random number generators and other randomness functionality."
thiserror 8615.0 "derive(Error)"
anyhow 8130.0 "Flexible concrete Error type built on std::error::Error"
futures 7398.0 "An implementation of futures and streams featuring zero allocations,composability, and iterator-like interfaces."
lazy_static 7118.0 "A macro for declaring lazily evaluated statics in Rust."

1 PageRank

Per ottenere una misura di importanza più elaborata, si è scelto di utilizzare PageRank, algoritmo iterativo che dà maggiore rilevanza alle crate con pochi dipendenze e molti dipendenti, utilizzando la funzione gds.pageRank.

Ancora, prima di eseguire l'algoritmo si stimano le risorse richieste:

CALL gds.pageRank.stream.estimate(
	"deps"
) YIELD
	nodeCount, 
	relationshipCount, 
	bytesMin, 
	bytesMax, 
	requiredMemory
nodeCount relationshipCount bytesMin bytesMax requiredMemory
105287 537154 2540880 2540880 "2481 KiB"

Si osserva come la quantità di memoria richiesta sia significativamente maggiore di quella richiesta dall'algoritmo di Degree Centrality, ma sempre una quantità accettabile con le risorse a disposizione dei computer moderni; dunque, si procede con l'esecuzione dell'algoritmo, sempre in modalità Stream per semplicità di uso:

CALL gds.pageRank.stream(
	"deps",
	{}
) YIELD
	nodeId,
	score
MATCH (n)
WHERE ID(n) = nodeId
RETURN n.name AS name, score, n.description AS description
ORDER BY score DESC
LIMIT 10
name score description
serde_derive 2633.874125046063 "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]"
serde 2600.440123009119 "A generic serialization/deserialization framework"
quote 1753.385696376074 "Quasi-quoting macro quote!(...)"
proc-macro2 1547.702293697151 "A substitute implementation of the compiler's proc_macro API to decouple token-based libraries from the procedural macro use case."
trybuild 1452.1162055975733 "Test harness for ui tests of compiler diagnostics"
rand 1108.4777776061019 "Random number generators and other randomness functionality."
syn 1047.3719317086066 "Parser for Rust source code"
rustc-std-workspace-core 997.5769831539209 "Explicitly empty crate for rust-lang/rust integration"
serde_json 885.3755595284102 "A JSON serialization file format"
criterion 845.3984645777582 "Statistics-driven micro-benchmarking library"

2 Realizzazione della Graph Projection nativa

Non essendo possibile creare grafi non diretti con la funzione gds.graph.project.cypher, si ricorre alla più efficiente ma complessa gds.graph.project, che invece supporta la funzionalità specificando orientation: "UNDIRECTED".

Creazione di collegamenti aggiuntivi nel grafo

gds.graph.project necessita che gli archi da proiettare siano già presenti all'interno del grafo principale; pertanto, si crea una nuova relazione :IS_RELATED_TO tra i nodi :Keyword presenti all'interno di ogni stessa crate:

MATCH (a:Keyword)<-[:IS_TAGGED_WITH]-(c:Crate)-[:IS_TAGGED_WITH]->(b:Keyword)
CREATE (a)-[:IS_RELATED_TO]->(b)

Creazione della Graph Projection

Si effettua poi la chiamata a gds.graph.project:

CALL gds.graph.project(
	// Crea una proiezione chiamata "kwds"
	"kwds",
	// Contenente i nodi con il label :Keyword
	"Keyword",
	// E gli archi con il label :IS_RELATED_TO, considerandoli come non-diretti
	{IS_RELATED_TO: {orientation: "UNDIRECTED"}}
) YIELD
	graphName,
	nodeCount,
	relationshipCount,
	projectMillis
graphName nodeCount relationshipCount projectMillis
"kwds" 24042 1075328 197

2 Label Propagation

Per la classificazione delle keyword, si sceglie di usare inizialmente l'algoritmo di Label Propagation attraverso la funzione gds.labelPropagation, in grado di identificare i singoli gruppi di nodi connessi densamente.

Funzionamento dell'algoritmo

L'algoritmo iterativo di Label Propagation:

  1. inizializza tutti i nodi con un label univoco
  2. per ogni iterazione:
    1. ogni nodo cambia il proprio label a quello posseduto dalla maggioranza dei propri vicini
    2. in caso di pareggio tra due o più label, ne viene selezionato deterministicamente uno arbitrario
    3. se nessun nodo ha cambiato label in questa iterazione, termina l'algoritmo
    4. se è stato raggiunto il numero massimo consentito di iterazioni, termina l'algoritmo

Esecuzione della query

Come effettuato per ogni analisi, si stimano le risorse necessarie all'esecuzione dell'algoritmo:

CALL gds.labelPropagation.write.estimate(
	"kwds",
	{
		maxIterations: 1000,
		writeProperty: "communityLabelPropagation" 
	}
) YIELD
	nodeCount, 
	relationshipCount, 
	bytesMin, 
	bytesMax, 
	requiredMemory
nodeCount relationshipCount bytesMin bytesMax requiredMemory
24042 1075328 194392 2291032 "[189 KiB ... 2237 KiB]"

Si osserva che la quantità di memoria richiesta per l'esecuzione di questo algoritmo è variabile, ma comunque ben contenuta all'interno delle capacità di elaborazione di qualsiasi computer moderno.

Si procede con l'esecuzione, questa volta in modalità Write, in modo da poter effettuare in seguito query sul grafo per poter filtrare le keyword in base al label a loro assegnato, salvato nella proprietà communityLabelPropagation:

CALL gds.labelPropagation.write(
	"kwds",
	{
		maxIterations: 1000,
		writeProperty: "communityLabelPropagation" 
	}
) YIELD
	preProcessingMillis,
	computeMillis,
	writeMillis,
	postProcessingMillis,
	nodePropertiesWritten,
	communityCount,
	ranIterations,
	didConverge,
	communityDistribution
preProcessingMillis computeMillis writeMillis postProcessingMillis nodePropertiesWritten communityCount ranIterations didConverge communityDistribution
1 760 437 48 24042 2335 15 true {p99: 15, min: 1, max: 17596, mean: 10.296359743040686, p90: 5, p50: 2, p999: 204, p95: 6, p75: 3}

Si osserva che l'algoritmo è giunto a convergenza.

Campionamento delle community

Si individuano gli identificatori dei label rimasti al termine dell'algoritmo:

MATCH (k:Keyword)
RETURN collect(DISTINCT k.communityLabelPropagation)
[129222, 107005, 129156, 105304, 105306, 105308, 105324, 114977, 105426, 128851, 105318, 105320, 105322, 105323, 105327, 120684, 105332, 105334, 126341, 105368, 129138, 118398, 109137, 105355, 105356, 124270, 105604, 105365, 114271, 105373, 126354, 113751, 105424, 109885, 105448, 111400, 105451, 105457, 105749, 105576, 105470, 105472, 105511, 105485, 106226, 105501, 105504, 114487, 105509, 105513, 109628, 105527, 105525, 105534, 110378, 105537, 105541, 116341, 118387, 128674, 105552, 105553, 105559, 105572, 112844, 122130, 105600, 111384, 105607, 105609, 109518, 105675, 108964, 116475, 105883, 114270, 113524, 112223, 105652, 120156, 105940, 105659, 119274, 105664, 105783, 105671, 105677, 105679, 113515, 105686, 119232, 105699, 112831, 105835, 105867, 105745, 105754, 117345, 105763, 105853, 105768, 105770, 105773, 105777, 105792, 105798, 118282, 118449, 105802, 105805, 113140, 117045, 115822, 105820, 128736, 122383, 105830, 105839, 126956, 114811, 105846, 105857, 118830, 105989, 119738, 120607, 106049, 105885, 105892, 105983, 105996, 108047, 105910, 111844, 105913, 105916, 105919, 105921, 113265, 110097, 105944, 105953, 105956, 105960, 105962, 111858, 106574, 105971, 105976, 106164, 106005, 106009, 106011, 108487, 106018, 106162, 106038, 106028, 106132, 106032, 106151, 106143, 106044, 106056, 106191, 121385, 112153, 115790, 106079, 114003, 106101, 111372, 108095, 111111, 106223, 106535, 106701, 106121, 106129, 106130, 106145, 106155, 106208, 114594, 112437, 107874, 116998, 106195, 107502, 106201, 106212, 106213, 106216, 106218, 129061, 111023, 106232, 106235, 109977, 106246, 108654, 106262, 106267, 106276, 106271, 119497, 106288, 106583, 113328, 119823, 106314, 106315, 106596, 106326, 106482, 112396, 116625, 116405, 106341, 117254, 106346, 115131, 106350, 106635, 113349, 108271, 108001, 106377, 106382, 106385, 106388, 116190, 106667, 106393, 106552, 113087, 127965, 106553, 106424, 106442, 106431, 106432, 108202, 106466, 106460, 114620, 106470, 106633, 111166, 123807, 106497, 113604, 110939, 115840, 118735, 106510, 113627, 106516, 106527, 106520, 120189, 107189, 109061, 113230, 106564, 106825, 106578, 111423, 106595, 106599, 106608, 106673, 116760, 106617, 106625, 114580, 106639, 106927, 106648, 106649, 124291, 106665, 106672, 126561, 106687, 106814, 106688, 125017, 106843, 106708, 118834, 106746, 106720, 111170, 106727, 106767, 106739, 112901, 106742, 108121, 119305, 128824, 121482, 106777, 121663, 106792, 107534, 106806, 112496, 106976, 106993, 106824, 106830, 106944, 123867, 108072, 106872, 106838, 106851, 107223, 128486, 106859, 106862, 106863, 107057, 106884, 106890, 106894, 106895, 122399, 119794, 106915, 106917, 106931, 106932, 106936, 106942, 106945, 107092, 106967, 106968, 106987, 106974, 106992, 107598, 108539, 107008, 107131, 107013, 110952, 107021, 107022, 107027, 107031, 107033, 107156, 107853, 108953, 107068, 107073, 107074, 107075, 107081, 109528, 120025, 107111, 108583, 110250, 107115, 109543, 107163, 107188, 118416, 107329, 107966, 107204, 112071, 108126, 120289, 107976, 107230, 107246, 107235, 109164, 108368, 107291, 107281, 114867, 127281, 107671, 120350, 109436, 107351, 113414, 107304, 107306, 107308, 107314, 122721, 107320, 109364, 114767, 113506, 107364, 107379, 117294, 107389, 107392, 124187, 107402, 107406, 112493, 107408, 107417, 113076, 107434, 107440, 107581, 107448, 116485, 115100, 107460, 107637, 107498, 118926, 107643, 107512, 107515, 107742, 107633, 107620, 107549, 107552, 107647, 107557, 128348, 108981, 107572, 107594, 107596, 109372, 107609, 107619, 107765, 107626, 123070, 107631, 110484, 107641, 107644, 107654, 107691, 107658, 107923, 119806, 107900, 107679, 111500, 110088, 107690, 113194, 126067, 107701, 107710, 107711, 110015, 107726, 109718, 108220, 108937, 109328, 116791, 107758, 124785, 107764, 107767, 108176, 116538, 118184, 107836, 107806, 107810, 107930, 108868, 108002, 108469, 107850, 112020, 107885, 107894, 121088, 107910, 107947, 116481, 107944, 124095, 107962, 107960, 109152, 119162, ...]

Si campiona un label per verificarne i contenuti:

MATCH (k:Keyword { communityLabelPropagation: 129222 }) 
RETURN k.name
["urbandictionary", "json2pdf", "flickrapi", "robotstxt", "bookmarking", "gameboy-advance", ...]

Si osserva che in questa query sono contenute molte keyword relative a Internet e servizi disponibili su esso, che potrebbero individuare quindi una categoria "Internet".

Si campiona un altro label:

MATCH (k:Keyword { communityLabelPropagation: 107005 }) 
RETURN k.name
["temporary-files", "caches", "backups", "time-machine"]

Si osserva come questo label sia stato assegnato a un singolo nodo, e che quindi non fornisce informazioni particolarmente significative.

Si campiona un terzo e ultimo label:

MATCH (k:Keyword { communityLabelPropagation: 129156 }) 
RETURN k.name
["max32660", "cortex-a", "jtag", "bitbang", "msp432", "capacitive", "stm32l5xx", "stm32f072", ...]

Si osserva una community molto ben definita di label relativi all'elettronica e alla programmazione embedded, che potrebbero individuare una categoria "Electronics and embedded programming".

2 Louvain

Si effettua un approccio diverso alla community detection, ovvero quello di usare l'algoritmo Louvain (gds.louvain) per identificare i raggruppamenti che massimizzano la modularity del grafo.

Si inizia stimando le risorse necessarie all'esecuzione dell'algoritmo:

CALL gds.louvain.write.estimate(
	"kwds",
	{
		writeProperty: "communityLouvain"
	}
) YIELD
	nodeCount, 
	relationshipCount, 
	bytesMin, 
	bytesMax, 
	requiredMemory
nodeCount relationshipCount bytesMin bytesMax requiredMemory
24042 1075328 1546985 26744496 "[1510 KiB ... 25 MiB]"

La quantità di memoria massima utilizzabile continua a essere all'interno le risorse disponibili a qualsiasi calcolatore moderno, pertanto si procede all'esecuzione in modalità Write:

CALL gds.louvain.write(
	"kwds",
	{
		writeProperty: "communityLouvain" 
	}
) YIELD
	preProcessingMillis,
	computeMillis,
	writeMillis,
	postProcessingMillis,
	nodePropertiesWritten,
	communityCount,
	ranLevels,
	modularity,
	modularities,
	communityDistribution
preProcessingMillis computeMillis writeMillis postProcessingMillis nodePropertiesWritten communityCount ranLevels modularity modularities communityDistribution
0 6234 271 26 24042 1322 3 0.5470496088005096 [0.434767324919813, 0.5403286363404538, 0.5470496088005096] {p99: 688, min: 1, max: 2280, mean: 18.18608169440242, p90: 3, p50: 1, p999: 1994, p95: 4, p75: 1}

Si osserva come l'algoritmo Louvain abbia individuato poco più della metà delle community individuate dalla Label Propagation.

Campionamento delle community

Si effettuano alcuni campionamenti per verificare il contenuto delle community identificate.

Si identificano le community formate:

MATCH (k:Keyword)
RETURN collect(DISTINCT k.communityLouvain)
[2314, 9029, 23958, 23353, 22855, 17002, 23935, 24019, 23869, 23923, 17, 19, 21, 23853, 7113, 23830, 17993, 23061, 31, 33, 35, 36, 1825, 40, 13102, 45, 47, 23601, 23851, 13111, 68, 69, 78, 137, 161, 164, 170, 12058, 183, 185, 198, 939, 214, 217, 222, 226, 240, 238, 247, 250, 266, 272, 285, 22190, 320, 388, 596, 23966, 365, 372, 377, 9782, 384, 390, 399, 412, 458, 476, 481, 483, 486, 490, 505, 514, 515, 518, 7853, 533, 17096, 552, 559, 23922, 598, 623, 626, 632, 634, 657, 666, 669, 675, 6571, 684, 689, 724, 731, 875, 741, 745, 757, 769, 792, 814, 834, 842, 843, 7150, 908, 914, 925, 926, 929, 931, 945, 948, 959, 3367, 975, 980, 984, 1001, 1027, 1028, 1039, 1054, 1059, 1348, 8062, 1090, 1098, 1101, 1106, 1265, 1266, 1155, 1144, 1145, 1173, 1183, 1210, 12165, 1223, 1229, 1233, 14902, 1277, 1291, 1308, 1312, 1321, 1330, 1338, 1352, 1361, 19004, 1378, 1400, 1401, 1421, 1433, 5883, 1440, 1471, 1505, 1519, 1706, 1537, 1543, 1585, 1551, 1572, 1575, 1576, 1597, 1603, 1607, 1608, 17112, 1628, 1630, 1644, 1645, 1649, 1658, 1680, 1681, 1687, 1705, 3252, 1721, 1726, 5665, 1734, 1735, 1740, 1744, 1746, 1781, 1786, 1787, 1788, 1794, 4963, 1828, 1901, 1917, 6784, 1948, 1994, 2384, 2017, 2019, 2021, 2027, 2033, 4077, 2077, 12007, 2102, 2105, 2115, 2119, 2121, 2130, 7789, 2147, 2153, 2161, 2173, 2350, 2225, 2228, 2262, 2265, 2360, 2270, 3694, 2285, 2307, 2309, 2322, 2339, 17783, 2344, 5197, 2357, 2367, 2636, 2392, 2403, 2409, 2414, 2423, 2439, 11504, 2477, 2480, 2519, 2523, 2643, 2563, 2598, 2607, 2623, 2660, 2657, 2673, 2736, 2743, 2768, 2904, 2793, 3899, 2801, 2824, 10288, 2873, 2916, 2935, 2939, 2941, 2951, 2952, 2977, 3059, 3014, 3015, 3019, 3023, 3024, 3029, 3066, 3064, 3070, 3106, 3107, 3109, 3133, 3284, 3158, 3171, 3176, 3186, 3195, 3201, 3203, 3227, 3265, 3272, 19583, 3307, 3308, 3341, 3347, 3364, 3365, 3374, 3396, 3410, 19941, 3418, 3431, 3444, 7510, 3573, 3450, 3480, 3489, 3537, 3538, 3542, 3558, 3560, 3563, 3590, 3607, 3628, 3639, 3664, 3687, 3690, 3702, 3710, 3731, 3736, 3766, 3770, 3790, 3802, 22181, 3846, 3851, 3855, 3868, 3879, 4186, 3906, 3922, 4187, 4836, 6585, 3940, 4020, 6534, 3964, 3979, 3989, 4006, 4017, 4033, 4055, 4125, 4133, 4159, 4171, 4164, 6587, 12973, 4184, 4195, 4216, 4220, 4230, 5098, 4239, 4257, 4259, 4265, 4269, 4272, 4276, 4288, 4291, 4297, 4304, 4326, 4322, 4324, 13037, 4335, 4344, 4340, 4349, 4364, 4368, 4374, 4377, 4379, 4399, 4405, 4422, 4424, 4433, 4450, 4452, 4463, 4464, 4466, 4480, 23964, 4971, 4499, 4500, 4502, 4519, 4531, 4569, 4579, 10688, 4602, 4622, 4627, 7531, 5047, 4653, 4685, 4699, 4701, 4710, 4748, 4747, 4774, 4795, 5304, 4946, 4849, 4873, 4869, 4889, 4904, 4915, 4917, 4920, 4930, 4940, 4952, 6376, 4976, 4977, 4982, 5021, 5028, 5040, 5068, 5092, 5101, 5112, 5113, 5117, 10746, 5137, 5160, 5200, 5181, 5195, 5196, 5300, 5269, 5278, 5282, 5288, 5290, 5294, 5561, 5338, 5339, 5368, 5656, 5483, 5428, 5448, 5468, 5482, 5496, 5517, 5520, 6798, 6596, 17476, 5564, ...]

Si campiona la prima community restituita dalla query precedente:

MATCH (k:Keyword { communityLouvain: 2314 }) 
RETURN k.name
["gameboy-advance", "intel-8080", "sm83", "textureatlas", "rocketleaguestats", "minesweeper", "rpg-maker", "ducktyping", ...]

Si osserva che questo campione contiene numerose keyword relative a videogiochi, e in particolare a architetture di calcolo, strutture dati, e algoritmi di grafica 2D e 3D utilizzati in essi: si identificano pertanto tre categorie, "Videogames :: Emulation", "Videogames :: Data structures" e "Videogames :: Graphics".

Si campiona la seconda community restituita:

MATCH (k:Keyword { communityLouvain: 9029 }) 
RETURN k.name
["unittest", "snapshot-testing", "wycheproof", "stress", "nouns", "parameterisation", ...]

Si osserva che questo campione è quasi interamente relativo allo sviluppo ed esecuzione di test su codice Rust: si identifica pertanto una categoria "Testing".

2 Leiden

Si prova a ridurre il rumore presente nelle community individuate dall'algoritmo Louvain attraverso l'utilizzo dell'algoritmo Leiden (gds.beta.leiden), che periodicamente separa le community individuate in community più piccole ma meglio connesse.

Si stimano ancora una volta le risorse necessarie:

CALL gds.beta.leiden.write.estimate(
	"kwds",
	{
		writeProperty: "communityLeiden"
	}
) YIELD
	nodeCount, 
	relationshipCount, 
	bytesMin, 
	bytesMax, 
	requiredMemory
nodeCount relationshipCount bytesMin bytesMax requiredMemory
24042 1075328 6913840 29249392 "[6751 KiB ... 27 MiB]"

Benchè rientrino nettamente nella capacità di memoria di un computer moderno, si osserva come l'algoritmo Leiden richieda un po' più di memoria rispetto all'algoritmo Louvain.

Si procede all'esecuzione, ancora una volta in modalità Write:

CALL gds.beta.leiden.write(
	"kwds",
	{
		writeProperty: "communityLeiden" 
	}
) YIELD
	preProcessingMillis,
	computeMillis,
	writeMillis,
	postProcessingMillis,
	nodePropertiesWritten,
	communityCount,
	ranLevels,
	modularity,
	modularities,
	communityDistribution
preProcessingMillis computeMillis writeMillis postProcessingMillis nodePropertiesWritten communityCount ranLevels modularity modularities communityDistribution
0 419 158 11 24042 1317 5 0.5473613732553853 [0.44177738280624534, 0.48671179504746837, 0.5396602348474452, 0.546111627553381, 0.5473613732553853] {p99: 695, min: 1, max: 2271, mean: 18.255125284738043, p90: 3, p50: 1, p999: 1925, p95: 4, p75: 1}

Si osserva come il numero di community individuate dall'algoritmo Leiden siano molto simili al numero di community individuate dall'algoritmo Louvain.

Campionamento delle community

Come nei due casi precedenti, si effettua il campionamento delle community individuate, questa volta attraverso il parametro communityLeiden:

MATCH (k:Keyword)
RETURN collect(DISTINCT k.communityLeiden)
[270, 355, 613, 293, 139, 91, 98, 800, 120, 308, 53, 54, 55, 913, 93, 121, 115, 56, 57, 58, 59, 137, 60, 61, 87, 62, 173, 97, 1126, 63, 64, 65, 403, 318, 66, 67, 68, 69, 262, 70, 71, 72, 73, 74, 75, 76, 77, 891, 78, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 1077, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 880, 247, 248, 636, 249, 805, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 683, 469, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 461, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 652, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 197, 733, 734, 735, 736, 737, 738, 739, 740, 741, 400, 742, 1079, 743, 744, 979, 567, 980, 146, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 806, 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 459, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1184, 1319, 449, 450, 484, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 378, 755, 756, 757, 758, 759, 760, 632, 1014, 0, 1, 2, 1091, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 345, 15, 16, 17, 18, 19, 20, 21, 215, 22, 23, 24, 781, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 1333, 45, 46, 47, 48, 49, 50, 51, 52, 79, 80, 81, 82, 83, 84, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 1092, 869, 870, 871, 872, 873, 874, 875, 876, 1211, 424, 1212, 1213, 1214, 570, 1215, 1216, 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 444, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236, 1237, 1238, 511, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1023, 618, 1248, 1249, 467, 1250, 1251, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 470, 797, 939, 940, 941, 942, 943, 1042, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 1120, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 376, 970, 766, 971, 972, 973, 974, 975, 1034, 976, 977, 978, 213, 1165, 1166, 1167, 580, 1168, 1169, 1170, 286, 1171, 1172, 1173, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 987, 1022, 189, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 451, 820, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1043, 1044, 1045, 1046, 1047, 1048, 988, 1049, 1050, 1051, 986, 981, 899, 982, 983, 984, 985, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 314, 471, 1204, 1002, 166, 1003, 1004, 1005, 1006, ...]

Si campiona la prima community restituita:

MATCH (k:Keyword { communityLeiden: 270 }) 
RETURN k.name
["curve", "rad", "vertex-attribute", "quakeworld", "pizza", "ffxv", "voronoi_diagram" ...]

Si osserva che questa community è molto simile in termini di contenuti alla prima community individuata dall'algoritmo Louvain; essendo così tanto simili, non si è in grado di determinare qualitativamente se il rumore è inferiore o superiore.

Si campiona quindi la seconda community:

MATCH (k:Keyword { communityLeiden: 355 }) 
RETURN k.name
["temporary-files", "recycle", "libfuse", "swarm", "createprocessexw", "time-tracker", "yaahc", ...]

Si osserva che questo campione contiene varie keyword relative a filesystem e chiamate di sistema; si individua pertanto la categoria "Foreign function interface :: Operating system calls".

Non si notano variazioni qualitative nel rumore presente all'interno della categoria rispetto all'algoritmo Louvain.