1、RDD提供了两种类型的操作:transformation和action
所有的transformation都是采用的懒策略,如果只是将transformation提交是不会执行计算的,计算只有在action被提交的时候才被触发。
1)transformation操作:得到一个新的RDD,比如从数据源生成一个新的RDD,从RDD生成一个新的RDD
map(func):对调用map的RDD数据集中的每个element都使用func,然后返回一个新的RDD,这个返回的数据集是分布式的数据集
mapValues顾名思义就是输入函数应用于RDD中Kev-Value的Value,原RDD中的Key保持不变,与新的Value一起组成新的RDD中的元素。因此,该函数只适用于元素为KV对的RDD。
mapWith是map的另外一个变种,map只需要一个输入函数,而mapWith有两个输入函数。第一个函数是把RDD的partition index(index从0开始)作为输入,输出为新类型A;第二个函数是把二元组(T, A)作为输入(其中T为原RDD中的元素,A为第一个函数的输出),输出类型为U。
mapPartitions(func):和map很像,但是map是每个element,而mapPartitions是每个partition
mapPartitionsWithSplit(func):和mapPartitions很像,但是func作用的是其中一个split上,所以func中应该有index
mapPartitionsWithIndex(func)函数:mapPartitionsWithIndex的func接受两个参数,第一个参数是分区的索引,第二个是一个数据集分区的迭代器。而输出的是一个包含经过该函数转换的迭代器。下面测试中,将分区索引和分区数据一起输出。
sample(withReplacement,faction,seed):抽样,withReplacement为true表示有放回;faction表示采样的比例;seed为随机种子
takeSample() 函数和上面的sample 函数是一个原理,但是不使用相对比例采样,而是按设定的采样个数进行采样,同时返回结果不再是RDD,而是相当于对采样后的数据进行Collect(),返回结果的集合为单机的数组。
filter(func) : 对调用filter的RDD数据集中的每个元素都使用func,然后返回一个包含使func为true的元素构成的RDD
flatMap(func):和map差不多,但是flatMap生成的是多个结果
flatMapValues类似于mapValues,不同的在于flatMapValues应用于元素为KV对的RDD中Value。每个一元素的Value被输入函数映射为一系列的值,然后这些值再与原RDD中的Key组成一系列新的KV对。
flatMapWith与mapWith很类似,都是接收两个函数,一个函数把partitionIndex作为输入,输出是一个新类型A;另外一个函数是以二元组(T,A)作为输入,输出为一个序列,这些序列里面的元素组成了新的RDD。
union(otherDataset):返回一个新的dataset,包含源dataset和给定dataset的元素的集合
distinct([numTasks]):返回一个包含源数据集中所有不重复元素的新数据集
groupByKey(numTasks):返回(K,Seq[V]),也就是Hadoop中reduce函数接受的key-valuelist
reduceByKey(func,[numTasks]):就是用一个给定的reducefunc再作用在groupByKey产生的(K,Seq[V]),比如求和,求平均数
sortBy (dataSet, boolean)函数:排序。第二个参数默认为true,即升序排序。
sortByKey([ascending],[numTasks]):按照key来进行排序,是升序还是降序,ascending是boolean类型
join(otherDataset,[numTasks]):当有两个KV的dataset(K,V)和(K,W),返回的是(K,(V,W))的dataset,numTasks为并发的任务数。本质是通过cogroup算子先进行协同划分,再通过flatMapValues 将合并的数据打散。
cogroup(otherDataset,[numTasks]):当有两个KV的dataset(K,V)和(K,W),返回的是(K,Seq[V],Seq[W])的dataset,numTasks为并发的任务数
cartesian(otherDataset):笛卡尔积就是m*n,大家懂的
coalesce(numPartitions, true)函数:对RDD中的分区重新进行合并。返回一个新的RDD,且该RDD的分区个数等于numPartitions个数。如果shuffle设置为true,则会进行shuffle。
repartition(numPartitions)随机重新shuffle RDD中的数据,并创建numPartitions个分区。此操作总会通过网络来shuffle全部数据
pipe(command, [envVars])通过POSIX 管道来将每个RDD分区的数据传入一个shell命令(例如Perl或bash脚本)。RDD元素会写入到进程的标准输入,其标准输出会作为RDD字符串返回。
2)action操作:action是得到一个值,或者一个结果(直接将RDD cache到内存中)
reduce(func):说白了就是聚集,但是传入的函数是两个参数输入返回一个值,这个函数必须是满足交换律和结合律的
collect():一般在filter或者足够小的结果的时候,再用collect封装返回一个数组
count():返回的是dataset中的element的个数
first():返回的是dataset中的第一个元素 类似于take(1)
take(n):返回前n个elements,这个士driver program返回的
takeSample(withReplacement,num,seed):抽样返回一个dataset中的num个元素,随机种子seed
takeOrdered(n, [ordering])返回一个由数据集的前n个元素组成的有序数组,使用自然序或自定义的比较器。
saveAsTextFile(path):把dataset写到一个text file中,或者hdfs,或者hdfs支持的文件系统中。对于每个元素,Spark将会调用toString方法,spark把每条记录都转换为一行记录,然后写到file中。
saveAsSequenceFile(path)将数据集的元素,以Hadoopsequencefile的格式,保存到指定的目录下,本地系统,HDFS或者任何其它hadoop支持的文件系统。这个只限于由key-value对组成,并实现了Hadoop的Writable接口,或者隐式的可以转换为Writable的RDD。(Spark包括了基本类型的转换,例如Int,Double,String,等等)
saveAsObjectFile(path)将数据集元素写入Java序列化的可以被SparkContext.objectFile()加载的简单格式中
countByKey():返回的是key对应的个数的一个map,作用于一个RDD
foreach(func):对dataset中的每个元素都使用func
3)其它 函数操作:
lookup操作:通过key找value值。Lookup 函数对(Key, Value) 型的RDD 操作,返回指定Key 对应的元素形成的Seq。
contains(str)函数:包含str字符串
take 和 takeAsTextFile操作
fold,foldLeft, and foldRight之间的区别
trim函数:把字符两端的空格截掉
top 返回最大的 k 个元素。
take 返回最小的 k 个元素。
takeOrdered 返回最小的 k 个元素,并且在返回的数组中保持元素的顺序。
first 相当于top(1) 返回整个RDD中的前k 个元素,可以定义排序的方式 Ordering[T]。返回的是一个含前k 个元素的数组。
fold 和reduce 的原理相同,但是与reduce 不同,相当于每个reduce 时,迭代器取的第一个元素是zeroValue。
aggregate 先对每个分区的所有元素进行aggregate 操作,再对分区的结果进行fold 操作。aggreagate 与fold 和reduce 的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,这种聚集是并行化的。而在fold 和reduce 函数的运算过程中,每个分区中需要进行串行处理,每个分区串行计算完结果,结果再按之前的方式进行聚集,并返回最终聚集结果。
4)Spark 1.4为DataFrame新增的统计与数学函数
随机数据生成(RandomData Generation)
describe函数:概要与描述性统计(Summary and descriptive statistics)
协方差与相关性(Samplecovariance and correlation)
交叉列表(Crosstabulation)
频率项(Frequentitems)
数学函数(Mathematicalfunctions)
2、transformation操作函数总结:
1)、sortBy函数与sortByKey函数:
①sortBy(func, assending, numPartitions)函数:有三个参数,第一个必须要有,后两个可以省略
第一个参数是一个函数,返回类型和RDD中元素的类型是一致的;
第二个参数是ascending,这参数决定排序后RDD中的元素是升序还是降序,默认是true,也就是升序;
第三个参数是numPartitions,该参数决定排序后的RDD的分区个数,默认排序后的分区个数和排序之前的个数相等,即为this.partitions.size。
Scala>val data = List(3,1,90,3,5,12) data: List[Int] =List(3, 1, 90, 3, 5, 12)
scala>val rdd = sc.parallelize(data)
scala> val result = rdd.sortBy(x => x, false, 1) false 为倒序排列;第三个参数为设置rdd的分区个数,默认为原rdd的个数
scala>result.partitions.size res4: Int = 1
②sortByKey(boolean)函数:升序或降序由ascending布尔参数决定,默认true为升序。sortByKey函数作用于Key-Value形式的RDD,并对Key进行排序:主要接受两个函数,含义和sortBy一样。该函数返回的RDD一定是ShuffledRDD类型的,因为对源RDD进行排序,必须进行Shuffle操作,而Shuffle操作的结果RDD就是ShuffledRDD。
对Key进行了排序:key为数字,也可以为字符
scala> val a =sc.parallelize(List("wyp", "iteblog", "com","397090770", "test"), 2)
scala> val b = sc.parallelize(List(3,1,9,12,4)) 也可以为字符型 val b2 = sc.parallelize(List("3","1", "9","12", "4"))
scala> val c = b.zip(a) zip把b和a组成key-value形式,其中b为key,a为value
scala> c.sortByKey().collect 注意sortByKey的小括号不能省
res33: Array[(Int,String)] = Array((1,iteblog), (3,wyp), (4,test), (9,com), (12,397090770))
2)、map函数、flatMap函数
①map(func)与flatMap(func)函数
map是对RDD中的每个元素都执行一个指定的函数来产生一个新的RDD。任何原RDD中的元素在新RDD中都有且只有一个元素与之对应。
scala> val a =sc.parallelize(1 to 9, 3)
scala> val b =a.map(x=>x*2)
scala> b.collect
res48: Array[Int] =Array(2, 4, 6, 8, 10, 12, 14, 16, 18)
把原RDD中每个元素都乘以2来产生一个新的RDD
flatMap(func)函数:与map类似,区别是原RDD中的元素经map处理后只能生成一个元素,而原RDD中的元素经flatmap处理后可生成多个元素来构建新RDD。 如果是多个集合,最后合并为一个集合
举例:对原RDD中的每个元素x产生y个元素(从1到y,y为元素x的值)
scala> val a=sc.parallelize(1 to 4, 2)
scala> val b=a.flatMap(x => 1 to x)
res57: Array[Int] =Array(1, 1, 2, 1, 2, 3, 1, 2, 3, 4)
将Rdd的每个集合元素合并为一个集合:
scala> valkv =sc.parallelize(List(List(1,2),List(3,4),List(3,6,8)))
scala> kv.collect
res8: Array[List[Int]] = Array(List(1, 2), List(3, 4), List(3, 6, 8)) 多个集合
scala>kv.flatMap(x=>x.map(_+1)).collect
res9: Array[Int] = Array(2, 3, 4, 5, 4, 7, 9) 一个集合
②mapPartitions(func)函数:map的一个变种。map的输入函数是应用于RDD中每个元素,而mapPartitions的输入函数是应用于每个分区,也就是把每个分区中的内容作为整体来处理的。最终的RDD由所有分区经过输入函数处理后的结果合并起来的。
mapPartitions还有些变种,比如mapPartitionsWithContext,它能把处理过程中的一些状态信息传递给用户指定的输入函数。还有mapPartitionsWithIndex,它能把分区的index传递给用户指定的输入函数。
scala> val nums =sc.parallelize(1 to 9, 3)
scala> defmyfunc[T](iter: Iterator[T]): Iterator[(T, T)]={
| var res = List[(T, T)]()
| var pre = iter.next
| while(iter.hasNext) {
| val cur = iter.next;
| res.::= (pre, cur)
| }
| res.iterator
| }
scala>nums.mapPartitions(myfunc).collect
res10: Array[(Int,Int)] = Array((1,3), (1,2), (4,6), (4,5), (7,9), (7,8))
mapPartitionsWithIndex(func)函数:mapPartitionsWithIndex的func接受两个参数,第一个参数是分区的索引,第二个是一个数据集分区的迭代器。而输出的是一个包含经过该函数转换的迭代器。下面测试中,将分区索引和分区数据一起输出。
scala> val x =sc.parallelize(List(1,2,3,4,5,6,7,8,9,10), 3)
scala>def myfunc(index: Int, iter:Iterator[Int]): Iterator[String] = {
| iter.toList.map(x => index+"-"+x).iterator
| }
scala>x.mapPartitionsWithIndex(myfunc).collect
res12: Array[String]= Array(0-1, 0-2, 0-3, 1-4, 1-5, 1-6, 2-7, 2-8, 2-9, 2-10)
③mapValues与flatMapValues函数
mapValues顾名思义就是输入函数应用于RDD中Kev-Value的Value,原RDD中的Key保持不变,与新的Value一起组成新的RDD中的元素。因此,该函数只适用于元素为KV对的RDD。
scala> val a =sc.parallelize(List("dog", "tiger", "lion","cat", "panther", "eagle"), 2)
scala> val b =a.map(x => (x.length, x))
scala>b.mapValues("x"+_+"x").collect
res54: Array[(Int,String)] = Array((3,xdogx), (5,xtigerx), (4,xlionx), (3,xcatx), (7,xpantherx),(5,xeaglex))
flatMapValues
flatMapValues类似于mapValues,不同的在于flatMapValues应用于元素为KV对的RDD中Value。每个一元素的Value被输入函数映射为一系列的值,然后这些值再与原RDD中的Key组成一系列新的KV对。
scala> val a =sc.parallelize(List((1,2),(3,4),(3,6)))
scala> val b =a.flatMapValues(x=>x.to(5))
scala> b.collect
res59: Array[(Int,Int)] = Array((1,2), (1,3), (1,4), (1,5), (3,4), (3,5))
原RDD中每个元素的值被转换为一个序列(从其当前值到5),比如第一个KV对(1,2), 其值2被转换为2,3,4,5。然后其再与原KV对中Key组成一系列新的KV对(1,2),(1,3),(1,4),(1,5)。
④mapWith与flatMapWith函数
mapWith是map的另外一个变种,map只需要一个输入函数,而mapWith有两个输入函数。第一个函数是把RDD的partition index(index从0开始)作为输入,输出为新类型A;第二个函数是把二元组(T, A)作为输入(其中T为原RDD中的元素,A为第一个函数的输出),输出类型为U。
举例:把partitionindex 乘以10,然后加上2作为新的RDD的元素。
scala> val x = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10), 3) 有三个分区,每个分区的下标为0,1,2
scala>x.mapWith(a => a*10)((a, b)=>(b+2)).collect
res56: Array[Int] =Array(2, 2, 2, 12, 12, 12, 22, 22, 22, 22)
flatMapWith与mapWith很类似,都是接收两个函数,一个函数把partitionIndex作为输入,输出是一个新类型A;另外一个函数是以二元组(T,A)作为输入,输出为一个序列,这些序列里面的元素组成了新的RDD。
scala> val a =sc.parallelize(List(1,2,3,4,5,6,7,8,9), 3)
scala>a.flatMapWith(x=>x, true)((x, y)=>List(y, x)).collect
res58: Array[Int] =Array(0, 1, 0, 2, 0, 3, 1, 4, 1, 5, 1, 6, 2, 7, 2, 8, 2, 9)
3)、sample函数:sample(withReplacement,faction,seed):抽样,withReplacement为true表示又放回;faction表示采样的比例;seed为随机种子
scala> val a =sc.parallelize(1 to 10000, 3)
scala>a.sample(false, 0.1, 0).count
res15: Long = 956
4)、distinct去重函数、union求并函数、intersection求交集函数
scala> val kv1 =sc.parallelize(List(("A", 1), ("B", 2), ("C", 3),("A", 4), ("B", 5)))
scala> val kv2 =sc.parallelize(List(("A", 4), ("A", 2), ("C", 3),("A", 4), ("B", 5)))
scala> kv2.distinct.collect
res17:Array[(String, Int)] = Array((A,4), (B,5), (C,3), (A,2))
scala> kv1.union(kv2).collect
res18:Array[(String, Int)] = Array((A,1), (B,2), (C,3), (A,4), (B,5), (A,4), (A,2),(C,3), (A,4), (B,5))
scala> kv1.intersection(kv2).collect
res19:Array[(String, Int)] = Array((A,4), (B,5), (C,3))
5)、sortByKey,groupByKey, reduceByKey等transformation操作涉及到了shuffle操作,所以这里引出两个概念宽依赖和窄依赖。
窄依赖(narrowdependencies)
子RDD的每个分区依赖于常数个父分区(与数据规模无关)
输入输出一对一的算子,且结果RDD的分区结构不变。主要是map/flatmap
输入输出一对一的算子,但结果RDD的分区结构发生了变化,如union/coalesce
从输入中选择部分元素的算子,如filter、distinct、substract、sample
宽依赖(widedependencies)
子RDD的每个分区依赖于所有的父RDD分区
对单个RDD基于key进行重组和reduce,如groupByKey,reduceByKey
对两个RDD基于key进行join和重组,如join
经过大量shuffle生成的RDD,建议进行缓存。这样避免失败后重新计算带来的开销。
注意:reduce是一个action,和reduceByKey完全不同,reduceByKey是transformation操作。
sortByKey(boolean)函数:升序或降序由ascending布尔参数决定,默认true为升序
scala> val kv1 =sc.parallelize(List(("A", 1), ("B", 2), ("C", 3),("A", 4), ("B", 5)))
kv1:org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[28] atparallelize at <console>:21
scala> kv1.sortByKey().collect 注意sortByKey的小括号不能省
res23:Array[(String, Int)] = Array((A,1), (A,4), (B,2), (B,5), (C,3))
groupByKey函数:在一个(K,V)对的数据集上调用,返回一个(K,Seq[V])对的数据集
注意:默认情况下,只有8个并行任务来做操作,但是你可以传入一个可选的numTasks参数来改变它。如果分组是用来计算聚合操作(如sum或average),那么应该使用reduceByKey或combineByKey 来提供更好的性能。
scala>kv1.groupByKey().collect
res21:Array[(String, Iterable[Int])] = Array((B,CompactBuffer(2, 5)),(A,CompactBuffer(4, 1)), (C,CompactBuffer(3)))
reduceByKey函数:在一个(K,V)对的数据集上调用时,返回一个(K,V)对的数据集,使用指定的reduce函数,将相同key的值聚合到一起。类似groupByKey,reduce任务个数是可以通过第二个可选参数来配置的
scala>kv1.reduceByKey(_+_).collect
res22:Array[(String, Int)] = Array((B,7), (A,5), (C,3))
6)、coalesce(numPartitions, true)函数:对RDD中的分区重新进行合并(减少rdd的分)。返回一个新的RDD,且该RDD的分区个数等于numPartitions个数。如果shuffle设置为true,则会进行shuffle。
scala> var data =sc.parallelize(List(1,2,3,4), 4)
scala>data.partitions.length
res27: Int = 4
scala> val result= data.coalesce(2, false)
scala>result.partitions.length
res28: Int = 2
repartition(numPartitions)随机重新shuffle RDD中的数据,并创建numPartitions个分区。这个操作总会通过网络来shuffle全部数据
7)、join函数和cogroup函数:
join(otherDataset,[numTasks])在类型为(K,V)和(K,W)类型的数据集上调用时,返回一个相同key对应的所有元素对在一起的(K, (V, W))数据集
scala> valkv1=sc.parallelize(List(("A",1),("B",2),("C",3),("A",4),("B",5)))
scala> valkv3=sc.parallelize(List(("A",10),("B",20),("D",30)))
scala>kv1.join(kv3).collect
res31:Array[(String, (Int, Int))] = Array((B,(2,20)), (B,(5,20)), (A,(4,10)),(A,(1,10)))
cogroup(otherDataset, [numTasks])在类型为(K,V)和(K,W)的数据集上调用,返回一个 (K, Seq[V], Seq[W])元组的数据集。这个操作也可以称之为groupwith
scala>kv1.cogroup(kv3).collect
res32:Array[(String, (Iterable[Int], Iterable[Int]))] = Array((B,(CompactBuffer(5,2),CompactBuffer(20))), (D,(CompactBuffer(),CompactBuffer(30))),(A,(CompactBuffer(1, 4),CompactBuffer(10))),(C,(CompactBuffer(3),CompactBuffer())))
8)、sortBy (dataSet, boolean)函数:排序。第二个参数默认为true,即升序排序。
scala> val rdd =sc.parallelize(List(3,1,90,3,5,12))
scala> rdd.sortBy(x => x, false) 第二个参数默认为true,即升序排列
res40: Array[Int] =Array(90, 12, 5, 3, 3, 1)
查看执行后rdd 分区的个数: scala> result.partitions.size
res18: Int = 2
注意:默认为两个,但可以手动修改rdd的分区个数手动修改rdd分区的个数
scala> val result= rdd.sortBy(x => x, false, 1)
scala>result.partitions.size
res21: Int = 1
3、action操作函数总结:
1)、reduce(func)函数、reduceByKey函数(transformation操作)
reduce(func)将RDD中元素两两传递给输入函数,同时产生一个新的值,新产生的值与RDD中下一个元素再被传递给输入函数直到最后只有一个值为止。
scala> val c =sc.parallelize(1 to 10)
scala> c.reduce((x, y) => x+y) res60: Int = 55 对RDD中的元素求和
reduceByKey就是对元素为KV对的RDD中Key相同的元素的Value进行reduce,因此,Key相同的多个元素的值被reduce为一个值,然后与原RDD中的Key组成一个新的KV对。
scala> val a =sc.parallelize(List((1,2), (3,4), (3,6)))
scala>a.reduceByKey((x, y) => x +y).collect
res61: Array[(Int,Int)] = Array((1,2), (3,10))
对Key相同的元素的值求和,因此Key为3的两个元素被转为了(3,10)。
2)、foreach(func)在数据集的每一个元素上,运行函数func进行更新。这通常用于边缘效果,例如更新一个累加器,或者和外部存储系统进行交互,例如Hbase.
scala> valnum=sc.parallelize(1 to 10)
scala>num.take(5).foreach(println)
3)、
4、其它函数总结:
1)、zip函数:把两个单独的rdd组合为key-value形式
scala> val a =sc.parallelize(List("wyp", "iteblog", "com","397090770", "test"), 2)
scala> val b =sc.parallelize(List(3,1,9,12,4))
scala> val c = b.zip(a) zip把b和a组成key-value形式,其中b为key,a为value
scala>c.sortByKey().collect
res33: Array[(Int,String)] = Array((1,iteblog), (3,wyp), (4,test), (9,com), (12,397090770))
2)、lookup操作:通过key找value值。
scala> val rdd2 =sc.parallelize(List(('a', 1), ('a', 2), ('b', 1), ('b', 3)))
scala>rdd2.lookup('a')
res15: Seq[Int] =WrappedArray(1, 2)
scala>rdd2.lookup('b')
res16: Seq[Int] =WrappedArray(1, 3)
3)take 和takeAsTextFile操作
[hadoop@hadoop1 ~]$ cat a.txt
2014-03-04 121212121212 12
2014-03-06 161616161616 16
[hadoop@hadoop1 ~]$ cat b.txt
2014-03-02 121212121212 1 33.555333 -117.888878786
2014-03-06 161616161616 2 33.677666 -117.886868687
scala> val format= new java.text.SimpleDateFormat("yyyy-mm-dd")
scala> case classRegister(d: java.util.Date, uuid: String, cust_id: String , lat: Float, lng:Float)
scala> case classClick(d: java.util.Date, uuid: String, landing_page: Int)
首先,读取文件的内容;然后,以tab键进行分词,接着以第二列为key,每一行的所有内容为Value构建起的Register作为Value的值
scala> val reg =sc.textFile("/ai/b.txt").map(_.split('\t')).map(r => (r(1),Register(format.parse(r(0)), r(1), r(2), r(3).toFloat, r(4).toFloat)))
scala> val clk =sc.textFile("/ai/a.txt").map(_.split('\t')).map(c =>(c(1),Click(format.parse(c(0)), c(1), c(2).trim.toInt)))
clk:org.apache.spark.rdd.RDD[(String, Click)] =MapPartitionsRDD[86] at map at <console>:25
scala> val result = reg join clk ——》result: org.apache.spark.rdd.RDD[(String, (Register, Click))] = MapPartitionsRDD[89] at join at <console>:31
scala>result.take(2)
res17:Array[(String, (Register, Click))] = Array((121212121212,(Register(Thu Jan 02 00:03:00 CST 2014,121212121212,1,33.555332,-117.88888),Click(Sat Jan 04 00:03:00 CST 2014,121212121212,12))),(161616161616,(Register(Mon Jan 06 00:03:00 CST2014,161616161616,2,33.677666,-117.88687),Click(Mon Jan 06 00:03:00 CST2014,161616161616,16))))
scala>result.saveAsTextFile("/ai/join/result")
[hadoop@hadoop1 ~]$hadoop fs -cat /ai/join/result/part-00000
(121212121212,(Register(ThuJan 02 00:03:00 CST 2014,121212121212,1,33.555332,-117.88888),Click(Sat Jan 0400:03:00 CST 2014,121212121212,12)))
(161616161616,(Register(MonJan 06 00:03:00 CST 2014,161616161616,2,33.677666,-117.88687),Click(Mon Jan 0600:03:00 CST 2014,161616161616,16)))
4)fold, foldLeft, and foldRight之间的区别
主要的区别是fold函数操作遍历问题集合的顺序。foldLeft是从左开始计算,然后往右遍历。foldRight是从右开始算,然后往左遍历。而fold遍历的顺序没有特殊的次序。
scala> valnumbers = List(5,4,8,6,2)
scala>numbers.fold(0){(z,i)=>z+i}
res50: Int = 25
List中的fold方法需要输入两个参数:初始值以及一个函数。输入的函数也需要输入两个参数:累加值和当前item的索引。那么上面的代码片段发生了什么事?
代码开始运行的时候,初始值0作为第一个参数传进到fold函数中,list中的第一个item作为第二个参数传进fold函数中。
1、fold函数开始对传进的两个参数进行计算,在本例中,仅仅是做加法计算,然后返回计算的值;
2、Fold函数然后将上一步返回的值作为输入函数的第一个参数,并且把list中的下一个item作为第二个参数传进继续计算,同样返回计算的值;
3、第2步将重复计算,直到list中的所有元素都被遍历之后,返回最后的计算值,整个过程结束;
4、这虽然是一个简单的例子,让我们来看看一些比较有用的东西。早在后面将会介绍foldLeft函数,并解释它和fold之间的区别,目前,你只需要想象foldLeft函数和fold函数运行过程一样。
下面是一个简单的类和伴生类
scala> classFoo(val name:String, val age:Int, val sex:Symbol)
scala> objectFoo{
| def apply(name:String, age:Int, sex:Symbol)=new Foo(name, age, sex)
| }
假如我们有很多的Foo实例,并存在list中:
scala> valfooList=Foo("Hugh Jass", 25, 'male)::Foo("Biggus Dickus",43, 'male)::Foo("Incontinetia Buttocks", 37, 'female)::Nil
我们想将上面的list转换成一个存储[title] [name], [age]格式的String链表:
scala> valstringList = fooList.foldLeft(List[String]()) {(z,f)=>
| val title=f.sex match{
| case 'male=>"Mr."
| case 'female=>"Ms."
| }
| z:+ s"$title ${f.name},${f.age}"
| }
5)trim函数:把字符两端的空格截掉
scala>val people =sc.textFile("/spark/people.txt").map(_.split(",")).map(p=>Person(p(0),p(1).trim.toInt)).toDF()
people: org.apache.spark.sql.DataFrame = [name: string, age: int] 注意:如果去掉trim,会报错
scala>people.registerTempTable("people")
5、spark的统计与数学函数
1)随机数据生成(Random Data Generation)主要是为测试数据提供方便快捷的接口,如range、rand和randn。rand函数提供均匀正态分布,而randn则提供标准正态分布。在调用这些函数时,还可以指定列的别名,以方便我们对这些数据进行测试。
scala> val df =sqlContext.range(1, 10).withColumn("uniform",rand(seed=10)).withColumn("normal", randn(seed=27))
scala> sqlContext.range(1, 10).show 生成一个属性为id的数据框,左闭右开,即不包含10
+---+
| id|
+---+
| 1|
。。。
| 9|
+---+
2)概要与描述性统计(Summary and Descriptive Statistics)包含了计数、平均值、标准差、最大值、最小值运算。只需要针对DataFrame调用describe函数即可:
scala>df.describe().show
scala> people.describe().show 注意:此括号不能省略
+-------+------------------+--------------------+-------------------+
|summary| id| uniform| normal|
+-------+------------------+--------------------+-------------------+
| count| 9| 9| 9|
| mean| 5.0| 0.3501113296528809|-0.4749238142128228|
|stddev|2.5819888974716116| 0.18389046183528623| 0.9742194777287445|
| min| 1|0.018326130186194667|-2.372340011831022|
| max| 9| 0.7224977951905031| 0.7873642272821919|
+-------+------------------+--------------------+-------------------+
如果返回的DataFrame含有大量的列,你可以返回其中的一部分列:
scala>df.describe("uniform", "normal").show()
+-------+--------------------+-------------------+
|summary| uniform| normal|
+-------+--------------------+-------------------+
| count| 9| 9|
| mean| 0.3501113296528809|-0.4749238142128228|
| stddev|0.18389046183528623| 0.9742194777287445|
| min|0.018326130186194667|-2.372340011831022|
| max| 0.7224977951905031| 0.7873642272821919|
+-------+--------------------+-------------------+
自定选择要统计的列及函数:
scala>df.select(mean("uniform"), min("uniform"),max("uniform")).show()
+------------------+--------------------+------------------+
| avg(uniform)| min(uniform)| max(uniform)|
+------------------+--------------------+------------------+
|0.3501113296528809|0.018326130186194667|0.7224977951905031|
+------------------+--------------------+------------------+
3)样本协方差和相关性(Sample covariance and correlation)
协方差表示的是两个变量的总体的误差。正数意味着其中一个增加,另外一个也有增加的趋势;而负数意味着其中一个数增加,另外一个有降低的趋势。DataFrame两列中的样本协方差计算可以如下:
scala> val df =sqlContext.range(0, 10).withColumn("rand1",rand(seed=10)).withColumn("rand2", rand(seed=27))
scala> df.stat.cov("rand1", "rand2")
res18: Double =0.003702640706789616
scala>df.stat.cov("id", "id")
res19: Double =9.166666666666666
两个随机生成的列之间的协方差接近零;而id列和它自己的协方差非常大。
协方差的值为9.17可能很难解释,而相关是协方差的归一化度量,这个相对更好理解,因为它提供了两个随机变量之间的统计相关性的定量测量。
scala> df.stat.corr("rand1", "rand2")
res21: Double =0.07372733310735549
scala> df.stat.corr("id", "id")
res22: Double = 1.0
ID那列完全与相关本身;而两个随机生成的列之间的相关性非常低。
4)交叉分类汇总表(又称列联表)(Cross tabulation)
如果同时按几个变量或特征,把数据分类列表时,这样的统计表叫作交叉分类汇总表,其主要用来检验两个变量之间是否存在关系,或者说是否独立。在Spark 1.4中,我们可以计算DataFrame中两列之间的交叉分类汇总表,以便获取计算的两列中不同对的数量,下面是关于如何使用交叉表来获取列联表的例子
scala> val df =sqlContext.jdbc("jdbc:MySQL://node3:3306/test?user=Hive&password=mysql","cross_table")
scala> df.show
+-----+------+
| name| item|
+-----+------+
|Alice| milk|
|Alice| milk|
|Alice| bread|
|Alice|butter|
| Bob|butter|
| Bob|butter|
| Bob|butter|
| Bob|apples|
+-----+------+
scala>df.stat.crosstab("name", "item").show()
+---------+------+------+----+-----+
|name_item|apples|butter|milk|bread|
+---------+------+------+----+-----+
| Bob| 1| 3| 0| 0|
| Alice| 0| 1| 2| 1|
+---------+------+------+----+-----+
我们需要记住,列的基数不能太大。也就是说,name和item distinct之后的数量不能过多。试想,如果item distinct之后的数量为10亿,那么你如何在屏幕上显示这个表??
5)频繁项(Frequent items)
了解列中那些频繁出现的item对于我们了解数据集非常重要。在Spark 1.4中,我们可以通过使用DataFrames来发现列中的频繁项,
6)数学函数(Mathematical functions)
Spark 1.4中增加了一系列的数学函数,用户可以自如地将这些操作应用到他们列。我可以在这里看到所有的数学函数。输入必须是一个列函数,并且这个列函数只能输入一个参数,比如cos, sin, floor, ceil。对于那些需要输入两个参数的列函数,比如pow, hypot,我们可以输入两列或者列的组合。