一、源数据
本章所分析的数据来自于纽约时报发布的美国新冠肺炎疫情数据(https://github.com/nytimes/covid-19-data/blob/master/us-counties.csv),它记录了从美国发现首例确诊病例以来各县截至当天的累计确诊病例和累计死亡病例数。具体的数据格式如下:
date,county,state,fips,cases,deaths
2020-01-21,Snohomish,Washington,53061,1,0
2020-01-22,Snohomish,Washington,53061,1,0
2020-01-23,Snohomish,Washington,53061,1,0
2020-01-24,Cook,Illinois,17031,1,0
2020-01-24,Snohomish,Washington,53061,1,0
2020-01-25,Orange,California,06059,1,0
2020-01-25,Cook,Illinois,17031,1,0
2020-01-25,Snohomish,Washington,53061,1,0
2020-01-26,Maricopa,Arizona,04013,1,0
- date:日期
- county:县名
- state:该县所属的州
- fips:FIPS代码,前2位标识州,后3位标识县
- cases:该县截至当日的累计确诊病例数
- deaths:该县截至当日的累计死亡病例数
需要注意的是,表格中的数据都是累计数据,而非每天的新增数据。
二、练习题
0. 数据预处理
我们将原始文件存放在HDFS的/SparkLearning目录下,然后从HDFS中读取数据到RDD格式。后续我们将只使用data这个RDD进行数据分析。
// 读取源数据
val rawFile = sc.textFile("hdfs:///SparkLearning/us-counties.csv")
val data = rawFile
// 删除表头,即"date,county,state,fips,cases,deaths"
.mapPartitionsWithIndex((idx, iter) => if (idx == 0) iter.drop(1) else iter)
// 仅保留date、county、state、cases和deaths字段,并将cases和deaths转为整型
.map(line => {
val entries = line.split(',')
(
entries(0),
entries(1),
entries(2),
entries.lift(4).getOrElse("0").toInt,
entries.lift(5).getOrElse("0").toInt
)
})
1. 统计美国截止每日的累计确诊人数和累计死亡人数
我们可以以date作为分组字段,对cases和deaths字段进行汇总统计。
val res = data
// 转为(date, (cases, deaths))格式
.map(x => (x._1, (x._4, x._5)))
// 以date为key进行规约,得到截止每日的累计确诊人数和累计死亡人数
.reduceByKey((x, y) => (x._1 + y._1, x._2 + y._2))
// 按date从早到晚进行排序
.sortByKey()
// 将数据转为(date, cases, deaths)格式
.map(x => (x._1, x._2._1, x._2._2))
2. 统计截至2020.5.19,美国累计确诊人数最多的十个州
首先计算出截止2020.5.19美国各州的累计确诊人数,然后按确诊人数降序排列,并取前10个州。
val res = data
// 仅保留2020-05-19当天的疫情数据
.filter(_._1 == "2020-05-19")
// 转为(state, cases)格式
.map(x => (x._3, x._4))
// 以state为key进行规约,得到各州截至2020-05-19的累计确诊人数
.reduceByKey(_ + _)
// 按确诊人数对数据进行从大到小的排序
.sortBy(_._2, ascending = false)
// 取出确诊人数前十的州
.take(10)
3. 统计截止2020.5.19,全美各州的病死率
按照“病死率 = 死亡数 / 确诊数”计算各州的病死率。
val res = data
// 仅保留2020-05-19当天的疫情数据
.filter(_._1 == "2020-05-19")
// 转为(state, (cases, deaths))格式
.map(x => (x._3, (x._4, x._5)))
// 以state为key进行规约,计算出各州的累计确诊数和死亡数
.reduceByKey((x, y) => (x._1 + y._1, x._2 + y._2))
// 按照"病死率 = 死亡数 / 确诊数"计算,并转换为Double类型
.mapValues(x => 1.0 * x._2 / x._1)
4. 统计美国每日的新增确诊人数
因为“新增数 = 今日数 - 昨日数”,这里可以使用连接(join),将今日的累计数据t1和昨日的累计数据t2进行连接,连接条件是t1.date = t2.date + 1,然后使用t1.totalCases – t2.totalCases计算该日新增。
import java.time.LocalDate
// 截至今日的累计确诊人数
val confirmed = data
// 转为(date, cases)格式
.map(x => (x._1, x._4))
// 以date为key进行规约,得到截止每日的累计确诊人数
.reduceByKey(_ + _)
// 截至昨天的累计确诊人数
val confirmedYesterday = confirmed
// 转为(date, cases)格式,其中date为昨天的日期
.map(x => (LocalDate.parse(x._1).plusDays(1).toString, x._2))
// 求今日的新增确诊人数
val res = confirmed
.join(confirmedYesterday)
// 计算新增确诊人数,即今日累计确诊数-昨日累计确诊数
.mapValues(x => x._1 - x._2)
// 按date从早到晚进行排序
.sortByKey()