3、Spark 和 D3.js 分析航班大数据

实验资源

1998.csv
airports.csv

实验环境

VMware Workstation
Ubuntu 16.04
spark-2.4.5
scala-2.12.10

实验内容

“我们很抱歉地通知您,您乘坐的由 XX 飞往 XX 的 XXXX 航班延误。”
相信很多在机场等待飞行的旅客都不愿意听到这句话。随着乘坐飞机这种交通方式的逐渐普及,航延延误问题也一直困扰着我们。航班延误通常会造成两种结果,一种是航班取消,另一种是航班晚点。
在本次实验中,我们将通过 Spark 提供的 DataFrame、 SQL 和机器学习框架等工具,基于 D3.js 数据可视化技术,对航班起降的记录数据进行分析,尝试找出造成航班延误的原因,以及对航班延误情况进行预测。

实验步骤

一、数据集简介及准备

1、数据集简介

本节实验用到的航班数据集是 2009 年 Data Expo 上提供的飞行准点率统计数据。此次我们选用 1998 年的数据集。

该数据集的各个字段解释如下:
在这里插入图片描述
此外,我们还会用到一些补充信息。如机场信息数据集等。

2、下载数据集
(1)在虚拟机中输入如下命令

wget https://labfile.oss.aliyuncs.com/courses/610/1998.csv.bz2

(2)然后使用解压缩命令对其进行解压

bunzip2 1998.csv.bz2

解压后的 CSV 数据文件位于你使用解压命令时的工作目录中,默认情况是在 /home/用户名 目录中。
(3)同样地,下载 airports 机场信息数据集,命令如下所示

wget https://labfile.oss.aliyuncs.com/courses/610/airports.csv

3、数据清洗
由于 airports 数据集中含有一些非常用字符,我们需要对其进行清洗处理,以防止部分记录字符的不能被识别错误引起后续检索的错误。
OpenRefine 是 Google 主导开发的一款开源数据清洗工具。我们先在环境中安装它:

wget https://labfile.oss.aliyuncs.com/courses/610/openrefine-linux-3.2.tar.gz
tar -zxvf openrefine-linux-3.2.tar.gz
cd openrefine-3.2
# 启动命令
./refine

当出现下图所示的提示信息后,在浏览器中打开 URL http://127.0.0.1:3333/
在这里插入图片描述
Open Refine 启动成功的标志是出现 Point your browser to http://127.0.0.1:3333 to start using Refine 的提示。
浏览器中会出现 OpenRefine 的应用网页,如下图所示。请选择刚刚下载的机场信息数据集,并点击 Next 按钮进入下一步。
在这里插入图片描述
在数据解析步骤中,直接点击右上角的 Create Project 按钮创建数据清洗项目。
在这里插入图片描述
稍作等待,项目创建完成后,就可以对数据进行各种操作。在稍后会提供 OpenRefine 的详细教程,此处只需要按照提示对数据集进行相应操作即可。
点击 airport 列旁边的下拉菜单按钮,然后在菜单中选择 Edit Column -> Remove this column 选项,以移除 airport 列。具体操作如下图所示。
在这里插入图片描述
请按照同样的方法,移除 lat 和 long 列。最后的数据集应只包含 iata 、city、state、country 四列。
最后我们点击右上角的 Export 按钮导出数据集。导出选项选择 Comma-separated value,即 CSV 文件。
在这里插入图片描述
然后在弹出的下载提示对话框中选择“保存文件”,并确定。
在这里插入图片描述

该文件位于 /home/用户名/下载 目录中,请在文件管理器中将其剪切至 /home/用户名 目录,并覆盖源文件。步骤如下图所示。
首先双击打开桌面上的 主文件夹,找到其中的 下载 目录。右键点击 CSV 文件,选择剪切。
在这里插入图片描述
然后回到主目录,在空白处右键点击,选择“粘贴”即可。
在这里插入图片描述
最后关闭浏览器和运行着 OpenRefine 的终端即可。

4、启动 Spark Shell
为了更好地处理 CSV 格式的数据集,我们可以直接使用由 DataBricks 公司提供的第三方 Spark CSV 解析库来读取。
首先是启动 Spark Shell。在启动的同时,附上参数--packages com.databricks:spark-csv_2.11:1.1.0

spark-shell --packages com.databricks:spark-csv_2.11:1.1.0

5、导入数据及处理格式
等待 Spark Shell 启动完成后,输入以下命令来导入数据集。

val sqlContext = new org.apache.spark.sql.SQLContext(sc)
val flightData = sqlContext.read.format("com.databricks.spark.csv").option("header","true").load("/home/shiyanlou/1998.csv")

上述命令中,我们调用了 sqlContext 提供的 read 接口,指定加载格式 format 为第三方库中定义的格式 com.databricks.spark.csv 。同时设置了一个读取选项 header 为 true,这表示将数据集中的首行内容解析为字段名称。最后在 load 方法中 指明了待读取的数据集文件为我们刚刚下载的这个数据集。

此时, flightData 的数据类型为 Spark SQL 中常用的 DataFrame。着将 flightData 其注册为临时表,命令为:

flightData.registerTempTable("flights")

使用相同的方法导入机场信息数据集 airports.csv ,并将其注册为临时表。

val airportData = sqlContext.read.format("com.databricks.spark.csv").option("header","true").load("/home/shiyanlou/airports-csv.csv")
airportData.registerTempTable("airports")
二、数据探索

1、问题设计
在探索数据之前,我们已经知道该数据共有 29 个字段。根据出发时间、出发 / 抵达延误时间等信息,我们可以大胆地提出下面这些问题:

  • **每天航班最繁忙的时间段是哪些?**通常早晚都容易有大雾等极端天气,是否中午的时候到港和离港航班更多呢?
  • **飞哪最准时?**在设计旅行方案时,如果达到某个目的地有两个相邻的机场,我们似乎可以比较到哪里更准时,以减少可能发生的延误给我们出行带来的影响。
  • **出发延误的重灾区都有哪些?**同样,从哪些地方出发最容易遭到延误?下次再要从这些地方出发的时候,就要考虑是不是要改乘地面交通工具了。

2、问题解答
我们已经把数据注册为临时表,对于上述问题的解答实际上就变成了如何设计合适的 SQL 查询语句。在数据量非常大的时候,Spark SQL 的使用尤为方便,它能够直接从 HDFS 等分布式存储系统中拉取数据进行查询,并且能够最大化地利用集群性能进行查询计算。
(1)每天航班最繁忙的时间段是哪些
分析某个问题时,要想办法将解答问题的来源落实在数据集的各个指标上。当问题不够详细时,可以取一些具有代表性的值作为该问题的答案。

例如,航班分为到港(Arrive)和离港(Depart)航班,若统计所有机场在每天的某个时间段内离港航班数量,就能在一定程序上反映这个时段的航班是否繁忙。

数据集中的每一条记录都朴实地反映了航班的基本情况,但它们并不会直接告诉我们每一天、每一个时段都发生了什么。为了得到后者这样的信息,我们需要对数据进行筛选和统计。

于是我们会顺理成章地用到 AVG(平均值)、COUNT(计数)和 SUM(求和)等统计函数。

为了分时间段统计航班数量,我们可以大致地将一天的时间分为以下五段:

  • 凌晨(00:00 - 06:00):大部分人在这个时段都在休息,所以我们可以合理假设该时间段内航班数量较少。
  • 早上(06:01 - 10:00):一些早班机会选择在此时间出发,机场也通常从这个时间段起逐渐进入高峰。
  • 中午(10:01 - 14:00):早上从居住地出发的人们通常在这个时候方便抵达机场,因此选择在该时间段出发的航班可能更多。
  • 下午(14:01 - 19:00):同样,在下午出发更为方便,抵达目的地是刚好是晚上,又不至于太晚,方便找到落脚之处。
  • 晚上(19:01 - 23:59):在一天结束之际,接近凌晨的航班数量可能会更少。

当我们所需的数据不是单个离散的数据而是基于一定范围的时候,我们可以用关键字 BETWEEN x AND y 来设置数据的起止范围。

有了上述准备,我们可以尝试写出统计离港时间在 0 点 至 6 点 间的航班总数。首先选取的目标是 flights 这张表,即 FROM flights。航班总数可以对 FlightNum 进行统计(使用 COUNT 函数),即 COUNT(FlightNum)。限定的条件是离港时间在 0 (代表 00:00)至 600 (代表 6:00)之间,即 WHERE DepTime BETWEEN 0 AND 600。所以我们要写出的语句是:

val queryFlightNumResult = sqlContext.sql("SELECT COUNT(FlightNum) FROM flights WHERE DepTime BETWEEN 0 AND 600")

查看其中 1 条结果:

queryFlightNumResult.take(1)

在这里插入图片描述

在此基础上我们可以细化一下,计算出每天的平均离港航班数量,并且每次只选择 1 个月的数据。这里我们选择的时间段为 10:00 至 14:00 。

// COUNT(DISTINCT DayofMonth) 的作用是计算每个月的天数
val queryFlightNumResult1 = sqlContext.sql("SELECT COUNT(FlightNum)/COUNT(DISTINCT DayofMonth) FROM flights WHERE Month = 1 AND DepTime BETWEEN 1001 AND 1400")

查询得到的结果只有一条,即该月每天的平均离港航班数量。查看一下:

queryFlightNumResult1.take(1)

在这里插入图片描述
你可以尝试计算出其他时间段的平均离港航班数量,并作记录。
最终统计的结果表明:1998 年 1 月,每天最繁忙的时段为下午。该时段的平均离港航班数量为 4356.7 个。
(2)飞哪最准时
要看飞哪最准时,实际上就是统计航班到港准点率。可以先来查询到港延误时间为 0 的航班都是飞往哪里的。
在上面这句话中,有几个信息:

  • 要查询的主要信息为目的地代码。
  • 信息的来源为 flights 表。
  • 查询的条件为到港延误时间(ArrDelay)为 0 。

在面对任何一个问题时,我们都可以仿照上面的思路对问题进行拆解,然后将每一条信息转化为对应的 SQL 语句。

于是最终我们可以得到这样的查询代码:

val queryDestResult = sqlContext.sql("SELECT DISTINCT Dest, ArrDelay FROM flights WHERE ArrDelay = 0")

取出其中 5 条结果来看看。

queryDestResult.head(5)

在这里插入图片描述
在此基础上,我们尝试加入更多的限定条件。

我们可以统计出到港航班延误时间为 0 的次数(准点次数),并且最终输出的结果为 [目的地, 准点次数] ,并且按照降序对它们进行排列。

val queryDestResult2 = sqlContext.sql("SELECT DISTINCT Dest, COUNT(ArrDelay) AS delayTimes FROM flights where ArrDelay = 0 GROUP BY Dest ORDER BY delayTimes DESC")

查看其中 10 条结果。

queryDestResult2.head(10)

在这里插入图片描述

在美国,一个州通常会有多个机场。我们在上一步得到的查询结果都是按照目的地的机场代码进行输出的。那么抽象到每一个州都有多少个准点的到港航班呢?

我们可以在上一次查询的基础上,再次进行嵌套的查询。并且,我们会用到另一个数据集 airports 中的信息:目的地中的三字代码(Dest)即该数据集中的 IATA 代码(iata),而每个机场都给出了它所在的州的信息(state)。我们可以通过一个联结操作将 airports 表加入到查询中。

val queryDestResult3 = sqlContext.sql("SELECT DISTINCT state, SUM(delayTimes) AS s FROM (SELECT DISTINCT Dest, COUNT(ArrDelay) AS delayTimes FROM flights WHERE ArrDelay = 0 GROUP BY Dest ) a JOIN airports b ON a.Dest = b.iata GROUP BY state ORDER BY s DESC")

查看其中 10 条结果。

queryDestResult3.head(10)

在这里插入图片描述
最后还可以将结果输出为 CSV 格式,保存在用户主目录下。

// QueryDestResult.csv只是保存结果的文件夹名
queryDestResult3.rdd.saveAsTextFile("/home/shiyanlou/QueryDestResult.csv")

保存完毕后,我们还需要手动将其合并为一个文件。新打开一个终端,在终端中输入以下命令来进行文件合并。

# 进入到结果文件的目录
cd ~/QueryDestResult.csv/

# 使用通配符将每个part文件中的内容追加到 result.csv 文件中
cat part-* >> result.csv

最后打开 result.csv 文件就能看到最终结果,如下图所示。
在这里插入图片描述

(3)出发延误的重灾区都有哪些
可以大胆地设置查询条件为离港延误时间大于 60 分钟,写出查询语句如下:

val queryOriginResult = sqlContext.sql("SELECT DISTINCT Origin, DepDelay FROM flights where DepDelay > 60 ORDER BY DepDelay DESC")

因为数据已经按照降序的形式进行排列,所以我们取出前 10 个查询结果即为 1998 年内,延误最严重的十次航班及所在的离港机场。

queryOriginResult.head(10)

在这里插入图片描述

三、航班延误时间预测

1、引言

历史数据是对于过去已经发生的事情的一种记录,我们可以根据历史数据对过去进行总结。那么我们是否还能否据此来展望未来呢?

也许你首先想到的方法就是预测。谈到预测就不得不提到当下最热门的学科之一——机器学习。预测也是机器学习相关知识能够完成的任务之一。

作为数据分析人员,我们学习机器学习的主要目的不是对机器学习算法进行各方面的改进(机器学习专家们在为此努力),最低的要求应当是能够将机器学习算法应用到实际的数据分析问题中。

2、引入相关的包

import org.apache.spark._
import org.apache.spark.rdd.RDD
import org.apache.spark.mllib.util.MLUtils
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.mllib.tree.model.DecisionTreeModel

3、DataFrame 转换为 RDD

Spark ML 中的操作大部分是基于 RDD (分布式弹性数据集)来进行的。而之前我们读进来的数据集的数据类型为 DataFrame 。在 DataFrame 中的每一条记录即对应于 RDD 中的每一行值。为此,我们需要将 DataFrame 转换为 RDD。

首先数据从 DataFrame 类型转换为 RDD 类型。从 row 中取值时是按照数据集中各个字段取出 Flight 类中对应字段的值。例如排在第二的 row(3) 取出的是 DataFrame 中 DayofWeek 字段的值,对应的是 Flight 类中的 dayOfWeek 成员变量。

val tmpFlightDataRDD = flightData.map(row => row(2).toString+","+row(3).toString+","+row(5).toString+","+row(7).toString+","+row(8).toString+","+row(12).toString+
  • 5
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值