航班飞行网图分析
一 项目技能
- 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").repartition(1).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.degrees.sortBy(x=>x._2,false).take(1).foreach(println)
找出最重要的飞行航线
- PageRank
- 收敛误差:0.05
graph.pageRank(0.05).vertices.sortBy(-_._2).take(1).foreach(println)
//等价
graph.pageRank(0.05).vertices.takeOrdered(1)(Ordering.by(-_._2)).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.mapEdges(e=>180.0+e.attr*0.15)
.mapVertices((id,prop)=>{
if(id==srcVertexId)
0
else
Double.PositiveInfinity
})
//调用pregel
val pregelGraph: Graph[Double, Double] = 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, Double]) => {
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)
math.min(msg1, msg2) //返回各自激活态顶点属性加上各自边的属性之和的最小值进入vprog函数
}
)
pregelGraph.vertices.sortBy(_._2).take(3).foreach(println)
/*
(12478,0.0)
(10821,207.6)
(10721,208.05)