<Zhuuu_ZZ>Spark專案實戰-航班飛行網圖分析

Zhuuu_ZZ發表於2020-11-29

一 專案技能

  • Spark GraphX API
    • vertices、edges、triplets、
    • numEdges、numVertices
    • inDegrees、outDegrees、degrees
    • mapVertices、mapEdges、mapTriplets
  • Spark GraphX PageRank
  • Spark GraphX Pregel

二 專案需求

  • 探索航班飛行網圖資料
  • 構建航班飛行網圖
  • 使用Spark GraphX完成下列任務
    • 統計航班飛行網圖中機場的數量
    • 統計航班飛行網圖中航線的數量
    • 計算最長的飛行航線(Point to Point)
    • 找出最繁忙的機場
    • 找出最重要的飛行航線(PageRank)
    • 找出最便宜的飛行航線(SSSP)

三 資料探索

下載資料

連結: 航班飛行網圖資料.提取碼:gvyd

資料格式

  • 檔案格式為CSV,欄位之間分隔符為“,”
  • 依次為:#日、周#、航空公司、飛機註冊號、航班號、起飛機場編號、起飛機場、到達機場編號、到達機場、預計起飛時間(時分)、起飛時間、起飛延遲(分鐘)、到達預計時間、到達時間、到達延遲(分鐘)、預計飛行時間、飛行距離
    在這裡插入圖片描述

四 專案實戰

構建航班飛行網圖

  • 建立屬性圖Graph[VD,ED]
    • 裝載CSV為RDD,每個機場作為頂點。關鍵欄位:起飛機場編號、起飛機場、到達機場編號、到達機場、飛行距離
    • 初始化頂點集airports:RDD[(VertexId,String)],頂點屬性為機場名稱
    • 初始化邊集lines:RDD[Edge],邊屬性為飛行距離
  val flight: RDD[Array[String]] = sc.textFile("in/project/fly.csv").map(_.split(","))
    //flatMap的返回值需要是TraversableOnce,即可反覆迭代的,如陣列集合等
    //一行資料進來會取下標5,6做一個元素,再取下標7,8做另外一個元素,然後所有元素返回進入一個集合中使維度相同
    //flatMap是扁平化函式,如“hello world”,“hello spark”進入.flatMap(_.split(","))會是hello,world,hello,spark而進入map(_.split(","))則是Array(hello, world), Array(hello,spark)
    //也就是flatMap會切割成一個個獨立的元素,並把這些元素放入一個集合中使之成為一個維度。
 val vertex: RDD[(VertexId, String)] = flight.flatMap(x=>Array((x(5).toLong,x(6)),(x(7).toLong,x(8)))).distinct()
 val lines: RDD[Edge[PartitionID]] = flight.map(x=>(x(5).toLong,x(7).toLong,x(16).toInt)).distinct().map(x=>Edge(x._1,x._2,x._3))

 val graph: Graph[String, PartitionID] = Graph(vertex,lines)

統計航班飛行網圖中機場與航線的數量

  • 機場數量
  • 航線數量
println("機場數量:"+graph.numVertices)
println("航線數量:"+graph.numEdges)

計算最長的飛行航線

  • 最大的邊屬性
    • 對triplets按飛行距離排序(降序)並取第一個
graph.triplets.sortBy(x => x.attr * (-1)).take(2).foreach(x=>println("最長的航線:"+x))

找出最繁忙的機場

-哪個機場到達航班最多

  • 計算頂點的入度並排序
graph.inDegrees.sortBy(x=>x._2,false).collect().foreach(println)

找出最重要的飛行航線

  • PageRank
    • 收斂誤差:0.05
graph.pageRank(0.05).vertices.sortBy(-_._2).collect().foreach(println)

找出最便宜的飛行航線

  • 定價模型
    • price = 180.0 + distance * 0.15
  • SSSP問題
    • 從初始指定的源點到達任意點的最短距離
  • pregel
    • 初始化源點(0)與其它頂點(Double.PositiveInfinity)
  • 初始訊息(Double.PositiveInfinity)
  • vprog函式計算最小值
  • sendMsg函式計算進行是否下一個迭代
  • mergeMsg函式合併接受的訊息,取最小值
   //定義圖中起始頂點id
    val srcVertexId=12478L
    //修改頂點屬性,起始頂點為0,其餘全部為正無窮大
    val initialGraph=graph.mapVertices((id,prop)=>{
      if(id==srcVertexId)
        0
      else
        Double.PositiveInfinity
    })
    //呼叫pregel
  val pregelGraph: Graph[Double, PartitionID] = initialGraph.pregel(
      Double.PositiveInfinity,
      Int.MaxValue,
      EdgeDirection.Out
    )(
      //接收訊息函式:接收下面sendMsg的資訊
      (vid: VertexId, vd: Double, distMsg: Double) => {
        //返回相同VertexId的情況下,傳送頂點的屬性加上邊屬性和與目標頂點屬性的最小值
        val minDist: Double = math.min(vd, distMsg)
       // println(s"頂點${vid},屬性${vd},收到訊息${distMsg},合併後的屬性${minDist}")
        minDist
      },
      //傳送訊息函式:先傳送後接受,所以先執行這一步
      (edgeTriplet: EdgeTriplet[Double, PartitionID]) => {
        if (edgeTriplet.srcAttr + edgeTriplet.attr < edgeTriplet.dstAttr) { //如果傳送頂點的屬性加上邊屬性小於目標頂點屬性
        //  println(s"頂點${edgeTriplet.srcId} 給 頂點${edgeTriplet.dstId} 傳送訊息 ${edgeTriplet.srcAttr + edgeTriplet.attr}")
          //則返回一個(目標頂點id,傳送頂點屬性加上邊屬性)
          Iterator[(VertexId, Double)]((edgeTriplet.dstId, edgeTriplet.srcAttr + edgeTriplet.attr))
        } else { //否則返回空,即訊息傳送失敗
          Iterator.empty
        }
      },
      //合併訊息函式:指有兩個及以上的啟用態頂點給同一個頂點傳送訊息,且都傳送成功,則執行完sendMsg後呼叫mergeMsg再執行vprog
      (msg1: Double, msg2: Double) =>{
       // println("mergeMsg:",msg1,msg2) //本次demo沒有符合條件的,所以沒有呼叫
        math.min(msg1, msg2) //返回各自啟用態頂點屬性加上各自邊的屬性之和的最小值進入vprog函式
      }
    )
    pregelGraph.vertices.sortBy(_._2).take(3).foreach(println)

/*
(12478,0.0)
(10821,184.0)
(10721,187.0)

相關文章