使用加权PageRank发现最好的网球运动员

原文链接:https://medium.com/neo4j/finding-the-best-tennis-players-of-all-time-using-weighted-pagerank-6950ed5fc98e

最新版本的Neo4j图形算法库对PageRank算法增加了权重变量的支持。

我的同事Ryan(https://twitter.com/ryguyrg/)最近发表的一篇论文《谁是有史以来最好的网球运动员?基于职业网球历史的复杂网络分析》(https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0017249),在这篇论文中,他使用了一种PageRank的变种算法,于是我就在想,我也是一名网球爱好者,我是不是也可以基本这种算法去做点什么呢?

我原计划要做一些数据的抓取,但是Kevin Lin已经做了一些比较困难的工作,他将到2017年底的所有比赛结果都以csv文件的形式放到了Github《atp-world-tour-tennis-data》(https://github.com/serve-and-volley/atp-world-tour-tennis-data)上.

感谢Kevin的付出。

数据导入

在导入数据之前,我们先创建一些约束,以避免导入一些重复的数据:

CREATE constraint on (p:Player)ASSERT p.id is unique;CREATE constraint on (m:Match)ASSERT m.id is unique;

接下来我们将把数据导入到Neo4j中。先把Kevin创建的CSV文件拷贝到Neo4j的import目录下。

完成之后我们就可以使用Cypher的LOAD CSV命令将数据导入到Neo4j里了。

LOAD CSV FROM "file:///match_scores_1968-1990_UNINDEXED.csv" AS rowMERGE (winner:Player {id: row[8]}) ON CREATE SET winner.name = row[7]MERGE (loser:Player {id: row[11]}) ON CREATE SET loser.name = row[10]MERGE (m:Match {id: row[22]})SET m.score = row[15], m.year = toInteger(split(row[0], "-")[0])MERGE (m)-[w:WINNER]->(winner) SET w.seed = toInteger(row[13])MERGE (m)-[l:LOSER]->(loser) SET l.seed = toInteger(row[14]);LOAD CSV FROM "file:///match_scores_1991-2016_UNINDEXED.csv" AS rowMERGE (winner:Player {id: row[8]})ON CREATE SET winner.name = row[7]MERGE (loser:Player {id: row[11]})ON CREATE SET loser.name = row[10]MERGE (m:Match {id: row[22]})SET m.score = row[15], m.year = toInteger(split(row[0], "-")[0])MERGE (m)-[w:WINNER]->(winner) SET w.seed = toInteger(row[13])MERGE (m)-[l:LOSER]->(loser) SET l.seed = toInteger(row[14]);LOAD CSV FROM "file:///match_scores_2017_UNINDEXED.csv" AS rowMERGE (winner:Player {id: row[8]}) ON CREATE SET winner.name = row[7]MERGE (loser:Player {id: row[11]}) ON CREATE SET loser.name = row[10]MERGE (m:Match {id: row[22]})SET m.score = row[15], m.year = toInteger(split(row[0], "-")[0])MERGE (m)-[w:WINNER]->(winner) SET w.seed = toInteger(row[13])MERGE (m)-[l:LOSER]->(loser) SET l.seed = toInteger(row[14]);

这个模型非常简单,可以运行下面的请求看到可视化的描述:

CALL db.schema()
可以看到:


看起来不错。在继续写之前,我们先写个简单查询来看一下数据情况:

MATCH p=()<-[:LOSER]-()-[r:WINNER]->() RETURN p LIMIT 25



获胜最多的选手

现在,我们想看一下获胜最多选手,这个语句要怎么写呢?

MATCH (p:Player)WITH p,  size((p)<-[:WINNER]-()) AS wins,  size((p)<-[:LOSER]-()) as defeatsRETURN p.name, wins, defeats,  CASE WHEN wins+defeats = 0 THEN 0  ELSE (wins * 100.0) / (wins + defeats) END AS percentageWinsORDER BY wins DESCLIMIT 10

运行上面的语句,将看到下面的输出:


如果你也是一名网球迷,你可能认识这份名单中的大部分名字。他们中的大部分都被认为是有史以来最优秀的球员,但是仅仅计算赢球的场数好像并不太严谨。

此时,似乎我们可以试试更高级的方法---PageRank算法.....

建立一个可信的投影图

通过一个结点的入口关系来决定这个结点的可信度,这就是PageRank算法的工作原理。例如,在网络的世界里,一个网页通过链接到另一个网页,而给他带来可信度。这个可信度可以通过这个关系的权重属性来决定。

在我们的网球世界里,运动员的可信度则由他们彼此之间比较的胜负数来决定。例如,下面的查询显示了费德勒和纳达尔相互赢了多少次。

MATCH (p1:Player {name: "Roger Federer"}), (p2:Player {name: "Rafael Nadal"})RETURN p1.name, p2.name,  size((p1)<-[:WINNER]-()-[:LOSER]->(p2)) AS p1Wins,  size((p1)<-[:LOSER]-()-[:WINNER]->(p2)) AS p2Wins

运行输出结果如下:


我们的投影图应该在费德勒和纳达尔之间建立直接关系,用权重表示他们互相之间比赛赢的次数。从费德勒到纳达尔的关系上权重是23,表示费德勒赢了纳达尔23次。而纳达尔到费德勒的有关系上权重就是15.

我们写下面的查询语句将投影出这个图:

MATCH (p1)<-[:WINNER]-(match)-[:LOSER]->(p2) WHERE p1.name IN ["Roger Federer", "Rafael Nadal"]AND p2.name IN ["Roger Federer", "Rafael Nadal"]RETURN p2.name AS source, p1.name AS target, count(*) as weightLIMIT 10

这个查询的输出结果如下所示:


接下来我们要做的是删除WHERE条件,使这个查询可以在全图中进行。

使用加权PageRank来发现最好的网球运动员

现在我们通过PageRank算法的weightProperty参数来调用加权PageRank算法。默认情况下,PageRank算法是非加权模式。

下面的语句即是在全图上运行加权PageRank算法:

CALL algo.pageRank.stream( "MATCH (p:Player) RETURN id(p) AS id", "MATCH (p1)<-[:WINNER]-(match)-[:LOSER]->(p2)  RETURN id(p2) AS source, id(p1) AS target, count(*) as weight ", {graph:"cypher", weightProperty: "weight"})YIELD nodeId, scoreRETURN algo.getNodeById(nodeId).name AS player, scoreORDER BY score DESCLIMIT 10

其运行结果如下:


我们看到,我们排名的头部与Filippo Radicchi论文的排名是不一样的,主要区别是费德勒,纳达尔和德约科维奇进入了前五。这是因为Radicchi的分析仅到2010年,而这三名球员在此后的8年时间里一直非常优秀,所以,这也就是我们排名不太一样的原因。

我们可以模板仅包括2010年之前的比赛,则如下查询语句:

CALL algo.pageRank.stream( "MATCH (p:Player) RETURN id(p) AS id", "MATCH (p1)<-[:WINNER]-(match)-[:LOSER]->(p2)  WHERE match.year <= $year RETURN id(p2) AS source, id(p1) AS target, count(*) as weight ", {graph:"cypher", weightProperty: "weight", params: {year: 2010}})YIELD nodeId, scoreRETURN algo.getNodeById(nodeId).name AS player, scoreORDER BY score DESCLIMIT 10

运行效果如下:


注意,在这个查询中,我们将年份值通过params键作为参数传递到Cypher投影查询中。

我们排行榜的前两名现在与Radicche的一样了,但是费德勒目前在第三位并不是Radicche的榜单中是第七位,同时纳达尔和德约科维奇在我们榜单中已经排到前十之外了。

我们还可能查询某一个比赛的PageRank排名,下面的查询是2017年的PageRank排名

CALL algo.pageRank.stream( "MATCH (p:Player) RETURN id(p) AS id", "MATCH (p1)<-[:WINNER]-(match)-[:LOSER]->(p2) WHERE match.year = $year  RETURN id(p2) AS source, id(p1) AS target, count(*) as weight ", {graph:"cypher", weightProperty: "weight", params: {year: 2017}})YIELD nodeId, scoreRETURN algo.getNodeById(nodeId).name AS player, scoreORDER BY score DESCLIMIT 10

运行效果如下:


下图是2017年ATP世界巡回赛的年终排名


这个排名与我们的排名完全不同!这是什么原因呢?这是因为官方排名为每场比赛都给予了不同的权重,而我们的PageRank排名在每场比赛上给予的是相同权重。

好,关于使用加权PageRank找史上最优化网球选手的问题就介绍到这,我期待着看到更多人使用加权PageRank去解决其他问题。如是你也用了,请告诉我[email protected]

Enjoy!

译者言:作者仅从应用角度介绍了如果实现这个事,完成没有介绍algo.pageRank.stream这个方法各个参数的功能,以后有空找到相关文章再给大家介绍。

你可能感兴趣的:(使用加权PageRank发现最好的网球运动员)