Neo4j first node to meet relationship in a movie model - neo4j

I have read the Neo4j manual and saw the numerous short examples regarding movie graph. I have also installed it locally and played with the cypher.
Here is the setup:
I have the following nodes: Movies (with name and id, owned by friend), Actors(with name and ids) Directors (with names and id), Genre (with id and name)
Relations are: Actors acted in Movies (1 movie - many actors), Directors directed a movie (1 director per movie but a director can direct many movies), and Movies has several genre "(many to many)
1) Owned by friend I dont know why but following the LOAD CSV example they put USA as a node rather than a property but is there a logical reason why its better to put it as a node rather than a property like i did?
2)
What I want to search is similar to the answer given to this question:
Nearest nodes to a give node, assigning dynamically weight to relationship types
However - I do not have a weight on the relationship and its more of a "go find the first give nodes connected to it"
Given that the "owned by friend" can only be owned by 1 person.
If given movie title "Spider-Man" (which for example purpose is owned by frank) go find the next occurrence of a movie that is owned by John.
So after reading Neo4j I believe that I dont need to specify which relationship is needed to traverse but just go find the next movie that meets my criteria, right?
So Following the above link
MATCH (n:Start { title: 'Spider-Man' }),
(n)-[:CONNECTED*0..2]-(x)
RETURN x
So go to node Spider-Man and go find me X as long as it is connected but I got stump by *0..2 because its the range...what if I just say "go find me the first you that means the own by John"
3) following up to #2 - how do i insert the fitler "own by john" ?

There are a number of things in your question that don't quite make sense. Here's a stab at an answer.
1) Making 'USA' a node rather than a property is useful if you want to search based on country. If 'USA' is a node, you are able to limit your search by starting at the 'USA' node. If you don't care to do this, then it doesn't really matter. It may also save a small amount of space for longer country names to store the name once and link to it via relationships.
2) Your example doesn't match your described graph. I can't really speak to it without a better example.
3) This is probably easy to answer once you improve your example.
OK. Based on the comments to the answer, here's what you need. To find one movie owned by John that is connected via common actors, directors, etc to the movie Spider-man owned by Frank (that is, sub-graphs like (movie)<--(actor)-->(movie) ) you can write:
MATCH (n:Movie {title : 'Spider-Man', owned_by : 'Frank'})<-[*2]->(m:Movie {owned_by : 'John'})
RETURN m LIMIT 1
If you want more responses, alter or remove the LIMIT on the RETURN clause. If you want to allow chains that pass through chains like (movie)<--(actor)-->(movie)<--(director)-->(movie), you can increase the number of relationships matched (the *2) to 4, 6, 8, etc. You probably shouldn't just write the relationship part of the MATCH clause as -[*]-, because this could get into infinite loops.

Related

Ne04j Graph Nodes, Expand a spanning tree but stop at specific node (but don't stop for other nodes)

I have the following setup
Person is part of an organisation
Person attends meeting
Meeting is held in a location
More than one person can attend a meeting
More than one person can be part of same organisation
Persons from different organisation can attend same meeting
Multiple meeting can be held at same location
Of all the locations, there is a very oft-used one (the home base).
Meaning that when I "Expand a spanning Tree", and when I hit that location, my graph "explodes"
Example code I use:
MATCH (p:Person {pcode: 123456})
MATCH (terminator:Location) WHERE terminator.LocCode = 1
CALL apoc.path.spanningTree(p, {
minLevel: 1,
maxLevel: 3,
terminatorNodes: terminator
})
YIELD path
RETURN path
;
My hope when using terminatorNodes is that the path would stop at that particular node and ignore everything that's "beyond" .. but that's not what happens, in actual fact I see all the nodes "beyond"
I have tried using endNodes too, but there it looks like the code bombs out as soon as it bumps into that particular node and stops spanning trees everywhere else too!
I would like to obtain the same effect for a particular organisation too (mine!) but one step at a time!
What I am really trying to achieve is to retrieve all Persons connected to a starting person via meetings.
I.e. "Start Person" A attends a meeting with another 3 people from different organisations, then I want to see those Persons returned, and their organisation, and then all the people linked to their organisation.
The above is just a start, in the sense that I have other Node labels to deal with but with the same aim.
Couldn't you use a depth search in your case?
MATCH path = (p:Person {pcode: 123456})-[:RELATIONSHIP_NAME*1..3]->(terminator:Loaction)
WHERE terminator.LocCode = 1
RETURN path

Collaborative filtering cypher with attributes in neo4j

I am using neo4j to setup a recommender system. I have the following setup:
Nodes:
Users
Movies
Movie attributes (e.g. genre)
Relationships
(m:Movie)-[w:WEIGHT {weight: 10}]->(a:Attribute)
(u:User)-[r:RATED {rating: 5}]->(m:Movie)
Here is a diagram of how it looks:
I am now trying to figure out how to apply a collaborative filtering scheme that works as follows:
Checks which attributes the user has liked (implicitly by liking the movies)
Find similar other users that have liked these similar attributes
Recommend the top movies to the user, which the user has NOT seen, but similar other users have seen.
The condition is obviously that each attribute has a certain weight for each movie. E.g. the genre adventure can have a weight of 10 for the Lord of Rings but a weight of 5 for the Titanic.
In addition, the system needs to take into account the ratings for each movies. E.g. if other user has rated Lord of the Rings 5, then his/her attributes of the Lord of Ranges are scaled by 5 and not 10. The user that has rated the implicit attributes also close to 5 should then get this movie recommended as opposed to another user that has rated similar attributes higher.
I made a start by simply recommending only other movies that other users have rated, but I am not sure how to take into account the relationships RATING and WEIGHT. It also did not work:
MATCH (user:User)-[:RATED]->(movie1)<-[:RATED]-(ouser:User),
(ouser)-[:RATED]->(movie2)<-[:RATED]-(oouser:User)
WHERE user.uid = "user4"
AND NOT (user)-[:RATED]->(movie2)
RETURN oouser
What you are looking for, mathematically speaking, is a simplified Jaccard index between two users. That is, how similar are they based on how many things they have in common. I say simplified because we are not taking into account the movies they disagree about. Essentially, and following your order, it would be:
1) Get the total weight of every Attribute for every user. For instance:
MATCH (user:User{name:'user1'})
OPTIONAL MATCH (user)-[r:RATED]->(m:Movie)->[w:WEIGHT]->(a:Attribute)
WITH user, r.rating * w.weight AS totalWeight, a
WITH user, a, sum(totalWeight) AS totalWeight
We need the last line because we had a row for each Movie-Attribute combination
2) Then, we get users with similar tastes. This is a performance danger zone, some filtering might be neccesary. But brute forcing it, we get users that like each attribute within an 10% error (for instance)
WITH user, a, totalWeight*0.9 AS minimum, totalWeight*1.10 AS maximum
MATCH (a)<-[w:WEIGHT]-(m:Movie)<-[r:RATES]-(otherUser:User)
WITH user, a, otherUser
WHERE w.weight * r.rating > minimum AND w.weight * r.rating < maximum
WITH user, otherUser
So now we have a row (unique because of last line) with any otherUser that is a match. Here, to be honest, I would need to try to be sure if otherUsers with only 1 genre match would be included.. if they are, an additional filter would be needed. But I think that should go after we get this going.
3) Now it´s easy:
MATCH (otherUser)-[r:RATES]->(m:Movie)
WHERE NOT (user)-[:RATES]->(m)
RETURN m, sum(r.rating) AS totalRating ORDER BY totalRating DESC
As mentioned before, the tricky part is 2), but after we know how to get the math going, it should be easier. Oh, and about math, for it to work properly, total weights for a movie should sum 1 (normalizing). In any other case, the difference between total weights for movies would cause an unfair comparison.
I wrote this without proper studying (paper, pencil, equations, statistics) and trying the code in a sample dataset. I hope it can help you anyway!
In case you want this recommendation without taking into account user ratings or attribute weights, it should be enough to substitute the math in lines in 1) and 2) with just r.rating or w.weight, respectively. RATES and WEIGHTS relationships would still be used, so for instance an avid consumer of Adventure movies would be recommended Movies by consumers of Adventure movies, but not modified by ratings or by attribute weight, as we chose.
EDIT: Code edited to fix syntax errors discussed in comments.
Answer to your 1st query:
Checks which attributes the user has liked (implicitly by liking the movies)
MATCH (user:User)
OPTIONAL MATCH (user)-[r:RATED]->(m:movie)
OPTIONAL MATCH (m)-[r:RATED]->(a:Attribute)
WHERE user.uid = "user4"
RETURN user, collect ({ a:a.title })
It is a subquery construct where in you find the movies rated by the user and then find attributes of the movies and finally return list of liked attributes
you can modify return statement to collect (a) as attributes if you need entire node

How can I mitigate having bidirectional relationships in a family tree, in Neo4j?

I am running into this wall regarding bidirectional relationships.
Say I am attempting to create a graph that represents a family tree. The problem here is that:
* Timmy can be Suzie's brother, but
* Suzie can not be Timmy's brother.
Thus, it becomes necessary to model this in 2 directions:
(Sure, technically I could say SIBLING_TO and leave only one edge...what I'm not sure what the vocabulary is when I try to connect a grandma to a grandson.)
When it's all said and done, I pretty sure there's no way around the fact that the direction matters in this example.
I was reading this blog post, regarding common Neo4j mistakes. The author states that this bidirectionality is not the most efficient way to model data in Neo4j and should be avoided.
And I am starting to agree. I set up a mock set of 2 families:
and I found that a lot of queries I was attempting to run were going very, very slow. This is because of the 'all connected to all' nature of the graph, at least within each respective family.
My question is this:
1) Am I correct to say that bidirectionality is not ideal?
2) If so, is my example of a family tree representable in any other way...and what is the 'best practice' in the many situations where my problem may occur?
3) If it is not possible to represent the family tree in another way, is it technically possible to still write queries in some manner that gets around the problem of 1) ?
Thanks for reading this and for your thoughts.
Storing redundant information (your bidirectional relationships) in a DB is never a good idea. Here is a better way to represent a family tree.
To indicate "siblingness", you only need a single relationship type, say SIBLING_OF, and you only need to have a single such relationship between 2 sibling nodes.
To indicate ancestry, you only need a single relationship type, say CHILD_OF, and you only need to have a single such relationship between a child to each of its parents.
You should also have a node label for each person, say Person. And each person should have a unique ID property (say, id), and some sort of property indicating gender (say, a boolean isMale).
With this very simple data model, here are some sample queries:
To find Person 123's sisters (note that the pattern does not specify a relationship direction):
MATCH (p:Person {id: 123})-[:SIBLING_OF]-(sister:Person {isMale: false})
RETURN sister;
To find Person 123's grandfathers (note that this pattern specifies that matching paths must have a depth of 2):
MATCH (p:Person {id: 123})-[:CHILD_OF*2..2]->(gf:Person {isMale: true})
RETURN gf;
To find Person 123's great-grandchildren:
MATCH (p:Person {id: 123})<-[:CHILD_OF*3..3]-(ggc:Person)
RETURN ggc;
To find Person 123's maternal uncles:
MATCH (p:Person {id: 123})-[:CHILD_OF]->(:Person {isMale: false})-[:SIBLING_OF]-(maternalUncle:Person {isMale: true})
RETURN maternalUncle;
I'm not sure if you are aware that it's possible to query bidirectionally (that is, to ignore the direction). So you can do:
MATCH (a)-[:SIBLING_OF]-(b)
and since I'm not matching a direction it will match both ways. This is how I would suggest modeling things.
Generally you only want to make multiple relationships if you actually want to store different state. For example a KNOWS relationship could only apply one way because person A might know person B, but B might not know A. Similarly, you might have a LIKES relationship with a value property showing how much A like B, and there might be different strengths of "liking" in the two directions

Simple recursive CYPHER query

This is an extremely simple question, but reading through the docs for the first time, I can't figure out how to construct this query. Let's say I have a graph that looks like:
and additionally each person has an age associated with them.
What CYPHER query will get me a list of John's age and all the ages of the entire friend tree of John?
What I've tried so far:
MATCH (start)-[:friend]>(others)
WHERE start.name="John"
RETURN start.age, others.age
This has several problems,
It only goes one one one friend deep and I'd like to go to all friends of John.
It doesn't return a list, but a series of (john.age, other.age).
So what you need is not only friend of john, but also friends of friends. So you need to tell neo4j to recursively go deep in the graph.
This query goes 2 level deep.
MATCH (john:Person { name:"John" })-[:friend *1..2]->(friend: Person)
RETURN friend.name, friend.age;
To go n nevel deep, don't put anything i.e. *1..
Oh and I also found this nice example in neo4j
So what does *1..2 here means:
* to denote that its a recursion.
1 to denote that do not include john itself i.e. is the start node. If you put 0 here, it will also include the John node itself.
.. to denote that go from this node till ...
2 denotes the level of recursion. Here you say to stop at level 2. i.e. dont go beyond steve. If you put nothing there, it will keep on going until it cannot find a node that "has" a friend relationship
Documentation for these types of query match is here and a similar answer here.

Neo4j getting child nodes of multiple parents

I've just recently started using neo4j and I've run into an issue. There doesn't seem to be an answer to this on here but I might also be wording it incorrectly. I'm building a small site that categorizes music. There are multiple song nodes with BELONGS_TO relationships to genre nodes. How can I get every song that belongs to a set of user specified genres.
For example. Song1, Song2, Song3 all belong to both Pop and Electronic. Song4 just belongs to Pop. How can I query to get every song belonging to both Pop and Electronic? In this case Song1, Song2, an Song3.
I've been struggling with this for a while. This is what I have so far but it doesn't return anything. If I replace AND with OR I get all the songs that belong to one of those genres.
MATCH (n:Song)-[r:BELONGS_TO]->(Genre)
WHERE (n)-[r]->(Genre{name:"Pop"}) AND (n)-[r]->(Genre{name:"Electronic"})
RETURN n
Thank you.
What you're trying to do in the WHERE clause you should actually do in the MATCH clause. Here you go:
MATCH (g1:Genre {name: "Pop"})<-[:BELONGS_TO]-(popElectronicSongs:Song)-[:BELONGS_TO]->(g2:Genre {name: "Electronic"});
RETURN popElectronicSongs;
You can actually do quite a lot with just the MATCH clause as you can see here. The WHERE bit usually gets used for filtering based on various predicates. For example you might say WHERE popElectronicSongs.title =~ /S.*/ to filter for only songs whose name starts with S.

Resources