L'objectif de cet exercice est d'analyser les liens entre Heroes et Comics Marvel grâce à la base graphe Neo4J.
Cet exercice est basé sur la version Neo4j Community Edition 4.3.7.
Voici la procédure d'installation en quelques étapes (pour Linux / MacOS) :
- Téléchargez
Neo4j Community Edition 4.3.7ici : https://neo4j.com/download-center/#community - Dézippez l'archive
neo4j-community-4.3.7-unix.tar.gz, par exemple dans le dossier$HOME/progs/neo4j-4.3.7 - Lancez la console Neo4j via la commande
$HOME/progs/neo4j-4.3.7/bin/neo4j console - Rendez-vous sur la console Neo4j dans un navigateur, à l'adresse http://localhost:7474/
- L'utilisateur par défaut est
neo4javec le mot de passeneo4j - Lors de la première utilisation de la console, vous devrez modifier le mot de passe de l'utilisateur
neo4j
- L'utilisateur par défaut est
Les objectifs sont les suivants :
- Créer le graphe des Heroes dans Neo4J, à partir des données disponibles dans le dossier
data. Les noeuds du graphe sont soit desHeroes, soit desComics. - Répondre à la question suivante : existe-t-il un
Heroqui connait deuxHeroesdifférents qui eux-mêmes ne se connaissent pas ? (unHero"connait" un autreHeros'il apparaissent tous les deux dans un mêmeComic)
- Neo4j Admin import : https://neo4j.com/docs/operations-manual/current/tutorial/neo4j-admin-import/
Il est d'abord nécessaire de préparer correctement les fichiers CSV afin de pouvoir les importer avec neo4j-admin import.
Pour cela, 3 scripts de préparation des CSV peuvent être utilisés :
yarn install
mkdir dist
node prepare-heroes.js > dist/heroes.csv
node prepare-comics.js > dist/comics.csv
node prepare-comics-heroes.js > dist/comics-heroes.csv
Ces scripts permettent notamment :
- De créer des entêtes CSV adéquates pour
neo4j-admin import. Tous les détails sont dans la documentation :- Pour les fichiers CSV des noeuds : https://neo4j.com/docs/operations-manual/current/tools/neo4j-admin/neo4j-admin-import/#import-tool-header-format-nodes
- Pour les fichiers CSV des relations : https://neo4j.com/docs/operations-manual/current/tools/neo4j-admin/neo4j-admin-import/#import-tool-header-format-rels
- De formatter correctement les valeurs des colonnes
- De supprimer les doublons (il y en a dans
comics.csvnotamment)
L'import peut donc maintenant se faire correctement avec les fichiers "préparés" :
neo4j-admin import --nodes=./dist/heroes.csv --nodes=./dist/comics.csv --relationships=./dist/comics-heroes.csv
Le résultat de l'import devrait être le suivant :
IMPORT DONE in 3s 946ms.
Imported:
40045 nodes
75257 relationships
80090 properties
Tip : la base doit être arrêtée lors de l'import, sinon l'erreur suivante risque de se produire :
org.neo4j.exceptions.UnderlyingStorageException: Unable to open store file: /Users/sebprunier/progs/neo4j/neo4j-community-4.3.7/data/databases/neo4j/neostore
at org.neo4j.kernel.impl.store.CommonAbstractStore.checkAndLoadStorage(CommonAbstractStore.java:258)
at org.neo4j.kernel.impl.store.CommonAbstractStore.initialise(CommonAbstractStore.java:156)
at org.neo4j.kernel.impl.store.NeoStores.initialize(NeoStores.java:262)
at org.neo4j.kernel.impl.store.NeoStores.createMetadataStore(NeoStores.java:537)
at org.neo4j.kernel.impl.store.StoreType$15.open(StoreType.java:148)
at org.neo4j.kernel.impl.store.NeoStores.openStore(NeoStores.java:255)
at org.neo4j.kernel.impl.store.NeoStores.getOrOpenStore(NeoStores.java:300)
at org.neo4j.kernel.impl.store.NeoStores.verifyRecordFormat(NeoStores.java:181)
at org.neo4j.kernel.impl.store.NeoStores.<init>(NeoStores.java:119)
at org.neo4j.kernel.impl.store.StoreFactory.openNeoStores(StoreFactory.java:138)
at org.neo4j.kernel.impl.store.StoreFactory.openAllNeoStores(StoreFactory.java:102)
at org.neo4j.internal.batchimport.store.BatchingNeoStores.instantiateStores(BatchingNeoStores.java:247)
at org.neo4j.internal.batchimport.store.BatchingNeoStores.createNew(BatchingNeoStores.java:203)
at org.neo4j.internal.batchimport.ParallelBatchImporter.doImport(ParallelBatchImporter.java:99)
at org.neo4j.importer.CsvImporter.doImport(CsvImporter.java:193)
at org.neo4j.importer.CsvImporter.doImport(CsvImporter.java:158)
at org.neo4j.importer.ImportCommand.execute(ImportCommand.java:256)
at org.neo4j.cli.AbstractCommand.call(AbstractCommand.java:71)
at org.neo4j.cli.AbstractCommand.call(AbstractCommand.java:34)
at picocli.CommandLine.executeUserObject(CommandLine.java:1953)
at picocli.CommandLine.access$1300(CommandLine.java:145)
at picocli.CommandLine$RunLast.executeUserObjectOfLastSubcommandWithSameParent(CommandLine.java:2352)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2346)
at picocli.CommandLine$RunLast.handle(CommandLine.java:2311)
at picocli.CommandLine$AbstractParseResultHandler.execute(CommandLine.java:2179)
at picocli.CommandLine.execute(CommandLine.java:2078)
at org.neo4j.cli.AdminTool.execute(AdminTool.java:89)
at org.neo4j.cli.AdminTool.main(AdminTool.java:67)
Caused by: org.neo4j.io.pagecache.impl.FileLockException: This file is locked by another process, please ensure you don't have another Neo4j process or tool using it: '/Users/sebprunier/progs/neo4j/neo4j-community-4.3.7/data/databases/neo4j/neostore'.'
Pour afficher tous les noeuds de type Hero :
MATCH (hero:Hero)
RETURN hero
On peut limiter le nombre de résultats, comme en SQL :
MATCH (hero:Hero)
RETURN hero
LIMIT 5
On peut rechercher un Hero par son nom :
MATCH (hero:Hero {name:"Captain America"})
RETURN hero
Des clauses WHERE peuvent être ajoutées, par exemple :
MATCH (hero:Hero)
WHERE hero.name CONTAINS "Captain"
RETURN hero
On peut rechercher les Comics d'un Hero en parcourant les relations de type APPEARED_IN. Par exemple pour Jessica Jones :
MATCH (jessica:Hero {name: "Jessica Jones"})-[:APPEARED_IN]->(comic:Comic)
RETURN jessica,comic
Pour simplifier la visualisation, il est possible d'afficher un tableau plutôt qu'un graphe en précisant (un peu comme une projection en SQL), les attributs des Comics que l'on souhaite afficher :
MATCH (jessica:Hero {name: "Jessica Jones"})-[:APPEARED_IN]->(comic:Comic)
RETURN comic.title AS Titre
Un besoin classique dans des graphes est la recherche de plus court chemin entre deux noeuds.
Ici par exemple, nous pouvons chercher les plus courts chemins entre deux Heroes, en l'occurrence Punisher et Colossus :
MATCH p=shortestPath(
(punisher:Hero {name:"Punisher"})-[:APPEARED_IN*]-(colossus:Hero {name:"Colossus"})
)
RETURN p
Ce qui nous donne le résultat suivant : Punisher et Colossus sont tous les deux présent dans le Comic intitulé Marvel Fanfare (1982) #45
Pour répondre à la question "Existe-t-il un Hero qui connait deux Heros différents qui eux-mêmes ne se connaissent pas ?", un algorithme de type "recommandation" est adatpté.
Voici un exemple de requête permettant de répondre à la question :
MATCH (hero:Hero)-[:APPEARED_IN]->(c1:Comic)<-[:APPEARED_IN]-(coHero:Hero),
(coHero)-[:APPEARED_IN]->(c2:Comic)<-[:APPEARED_IN]-(cocoHero)
WHERE NOT (hero)-[:APPEARED_IN]->()<-[:APPEARED_IN]-(cocoHero) AND hero <> cocoHero
RETURN DISTINCT hero.name, c1.title, coHero.name, c2.title, cocoHero.name
LIMIT 10
Et voici le résultat :
hero.name |
c1.title |
coHero.name |
c2.title |
cocoHero.name |
|---|---|---|---|---|
| Captain America | Cable and X-Force (2012) #9 | X-Force | Cable & X-Force: Onslaught Rising (Trade Paperback) | Risque |
| Captain America | Cable and X-Force (2012) #9 | X-Force | Cable & X-Force: Onslaught Rising (Trade Paperback) | Hellfire Club |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #1 | Wildside |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #1 | Stryfe |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #1 | Forearm |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #1 | Feral |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #1 | Boomer |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #1 | Black Tom |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #2 | Feral |
| Captain America | Cable and X-Force (2012) #9 | X-Force | X-Force (1991) #2 | Boomer |
On peut vérifier les liens entre les héros avec un algorithme de calcul de plus cours chemin, par exemple :
MATCH p=shortestPath(
(h1:Hero {name:"Captain America"})-[:APPEARED_IN*]-(h2:Hero {name:"Stryfe"})
)
RETURN p



