Spark简单数据分析---Spark学习笔记(2)
来源:互联网 发布:单片机芯片型号 编辑:程序博客网 时间:2024/06/16 20:10
上次学习Spark还是两个月前的事情,期中好多事情耽搁了,现在开始正式地学习Spark的使用。前面所学习的Scala基本知识也能用上了,终于可以从简单了解过渡到应用和实现的阶段。
这次学习的内容跟进的是《Spark高技术据分析》一章,里面的内容感觉很灵活,不仅是从最简单的Spark对数据的处理开始,而且其中稍带的Scala知识提及,可以加深对Scala的印象,从而运用起来就更加能了解其中的意义。
1.用Spark建立弹性分布式数据集(RDD)
弹性分布式数据集(Resilient Distributed Dataset):Spark所提供的基本抽象,代表分布在集群中多台机器上的对象集合。
SparkContext:负责协调集群上Spark作业的执行
有两种方式创建RDD:
- 基于外部数据源创建RDD
- 在一个过或多个RDD上执行转换操作来创建RDD,例如:过滤、汇总、关联等
例如可以调用SparkContext如下方法创建一个自定义的RDD:
scala> val rdds=sc.parallelize(Array(1,2,3,4),4)
第一个参数代表并行化的对象集合,第二个参数代表分区的个数。分区就是数据集中的子集,Spark的并行单位就是分区。
也可以调用textFile方法来获取本地文件或者集群上的文件,来得到数据源:
scala> val rdd=sc.textFile("/home/coder-z/linkage")
如果路径是一个目录的话,会将目录中所有的文件数据作为RDD的输入。
假设我们是从文件读入数据的,那么得到的RDD中的数据基本单位是String,现在的问题就是如何将数据转换为我们想要的数据类型。
2.RDD数据处理
RDD动作:RDD的操作并不会导致集群的分布式计算,只有调用了action时分布式计算才会执行。
当我们得到一个RDD时,为了检验获得的数据集是否是指定文件中的,可以调用RDD的first方法:
scala> rdd.first
这里的first是一个方法,而不是字段。因为scala中,如果方法没有设置参数,那么可以直接省略括号。
现在执行如下动作:
val head=rdd.take(10) //从rdd中抽取前10个数据记录head.fliter(!isHead(_)).foreach(println) //过滤掉头信息,并输出
head是一个Array[String]类型的数组,调用filter方法用来过滤数组中的信息。这行代码体现了Scala作为函数式编程的简易性,尽可能使用函数作为一种“变量”来使用。其中的isHead方法定义如下:
def isHead(line:String)=line.contains("id_1")
是一个返回Boolean类型的函数,Scala申明函数时可以不申明返回类型,Scala会自行推断,但是如果函数情况比较复杂,Scala不一定能够推断出函数的返回值类型。
foreach方法接收一个函数println作为参数对数据集上的每个元素进行println定义的处理。而每个元素就作为参数,传入了println函数中。
同样地,数据集中的每个元素作为参数也传入isHead中,可以用占位符”_”来表示传入的元素。当然,如下代码也做相同处理:
head.filter(x=>!isHead(x)).foreach(println)
x就代表正在处理的元素,交给isHead进行处理。
3.使用元组和case class进行数据结构化*
现在我们要将获得的字符串解析成所需要的格式,先来看一下我们需要的数据格式。
607,53170,1,?,1,?,1,1,1,1,1,TRUE
这是数据集中其中的一条记录,根据数据集的标题信息,我们得到格式:
(Int,Int,Double,Double,Double,Double,Double,Double,Double,Double,Double,Boolean)
其中记录里会有”?”数值,这是不确定的信息,应该在格式转化的时候过滤为”NaN”,这样的数据过滤在之后对数据集的分析中也是需要的。
先简单定义一个能够把第3-11个数据转换成Double的函数:
scala> def toDoubles(s:String)={ if("?".equals(s)) Double.NaN else s.toDouble }
再利用Scala中的case class 语法,我们可以自定义一条数据记录的格式:
case class MatchData(id_1:Int,id_2:Int,scores:Array[Double],matched:Boolean)
运用MatchData作为一条记录,可以直接使用传入的参数名作为字段名调用。
例如:matchData.id_1,而不是matchData(0)这样用序号使用了。
接着,我们就指定定义一个结构化一整条记录的函数:
def parse(line:String)={ val pieces=line.split(",") val scores=pieces.slice(2,11).map(toDoubles) val id_1=pieces(0).toInt val id_2=pieces(1).toInt val matched=pieces(11).toBoolean MatchData(id_1,id_2,scores,matched)}
如下代码运用parse进行对数据集的结构化:
val mas=head.filter(!isHead(_)).map(line=>parse(line))
map函数是对RDD数据集合上的所有数据进行处理。
RDD缓存:
对于parse的调用,parse只会在RDD执行某种输出调用时才会真正调用,所以每一次的这类调用parse都会执行一遍。因此需要有cache方法持久化数据。
Spark提供了缓存RDD的机制,RDD的cache方法调用后,会指示在下一次的RDD计算后对RDD进行存储。例如下面的示例:
cached.cache()cached.count()cached.take(10)
RDD的第一次计算出现在count()方法中,所以当count()方法调用完后,RDD会被储存起来,之后的take方法获得的就是已经经过储存的RDD,而不是从本地取出重新计算一次的RDD。
4.概要信息
关于前面获取的数据,我们已经做到可以将它们结构化为我们想要的格式了,但是里面还是稍微有一点点的瑕疵就是数据记录并不是完善的,其中的”?”符号代表数据记录项的缺失,那么我们如何统计我们的数据集的概要信息?
这里我们就要用到Spark提供的StatCounter对象生成统计概要信息,我们自定义一个NAStatCounter如下:
import org.apache.spark.rdd.RDDimport org.apache.spark.util.StatCounterclass NAStatCounter extends Serializable { val stats:StatCounter=new StatCounter() //StatCount对象,用来生成概要信息 var missing:Long=0 //统计NaN的个数 def add(x:Double): NAStatCounter = { if(java.lang.Double.isNaN(x)) { missing=missing+1 }else{ stats.merge(x) //向当前实例stats加入统计信息 } this } def merge(others:NAStatCounter):NAStatCounter= { //加入一组的概要信息 stats.merge(others.stats) missing+=others.missing this } override def toString: String = { "stats:"+stats.toString()+"NaN:"+missing }object NAStatCounter extends Serializable { def apply(x:Double)=new NAStatCounter().add(x)}
val arr=Array(1.0,Double.NaN,17.29) // 自定义的新数组val nas=arr.map(d=>NAStatCounter(d)) //对数组每个元素进行概要信息统计
这个时候我们也可以做到将多个Array[NAStatCount]h合并到一起,使用zip方法就可以将两个相同长度的数组进行合并。如下所示:
val nas1=Array(1.0,Double.NaN,20.0).map(d=>NAStatCounter(d))val nas2=Array(Double.NaN,2.0,20.7).map(d=>NAStatCounter(d))val merged=nas1.zip(nas2).map{case(a,b)=>a.merge(b)}
如果要在Scala集合上的所有记录执行merge操作,使用reduce方法就可以代替。
reduce 使用关联函数,把集合中两两为T类型的元素映射为一个T类型的元素。
由此,我们对上面的代码段进行修改,使用reduce方法代替:
val nas=List(nas1,nas2)val merge=nas.reduce((n1,n2)=>{ n1.zip(n2).map{(a,b)=>a.merge(b)}})
现在用一段精炼的函数,把对RDD数据集上所有数据记录进行概要信息统表示出来:
def statWithMissing(rdd:RDD[Array[Double]]):Array[NAStatCounter]={ val natats=rdd.mapPartitions((iter:Iterator[Array[Double]])=>{ val nas=iter.next().map(d=>NAStatCounter(d)) iter.foreach(arr=>{ //对每一个Array[Double]操作 nas.zip(arr).foreach{(n,d)=>n.add(d)} //对Array[NAStatCounter]操作 }) Iterator(nas) //最终返回一个Array[NAStatCounter]}) natats.reduce((n1,n2)=>{ n1.zip(n2).map{case(a,b)=>a.merge(b)} //将所有NAStatCounter做聚合处理 })}
我觉得以上这段代码应该多多分析分析透,因为它的风格是很多Spark程序都具有的,并且能够表达出函数式编程的风格所在。
终于等到寒假的第一天开始继续研究研究Spark了,前面事情太多,没有空闲把重心放在Spark上。这也是今年的第一篇吧,曾经看到别人将一礼拜更新一篇博客为目标,那个时候还觉得好像很轻松吧?现在看来根本不是啊,上个月劈头盖脸瞎忙,也没能做到一个礼拜写一次(两个礼拜一次都没有!),接下来要边学着用Spark一边把KMeans写出来,然后还要配合上Hadoop集群,感觉任务也不小呢,加油吧!
- Spark简单数据分析---Spark学习笔记(2)
- spark高级数据分析-scala学习(学习笔记)
- spark高级数据分析-推荐系统(学习笔记)
- Spark学习笔记2
- 大数据Spark企业级实战版【学习笔记】-----Spark Streaming案例分析
- Spark计算Pi---Spark学习笔记2
- 《Spark快速大数据分析》笔记Ch1、2
- spark快速大数据分析笔记_1
- 大数据Spark企业级实战版【学习笔记】---Spark简介
- 大数据Spark企业级实战版【学习笔记】----Spark术语
- 大数据Spark企业级实战版【学习笔记】----Spark Streaming
- 大数据Spark企业级实战版【学习笔记】----Spark Streaming
- spark学习笔记:Spark Streaming
- Spark学习笔记:初识Spark
- Spark学习笔记--Spark基础知识
- spark学习笔记:初识spark
- 慕课网学习spark笔记之数据清洗
- Spark学习笔记-GraphX-2
- 问题 B: 数制转换
- opensips安装教程
- react native -FSEventStreamStart: register_with_server: ERROR: f2d_register_rpc() => (null)
- 内网穿透软件-ngrok
- [刷题]算法竞赛入门经典(第2版) 6-7/UVa804 - Petri Net Simulation
- Spark简单数据分析---Spark学习笔记(2)
- mysql explain 说明
- Android源码分析之SystemServer的创建过程
- The APR based Apache Tomcat Native library which allows optimal performance in
- 集合的三种输出案例
- Java小白成长记-Structs2学习小结
- day14 多线程2
- 2.HTML5教程-HTML5集成环境搭建
- main函数可以写多个