PySpark学习 | 常用的 68 个函数 | 解释 + python代码

博文函数顺序以及代码部分参考Spark Python API函数学习:pyspark API系列,并在此基础上结合PySpark官方文档以及参考各位博主的优秀文章对各个函数进行了解释。代码全部手撸过,可以运行。

实验环境可以按照在windows上面安装并用jupyter运行pyspark进行配置:

python 3

Hadoop 2.7

PySpark 2.2.3

Windows 10

打开cmd命令窗口,输入pyspark --master local[2],运行notebook

sc = SparkContext.getOrCreate()

import numpy as np

TOTAL = 1000000
dots = sc.parallelize([2.0 * np.random.random(2) - 1.0 for i in range(TOTAL)]).cache()
print(dots.count())
1000000
print("pyspark version:" + str(sc.version))
pyspark version:2.2.3
import os
from pyspark import SparkFiles
path = os.path.join('./', "test.txt")
with open(path, "w") as testFile:
    _ = testFile.write("100")
sc.addFile(path)
def func(iterator):
    with open(SparkFiles.get("test.txt")) as testFile:
        fileVal = int(testFile.readline())
    return [x * fileVal for x in iterator]
sc.parallelize([1, 2, 3, 4]).mapPartitions(func).collect()
[100, 200, 300, 400]
try:
    sc.stop()
except:
    pass
sc=SparkContext('local[2]','First Spark App')

1 map

map会将每一条输入映射为一个新对象。{苹果,梨子}.map(去皮) = {去皮苹果,去皮梨子} 其中: “去皮”函数的类型为:A => B

x = sc.parallelize([1,2,3])
y = x.map(lambda x: (x, x**2))
print(x.collect()) # 从远程集群拉取数据到本地,经网络传输.如果数据量较大时,尽量不要用collect函数,可能导致Driver端内存溢出。
print(y.collect())
[1, 2, 3]
[(1, 1), (2, 4), (3, 9)]

注意 : map函数将x与y中的元素一一对应

2 flatmap

flatMap包含两个操作:会将每一个输入对象输入映射为一个新集合,然后把这些新集合连成一个大集合。 {苹果,梨子}.flatMap(切碎) = {苹果碎片1,苹果碎片2,梨子碎片1,梨子碎片2} 其中: “切碎”函数的类型为: A => List

x = sc.parallelize([1,2,3])
y1 = x.flatMap(lambda x : [(x, 100*x)])
y2 = x.flatMap(lambda x : (x, 100*x, x**2))
print(x.collect())
print(y1.collect())
print(y2.collect())
[1, 2, 3]
[(1, 100), (2, 200), (3, 300)]
[1, 100, 1, 2, 200, 4, 3, 300, 9]

3 mapPartitions

map是对rdd中的每一个元素进行操作,而mapPartitions(foreachPartition)则是对rdd中的每个分区的迭代器进行操作。如果在map过程中需要频繁创建额外的对象(例如将rdd中的数据通过jdbc写入数据库,map需要为每个元素创建一个链接而mapPartition为每个partition创建一个链接),则mapPartitions效率比map高的多。

如果是普通的map,比如一个partition中有1万条数据。ok,那么你的function要执行和计算1万次。

使用MapPartitions操作之后,一个task仅仅会执行一次function,function一次接收所有的partition数据。只要执行一次就可以了,性能比较高。

x = sc.parallelize([1,2,3], 2) # 2 表示分区的个数
def f(iterator):yield sum(iterator)
y = x.mapPartitions(f)
print(x.collect())
print(y.collect())
# 将RDD中每一个分区中类型为T的元素转换成Array[T],这样每一个分区就只有一个数组元素。
print(x.glom().collect())
print(y.glom().collect())
[1, 2, 3]
[1, 5]
[[1], [2, 3]]
[[1], [5]]

如果是普通的map操作,一次function的执行就处理一条数据;那么如果内存不够用的情况下, 比如处理了1千条数据了,那么这个时候内存不够了,那么就可以将已经处理完的1千条数据从内存里面垃圾回收掉,或者用其他方法,腾出空间来吧。
所以说普通的map操作通常不会导致内存的OOM异常。

但是MapPartitions操作,对于大量数据来说,比如甚至一个partition,100万数据,一次传入一个function以后,那么可能一下子内存不够,但是又没有办法去腾出内存空间来,可能就OOM,内存溢出。

4 mapPartitionsWithIndex

mapPartitionsWithIndex相比于mapPartitions多了一个index索引,每次调用时就会把分区的“编号”穿进去

x = sc.parallelize([1,2,3],2)
def f(partitionIndex, iterator): yield(partitionIndex, sum(iterator))
y = x.mapPartitionsWithIndex(f)
print(x.glom().collect())
print(y.glom().collect())
[[1], [2, 3]]
[[(0, 1)], [(1, 5)]]

5 getNumPartitions

获取RDD的partitions数目和index信息

x = sc.parallelize([1,2,3], 2) # 2 表示分区的个数
y = x.getNumPartitions()
print(x.glom().collect())
print(y)
[[1], [2, 3]]
2

6 filter

调用filter方法,rdd中的每个元素都会传入,然后判断这个元素是不是你想要的

x = sc.parallelize([1,2,3])
y = x.filter(lambda x: x % 2 == 1) # 选择奇数
print(x.collect())
print(y.collect())
[1, 2, 3]
[1, 3]

7 distinct

去掉重复数据

x = sc.parallelize(['A', 'B', 'C', 'A', 'A'])
y = x.distinct()
print(x.collect())
print(y.collect())
['A', 'B', 'C', 'A', 'A']
['C', 'A', 'B']

8 sample

对rdd中的数据集进行采样,并生成一个新的RDD,这个新的RDD只有原来RDD的部分数据,这个保留的数据集大小由fraction来进行控制

x = sc.parallelize(range(7))
ylist = [x.sample(withReplacement=False, fraction=0.7) for i in range(5)]
print('x = ' + str(x.collect()))
for cnt,y in zip(range(len(ylist)), ylist):
    print('sample:' + str(cnt) + ' y = ' +  str(y.collect()))
x = [0, 1, 2, 3, 4, 5, 6]
sample:0 y = [0, 1, 2, 3, 4]
sample:1 y = [0, 1, 2, 4, 5]
sample:2 y = [1, 2, 3, 4, 5]
sample:3 y = [0, 1, 2, 4, 5]
sample:4 y = [0, 2, 6]

9 takeSample

takeSample函数类似于sample函数,该函数接受三个参数:
第一个参数withReplacement ,表示采样是否放回,true表示有放回的采样,false表示无放回采样;
第二个参数num,表示返回的采样数据的个数,这个也是takeSample函数和sample函数的区别;
第三个参数seed,表示用于指定的随机数生成器种子。

x = sc.parallelize(range(7))
# call 'sample' 5 times
ylist = [x.takeSample(withReplacement=False, num=3) for i in range(5)]  
print('x = ' + str(x.collect()))
for cnt,y in zip(range(len(ylist)), ylist):
    print('sample:' + str(cnt) + ' y = ' +  str(y))  # no collect on y
x = [0, 1, 2, 3, 4, 5, 6]
sample:0 y = [0, 3, 4]
sample:1 y = [0, 4, 5]
sample:2 y = [0, 6, 2]
sample:3 y = [6, 1, 2]
sample:4 y = [3, 1, 5]

注意:与sample不同, 采样得到的数据不用collect

10 union

数据合并,返回一个新的数据集

x = sc.parallelize(['A', 'B', 'C'])
y = sc.parallelize(['d', 'A', 'T'])
z = x.union(y)
print(x.collect())
print(y.collect())
print(z.collect())
['A', 'B', 'C']
['d', 'A', 'T']
['A', 'B', 'C', 'd', 'A', 'T']

11 intersection

求两个交集

x = sc.parallelize(['A','A','B'])
y = sc.parallelize(['A','C','D'])
z = x.intersection(y)
print(x.collect())
print(y.collect())
print(z.collect())
['A', 'A', 'B']
['A', 'C', 'D']
['A']

12 subtract

求差集

x = sc.parallelize(['A','A','B','G'])
y = sc.parallelize(['A','C','D'])
z = x.subtract(y) # 返回在x中出现,但未在y中出现的元素
print(x.collect())
print(y.collect())
print(z.collect())
['A', 'A', 'B', 'G']
['A', 'C', 'D']
['B', 'G']

13 sortByKey

函数能够完成对(key,value)格式的数据进行排序,它是根据key进行排序

x = sc.parallelize([('B',1),('A',2),('C',3)])
y = x.sortByKey()
print(x.collect())
print(y.collect())
[('B', 1), ('A', 2), ('C', 3)]
[('A', 2), ('B', 1), ('C', 3)]

14 sortBy

根据value进行排序

x = sc.parallelize([('B',4),('A',2),('C',3)])
y = x.sortBy(lambda x : x[1], False) # False 降序排列
print(x.collect())
print(y.collect())
[('B', 4), ('A', 2), ('C', 3)]
[('B', 4), ('C', 3), ('A', 2)]
x = sc.parallelize(['Cat','Apple','Bat'])
def keyGen(val): return val[0] # 按照首字母排序
y = x.sortBy(keyGen)
print(y.collect())
['Apple', 'Bat', 'Cat']

15 glom

将RDD中每一个分区中类型为T的元素转换成Array[T],这样每一个分区就只有一个数组元素。

x = sc.parallelize(['C', 'B', 'A'], 2)
y = x.glom()
print(x.collect())
print(y.collect())
['C', 'B', 'A']
[['C'], ['B', 'A']]

16 cartesian

返回两个RDD的笛卡尔集.如果两个RDD中某一个RDD的结果集为空集时,这个结果集也是一个空集.

x = sc.parallelize(['A', 'B'])
y = sc.parallelize(['C', 'D'])
z = x.cartesian(y)
print(x.collect())
print(y.collect())
print(z.collect())
['A', 'B']
['C', 'D']
[('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D')]

17 groupBy

groupBy算子接收一个函数,这个函数返回的值作为key,然后通过这个key来对里面的元素进行分组。

x = sc.parallelize([1,2,3])
y = x.groupBy(lambda x: 'A' if (x % 2 == 1) else 'B')
print(x.collect())
print([(j[0],[i for i in j[1]]) for j in y.collect()]) 
[1, 2, 3]
[('A', [1, 3]), ('B', [2])]

18 pipe

将由管道元素创建的RDD返回到分叉的外部进程。

sc.parallelize(['1', '2', '', '3']).pipe('cat').collect() # ’cat‘是啥命令嘞我也不清楚
['1', '2', '', '3']
x = sc.parallelize(['A', 'Ba', 'C', 'AD'])
y = x.pipe('grep -i "A"') # 忽略字符大小写的差别。
print(x.collect())
print(y.collect())
['A', 'Ba', 'C', 'AD']
['A', 'Ba', 'AD']
x = sc.parallelize(['A', 'Ba', 'Cb', 'AD', 'ac'])
y = x.pipe('grep -i "b"') # 忽略字符大小写的差别。
print(x.collect())
print(y.collect())
['A', 'Ba', 'Cb', 'AD', 'ac']
['Ba', 'Cb']

19 foreach

def foreach(f: (T) ⇒ Unit): Unit

foreach用于遍历RDD,将函数f应用于每一个元素。

但要注意,如果对RDD执行foreach,只会在Executor端有效,而并不是Driver端。

x = sc.parallelize([1,2,3])
def f(el):
    """
    副作用(Side Effect)是指函数或者表达式的行为依赖于外部世界。
    副作用将当前RDD元素附加到文件
    """
    f1 = open("./foreachexample.txt", 'a+')
    print(el, file=f1)
open('./foreachexample.txt', 'w').close()
y = x.foreach(f) # writes into foreachexample.txt
print(x.collect())
print(y) # foreach函数返回None
with open("./foreachexample.txt", 'r') as ff:
    print(ff.read())
[1, 2, 3]
None
2
3
1

注意:y 的返回值为None!!! 原始的RDD x 没有发生改变

20 foreachPartition

def foreachPartition(f: (Iterator[T]) ⇒ Unit): Unit

foreachPartition和foreach类似,只不过是对每一个分区使用f。

x = sc.parallelize([1,2,3], 5)
def f(parition):
    """
    副作用(Side Effect)是指函数或者表达式的行为依赖于外部世界。
    副作用将当前RDD元素附加到文件
    """
    f1 = open("./foreachParitionexample.txt", 'a+')
    print([el for el in parition], file=f1)
open('./foreachParitionexample.txt', 'w').close()
y = x.foreachPartition(f) # writes into foreachexample.txt
print(x.glom().collect())
print(y) # foreach函数返回None
with open("./foreachParitionexample.txt", 'r') as ff:
    print(ff.read())
[[], [1], [], [2], [3]]
None
[]
[1]
[]
[2]
[3]

注意:foreach当中,函数文本希望的参数是T,也就是RDD当中的元素类型;foreachPartition当中,函数文本希望的参数是Iterator[T],也就是一个partition。

21 collect

数据量比较大的时候,尽量不要使用collect函数,因为这可能导致Driver端内存溢出问题

x = sc.parallelize([1,2,3])
y = x.collect()
print(x)  # distributed
print(y)  # not distributed
ParallelCollectionRDD[110] at parallelize at PythonRDD.scala:540
[1, 2, 3]

collect操作的特点是从远程集群是拉取数据到本地,经过网络传输,如果数据量大的话,会给网络造成很大的压力,和foreach的区别是,foreach是在远程集群上遍历rdd中的元素,如果是在本地的话,差别不大。建议使用foreach,不要用collect.

22 reduce

reduce先在各分区中做操作,随后进行整合。

reduce返回值类型和参加计算类型一样。

map的主要作用就是替换。reduce的主要作用就是计算。

x = sc.parallelize([1,2,3])
y = x.reduce(lambda obj, accumulated : obj + accumulated) # 求和
print(x.collect())
print(y)
[1, 2, 3]
6

reduce顺序是1+2,得到3,然后3+3,得到6

23 fold

reduce()与fold()方法是对同种元素类型数据的RDD进行操作,即必须同构。其返回值返回一个同样类型的新元素

fold()与reduce()类似,接收与reduce接收的函数签名相同的函数,另外再加上一个初始值作为第一次调用的结果。(例如,加法初始值应为0,乘法初始值应为1)

x = sc.parallelize([1,2,3])
y = x.fold(0, lambda obj, accumulated : obj + accumulated) # 加法操作取值为 0
print(x.collect())
print(y)
[1, 2, 3]
6
x = sc.parallelize([1,2,3])
y = x.fold(1, lambda obj, accumulated : obj * accumulated) # 乘法操作取值为 1
print(x.collect())
print(y)
[1, 2, 3]
6

24 aggregate

def aggregate [U: ClassTag] (zeroValue: U) (seqOp: (U,T)=>U,combOp: (U,U)=>U):U

(zeroValue: U)是给定一个初值,后半部分有两个函数,seqOp与combOp。 seqOp相当于是在各个分区里进行的聚合操作,它支持(U,T)=>U,也就是支持不同类型的聚合。 combOp是将seqOp后的结果再进行聚合,此时的结果全部是U类,只能进行同构聚合。

aggregate()方法可以对两个不同类型的元素进行聚合,即支持异构。

它先聚合每一个分区里的元素,然后将所有结果返回回来,再用一个给定的conbine方法以及给定的初始值zerovalue进行聚合。

x = sc.parallelize([2,3,4])
seqop = (lambda aggregated, el : (aggregated[0] + el, aggregated[1] * el))
combop = (lambda aggregated, el : (aggregated[0] + el[0], aggregated[1] * el[1]))
y = x.aggregate((0, 1), seqop, combop) 
print(x.collect())
print(y)
[2, 3, 4]
(9, 24)

注意:seqOp方法是对单独一个分区内的数据进行累加及累乘,所以lambda表达式为aggregated[0] + el, aggregated[1] * el
而combOp方法则是对以上每个分区的结果进行聚合汇总。这里要注意参数的写法,aggregated[0] + el[0],因为是对每一组序列的累加,所以不再用单独的el来表示了。

25 max

取最大值

x = sc.parallelize([2,3,4])
y = x.max()
print(y)
4

26 min

取最小值

x = sc.parallelize([2,3,4])
y = x.min()
print(y)
2

27 sum

求和

x = sc.parallelize([2,3,4])
y = x.sum()
print(y)
9

28 count

统计RDD中元素的个数

x = sc.parallelize([2,3,4])
y = x.count()
print(y)
3

29 histogram

histogram(buckets)

输入参数buckets可以是一个数字,也可以是一个列表

输出结果为一个元组,元组包含两个列表分别是桶和直方图

x = sc.parallelize([1,3,1,2,3])
y = x.histogram(buckets = 2)
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
([1, 2, 3], [2, 3])
x = sc.parallelize([1,3,1,2,3])
y = x.histogram(buckets = [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5])
print(x.collect())
print(y)
# 0~0.5之间有0个元素;0.5~1之间有0个元素;1~1.5之间有2个元素;。。。
[1, 3, 1, 2, 3]
([0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5], [0, 0, 2, 0, 1, 0, 2])

注意: 1、桶必须是排好序的,并且不包含重复元素,至少有两个元素。2、所有直方图的结果集合区右边是开区间,最后一个区间除外。

30 mean

求均值

x = sc.parallelize([1,3,1,2,3])
y = x.mean()
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
2.0

31 variance

求方差

x = sc.parallelize([1,3,1,2,3])
y = x.variance()
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
0.8

32 stdev

求标准差,对方差开方

x = sc.parallelize([1,3,1,2,3])
y = x.stdev()
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
0.8944271909999159

33 sampleStdev

stdev 调用stats 函数,取得RDD中的标准差,divides by N

sampleStdev 调用stats 函数,取得RDD中的标准差,divides by N-1,对于数据量很大的情况下适用

x = sc.parallelize([1, 3, 2])
y1 = x.sampleStdev()
y2 = x.stdev()
print(x.collect())
print(y1)
print(y2)
[1, 3, 2]
1.0
0.816496580927726

34 sampleVariance

variance 调用stats 函数,取得RDD中的方差,divides by N

sampleVariance 调用stats 函数,取得RDD中的方差,divides by N-1,对于数据量很大的情况下适用

x = sc.parallelize([1, 3, 2])
y1 = x.sampleVariance()
y2 = x.variance()
print(x.collect())
print(y1)
print(y2)
[1, 3, 2]
1.0
0.6666666666666666

35 countByValue

统计一个RDD中各个value的出现次数。返回一个map,map的key是元素的值,value是出现的次数。

x = sc.parallelize([1, 3, 1, 2, 3])
y = x.countByValue()
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
defaultdict(<class 'int'>, {1: 2, 3: 2, 2: 1})

36 top

top函数用于从RDD中,按照默认(降序)或者指定的排序规则,返回前num个元素。

x = sc.parallelize([1, 3, 1, 2, 3])
y = x.top(num=3)
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
[3, 3, 2]

37 takeOrdered

以和top相反的顺序返回元素

x = sc.parallelize([1, 3, 1, 2, 3])
y = x.takeOrdered(num=3)
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
[1, 1, 2]

38 take

take用于获取RDD中从0到num-1下标的元素,不排序。

x = sc.parallelize([1, 3, 1, 2, 3])
y = x.take(num=3)
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
[1, 3, 1]

39 first

返回RDD中的第一个元素,不排序

x = sc.parallelize([1, 3, 1, 2, 3])
y = x.first()
print(x.collect())
print(y)
[1, 3, 1, 2, 3]
1

40 collectAsMap

将pair类型(键值对类型)的RDD转换成map

x = sc.parallelize([('C', 3), ('A', 1), ('B', 2)])
y = x.collectAsMap()
print(x.collect())
print(y)
[('C', 3), ('A', 1), ('B', 2)]
{'C': 3, 'A': 1, 'B': 2}

41 keys

x = sc.parallelize([('C', 3), ('A', 1), ('B', 2)])
y = x.keys()
print(y.collect())
['C', 'A', 'B']

42 values

x = sc.parallelize([('C', 3), ('A', 1), ('B', 2)])
y = x.values()
print(y.collect())
[3, 1, 2]

43 reduceByKey

用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义;

x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
y = x.reduceByKey(lambda x, y : x + y)
print(x.collect())
print(y.collect())
[('B', 1), ('B', 2), ('A', 3), ('A', 4), ('A', 5)]
[('B', 3), ('A', 12)]

45 reduceByKeyLocally

该函数将RDD[K,V]中每个K对应的V值根据映射函数来运算,运算结果映射到一个Map[K,V]中,而不是RDD[K,V]。

x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
y = x.reduceByKeyLocally(lambda x, y : x + y)
print(x.collect())
print(y)
[('B', 1), ('B', 2), ('A', 3), ('A', 4), ('A', 5)]
{'B': 3, 'A': 12}

注意: reduceByKey与reduceByKeyLocally的返回值不同哟!!!一个是RDD, 一个是 map!!!

46 countByKey

countByKey用于统计RDD[K,V]中每个K的数量。返回一个map,map的 key 是RDD的K,value是K出现的次数。

x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
y = x.countByKey()
print(x.collect())
print(y)
[('B', 1), ('B', 2), ('A', 3), ('A', 4), ('A', 5)]
defaultdict(<class 'int'>, {'B': 2, 'A': 3})

注意 : 与 countByValue类似, 均返回一个 map

47 join

内连接,两个表具有相同的 key 时进行连接。

x = sc.parallelize([('C',4),('B',3),('A',2),('A',1)])
y = sc.parallelize([('A',8),('B',7),('A',6),('D',5)])
z = x.join(y)
print(x.collect())
print(y.collect())
print(z.collect())
[('C', 4), ('B', 3), ('A', 2), ('A', 1)]
[('A', 8), ('B', 7), ('A', 6), ('D', 5)]
[('B', (3, 7)), ('A', (2, 8)), ('A', (2, 6)), ('A', (1, 8)), ('A', (1, 6))]

48 leftOuterJoin

左连接

x = sc.parallelize([('C',4),('B',3),('A',2),('A',1)])
y = sc.parallelize([('A',8),('B',7),('A',6),('D',5)])
z = x.leftOuterJoin(y)
print(x.collect())
print(y.collect())
print(z.collect())
[('C', 4), ('B', 3), ('A', 2), ('A', 1)]
[('A', 8), ('B', 7), ('A', 6), ('D', 5)]
[('B', (3, 7)), ('A', (2, 8)), ('A', (2, 6)), ('A', (1, 8)), ('A', (1, 6)), ('C', (4, None))]

49 rightOuterJoin

右连接

x = sc.parallelize([('C',4),('B',3),('A',2),('A',1)])
y = sc.parallelize([('A',8),('B',7),('A',6),('D',5)])
z = x.rightOuterJoin(y)
print(x.collect())
print(y.collect())
print(z.collect())
[('C', 4), ('B', 3), ('A', 2), ('A', 1)]
[('A', 8), ('B', 7), ('A', 6), ('D', 5)]
[('B', (3, 7)), ('A', (2, 8)), ('A', (2, 6)), ('A', (1, 8)), ('A', (1, 6)), ('D', (None, 5))]

50 fullOuterJoin

全连接

x = sc.parallelize([('C',4),('B',3),('A',2),('A',1)])
y = sc.parallelize([('A',8),('B',7),('A',6),('D',5)])
z = x.fullOuterJoin(y)
print(x.collect())
print(y.collect())
print(z.collect())
[('C', 4), ('B', 3), ('A', 2), ('A', 1)]
[('A', 8), ('B', 7), ('A', 6), ('D', 5)]
[('B', (3, 7)), ('A', (2, 8)), ('A', (2, 6)), ('A', (1, 8)), ('A', (1, 6)), ('C', (4, None)), ('D', (None, 5))]

51 partitionBy

partitionBy根据partitioner函数生成新的ShuffleRDD,将原RDD重新分区。

repartition默认采用HashPartitioner分区,自己设计合理的分区方法(比如数量比较大的key 加个随机数 随机分到更多的分区, 这样处理数据倾斜更彻底一些)

x = sc.parallelize([(0, 1), (1, 2), (1, 3), (0, 2), (3, 5), (5, 6)], 2)
y1 = x.partitionBy(numPartitions=6, partitionFunc=lambda x : x)
print(x.glom().collect())
print(y1.glom().collect())
[[(0, 1), (1, 2), (1, 3)], [(0, 2), (3, 5), (5, 6)]]
[[(0, 1), (0, 2)], [(1, 2), (1, 3)], [], [(3, 5)], [], [(5, 6)]]

52 combineByKey

combineByKey()是最为常用的基于键进行聚合的函数。大多数基于键聚合的函数都是用它实现的。和aggregate()一样,combineByKey()可以让用户返回与输入数据的类型不同的返回值。

def combineByKey[C](createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C, partitioner: Partitioner, mapSideCombine: Boolean = true, serializer: Serializer = null)

如下解释下3个重要的函数参数:

createCombiner: V => C ,这个函数把当前的值作为参数,此时我们可以对其做些附加操作(类型转换)并把它返回 (这一步类似于初始化操作)

mergeValue: (C, V) => C,该函数把元素V合并到之前的元素C(createCombiner)上 (这个操作在每个分区内进行)

mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行)

# 合并
x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
createCombiner = (lambda el : [(el, el ** 2)])
mergeVal = (lambda aggregated, el : aggregated + [(el, el ** 2)])
mergeComb = (lambda agg1, agg2 : agg1 + agg2)
y = x.combineByKey(createCombiner, mergeVal, mergeComb)
print(x.collect())
print(y.collect())
[('B', 1), ('B', 2), ('A', 3), ('A', 4), ('A', 5)]
[('B', [(1, 1), (2, 4)]), ('A', [(3, 9), (4, 16), (5, 25)])]
x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
def to_list(a):
    return [a]
def append(a, b):
    a.append(b)
    return a
def extend(a, b):
    a.extend(b)
    return a
sorted(x.combineByKey(to_list, append, extend).collect())
[('A', [3, 4, 5]), ('B', [1, 2])]
# 计算平均值
x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
createCombiner = (lambda el : [(el)])
mergeVal = (lambda aggregated, el : aggregated + [el])
mergeComb = (lambda C1, C2 : C1 + C2)
def f(N):
    return (N[0], sum(N[1]) / len(N[1]))
y = x.combineByKey(createCombiner, mergeVal, mergeComb).map(f)
print(x.collect())
print(y.collect())
[('B', 1), ('B', 2), ('A', 3), ('A', 4), ('A', 5)]
[('B', 1.5), ('A', 4.0)]

注意:createCombiner将每个RDD中的 K:V 转换成 K:(V, V^2);mergeVal将每个分区的相同 K 的 (V, V^2) 合并在一起;mergeComb将不同分区的合并在一起!!!

53 aggregateByKey

rdd.aggregateByKey(zerovalue, seqFunc, combFunc) 其中第一个函数是初始值; seqFunc代表combine的聚合逻辑,每一个mapTask的结果的聚合成为combine; combFunc reduce端大聚合的逻辑

aggregateByKey函数对PairRDD中相同Key的值进行聚合操作,在聚合过程中同样使用了一个中立的初始值。

和aggregate函数类似,aggregateByKey返回值的类型不需要和RDD中value的类型一致。因为aggregateByKey是对相同Key中的值进行聚合操作,所以aggregateByKey函数最终返回的类型还是Pair RDD,对应的结果是Key和聚合好的值;

aggregate函数直接是返回非RDD的结果,这点需要注意。

x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
zeroValue = []
mergeVal = (lambda aggregated, el : aggregated + [(el, el**2)])
mergeComb = (lambda agg1, agg2 : agg1 + agg2)
y = x.aggregateByKey(zeroValue, mergeVal, mergeComb)
print(x.collect())
print(y.collect())
[('B', 1), ('B', 2), ('A', 3), ('A', 4), ('A', 5)]
[('B', [(1, 1), (2, 4)]), ('A', [(3, 9), (4, 16), (5, 25)])]

54 foldByKey

def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]

def foldByKey(zeroValue: V, numPartitions: Int)(func: (V, V) => V): RDD[(K, V)]

def foldByKey(zeroValue: V, partitioner: Partitioner)(func: (V, V) => V): RDD[(K, V)]

该函数用于RDD[K,V]根据K将V做折叠、合并处理,其中的参数zeroValue表示先根据映射函数将zeroValue应用于V,进行初始化V,再将映射函数应用于初始化后的V.

x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
zeroValue = 1
y = x.foldByKey(zeroValue, lambda agg, x : agg * x)
print(x.collect())
print(y.collect())
[('B', 1), ('B', 2), ('A', 3), ('A', 4), ('A', 5)]
[('B', 2), ('A', 60)]

55 groupByKey

def groupByKey(): RDD[(K, Iterable[V])]

def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]

def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]

该函数用于将RDD[K,V]中每个K对应的V值,合并到一个集合Iterable[V]中,

参数numPartitions用于指定分区数;

参数partitioner用于指定分区函数;

x = sc.parallelize([('B',1),('B',2),('A',3),('A',4),('A',5)])
print(sorted(x.groupByKey().mapValues(len).collect()))
print(sorted(x.groupByKey().mapValues(list).collect()))
[('A', 3), ('B', 2)]
[('A', [3, 4, 5]), ('B', [1, 2])]

56 flatMapValues

基本转换操作与flatMap类似,只不过flatMapValues是针对[K,V]中的V值进行flatMap操作。

与mapValues类似,但可以将一个value展开成多个value。

x = sc.parallelize([('A',(1,2,3)),('B',(4,5))])
y = x.flatMapValues(lambda x: [i for i in x]) 
print(x.collect())
print(y.collect())
[('A', (1, 2, 3)), ('B', (4, 5))]
[('A', 1), ('A', 2), ('A', 3), ('B', 4), ('B', 5)]
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')]

57 mapValues

对键值对每个value都应用一个函数,但是,key不会发生变化。

x = sc.parallelize([('A',(1,2,3)),('B',(4,5))])
y = x.mapValues(lambda x: [i for i in x]) 
print(x.collect())
print(y.collect())
[('A', (1, 2, 3)), ('B', (4, 5))]
[('A', [1, 2, 3]), ('B', [4, 5])]

58 groupWith

x = sc.parallelize([('C',4),('B',(3,3)),('A',2),('A',(1,1))])
y = sc.parallelize([('B',(7,7)),('A',6),('D',(5,5))])
z = sc.parallelize([('D',9),('B',(8,8))])
a = x.groupWith(y,z)
print(x.collect())
print(y.collect())
print(z.collect())
print("Result:")
for key,val in list(a.collect()): 
    print(key, [list(i) for i in val])
print(a.collect())
[('C', 4), ('B', (3, 3)), ('A', 2), ('A', (1, 1))]
[('B', (7, 7)), ('A', 6), ('D', (5, 5))]
[('D', 9), ('B', (8, 8))]
Result:
C [[4], [], []]
A [[2, (1, 1)], [6], []]
B [[(3, 3)], [(7, 7)], [(8, 8)]]
D [[], [(5, 5)], [9]]
[('C', (<pyspark.resultiterable.ResultIterable object at 0x0000024BC445D390>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445D9B0>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445DD30>)), ('A', (<pyspark.resultiterable.ResultIterable object at 0x0000024BC445D748>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445D400>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445D128>)), ('B', (<pyspark.resultiterable.ResultIterable object at 0x0000024BC445D080>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445D630>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445D320>)), ('D', (<pyspark.resultiterable.ResultIterable object at 0x0000024BC445D940>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445D828>, <pyspark.resultiterable.ResultIterable object at 0x0000024BC445D438>))]

59 cogroup

将多个RDD中同一个Key对应的Value组合到一起

x = sc.parallelize([('C',4),('B',(3,3)),('A',2),('A',(1,1))])
y = sc.parallelize([('A',8),('B',7),('A',6),('D',(5,5))])
z = x.cogroup(y)
print(x.collect())
print(y.collect())
for key,val in list(z.collect()):
    print(key, [list(i) for i in val])
[('C', 4), ('B', (3, 3)), ('A', 2), ('A', (1, 1))]
[('A', 8), ('B', 7), ('A', 6), ('D', (5, 5))]
B [[(3, 3)], [7]]
A [[2, (1, 1)], [8, 6]]
C [[4], []]
D [[], [(5, 5)]]
zzz = x.groupWith(y)
print(x.collect())
print(y.collect())
for key,val in list(zzz.collect()):
    print(key, [list(i) for i in val])
[('C', 4), ('B', (3, 3)), ('A', 2), ('A', (1, 1))]
[('A', 8), ('B', 7), ('A', 6), ('D', (5, 5))]
B [[(3, 3)], [7]]
A [[2, (1, 1)], [8, 6]]
C [[4], []]
D [[], [(5, 5)]]

60 sampleByKey

官网这么说:Return a subset of this RDD sampled by key (via stratified sampling). Create a sample of this RDD using variable sampling rates for different keys as specified by fractions, a key to sampling rate map.

x = sc.parallelize([('A',1),('B',2),('C',3),('B',4),('A',5)])
y = x.sampleByKey(withReplacement=False, fractions={'A':0.5, 'B':1, 'C':0.2}) # fractions为每个key设置被选到的概率
print(x.collect())
print(y.collect())
[('A', 1), ('B', 2), ('C', 3), ('B', 4), ('A', 5)]
[('B', 2), ('B', 4)]

61 subtractByKey

rdd1.subtractByKey(rdd2), 去掉rdd1中与rdd2共有的key对应的 KV pairs.

x = sc.parallelize([('C',1),('B',2),('A',3),('A',4)])
y = sc.parallelize([('A',5),('D',6),('A',7),('D',8)])
z = x.subtractByKey(y)
print(x.collect())
print(y.collect())
print(z.collect())
[('C', 1), ('B', 2), ('A', 3), ('A', 4)]
[('A', 5), ('D', 6), ('A', 7), ('D', 8)]
[('B', 2), ('C', 1)]

62 subtract

官网说:Return each value in self that is not contained in other.

rdd1.subtract(rdd2), 去掉rdd1中与rdd2共有的 (K, V) 对应的 KV pairs.

x = sc.parallelize([('C',4),('B',3),('A',2),('A',1)])
y = sc.parallelize([('C',8),('A',2),('D',1)])
z = x.subtract(y)
print(x.collect())
print(y.collect())
print(z.collect())
[('C', 4), ('B', 3), ('A', 2), ('A', 1)]
[('C', 8), ('A', 2), ('D', 1)]
[('C', 4), ('A', 1), ('B', 3)]

63 keyBy

keyBy(f) Creates tuples of the elements in this RDD by applying f.

x = sc.parallelize([1, 2, 3])
y = x.keyBy(lambda x : x ** 2)
print(x.collect())
print(y.collect())
[1, 2, 3]
[(1, 1), (4, 2), (9, 3)]

64 repartition

def repartition(numPartitions: Int): RDD[T]

该函数其实就是coalesce函数第二个参数为true的实现。

x = sc.parallelize([1,2,3,4,5],2)
y = x.repartition(numPartitions=3)
print(x.glom().collect())
print(y.glom().collect())
[[1, 2], [3, 4, 5]]
[[], [1, 2, 3, 4, 5], []]

65 coalesce

def coalesce(numPartitions:Int,shuffle:Boolean=false):RDD[T]

该函数用于将RDD进行重分区,使用HashPartitioner。

第一个参数为重分区的数目,第二个为是否进行shuffle,默认为false。

当spark程序中,存在过多的小任务的时候,可以通过 RDD.coalesce方法,收缩合并分区,减少分区的个数,减小任务调度成本

# shuffle=False,新的分区数小于原来的分区数,分区
x = sc.parallelize([1,2,3,4,5],2)
y = x.coalesce(numPartitions=1, shuffle=False)
print(x.glom().collect())
print(y.glom().collect())
[[1, 2], [3, 4, 5]]
[[1, 2, 3, 4, 5]]
# shuffle=False,新的分区数大于原来的分区数,不分区
x = sc.parallelize([1,2,3,4,5],2)
y = x.coalesce(numPartitions=3, shuffle=False)
print(x.glom().collect())
print(y.glom().collect())
[[1, 2], [3, 4, 5]]
[[1, 2], [3, 4, 5]]
# shuffle=True,新的分区数小于原来的分区数,分区
x = sc.parallelize([1,2,3,4,5],2)
y = x.coalesce(numPartitions=1, shuffle=True)
print(x.glom().collect())
print(y.glom().collect())
[[1, 2], [3, 4, 5]]
[[1, 2, 3, 4, 5]]
# shuffle=True,新的分区数大于原来的分区数,分区
x = sc.parallelize([1,2,3,4,5],2)
y = x.coalesce(numPartitions=3, shuffle=True)
print(x.glom().collect())
print(y.glom().collect())
[[1, 2], [3, 4, 5]]
[[], [1, 2, 3, 4, 5], []]

如果shuff为false时,如果传入的参数大于现有的分区数目,RDD的分区数不变,也就是说不经过shuffle,是无法将RDDde分区数变多的。

我们常认为coalesce不产生shuffle会比repartition 产生shuffle效率高,而实际情况往往要根据具体问题具体分析,coalesce效率不一定高,有时还有大坑,大家要慎用。

coalesce 与 repartition 他们两个都是RDD的分区进行重新划分,repartition只是coalesce接口中shuffle为true的实现(假设源RDD有N个分区,需要重新划分成M个分区)

1)如果N<M。一般情况下N个分区有数据分布不均匀的状况,利用HashPartitioner函数将数据重新分区为M个,这时需要将shuffle设置为true(repartition实现,coalesce也实现不了)。

2)如果N>M并且N和M相差不多,(假如N是1000,M是100)那么就可以将N个分区中的若干个分区合并成一个新的分区,最终合并为M个分区,这时可以将shuff设置为false(coalesce实现),如果M>N时,coalesce是无效的,不进行shuffle过程,父RDD和子RDD之间是窄依赖关系,无法使文件数(partiton)变多。

总之如果shuffle为false时,如果传入的参数大于现有的分区数目,RDD的分区数不变,也就是说不经过shuffle,是无法将RDD的分区数变多的

3)如果N>M并且两者相差悬殊,这时你要看executor数与要生成的partition关系,如果executor数 <= 要生成partition数,coalesce效率高,反之如果用coalesce会导致(executor数-要生成partiton数)个excutor空跑从而降低效率。如果在M为1的时候,为了使coalesce之前的操作有更好的并行度,可以将shuffle设置为true。

66 zip

def zip[U](other: RDD[U])(implicit arg0: ClassTag[U]): RDD[(T, U)]

zip函数用于将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。

x = sc.parallelize(['B','A','A'])
y = sc.parallelize(range(0,3)) 
z = x.zip(y)
print(x.collect())
print(y.collect())
print(z.collect())
['B', 'A', 'A']
[0, 1, 2]
[('B', 0), ('A', 1), ('A', 2)]

67 zipWithIndex

def zipWithIndex(): RDD[(T, Long)]

表示将rdd的元素和它的索引的值进行拉练操作。索引开始于0组合成为键值对。

x = sc.parallelize(['B','A','A'], 2)
y = x.zipWithIndex()
print(x.glom().collect())
print(y.collect())
[['B'], ['A', 'A']]
[('B', 0), ('A', 1), ('A', 2)]

68 zipWithUniqueId

def zipWithUniqueId(): RDD[(T, Long)]

这个表示的是给每一个元素一个新的id值,这个id值不一定和真实的元素的索引值一致。返回的同样是一个元祖

这个唯一ID生成算法如下:

每个分区中第一个元素的唯一ID值为:该分区索引号,

每个分区中第N个元素的唯一ID值为:(前一个元素的唯一ID值) + (该RDD总的分区数)

x = sc.parallelize(['B','A','A'],2)
y = x.zipWithIndex()
print(x.glom().collect())
print(y.collect())
[['B'], ['A', 'A']]
[('B', 0), ('A', 1), ('A', 2)]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值