Spark RDD常用算子学习笔记详解(python版)

官网链接:
http://spark.apache.org/docs/latest/api/python/pyspark.html#pyspark.RDD

RDD是Spark中的抽象数据结构类型,任何数据在Spark中都被表示为RDD。从编程的角度来看,RDD可以简单看成是一个数组。和普通数组的区别是,RDD中的数据是分区存储的,这样不同分区的数据就可以分布在不同的机器上,同时可以被并行处理。因此,Spark应用程序所做的无非是把需要处理的数据转换为RDD,然后对RDD进行一系列的变换和操作从而得到结果。
RDD详细介绍链接:
http://dblab.xmu.edu.cn/blog/1681-2/

parallelize(c, numSlices=None)

意思是将数组中的元素转换为RDD,并且存储在分区上,numSlices是设置多少个分区用的,好像一般默认是两个分区,其实看了一些资料说是根据cpu核心个数来确定这里的默认分区的,比如两个cpu核心,默认为2,官网案例如下:

>>> sc.parallelize([0, 2, 3, 4, 6], 5).glom().collect()
[[0], [2], [3], [4], [6]]
>>> sc.parallelize(xrange(0, 6, 2), 5).glom().collect()
[[], [0], [], [2], [4]]

aggregate(zeroValue, seqOp, combOp)

将初始值和第一个分区中的第一个元素传递给seq函数进行计算,然后将计算结果和第二个元素传递给seq函数,直到计算到最后一个值。第二个分区中也是同理操作。最后将初始值、所有分区的结果经过combine函数进行计算(先将前两个结果进行计算,将返回结果和下一个结果传给combine函数,以此类推),并返回最终结果。
参考学习:https://blog.csdn.net/u013514928/article/details/56680825
官方案例如下:

>>> seqOp = (lambda x, y: (x[0] + y, x[1] + 1))
>>> combOp = (lambda x, y: (x[0] + y[0], x[1] + y[1]))
>>> sc.parallelize([1, 2, 3, 4]).aggregate((0, 0), seqOp, combOp)
(10, 4)
>>> sc.parallelize([]).aggregate((0, 0), seqOp, combOp)
(0, 0)

上面为什么得出(10,4)呢,计算过程如下:

假设为单线程状态,一个分区,那么算法只用到seqOp函数,lambda x,y中x其实就是aggregate((0, 0))中的0,也就是有x[0]=0,x[1]=0,y其实就是[1, 2, 3, 4]的每个值,计算逻辑是(x[0] + y, x[1] + 1), 那么计算结果如下:
第一次计算用初始值:
x[0] + y=0+1 x[1]+1=0+1
第二次计算,用上面的结果作为x[0]和x[1]
x[0] + y=1+2 x[1]+1=1+1
x[0] + y=3+3 x[1]+1=2+1
x[0] + y=4+6 x[1]+1=3+1
得出(10,4)
实际Spark执行中是分布式计算,可能会把List分成多个分区,假如分成两个区,p1(1,2,),p2(3,4),再用上面方法分别计算p1和p2,经过计算各分区的的结果(3,2),(7,2),然后执行combOp函数(x[0] + y[0], x[1] + y[1])
x[0] + y[0]=3+7 x[1] + y[1]=2+2
得出也是(10,4)

aggregateByKey(zeroValue, seqFunc, combFunc, numPartitions=None, partitionFunc=)

在kv对的RDD中,,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),将key与计算结果作为一个新的kv对输出。

>>> def seq(a,b):
...     print("seq"+" "+str(a)+"  "+str(b))
...     return a+b
... 
>>> def seq(a,b):
...     print("seq:"+" "+str(a)+"  "+str(b))
...     return a+b
... 
>>> def combine(a,b):
...     print("combine:"+" "+str(a)+"  "+str(b))
...     return a+b
... 
>>> sc.parallelize([(1,5),(1,4),(1,7),(2,4)]).aggregateByKey(3,seq,combine).collect()
seq: 3  7seq: 3  5
seq: 3  4

seq: 8  4
combine: 12  10
[(2, 7), (1, 22)]
>>> sc.parallelize([(1,5),(1,4),(1,7),(2,4)]).aggregateByKey(3,seq,combine,4).collect()
seq: 3  5
seq: 8  4
seq: 3  7
seq: 3  4
combine: 12  10
[(1, 22), (2, 7)]
>>> 

过程分析如下:
将数据拆分成两个分区

//分区一数据
(1,5)
(1,4)
//分区二数据
(1,7)
(2,4)

//分区一相同key的数据进行相加
seq: 3 5 //(1,5)开始和中立值进行相加 相加结果为 8
seq: 8 4 //(1,4)再次相加 结果为 12
得(1,12)

//分区二相同key的数据进行相加
seq: 3 7 //(1,4) 开始和中立值进行相加 10
seq: 3 4 //(2,3) 开始和中立值进行相加 7
得(2,7),(1,10)

将两个分区的结果进行合并
//key为2的,只在一个分区存在,不需要相加 (2,7)
(2,7)

//key为1的, 在两个分区存在,并且数据类型一致,相加
combine: 12 10
(1,22)

cache()

惰性机制

    >>> lines = sc.textFile("data.txt")
    >>> lineLengths = lines.map(lambda s : len(s))
    >>> totalLength = lineLengths.reduce( lambda a, b : a + b)

上面第一行首先从外部文件data.txt中构建得到一个RDD,名称为lines,但是,由于textFile()方法只是一个转换操作,因此,这行代码执行后,不会立即把data.txt文件加载到内存中,这时的lines只是一个指向这个文件的指针。
第二行代码用来计算每行的长度(即每行包含多少个单词),同样,由于map()方法只是一个转换操作,这行代码执行后,不会立即计算每行的长度。
第三行代码的reduce()方法是一个“动作”类型的操作,这时,就会触发真正的计算。这时,Spark会把计算分解成多个任务在不同的机器上执行,每台机器运行位于属于它自己的map和reduce,最后把结果返回给Driver Program
spark惰性机制就是在Transformation操作时候并没有开始计算,只是记下计算路径,等到Action算子的时候才开始计算,而且每次都是从头到尾开始计算,cache()函数就是解决这个问题,防止每次都要从头计算,耗费性能

>>> list = ["Hadoop","Spark","Hive"]
>>> rdd = sc.parallelize(list)
>>> rdd.cache()  //会调用persist(MEMORY_ONLY),但是,语句执行到这里,并不会缓存rdd,这是rdd还没有被计算生成
>>> print(rdd.count()) //第一次行动操作,触发一次真正从头到尾的计算,这时才会执行上面的rdd.cache(),把这个rdd放到缓存中
3
>>> print(','.join(rdd.collect())) //第二次行动操作,不需要触发从头到尾的计算,只需要重复使用上面缓存中的rdd
Hadoop,Spark,Hive

collect()

返回包含该RDD中所有元素的列表

count()

返回此RDD中的元素个数。

>>> sc.parallelize([2, 3, 4]).count()
3

countByKey()

根据相同的key统计每个键的元素数量,并将结果返回给master作为字典。

>>> rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)])
>>> sorted(rdd.countByKey().items())
[('a', 2), ('b', 1)]

countByValue()

计算一个RDD中,每一个元素出现的次数,返回的结果为一个map型,表示的是每个值出现了几次

>>> sorted(sc.parallelize([1, 2, 1, 2, 2], 2).countByValue().items())
[(1, 2), (2, 3)]

filter(f)

返回一个包含满足函数规则的元素的新RDD。

>>> rdd = sc.parallelize([1, 2, 3, 4, 5])
>>> rdd.filter(lambda x: x % 2 == 0).collect()
[2, 4]

first()

返回这个RDD中的第一个元素。

>>> sc.parallelize([2, 3, 4]).first()
2

items()

返回可遍历的(键, 值) 元组数组

flatMap(f, preservesPartitioning=False)

与map类似,但每个输入元素都可以映射到0或多个输出结果

>>> rdd = sc.parallelize([2, 3, 4])
>>> sorted(rdd.flatMap(lambda x: range(1, x)).collect())
[1, 1, 1, 2, 2, 3]
>>> sorted(rdd.flatMap(lambda x: [(x, x), (x, x)]).collect())
[(2, 2), (2, 2), (3, 3), (3, 3), (4, 4), (4, 4)]

flatMapValues(f)

通过flatMap函数传递键值对RDD中的每个值,在不改变键的情况返回新的键值对

>>> x = sc.parallelize([("a", ["x", "y", "z"]), ("b", ["p", "r"])])
>>> def f(x): return x
>>> x.flatMapValues(f).collect()
[('a', 'x'), ('a', 'y'), ('a', 'z'), ('b', 'p'), ('b', 'r')]

foreach(f)

将一个函数应用到这个RDD的所有元素上。一般可用来打印数组中的每个元素

>>> def f(x): print(x)
... 
>>> sc.parallelize([1, 2, 3, 4, 5]).foreach(f)
31

24

5
>>> 

glom()

一般情况下parallelize处理的数据是放在不同分区下的,这个函数可以把不同分区下的元素合并起来返回一个RDD列表

>>> rdd = sc.parallelize([1, 2, 3, 4], 2)
>>> sorted(rdd.glom().collect())
[[1, 2], [3, 4]]

groupBy(f, numPartitions=None, partitionFunc=)

根据给定的函数规则返回分组的RDD

>>> rdd = sc.parallelize([1, 1, 2, 3, 5, 8])
>>> result = rdd.groupBy(lambda x: x % 2).collect()
>>> sorted([(x, sorted(y)) for (x, y) in result])
[(0, [2, 8]), (1, [1, 1, 3, 5])]

groupByKey(numPartitions=None, partitionFunc=)
groupByKey是对每个key进行合并操作,但只生成一个sequence,groupByKey本身不能自定义操作函数。

>>> rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)])
>>> sorted(rdd.groupByKey().mapValues(len).collect())
[('a', 2), ('b', 1)]
>>> sorted(rdd.groupByKey().mapValues(list).collect())
[('a', [1, 1]), ('b', [1])]

groupByKey,会把相同key的元组合并到一起,形成,(“a”,(1,1)),(“b”,1)然后再计算

join(other, numPartitions=None)

返回一个包含所有成对元素的RDD,其中包含匹配的键在自身和other中。
每一对元素都将作为一个(k,(v1,v2))返回,其中(k,v1)在自身(自身即调用join函数那个x)的(k,v2)在other(即y)。
在集群中执行散列连接。

>>> x = sc.parallelize([("a", 1), ("b", 4)])
>>> y = sc.parallelize([("a", 2), ("a", 3)])
>>> sorted(x.join(y).collect())
[('a', (1, 2)), ('a', (1, 3))]

map(f, preservesPartitioning=False)

通过对这个RDD的每个元素应用一个函数来返回一个新的RDD。

>>> rdd = sc.parallelize(["b", "a", "c"])
>>> sorted(rdd.map(lambda x: (x, 1)).collect())
[('a', 1), ('b', 1), ('c', 1)]

案例2:
map输入的是整个元素

>>> sc.parallelize([(("a","b"),(1,2,5)),(("c","d"),(2,3,4))]).map(lambda (x,y):y[0]).collect()
[1, 2]
>>> 

mapPartitions(f, preservesPartitioning=False)

map()的输入函数是应用于RDD中每个元素,而mapPartitions()的输入函数是应用于每个分区

mapPartitions是map的一个变种。map的输入函数是应用于RDD中每个元素,而mapPartitions的输入函数是应用于每个分区,也就是把每个分区中的内容作为整体来处理的。

>>> rdd = sc.parallelize([1, 2, 3, 4], 2)
>>> def f(iterator): yield sum(iterator)
>>> rdd.mapPartitions(f).collect()
[3, 7]

mapValues(f)

原RDD中的Key保持不变,只用于键值对的,对每个RDD的值进行操作

>>> x = sc.parallelize([("a", ["apple", "banana", "lemon"]), ("b", ["grapes"])])
>>> def f(x): return len(x)
>>> x.mapValues(f).collect()
[('a', 3), ('b', 1)]

mean()

求平均值

>>> sc.parallelize([1, 2, 3]).mean()
2.0

reduce(f)

reduce将RDD中元素前两个传给输入函数,产生一个新的return值,新产生的return值与RDD中下一个元素(第三个元素)组成两个元素,再被传给输入函数,直到最后只有一个值为止。

>>> from operator import add
>>> sc.parallelize([1, 2, 3, 4, 5]).reduce(add)
15
>>> sc.parallelize((2 for _ in range(10))).map(lambda x: 1).cache().reduce(add)
10

分析如下:
1+2=3
3+3=6
6+4=10
10+5=15

reduceByKey(func, numPartitions=None, partitionFunc=)

>>> from operator import add
>>> rdd = sc.parallelize([("a", 1), ("b", 1), ("a", 1)])
>>> sorted(rdd.reduceByKey(add).collect())
[('a', 2), ('b', 1)]

reduceByKey(func)的功能是,使用func函数合并具有相同键的值。比如,reduceByKey((a,b) => a+b),有四个键值对(“spark”,1)、(“spark”,2)、(“hadoop”,3)和(“hadoop”,5),对具有相同key的键值对进行合并后的结果就是:(“spark”,3)、(“hadoop”,8)。可以看出,(a,b) => a+b这个Lamda表达式中,a和b都是指value,比如,对于两个具有相同key的键值对(“spark”,1)、(“spark”,2),a就是1,b就是2。
例子2:

>>> sc.parallelize([(1,5),(1,4),(1,7),(2,4)]).reduceByKey(lambda a,b : a+b).collect()
[(2, 4), (1, 16)]                                                               
>>> 
>>> def seq(a,b):
...     print("seq:"+" "+str(a)+"  "+str(b))
...     return a+b
... 
>>> sc.parallelize([(1,5),(1,4),(1,7),(2,4)],4).reduceByKey(seq).collect()
seq: 5  4
seq: 9  7
[(1, 16), (2, 4)]
>>> 

运算分析如下:

seq: 5 4 // 意思是先把(1,5),(1,4)中的值传进来,通过定义的函数相加得出9
seq: 9 7 //再把(1,7)的值传进来,上次计算的结果作为a,一起传入计算得出16
最终结果是(1, 16)
由于(2, 4),只有一个值,并不需要通过seq函数计算了

sortBy(keyfunc, ascending=True, numPartitions=None)

按给定的keyfunc对这个RDD进行排序

>>> tmp = [('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)]
>>> sc.parallelize(tmp).sortBy(lambda x: x[0]).collect()
[('1', 3), ('2', 5), ('a', 1), ('b', 2), ('d', 4)]
>>> sc.parallelize(tmp).sortBy(lambda x: x[1]).collect()
[('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)]

sortByKey()

sortByKey()的功能是返回一个根据键排序的RDD。

>>> sc.parallelize([(1,5),(6,4),(9,7),(2,4)]).sortByKey().collect()
[(1, 5), (2, 4), (6, 4), (9, 7)]

官方案例:

>>> tmp = [('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)]
>>> sc.parallelize(tmp).sortByKey().first()
('1', 3)
>>> sc.parallelize(tmp).sortByKey(True, 1).collect()
[('1', 3), ('2', 5), ('a', 1), ('b', 2), ('d', 4)]
>>> sc.parallelize(tmp).sortByKey(True, 2).collect()
[('1', 3), ('2', 5), ('a', 1), ('b', 2), ('d', 4)]
>>> tmp2 = [('Mary', 1), ('had', 2), ('a', 3), ('little', 4), ('lamb', 5)]
>>> tmp2.extend([('whose', 6), ('fleece', 7), ('was', 8), ('white', 9)])
>>> sc.parallelize(tmp2).sortByKey(True, 3, keyfunc=lambda k: k.lower()).collect()
[('a', 3), ('fleece', 7), ('had', 2), ('lamb', 5),...('white', 9), ('whose', 6)]
  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值