Graphx ~4:算子总结

本文深入解析GraphX中的图算子,包括基本算子、属性算子、结构化算子及join算子,通过实例展示如何进行图数据处理与分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

零:GraphX 中的一些图算子

在这里插入图片描述

一:基本算子

详见Graph 出入度等信息展示

二:属性算子

  • mapVertices
  • mapEdges
  • mapTriplets
class Graph[VD, ED] {
  def mapVertices[VD2](map: (VertexId, VD) => VD2): Graph[VD2, ED]
  def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2]
  def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]
}

这里每一个操作产生一个新图,其顶点和边被用户定义的map函数修改了。
注意:
在每一个实例图结构不受影响。这是这些操作的关键特征,这允许结果图重复利用原始图的结构索引。下面的代码片段逻辑上是等同的,但是第一个没有保存结构索引,其不会从GraphX系统优化中获益:

三: 结构化算子

spark有如下4种结构化算子:

  • reverse
  • subgraph
  • mask
  • groupEdges

为了演示以上4种结构化算子,我们首先初始化一个graph,代码如下
我们这里传过去的有3个参数

  • users就是所有顶点的rdd(RDD[(VertexId, VD)])
  • relationships就是所有边的集合RDD[Edge[ED]]
  • defaultUser是默认的顶点,也就是说如果relationships里面的源id或者目标id在users里面找不到,就会把找不到的id的那个人当成是defaultUser
val users: RDD[(VertexId, (String, String))] =
  sc.parallelize(Array((1L, ("a", "student")), (2L, ("b", "salesman")),
    (3L, ("c", "programmer")), (4L, ("d", "doctor")),
    (5L, ("e", "postman"))))

val relationships: RDD[Edge[String]] =
  sc.parallelize(Array(Edge(1L, 2L, "customer"),Edge(3L, 2L, "customer"),
    Edge(3L, 4L, "patient"), Edge(5L, 4L, "patient"),
    Edge(3L, 4L, "friend"),   Edge(5L, 99L, "father")))

val defaultUser = ("f", "none")

val graph = Graph(users, relationships, defaultUser)

接下来我们打印一下这个graph

graph.triplets.map(
      triplet => triplet.srcAttr._1 + " ——(" + triplet.attr + ")——> " + triplet.dstAttr._1
    ).collect.foreach(println(_))

打印结果如下,这个graph表示的是a到f这几个人之间的关系,a是b的客户,c是b的客户,c是d的病人,3是d的病人,c是d的朋友,e是f的爸爸

a ——(customer)——> b
c ——(customer)——> b
c ——(patient)——> d
e ——(patient)——> d
c ——(friend)——> d
e ——(father)——> f

reverse

首先我们演示一下reverse算子,reverse算子的作用就是把edge的方向反过来,在这里就是把每个人的关系反过来一下
代码如下:

val reverseGraph = graph.reverse
reverseGraph.triplets.map(
  triplet => triplet.srcAttr._1 + " ——(" + triplet.attr + ")——> " + triplet.dstAttr._1
).collect.foreach(println(_))

使用了reverse之后,打印出来的内容如下

b ——(customer)——> a
b ——(customer)——> c
d ——(patient)——> c
d ——(patient)——> e
d ——(friend)——> c
f ——(father)——> e

从这里我们可以看出来,原来e是f的爸爸,使用了reverse之后,变成了f是e的爸爸

subgraph

subgraph顾名思义就是取原来graph的子graph,获取子graph肯定是有条件过滤掉一部分数据,剩下来的就是子graph
代码如下:

val subGraph = graph.subgraph(vpred = (id, attr) => attr._1 > "b")
subGraph.triplets.map(
  triplet => triplet.srcAttr._1 + " ——(" + triplet.attr + ")——> " + triplet.dstAttr._1
).collect.foreach(println(_))

打印结果如下:

c ——(patient)——> d
e ——(patient)——> d
c ——(friend)——> d
e ——(father)——> f

我们看到了这个子graph,只保留了父graph中第一个属性比b的ascii码大的vertex

mask

mask算子就是求当前graph和另外一个graph的交集
代码如下,我们使用了上一个算子的结果作为当前graph进行mask的参数:

val maskGraph = graph.mask(subGraph)
    maskGraph.triplets.map(
      triplet => triplet.srcAttr._1 + " ——(" + triplet.attr + ")——> " + triplet.dstAttr._1
    ).collect.foreach(println(_))

因为我们的参数是上一个subgraph的结果,所以这2个graph的交集肯定就是那个子graph,打印结果如下:

c ——(patient)——> d
e ——(patient)——> d
c ——(friend)——> d
e ——(father)——> f

groupEdges

groupEdges的作用是将2个vertex之间的所有edge进行合并,我们知道graphx处理的是多重图,多重图的特征就是2个顶点之间可能有多个平行边,这里的groupEdges就可以把这些平行边合并
代码如下:

val combineGraph = graph
  .partitionBy(PartitionStrategy.EdgePartition1D)
  .groupEdges(merge = (e1, e2) => e1 + " and " + e2)
combineGraph.triplets.map(
  triplet => triplet.srcAttr._1 + " ——(" + triplet.attr + ")——> " + triplet.dstAttr._1
).collect.foreach(println(_))

我们这里将平行边的元素用and连接起来了,这里要注意的是,使用groupEdges算子之前,必须先用一下partitionBy,不然不起作用的
打印结果如下:

a ——(customer)——> b
c ——(customer)——> b
c ——(patient and friend)——> d
e ——(patient)——> d
e ——(father)——> f

四:join(关联) 算子

为了演示graph的join算子,首先我们定义一个graph

val users: RDD[(VertexId, (String, String))] =
  sc.parallelize(Array(
    (1L, ("a", "student")), (2L, ("b", "salesman")),
    (3L, ("c", "programmer")), (4L, ("d", "doctor")),
    (5L, ("e", "postman"))
  ))

val relationships: RDD[Edge[String]] =
  sc.parallelize(Array(Edge(1L, 2L, "customer"), Edge(3L, 2L, "customer"),
    Edge(3L, 4L, "patient"), Edge(5L, 4L, "patient"),
    Edge(3L, 4L, "friend"), Edge(5L, 99L, "father")))

val defaultUser = ("f", "none")

val graph = Graph(users, relationships, defaultUser)

这个graph描述了每个人的名字和工作,这里我们给每个人增加除了名字和工作的其他属性,这个属性就是年龄属性
因此,我们需要定义一个rdd,描述每个人的年龄
代码如下:

 val userWithAge: RDD[(VertexId, Int)] =
      sc.parallelize(Array(
        (3L, 2), (4L, 19), (5L, 23), (6L, 42), (7L, 59)
      ))

这里我们定义了id为3到7的这5个人的年龄,注意我们原来的graph的所有人的id为1到5
接下来有2种方法来把这个年龄属性加到我们graph中的每个人上面

outerJoinVertices

第一种方法就是outerJoinVertices,代码如下:

graph.outerJoinVertices(userWithAge) { (id, attr, age) =>
      age match {
        case Some(a) => (attr._1, attr._2, a)
        case None => (attr._1, attr._2)
      }
      //      (attr._1 + "", attr._2 + "," + A)
    }.vertices.collect.foreach(println)

打印结果如下:

(1,(a,student,-1))
(2,(b,salesman,-1))
(3,(c,programmer,2))
(99,(f,none,-1))
(4,(d,doctor,19))
(5,(e,postman,23))

joinVertices

第一种方法就是joinVertices,代码如下:

graph.joinVertices(userWithAge) { (id, attr, age) => {
  (attr._1 + "", attr._2 + "、" + age)
}}.vertices.collect.foreach(println)

打印结果如下

(1,(a,student))
(2,(b,salesman))
(3,(c,programmer、2))
(99,(f,none))
(4,(d,doctor、19))
(5,(e,postman、23))

实际上,当我们读spark graphx的源代码的时候会发现, joinVertices 的底层就是调用了 outerJoinVertices,源代码如下:

def joinVertices[U: ClassTag](table: RDD[(VertexId, U)])(mapFunc: (VertexId, VD, U) => VD)
    : Graph[VD, ED] = {
    val uf = (id: VertexId, data: VD, o: Option[U]) => {
      o match {
        case Some(u) => mapFunc(id, data, u)
        case None => data
      }
    }
    graph.outerJoinVertices(table)(uf)
  }

outerJoinVertices 和 joinVertices

  • 这2个算子都类似于sql中的left join
    从我上面举的例子你也可以看出这点,graph的vertex的id是1到5,age rdd的id是3到7,然后这2个算子返回的graph的vertex的id都是1到5,其实就是sql的left join嘛

  • outerJoinVertices返回的graph,它的vertex的属性可以是任意类型,而joinVertices返回的graph,它的vertex的属性类型只能是原来vertex的属性的类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值