spark高级数据分析第二章

来源:互联网 发布:php项目源代码下载 编辑:程序博客网 时间:2024/05/17 08:15

首先说一下为什么写这个呢?因为我在看第二章时候遇到了一段代码,看了好长时间才明白了 ,写这个博客主要就是为了讲解那段代码自己的理解,我也是根据最后的结果来猜测这段代码,然后再去深度理解代码,最后才搞懂了这段代码的含义。

其中数据类型是如下格式

“id_1” “id_2” “cmp_fname_c1” “cmp_fname_c2” “cmp_lname_c1” “cmp_lname_c2” “cmp_sex” “cmp_bd” “cmp_bm” “cmp_by” “cmp_plz” “is_match”
37291 53113 1 ? 1 1 1 1 1 1 1 TRUE
70031 70237 1 1 ? 1 1 1 1 1 1 TRUE

数据保存在txt文件中,并且每个数据之间都是有逗号隔开的,当然不是我画的表存在,但是我们我可以看做一个表,第一行是列名,然后第二行到最后分别是每列对应的数值。

当我们处理数据时候首先给过滤掉第一行:

def isHeader(line:String):Boolean{
line.contains(“id_1”)//head包含id_1 返回true 非head返回false
}
lines=sc.textFile(….)
noHeadLines=lines.filter(x=>!isHeader(x))//filter留下的是ture的类型 而这里我们需要的是非head

我们可以通过case class方式将每一行的数据封装到一个类里面,其中id1对应第一列、 id2对应第二列、scores对应第三到第十一列、matched对应第十二列。

case class MatchData(id1:Int,id2:Int,scores:Array[Double],matched:Boolean)

下一步就是讲数据封装到case class MatchData里面了,细心的人会发现这里面有?,也就是我们数据处理经常遇到的缺失值的情况,我们把第三列到第十一列封装到Array[Double]时候需要将数据进行toDouble转换操作,但是如果遇到?时候会报错,因此我们需要对其进行处理。

def toDouble(s:String)={
if(“?”.equals(s))Double.NaN else s.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
MatchData(id1,id2,scores,matched)
}

其中pieces是一行数据已经通过逗号分隔开的相当于一个数组,然后我们可以分别取索引对应的值,索引下标是0开始 一共十二列,所以对应的下标是0-11,然后pieces.slice(2,11)的作用是分别取pieces(2)……pieces(10)进行map(toDouble)操作,讲数据是String类型变为Double类型,?缺失值是NaN。

noHeadLines=lines.filter(x=>!isHeader(x))//对这一步的数据接着处理

val parsed=noHeadLines.map(line => parse(line))//将数据每一行都封装到MatchData中 一行对应一个MatchData

聚合操作

val mds=lines.filter(x =>isHeader(x)).map(x=>parse(x))
val grouped=mds.groupBy(md =>md.matched)//根据最后一列matched取值进行聚合 ture false 两组
grouped.mapValues(x=>x.size).foreach(println)//利用mapValues方法计数 返回这个类型(true,9)

创建直方图

val parsed=noHeadLines.map(line => parse(line))//下面代码接着parsed操作
val matchCounts =parsed.map(md=>md.matched).countByValue()//RDD[T]已经有countByValue()方法了
//可以跟上一步的聚合操作对比一下,输出是一样的类型(true,9) (false,10)
val matchCountsSeq=matchCounts.toSeq //Scala的map类没有提供排序
matchCountsSeq.sortBy(_._1).foreach(println) //按照true false属性值排序,,,

matchCountsSeq.sortBy(_._2).foreach(println) //按照属性值对应的数据个数排序 默认升序

matchCountsSeq.sortBy(_._2).reverse.foreach(println) //按照属性值对应的数据个数排序 降序

上面代码基本都是基于集群spark api写的 ,书中有scala跟spark api两种情况,可以自己去参考这本书。

这本书第二章最难理解的点到了

连续变量的概要统计。

val parsed=noHeadLines.map(line => parse(line))//下面代码接着parsed操作
parsed.map(md=>md.scores(0)).stats()
/**
*可以得到StatCounter=(count: ,mean: ,stdev: ,max: ,min: )这几个统计信息,基于scores(0)这一列的统计
*scores(0)是什么呢 就是我们封装到MatchData类中的scores变量,0是原数据里面第三列,
*但是这里会报错的因为数据里面toDouble时候有NaN,计算时候会出现错误
*做下面的处理就可以
*/

parsed.map(md=>md.scores(0)).filter(!isNaN).stats()

//scores是Array[Double]对应着九列的数值,我们每一列都去统计信息如下做法
val stats=(0 until 9).map(i=>{

parsed.map(md=>md.scores(i)).filter(!isNaN).stats()
})

//stats(1)方式取就可以

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

import org.apache.spark.util.StatCounter
class NAStatCounter extends Serializable{ //我要要在spark rdd内部使用该实例因此Serializable

val stats:StatCounter=new StatCounter()//不可变变量
var missing:Ling=0 //可变变量

def merge(other:NAStatCounter):NAStatCounter={//可以用来处理两个NAStatCounter实例直接的操作
stats.merge(other.stats) //StatCounter类中的merge方法
missing+=other.missing
this
}

def add(x:Double):NAStatCounter={//在一个实例中添加某个值后然后重新计算if(java.lang.Double.isNaN(x)){missing+=1} //缺失值的话对missing处理else{stats.merge(x)

       //否则就将其与stats进行merge,我们看到x是Double类型的,然而merge参数是NAStatCounter类型

//这里面涉及到下面的apply方法了,继续看下面 } this //返回的this}override def toString={“stats:”+stats.toString+”NaN:”+missing}/**object相当于java中的static,当我们new一个类时候自动会调用apply方法,scala工程师很不喜欢new有了下面这个代码 可以直接写类名字 不需要new这个关键字了*val nastats=NAStatCounter.apply(17.29)

*val nastats=NAStatCounter(17.29)

  • 上面两个代码执行一样,直接new了一个NAStatCounter对象然后调用apply方法然后apply里面调用add(),把17.29传入进去,由于非NaN因此调用else中的stats.merge(x)/object NAstatCounter extends Serializable{def apply(x:Double)=new NAStatCounter().add(x)}}下面是StatConter类里面merge方法源码,详细链接StatCount源码

def merge(value: Double): StatCounter = {
val delta = value - mu //离差或者差量: 该数与平均值的差值
n += 1 //总数加1
mu += delta / n // 原mu我们叫做mu1,新的叫mu2,那么mu1 = sum / count ,mu2 = (sum + value) / count + 1 ,所以两者之间的差为: (count * value - count * mu1) / (count * (count + 1)),进一步化简: (value - mu1) / (count + 1),而value - mu1 = delta,所以可以得到上述公式
m2 += delta * (value - mu) // 此时注意的是mu在上一步中完成更新,其推导过程类似上一步,这里就不再展开推导了
maxValue = math.max(maxValue, value)
minValue = math.min(minValue, value)
//maxValue = if(maxValue > value) maxValue else value
//minValue = if(minValue < value) minVa /* Clone this StatCounter /
def copy(): StatCounter = {
val other = new StatCounter
other.n = n
other.mu = mu
other.m2 = m2
other.maxValue = maxValue
other.minValue = minValue
other
}lue else value
this //返回本身
}

由于初学,我基础不是很好,这段代码也是看了半天,也比较考验思维能力,及其对代码的理解

import org.apache.spark.rdd.RDD

// rdd 输入参数类型是RDD[Array[Double]]

// Array[NAStatCounter] 返回类型
def statWithMissing(rdd:RDD[Array[Double]]):Array[NAStatCounter]={
val nastats=rdd.mapPartitions(
(iter:Iterator[Array[Double]]) => {

     //将一行里面的每一列数值生成NAStatCounter对象然后放到array里面 通过map实现的,并且先生成一个nas:Array[NAStatCounter]之后某一个分区都是返回的它

val nas:Array[NAStatCounter]=iter.next().map(d => NAStatCounter(p))
iter.foreach(arr=>{
/**
*

*nas当前行的下一行生成的Array[NAStatCounter]

*iter:Array[Double] 迭代的当前行 将其foreach,
*然后nas通过zip方法形成下标相同的组成一个元组形式( ,)( ,)…….
*zip后再foreach是处理每一个下标相同的元组内部两个元素( ,)即 _1 _2
*case(n,d) _1即n对应nas的NAStatCounter类型 _2即d对应的是iter的Double
*接着n:NAStatCounter就可以调用自己的方法add(d:Double)
* 非NaN的话将会进行merge处理然后返回n,NaN的直接返回this即n,n时nas在某一列的NAStatCounter
*宏观看是对每一列进行处理的,得到每一列的计算概要信息 ,
*而每一列的计算概要信息对应一个NAStatCounter的实例,
*/
nas.zip(arr).foreach{case(n,d)=>n.add(d)}
})
Iterator(nas)
}
)

nastats.reduce((n1,n2)=>{
n1.zip(n2).foreach(case(a,b)=>a.merge(b))
})
}

之后我们就可以去调用了:

val statsm=statsWithMissing(parsed.filter(.matched).map(.scores))

val statsn=statsWithMissing(parsed.filter(!.matched).map(.scores))
statsm.zip(statsn).map{case(m,n)=>
(m.missing+n.missing,m.stats.mean-n.stats.mean)
}.foreach(println)
………..输出是将MatchData的scores变量即原数据第三列到就十一列根据matched分成true跟false两类,
然后计算这两类的缺失值和,即每列的缺失值和,然后计算这两类的均值差大小,
然后我们根据缺失值及其均值差可以得到一些信息,比如均值差大的可能就比较有用,缺失值大的特征也就没有用

0 0
原创粉丝点击