图片来自pexels.com
“我们很抱歉地通知您,您乘坐的由XX飞往XX的XXXX航班延误。”
相信很多在机场等待飞行的旅客都不愿意听到这句话。随着乘坐飞机这种交通方式的逐渐普及,航延延误问题也一直困扰着我们。航班延误通常会造成两种结果,一种是航班取消,另一种是航班晚点。
在本课程中,我们将通过 Spark 提供的 DataFrame、 SQL 和机器学习框架等工具,基于 D3.js 数据可视化技术,对航班起降的记录数据进行分析,尝试找出造成航班延误的原因,以及对航班延误情况进行预测。
本课程难度较难,属于中级课程,适合具有 Spark 基础的用户学习大数据实际案例和数据可视化技术。
图片来自pexels.com
本课程需要你具有一定的 Spark 基础,以下为推荐在本课程之前需要学习的课程(已按先后顺序进行排列):
在学习过程中,建议手边能够准备纸和笔做相应的记录。代码写起来是非常快的,然而更重要的是如何通过思考去设计这些代码。因此我们会有大量的工作在书写伪代码和记录相关的字段上。
这也是数据分析工作中常见的一个习惯。
本节实验用到的航班数据集仍然是 2009 年 Data Expo 上提供的飞行准点率统计数据。
此次我们选用 1998 年的数据集。你可以通过官方下载链接来下载,也可以获取实验楼为你提供的副本。
如果你是在自己的 Spark 集群上进行学习,则可以选用 2007、2008 年等年份的数据集。它们含有的航班数量更多,能够得到更多的信息。
该数据集的各个字段解释如下:
此外,我们还会用到一些补充信息。如机场信息数据集等。
双击打开桌面上的 Xfce 终端,然后输入下面的命令以下载航班数据集:
wget http://labfile.oss.aliyuncs.com/courses/610/1998.csv.bz2
然后使用解压缩命令对其进行解压:
bunzip2 1998.csv.bz2
解压后的 CSV 数据文件位于你使用解压命令时的工作目录中,默认情况是在 /home/shiyanlou
目录中。
同样地,下载 airports 机场信息数据集,命令如下所示。
wget http://labfile.oss.aliyuncs.com/courses/610/airports.csv
由于 airports 数据集中含有一些非常用字符,我们需要对其进行清洗处理,以防止部分记录字符的不能被识别错误引起后续检索的错误。
在终端中输入命令 refine
来启动 OpenRefine。 OpenRefine 是 Google 主导开发的一款开源数据清洗工具。启动命令如下。
refine
当出现下图所示的提示信息后,在浏览器中打开 URL http://127.0.0.1:3333/
。
Open Refine 启动成功的标志是出现
Point your browser to http://127.0.0.1:3333 to start using Refine
的提示,否则请耐心等待它的启动(同时在线用户过多时,启动时间约在3-5分钟)。
浏览器中会出现 OpenRefine 的应用网页,如下图所示。请选择刚刚下载的机场信息数据集,并点击 Next 按钮进入下一步。
在数据解析步骤中,直接点击右上角的 Create Project
按钮创建数据清洗项目。
稍作等待,项目创建完成后,就可以对数据进行各种操作。实验楼在稍后会提供 OpenRefine 的详细教程,此处只需要按照提示对数据集进行相应操作即可。
点击 airport 列旁边的下拉菜单按钮,然后在菜单中选择 Edit Column -> Remove this column 选项,以移除 airport 列。具体操作如下图所示。
请按照同样的方法,移除 lat 和 long 列。最后的数据集应只包含 iata 、city、state、country 四列。
最后我们点击右上角的 Export 按钮导出数据集。导出选项选择 Comma-separated value,即 CSV 文件。
然后在弹出的下载提示对话框中选择“保存文件”,并确定。
该文件位于 /home/shiyanlou/下载
目录中,请在文件管理器中将其剪切至 /home/shiyanlou
目录,并覆盖源文件。步骤如下图所示。
首先双击打开桌面上的 主文件夹
,找到其中的 下载
目录。右键点击 CSV 文件,选择剪切。
然后回到主目录,在空白处右键点击,选择“粘贴”即可。
最后关闭浏览器和运行着 OpenRefine 的终端即可。
为了更好地处理 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
注意:
该操作需要联网权限。如果遇到网络访问较慢,或者是您当前不具备访问互联网的权限时,请参考文末的常见问题“无法访问外网时,应如何通过加载CSV解析库的方式进入 Spark Shell ”,问题解答中提供了解决方案。
等待 Spark Shell 启动完成后,输入以下命令来导入数据集。
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")
稍后我们将基于这些临时表来做一些 SQL 查询。
(图片来自airlines.net)
在探索数据之前,我们已经知道该数据共有 29 个字段。根据出发时间、出发 / 抵达延误时间等信息,我们可以大胆地提出下面这些问题:
请用一张纸记录下上述问题。如果你还有自己想要探索的问题,也请记录下来,稍后尝试自己完成。
图片来自pexels.com
我们已经把数据注册为临时表,对于上述问题的解答实际上就变成了如何设计合适的 SQL 查询语句。在数据量非常大的时候,Spark SQL 的使用尤为方便,它能够直接从 HDFS 等分布式存储系统中拉取数据进行查询,并且能够最大化地利用集群性能进行查询计算。
分析某个问题时,要想办法将解答问题的来源落实在数据集的各个指标上。当问题不够详细时,可以取一些具有代表性的值作为该问题的答案。
例如,航班分为到港(Arrive)和离港(Depart)航班,若统计所有机场在每天的某个时间段内离港航班数量,就能在一定程序上反映这个时段的航班是否繁忙。
那么,当我们提到“统计……的数量”的时候,我们在说什么?
数据集中的每一条记录都朴实地反映了航班的基本情况,但它们并不会直接告诉我们每一天、每一个时段都发生了什么。为了得到后者这样的信息,我们需要对数据进行筛选和统计。
于是我们会顺理成章地用到 AVG(平均值)、COUNT(计数)和 SUM(求和)等统计函数。
为了分时间段统计航班数量,我们可以大致地将一天的时间分为以下五段:
这样的分段都是基于一些假设的。如果你认为你有更合理的分段方式,不妨将它们用到后续的分析工作中。
不要忘了在草稿纸上记录下这些重要的时间段,在设计代码时会用到它们。
当我们所需的数据不是单个离散的数据而是基于一定范围的时候,我们可以用关键字 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
。所以我们要写出的语句是:
请在 Spark Shell 中输入以下代码。
val queryFlightNumResult = sqlContext.sql("SELECT COUNT(FlightNum) FROM flights WHERE DepTime BETWEEN 0 AND 600")
查看其中 1 条结果。
请在 Spark Shell 中输入以下代码。
注意:此步骤比较耗时,请耐心等待计算完成。
queryFlightNumResult.take(1)
在此基础上我们可以细化一下,计算出每天的平均离港航班数量,并且每次只选择 1 个月的数据。这里我们选择的时间段为 10:00 至 14:00 。
请在 Spark Shell 中输入以下代码。
// COUNT(DISTINCT DayofMonth) 的作用是计算每个月的天数
val queryFlightNumResult1 = sqlContext.sql("SELECT COUNT(FlightNum)/COUNT(DISTINCT DayofMonth) FROM flights WHERE Month = 1 AND DepTime BETWEEN 1001 AND 1400")
查询得到的结果只有一条,即该月每天的平均离港航班数量。查看一下:
请在 Spark Shell 中输入以下代码。
注意:此步骤比较耗时,请耐心等待计算完成。
queryFlightNumResult1.take(1)
你可以尝试计算出其他时间段的平均离港航班数量,并作记录。你的结论是否与下方的结论相同呢?
最终统计的结果表明:1998 年 1 月,每天最繁忙的时段为下午。该时段的平均离港航班数量为 4356.7 个。
要看飞哪最准时,实际上就是统计航班到港准点率。
可以先来查询到港延误时间为 0 的航班都是飞往哪里的。
在上面这句话中,有几个信息:
在面对任何一个问题时,我们都可以仿照上面的思路对问题进行拆解,然后将每一条信息转化为对应的 SQL 语句。
请尝试根据已提供的信息,完成 SQL 语句的设计。
于是最终我们可以得到这样的查询代码:
请在 Spark Shell 中输入以下代码。
val queryDestResult = sqlContext.sql("SELECT DISTINCT Dest, ArrDelay FROM flights WHERE ArrDelay = 0")