《Spark高级数据分析》学习一 —— 数据分析基础

简单数据分析

数据集下载:数据集下载

下载完成后,首先解压文件

创建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))

[外链图片转存失败(img-KJm3eZjL-1563175833782)(imgs/20190715-142224.png)]

聚合

用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()

[外链图片转存失败(img-M15avph8-1563175833785)(imgs/20190715-143540.png)]
因为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)

[外链图片转存失败(img-stAjPz9x-1563175833787)(imgs/20190715-144320.png)]

连续变量的概要统计

对类别变量技术相对较小的数据,可以使用Spark的countByValue动作创建直方图,对于连续变量,比如统计均值,标准差,和极值,使用RDD隐式动作stats来完成概要统计信息
在使用之前需要消除数据中缺失值所带来的影响,否则出现统计出错的情况,如下所示
[外链图片转存失败(img-UPEPO1Iz-1563175833788)(imgs/20190715-145120.png)]

import java.lang.Double.isNaN
parsed.map(md=>scores(0)).filter(!isNaN(_)).stats()

[外链图片转存失败(img-smzxVJhl-1563175833789)(imgs/20190715-145759.png)]
使用Scala的Range结构创建一个循环便利每个下标并统计信息

val stats = (0 until 9).map(i => {
parsed.map(md => md.scores(i)).filter(!isNaN(_)).stats()
})

[外链图片转存失败(img-EFxC4qne-1563175833791)(imgs/20190715-150555.png)]

为计算概要信息创建可重用代码

使用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)}
  })
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值