简单数据分析
数据集下载:数据集下载
下载完成后,首先解压文件
创建RDD
要在文件或目录上创建RDD,使用textFile方法传入文件或目录名称
val rawblocks=sc.textFile("file:///media/hadoop/Ubuntu/data/donation")
把数据从集群上获取到客户端
最简单的一个方法,使用first,即向客户端返回RDD的第一个元素
rawblocks.first
使用take方法获取记录关联数据集的前十行记录
val head=rawblocks.take(10)
使用foreach方法并结合println来打印数组的每个值
head.foreach(println)
接下来需要对CSV文件的标题行过滤调,以免影响后续分析,在这里使用标题行中出现的"id_1"字符串作为过滤条件
def isHeader(line: String): Boolean={
line.contains("id_1")
}
通过用Scala的Array类的filter方法打印结果
head.filter(isHeader).foreach(println)
完成过滤标题行这个目标,scala有几种方法
-
使用Array类的filterNot方法
head.filterNot(isHeader).foreach(println) -
通过scala对匿名函数的支持,在filter函数里面对isHeader取非
head.filter(x => !isHeader(x)).length -
使用下划线()表示匿名函数的参数
head.filter(!isHeader()).length
把代码从客户端发送到集群
代码
val noheader=rawblocks.filter(!isHeader(_))
用于过滤集群上整个数据集,与本地机器上的head数组语法完全相同
查看结果
noheader.first
用元组和case class对数据进行结构化
数据有如下结构,前两个字段是整数型ID,代表记录中匹配的两个病人,后面九个值是双浮点京都,代码病人记录中不同字段(包含数据丢失情况),最后一个字段是布尔型,代码该病人记录是否匹配
首先从head数组中取出一条记录
val line=head(5)
使用split函数将line中不同部分拆开
val pieces=line.split(',')
接下来使用类型转化函数把pieces的单个元素转换成合适的类型
val id1=pieces(0).toInt
val id2=pieces(2).toInt
val matched=pieces(11).toBoolean
因为在浮点数类型的九个匹配分值字段中存在 " ? ",所以使用如下方式转换
def toDouble(s:String)={
if("?".equals(s)) Double.NaN else s.toDouble
}
val rawscores=pieces.slice(2,11)
val scores=rawscores.map(toDouble)
把所有的解析代码合并到一个函数,在一个元组中返回所有解析好的值
def parse(line: String)={
val pieces=line.split(',')
val id1=pieces(0).toInt
val id2=pieces(1).toInt
val scores=pieces.slice(2,11).map(toDouble)
val matched=pieces(11).toBoolean
(id1,id2,scores,matched)
}
使用case class 来根据名称访问字段
case class MatchData(id1:Int,id2:Int,scores:Array[Double],matched:Boolean)
def parse(line: String)={
val pieces=line.split(',')
val id1=pieces(0).toInt
val id2=pieces(1).toInt
val scores=pieces.slice(2,11).map(toDouble)
val matched=pieces(11).toBoolean
MatchData(id1,id2,scores,matched)
}
通过名字来访问MathcData的字段
val md=parsed(line)
md.matched
md.id1
完成的在单条记录上测试解析函数,接下来甬道head数组所在元素上
val mds=head.filter(!isHeader(x)).map(x=>parse(x))
测试head数组无误,将函数用于集群
val parsed=noheader.map(line=>parse(line))
聚合
用spark分别在本地客户端和集群上对MatchData执行简单聚合操作来计算匹配和不匹配的记录数量,使用groupBy方法
val grouped=mds.groupBy(md=>md.matched)
grouped.mapValues(x=>x.size).foreach(println)
因为在本地数据中所有的条目都是匹配的,因此返回唯一的条目是元组(true,9)
创建直方图
使用countByValue来计算parsed中的MatchData记录有多少个matched的字段值为true或false
val matchCounts=parsed.map(md=>md.matched).countByValue()
因为scala的ma类没有提供根据内容的键或值的排序方法,所以将Map转换成Scala的可支持排序的Seq类型,然后使用sortBy方法控制按照何值排序
val matchCountsSeq=matchCounts.toSeq
matchCountsSeq.sortBy(_._1).foreach(println)
matchCountsSeq.sortBy(_._2).foreach(println)
默认情况下sortBy函数对数值按照升序排序,可以使用reverse方法改变排序方式
matchCountsSeq.sortBy(_._2).reverse.foreach(println)
连续变量的概要统计
对类别变量技术相对较小的数据,可以使用Spark的countByValue动作创建直方图,对于连续变量,比如统计均值,标准差,和极值,使用RDD隐式动作stats来完成概要统计信息
在使用之前需要消除数据中缺失值所带来的影响,否则出现统计出错的情况,如下所示
import java.lang.Double.isNaN
parsed.map(md=>scores(0)).filter(!isNaN(_)).stats()
使用Scala的Range结构创建一个循环便利每个下标并统计信息
val stats = (0 until 9).map(i => {
parsed.map(md => md.scores(i)).filter(!isNaN(_)).stats()
})
为计算概要信息创建可重用代码
使用StatsWithMissing处理缺失值
import org.apache.spark.util.StatCounter
class NAStatCounter extends Serializable {
val stats: StatCounter = new StatCounter()
var missing: Long = 0
def add(x: Double): NAStatCounter = {
if (java.lang.Double.isNaN(x)) {
missing += 1
} else{
stats.merge(x)
}
this
}
def merge(other: NAStatCounter): NAStatCounter = {
stats.merge(other.stats)
this
}
override def toString = {
"stats: " + stats.toString + " NaN: " + missing
}
}
object NAStatCounter extends Serializable {
def apply(x: Double) = new NAStatCounter().add(x)
}
将多个Array[NAStatCounter]聚合到一个Array[NAStatCounter]中
val nas1 = Array(1.0, Double.NaN).map(d => NAStatCounter(d))
val nas2 = Array(Double.NaN, 2.0).map(d => NAStatCounter(d))
val merged = nas1.zip(nas2).map(p => p._1.merge(p._2))
或者使用
val merged = nas1.zip(nas2).map { case (a, b) => a.merge(b) }
也可以使用reduce函数
val nas = List(nas1, nas2)
val merged = nas.reduce((n1, n2) => {
n1.zip(n2).map { case (a, b) => a.merge(b) }
})
综上可以得出为任何RDD[Array[Double]]计算所需要统计信息的函数
def statsWithMissing(rdd: RDD[Array[Double]]): Array[NAStatCounter]={
val nastats=rdd.mapPartitions((iter: Iterator[Array[Double]])=>{
val nas: Array[NAStatCounter]=iter.next().map(d=>NAStatCounter(d))
iter.foreach(arr=>{
nas.zip(arr).foreach{case(n,d)=>n.add(d)}
})
Iterator(nas)
})
nastats.reduce((n1,n2)=>{
n1.zip(n2).map{case(a,b)=>a.merge(b)}
})
}