spark-20.sparkGraphx_2_圖的轉換

1.Graph的創建

1.根據邊和頂點來創建。

def apply[VD: ClassTag, ED: ClassTag](
      vertices: RDD[(VertexId, VD)],
      edges: RDD[Edge[ED]],
      defaultVertexAttr: VD = null.asInstanceOf[VD],
      edgeStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY,
      vertexStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]

2.根據邊來創建。

所有頂點的屬性相同,都是VD類型的defaultValue。

def fromEdges[VD: ClassTag, ED: ClassTag](
      edges: RDD[Edge[ED]],
      defaultValue: VD,
      edgeStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY,
      vertexStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]

3.根據裸邊(只有頂點ID)進行創建。

頂點的屬性是defaultValue,邊的屬性爲相同頂點邊的個數,默認爲1。

def fromEdgeTuples[VD: ClassTag](
      rawEdges: RDD[(VertexId, VertexId)],
      defaultValue: VD,
      uniqueEdges: Option[PartitionStrategy] = None,
      edgeStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY,
      vertexStorageLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, Int]

2.Graph的轉換

1.基本信息

  1. numEdges 返回邊的數量
  2. numVertices 頂點的個數
  3. inDegrees:VertexRDD[Int] 返回頂點的入度,返回類型爲RDD[(VertexId,Int)] Int是入度的具體值。
  4. outDegrees:VertexRDD[Int] 返回頂點的出度,返回類型爲RDD[(VertexId,Int)] Int是出度的具體值。
  5. degrees:VertexRDD[Int] 返回頂點的入度與出度之和,返回類型爲RDD[(VertexId,Int)] Int是度的具體值。

2.轉換操作

def mapVertices[VD2: ClassTag](map: (VertexId, VD) => VD2)
    (implicit eq: VD =:= VD2 = null): Graph[VD2, ED]

對圖中的每一個頂點進行map操作,頂點ID不能變,可以將頂點的屬性改變成另一種類型。
如:scala> graph.mapVertices((id,attr)=>attr._1+":"+attr._2)

def mapEdges[ED2: ClassTag](map: Edge[ED] => ED2): Graph[VD, ED2]

對圖中的每個邊進行map操作,邊的方向不能改變,可以將邊的屬性改爲lin一種類型。

def mapTriplets[ED2: ClassTag](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]

對圖中的每個三元組進行map操作,只能修改邊的屬性。

3.結構操作

  • reverse 反轉
def reverse: Graph[VD, ED]

反轉整個圖,將邊的方向調頭。
如:

graph.reverse.triplets.map(x=>"["+x.srcId+":"+x.srcAttr+"-->"+x.attr+"-->"+x.dstId+":"+x.dstAttr+"]").collect.foreach(println)
  • subgrap 獲取子圖
def subgraph(
      epred: EdgeTriplet[VD, ED] => Boolean = (x => true),
      vpred: (VertexId, VD) => Boolean = ((v, d) => true))
    : Graph[VD, ED]

獲取子圖。可以通過參數名來指定傳參,如果subgraph中有的邊沒有頂點對應,那麼會自動將該邊去除。

graph.subgraph(vpred=(id,attr)=>attr._2 == "professor").triplets.map(x=>"["+x.srcId+":"+x.srcAttr+"-->"+x.attr+"-->"+x.dstId+":"+x.dstAttr+"]").collect.foreach(println)

沒有邊的頂點不會自動被刪除。

graph.subgraph((x=>false)).numVertices
  • mask 求兩個圖的交集
def mask[VD2: ClassTag, ED2: ClassTag](other: Graph[VD2, ED2]): Graph[VD, ED]

將當前圖和other圖做交集,返回一個新圖。如果other中的屬性與原圖的屬性不同,那麼保留原圖的屬性。

val other =graph.subgraph(vpred=(id,attr)=>attr._2 == "professor").mapVertices((id,attr)=>attr._1 +":"+attr._2)
other.triplets.map(x=>"["+x.srcId+":"+x.srcAttr+"-->"+x.attr+"-->"+x.dstId+":"+x.dstAttr+"]").collect.foreach(println) //輸出other
graph.mask(other).triplets.map(x=>"["+x.srcId+":"+x.srcAttr+"-->"+x.attr+"-->"+x.dstId+":"+x.dstAttr+"]").collect.foreach(println)
  • groupEdges 合併兩條邊
def groupEdges(merge: (ED, ED) => ED): Graph[VD, ED]

合併兩條邊,通過函數合併邊的屬性。【注意:兩條邊要在一個分區內。】

4.聚合

collectNeighbors

def collectNeighbors(edgeDirection: EdgeDirection): VertexRDD[Array[(VertexId, VD)]]

收集鄰居節點的數據,根據指定的方向。返回的數據爲RDD[(VertexId,Array[(VertexId,VD)])] 頂點的屬性的一個數組。數組中包含鄰居節點的頂點。

graph.collectNeighbors(EdgeDirection.In).collect

collectNeighborIds

def collectNeighborIds(edgeDirection: EdgeDirection): VertexRDD[Array[VertexId]]

與上一個方法類似,只收集ID。

graph.collectNeighborIds(EdgeDirection.In).collect

aggregateMessages

def aggregateMessages[A: ClassTag](
      sendMsg: EdgeContext[VD, ED, A] => Unit,
      mergeMsg: (A, A) => A,
      tripletFields: TripletFields = TripletFields.All)
    : VertexRDD[A]

每個邊都會通過sendMsg發送一個消息,每個頂點都會通過mergeMsg來處理它收到的消息,tripletFields存在主要用於定製EdgeContext對象中的屬性的值是否存在,爲了減少數據通信量。

//初始化頂點集合
  val vertexArray = Array(
    (1L, ("Alice", 28)),
    (2L, ("Bob", 27)),
    (3L, ("Charlie", 65)),
    (4L, ("David", 42)),
    (5L, ("Ed", 55)),
    (6L, ("Fran", 50))
  )
  //創建頂點的RDD表示
  val vertexRDD: RDD[(Long, (String, Int))] = sc.parallelize(vertexArray)

  //初始化邊的集合
  val edgeArray = Array(
    Edge(2L, 1L, 7),
    Edge(2L, 4L, 2),
    Edge(3L, 2L, 4),
    Edge(3L, 6L, 3),
    Edge(4L, 1L, 1),
    Edge(2L, 5L, 2),
    Edge(5L, 3L, 8),
    Edge(5L, 6L, 3)
  )

  //創建邊的RDD表示
  val edgeRDD: RDD[Edge[Int]] = sc.parallelize(edgeArray)

  //創建一個圖
  val graph: Graph[(String, Int), Int] = Graph(vertexRDD, edgeRDD)
graph.aggregateMessages[Array[(VertexId, (String, Int))]](ctx => ctx.sendToDst(Array((ctx.srcId.toLong, (ctx.srcAttr._1, ctx.srcAttr._2)))), _ ++ _).collect.foreach(v => {
    println(s"id: ${v._1}"); for (arr <- v._2) {
      println(s"    ${arr._1} (name: ${arr._2._1}  age: ${arr._2._2})")
    }
  })

5.關聯操作

joinVertices

def joinVertices[U: ClassTag](table: RDD[(VertexId, U)])(mapFunc: (VertexId, VD, U) => VD): Graph[VD, ED]

將相同頂點ID的數據進行加權,將U這種類型的數據加入到VD這種類型的數據上,但是不能修改VD的類型。可以使用case class 類型,將VD封裝爲case class,mapFunc對VD進行屬性補全。

outerJoinVertices

 def outerJoinVertices[U: ClassTag, VD2: ClassTag](other: RDD[(VertexId, U)])( mapFunc: (VertexId, VD, Option[U]) => VD2)(implicit eq: VD =:= VD2 = null) : Graph[VD2, ED]

和joinVertices類似,只是如果沒有相應的節點,那麼join的值默認爲None。

6.Pregel

前提:

對於節點來說有兩種狀態:1.鈍化態,類似於休眠,不做任何事。2.激活態,幹活。
節點能夠處於激活態需要有條件:1.節點收到消息或者2.成功發送了任何一條消息。

def pregel[A: ClassTag](
      initialMsg: A,
      maxIterations: Int = Int.MaxValue,
      activeDirection: EdgeDirection = EdgeDirection.Either)(
      vprog: (VertexId, VD, A) => VD,
      sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexId, A)],
      mergeMsg: (A, A) => A)
    : Graph[VD, ED]

initialMsg 圖初試化的時候,開始模型計算的時候,所有節點都會收到一個消息,所有節點都是active的。
maxIterations 最大迭代次數。
activeDirection 規定了發送消息的方向。
柯里化:
vprog 激活態且具有activeDirection的節點調用該消息將聚合後的數據和本節點進行屬性的合併。
sendMsg 激活態的節點調用該方法發送消息。
mergeMsg 如果一個節點接收到多個消息,先用mergeMsg來將多條消息聚合成一條消息。如果節點只收到一條消息,則不會調用該函數。
實例:求節點5到各個節點的最短距離。

代碼實現:

package com.dengdan.practice

import org.apache.log4j.{Level, Logger}
import org.apache.spark.graphx.{Edge, _}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Practice extends App {
  //屏蔽日誌
  Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
  Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

  //設定一個SparkConf
  val conf = new SparkConf().setAppName("simpleGraphx").setMaster("local[*]")
  val sc = new SparkContext(conf)

  //初始化頂點集合
  val vertexArray = Array(
    (1L, ("Alice", 28)),
    (2L, ("Bob", 27)),
    (3L, ("Charlie", 65)),
    (4L, ("David", 42)),
    (5L, ("Ed", 55)),
    (6L, ("Fran", 50))
  )
  //創建頂點RDD表示
  val vertexRDD: RDD[(Long, (String, Int))] = sc.parallelize(vertexArray)
  //初始化邊表示
  val edgeArray = Array(
    Edge(2L, 1L, 7),
    Edge(2L, 4L, 2),
    Edge(3L, 2L, 4),
    Edge(3L, 6L, 3),
    Edge(4L, 1L, 1),
    Edge(2L, 5L, 2),
    Edge(5L, 3L, 8),
    Edge(5L, 6L, 3)
  )
  //創建邊RDD表示
  val edgeRDD: RDD[Edge[Int]] = sc.parallelize(edgeArray)

  //創建圖
  val graph: Graph[(String, Int), Int] = Graph(vertexRDD, edgeRDD)
  //***************************  實用操作    ****************************************
  println("聚合操作")
  println("**********************************************************")
  val sourceId: VertexId = 5L //定義源點
  val initialGraph = graph.mapVertices((id, _) => if (id == sourceId) 0.0 else Double.PositiveInfinity)

  initialGraph.triplets.collect() foreach (println)

  println("找出5到各頂點的最短距離")
  val sssp = initialGraph.pregel(Double.PositiveInfinity, Int.MaxValue, EdgeDirection.Out)(
    //id爲滿足條件的節點的編號,dist是該節點的屬性值,newDist爲收到消息後,新的屬性
    (id, dist, newDist) => {
      println("||||" + id + "  收到消息")
      math.min(dist, newDist)
    },
    triplet => {
      println(">>>>" + triplet.srcId + "  發送消息")
      //源節點的屬性 + 邊的屬性值 <目標節點的屬性
      if (triplet.srcAttr + triplet.attr < triplet.dstAttr) {
        //發送成功
        // Interator 表示發送,發送給dstId,發送的內容爲 triplet.srcAttr + triplet.attr
        Iterator((triplet.dstId, triplet.srcAttr + triplet.attr))
      } else {
        //發送失敗
        Iterator.empty
      }
    },
    (a, b) => {
      println("$$$$$")
      math.min(a, b)
    } //當前節點所有輸入的最短距離
  )
  println("--------------圖信息------------------")
  sssp.triplets.collect().foreach(println)

  println("-------------節點5到各個節點的最短距離---")
  println(sssp.vertices.collect.mkString("\n"))

  sc.stop()
}

運行結果:

聚合操作
**********************************************************
((2,Infinity),(1,Infinity),7)
((2,Infinity),(4,Infinity),2)
((3,Infinity),(2,Infinity),4)
((3,Infinity),(6,Infinity),3)
((4,Infinity),(1,Infinity),1)
((2,Infinity),(5,0.0),2)
((5,0.0),(3,Infinity),8)
((5,0.0),(6,Infinity),3)
找出5到各頂點的最短距離
# 初始化時
||||4  收到消息
||||6  收到消息
||||5  收到消息
||||2  收到消息
||||3  收到消息
||||1  收到消息
>>>>4  發送消息
>>>>2  發送消息
>>>>2  發送消息
>>>>5  發送消息
>>>>3  發送消息
>>>>2  發送消息
>>>>3  發送消息
>>>>5  發送消息
# 第一輪迭代
||||3  收到消息
||||6  收到消息
>>>>3  發送消息
>>>>3  發送消息
# 第二輪迭代
||||2  收到消息
>>>>2  發送消息
>>>>2  發送消息
>>>>2  發送消息
# 第三輪迭代
||||1  收到消息
||||4  收到消息
>>>>4  發送消息
# 第四輪迭代
||||1  收到消息
# 迭代結束
--------------圖信息------------------
((2,12.0),(1,15.0),7)
((2,12.0),(4,14.0),2)
((3,8.0),(2,12.0),4)
((3,8.0),(6,3.0),3)
((4,14.0),(1,15.0),1)
((2,12.0),(5,0.0),2)
((5,0.0),(3,8.0),8)
((5,0.0),(6,3.0),3)
-------------節點5到各個節點的最短距離---
(1,15.0)
(2,12.0)
(3,8.0)
(4,14.0)
(5,0.0)
(6,3.0)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章