--- title: "Getting Started with neo2R and Neo4j Aura" author: "[Patrice Godard](mailto:patrice.godard@gmail.com?Subject=neo2R)" date: "`r format(Sys.time(), '%B %d %Y')`" always_allow_html: true output: html_document: keep_md: no number_sections: yes self_contained: yes theme: cerulean toc: yes toc_float: yes pdf_document: toc: yes number_sections: yes package: neo2R (version `r packageVersion('neo2R')`) vignette: > %\VignetteIndexEntry{Getting Started with neo2R and Neo4j Aura} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: chunk_output_type: console --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.path = "man/figures/README-", echo = TRUE ) ``` # Introduction Graph databases excel at storing and traversing highly connected data used for recommendation engines, fraud detection, knowledge graphs, and social networks. **Neo4j** is one of the most widely used graph databases, and with **Neo4j Aura**, its managed cloud service, you can spin up a production-grade instance without any infrastructure overhead. The [neo2R](https://cran.r-project.org/package=neo2R) package provides a seamless interface for querying Neo4j from R. Version 3.0.0 introduced two important improvements: 1. **Unified connection model** — a single `startGraph()` call handles both self-hosted Neo4j instances (`http://localhost:7474`) and cloud Neo4j Aura instances (`https://.databases.neo4j.io`) 2. **httr2 backend** — migrated from the deprecated `httr` package to [`httr2`](https://httr2.r-lib.org/), providing reliable retries and clean error handling This vignette demonstrates how to connect to Neo4j Aura, explore the Movie Recommendations dataset, and visualize networks using neo2R. # Prerequisites The core functionality of neo2R requires only the package itself. Some examples in this vignette use {dplyr} and {visNetwork} packages for data manipulation and visualization: ```{r, eval = FALSE} # Optional packages used in examples # install.packages(c("dplyr", "visNetwork")) library(dplyr) library(visNetwork) ``` ```{r, include = FALSE} # Load required packages - suppress messages for cleaner output suppressPackageStartupMessages({ library(neo2R) library(dplyr) library(visNetwork) }) ``` # Connecting to Neo4j Aura ## Create and Connect to an Aura Instance Neo4j provides a **free Aura Free** tier (up to 200k nodes / 400k relationships). Create a free instance at and get your connection details. Connect to your instance with `startGraph()`. neo2R automatically detects Aura URLs (those ending with `.databases.neo4j.io`) and selects the [Query API v2](https://neo4j.com/docs/query-api/current/) — no extra configuration is needed: ```{r, eval = FALSE} library(neo2R) my_aura <- startGraph( url = "https://.databases.neo4j.io", database = "INSTANCEID", username = "INSTANCEID", password = "INSTANCEPASSWORD" # api = "v2" is set automatically for *.databases.neo4j.io URLs ) ``` ## The Movie Recommendations Dataset Neo4j provides [example datasets](https://neo4j.com/docs/getting-started/appendix/example-data/). The Movie Recommendations dataset is a classic example available on a demo server: ```{r, eval = FALSE} library(neo2R) graph <- startGraph( url = "https://demo.neo4jlabs.com:7473", database = "recommendations", username = "recommendations", password = "recommendations" ) ``` # Exploring the Database Schema The Movie database contains nodes representing movies, people (actors and directors), genres, and users, connected by relationships that capture who acted in which movies, who directed them, their genres, and user ratings. Let's first connect to the demo database and examine its structure: ```{r, include = FALSE} library(neo2R) graph <- tryCatch( { startGraph( url = "https://demo.neo4jlabs.com:7473", database = "recommendations", username = "recommendations", password = "recommendations" ) }, error = function(e) NULL ) # Check if connection succeeded if (is.null(graph)) { message( "Could not connect to demo database. Examples will show code but not execute." ) should_run <- FALSE } else { should_run <- TRUE } ``` ## Node and Relationship Types The Movie Recommendations database includes the following node labels and relationship types: ### Node Labels | Node label | Key properties | |------------|-----------------| | Movie | title, released, imdbId | | Genre | name | | Person | name, born, imdbId | | User | name | ### Relationship Types | Relationship type | Key properties | |-------------------|-----------------| | IN_GENRE | | | ACTED_IN | role | | DIRECTED | | | RATED | rating, timestamp | ## Counting Database Elements Let's count the number of each node label and relationship type (filtering out technical nodes): ```{r, eval = should_run} # Node types and counts node_counts <- cypher( graph, " MATCH (n) RETURN labels(n) AS label, count(n) AS n ORDER BY n DESC " ) # Filter out technical nodes node_counts_filtered <- node_counts[ node_counts$label != "_Bloom_Perspective_" & node_counts$label != "_Bloom_Scene_" & node_counts$label != "", ] print(node_counts_filtered) # Relationship types and counts rel_counts <- cypher( graph, " MATCH ()-[r]->() RETURN type(r) AS type, count(r) AS n ORDER BY n DESC " ) # Filter out technical relationships rel_counts_filtered <- rel_counts[rel_counts$type != "_Bloom_HAS_SCENE_", ] print(rel_counts_filtered) ``` # Querying with Cypher ## Top Prolific Actors Find the actors who have appeared in the most movies: ```{r, eval = should_run} top_actors <- cypher( graph, " MATCH (p:Person)-[:ACTED_IN]->(m:Movie) RETURN p.name AS actor, count(m) AS movies ORDER BY movies DESC LIMIT 10 " ) print(top_actors) ``` ## Movies and Their Directors Retrieve movies with their release years and directors: ```{r, eval = should_run} movies_directors <- cypher( graph, " MATCH (d:Person)-[:DIRECTED]->(m:Movie) RETURN m.title AS movie, m.released AS released, d.name AS director ORDER BY m.released IS NOT NULL DESC, m.released DESC LIMIT 10 " ) print(movies_directors) ``` ## Parameterized Queries neo2R supports **named parameters**, keeping queries safe from injection and easy to reuse: ```{r, eval = should_run} # Find all co-stars of a given actor co_stars <- cypher( graph, " MATCH (a:Person {name: $actor})-[:ACTED_IN]->(m:Movie)<-[:ACTED_IN]-(co:Person) RETURN DISTINCT co.name AS co_star, m.title AS shared_movie ORDER BY co_star LIMIT 10 ", parameters = list(actor = "Tom Hanks") ) print(co_stars) ``` # Graph Result Format The `cypher()` function can return results in different formats. By default, it returns a data frame with rows. For queries that return nodes, relationships, and paths, use `result = "graph"`: ```{r, eval = should_run} # Graph result: co-actors of Tom Hanks co_actors <- cypher( graph, 'MATCH p=(tom:Person {name:"Tom Hanks"})-[mt:ACTED_IN]-> (m:Movie)<-[mca:ACTED_IN]-(coActor:Person) RETURN p', result = "graph" ) # The result contains nodes, relationships and paths lengths(co_actors) # Extract node names from the first few nodes node_names <- co_actors$nodes |> head() |> lapply(function(n) { if (!is.null(n$properties$name)) { n$properties$name } else if (!is.null(n$properties$title)) { n$properties$title } else { NA_character_ } }) print(node_names) ``` # Network Visualization with visNetwork The real power of a graph database becomes apparent when you visualize the graph. This section shows how to pull Tom Hanks's ego network and render it with **visNetwork**. This example uses the `dplyr` and `visNetwork` packages. ## Step 1: Fetch Nodes and Edges ```{r, eval = should_run} hub <- "Tom Hanks" nodes_raw <- cypher( graph, " MATCH (hub:Person {name: $hub})-[hr:ACTED_IN]->(m:Movie) <-[cr:ACTED_IN]-(co:Person) RETURN hub.name AS hub, hr.role AS hub_role, m.title AS movie, m.released AS year, co.name AS co, cr.role AS co_role ", parameters = list(hub = hub) ) |> as_tibble() print(nodes_raw) ``` ## Step 2: Shape Data for visNetwork visNetwork expects two data frames: `nodes` (with columns `id`, `label`, `group`, etc.) and `edges` (with columns `from`, `to`, etc.). ```{r, eval = should_run} nodes <- bind_rows( nodes_raw |> distinct( id = hub, group = "Hub" ), nodes_raw |> distinct( id = co, group = "Co-star" ), nodes_raw |> distinct( id = movie, group = "Movie", year ) ) |> distinct() |> mutate( title = sprintf( '%s: %s%s', group, id, ifelse(!is.na(year), sprintf(" (%s)", year), "") ), shape = ifelse(group == "Movie", "dot", "star"), size = ifelse(group == "Hub", 30, 18) ) |> arrange(id) edges <- bind_rows( nodes_raw |> distinct( from = hub, to = movie, role = hub_role ), nodes_raw |> distinct( from = co, to = movie, role = co_role ) ) |> mutate( title = sprintf('Role: %s', role), arrows = "to" ) print(nodes) print(edges) ``` ## Step 3: Draw the Network ```{r, eval = should_run, fig.height = 8, fig.width = 10} visNetwork(nodes, edges) |> visGroups( groupname = "Hub", color = list( background = "#3B82F6", border = "#1D4ED8", highlight = "#93C5FD" ) ) |> visGroups( groupname = "Movie", color = list( background = "#F97316", border = "#C2410C", highlight = "#FED7AA" ), shape = "square" ) |> visGroups( groupname = "Co-star", color = list( background = "#6B7280", border = "#374151", highlight = "#D1D5DB" ) ) |> visEdges( color = list(color = "#CBD5E1", highlight = "#3B82F6"), width = 1.5 ) |> visOptions( highlightNearest = list(enabled = TRUE, degree = 1, hover = TRUE), nodesIdSelection = TRUE ) |> visLayout(randomSeed = 42) |> visPhysics( solver = "forceAtlas2Based", forceAtlas2Based = list( gravitationalConstant = -60, springLength = 120, springConstant = 0.04 ) ) |> visLegend(position = "right", main = "Node type") ``` Hover over any node to see its label. Use the **Select by id** dropdown or click a node to highlight movies shared with Tom Hanks. ## Further Reading - [Neo4j Aura console](https://console.neo4j.io) — create your free instance - [Neo4j Cypher reference](https://neo4j.com/docs/cypher-manual/current/) — query language docs - [visNetwork documentation](https://datastorm-open.github.io/visNetwork/) — interactive network visualization