Spark 基本函数学习笔记一
Spark 基本函数学习笔记一¶
spark的函数主要分两类,Transformations和Actions。
Transformations为一些数据转换类函数,actions为一些行动类函数:
- 转换:转换的返回值是一个新的RDD集合,而不是单个值。调用一个变换方法,
不会有任何求值计算,它只获取一个RDD作为参数,然后返回一个新的RDD。 - 行动:行动操作计算并返回一个新的值。当在一个RDD对象上调用行动函数时,
会在这一时刻计算全部的数据处理查询并返回结果值。
这里介绍pyspark中常用函数功能以及代码示例。
官方文档链接:http://spark.apache.org/docs/2.3.3/api/python/pyspark.html#pyspark.RDD
文档github链接:Spark基本函数学习
RDD下面的Transformations函数,这些函数适用于RDD集合操作:
- map(func)
- flatMap(func)
- mapPartitions(func)
- mapPartitionsWithIndex(func)
- foreach(f)
- foreachPartition(f)
- filter(func)
- sample()
- union()
- intersection()
- distinct()
- groupBy()
- groupByKey()
- reduce
- reduceByKey()
- aggregate
- aggregateByKey()
- sortBy
- sortByKey()
- join()
- cogroup()
- cartesian()
- coalesce()
- Pipe()
- Repartition()
- rePartitionAndSortWithinPartitions()
from pyspark.sql import SparkSession
import numpy as np
from pyspark import SparkContext
spark = SparkSession.Builder().getOrCreate()
sc = spark.sparkContext
rdd = sc.parallelize(range(10))
rdd.collect()
map(func)转换¶
map(func) 与python的map函数功能一样,都是对每个元素执行func函数的计算。
返回一个新的数据集,数据集的每个元素都是经过func函数处理的
我们这里以对每个元素乘以10计算示例
# 使用lambda 函数
temp = rdd.map(lambda x: x*10)
temp.collect()
# 使用自定义函数
def multi_10(x):
return x * 10
temp = rdd.map(multi_10)
temp.collect()
flatMap(func)¶
类似于map(func), 但是不同的是map对每个元素处理完后返回与原数据集相同元素数量的数据集,而flatMap返回的元素数不一定和原数据集相同
rdd = sc.parallelize([[1,2],[2,3],[3,4]])
d = rdd.flatMap(lambda x: x)
d.collect()
mapPartitions(func)¶
mapPartitions是map的一个变种。
map的输入函数是应用于RDD中每个元素,而mapPartitions的输入函数是应用于每个分区,
也就是把每个分区中的内容作为整体来处理的。
rdd = sc.parallelize([1,2,3,4,5], 3)
def f(iterator):
yield sum(iterator)
rdd.mapPartitions(f).collect()
glom()函数就是要显示出RDD对象的分区情况
可以看到rdd分了三个区,每个区的数据为: [[1], [2, 3], [4, 5]]
所以上面的例子中mapPartitions计算sum的结果是三个,
每个分区求和结果是[1,5,9]
rdd.glom().collect()
mapPartitionsWithIndex(func)¶
与mapPartition相比,mapPartitionWithIndex能够保留分区索引,函数的传入参数也是分区索引和iterator构成的键值对。
def f1(partitionIndex,iterator):
yield (partitionIndex,sum(iterator))
def f2(partitionIndex,iterator):
yield sum(iterator)
rf1 = rdd.mapPartitionsWithIndex(f1)
rf2 = rdd.mapPartitionsWithIndex(f2)
# f1 的返回值可以保留分区索引
print(rf1.glom().collect())
print(rf2.glom().collect())
rdd = sc.parallelize([1,2,3,4,5])
res = rdd.foreach(lambda x: x*2)
print(res) # 打印结果为None
rdd.collect() # 输出为 [1, 2, 3, 4, 5]
# 打印元素,如果使用jupyter,在启动页面可以看到打印
def f(x):
print(x)
sc.parallelize([1, 2, 3, 4, 5]).foreach(f)
foreachPartition(f)¶
与foreach一样,只是操作的对象为RDD的每个数据分区
def f(iterator):
for x in iterator:
print(x)
sc.parallelize([1, 2, 3, 4, 5]).foreachPartition(f)
filter(func)函数¶
返回一个新的数据集,这个数据集中的元素是通过func函数筛选后返回为true的元素
简单的说就是,对数据集中的每个元素进行筛选,如果符合条件则返回true,不符合返回false,
最后将返回为true的元素组成新的数据集返回
# 选择偶数
d = rdd.filter(lambda x: x % 2 ==0)
d.collect()
# 整除3
def three(x):
return x % 3 == 0
d = rdd.filter(three)
d.collect()
rdd.sample(False, 0.4, 40).collect()
union¶
union(otherDataset)是数据合并,返回一个新的数据集,由原数据集和otherDataset联合而成。
rdd1 = rdd.map(lambda x: x + 10)
rdd.union(rdd1).collect()
intersection()¶
返回两个数据集的交集
rdd1 = sc.parallelize([1, 10, 2, 3, 4, 5])
rdd2 = sc.parallelize([1, 6, 2, 3, 7, 8])
rdd1.intersection(rdd2).collect()
distinct()¶
返回数据集中不同值的列表,即去除数据集中重复的元素
rdd = sc.parallelize([1,2,3,4,1,2,3])
rdd.distinct().collect()
groupBy¶
给定一个分组条件,返回分组后的key value数据集
rdd = sc.parallelize(range(10))
# 按照模2结果来分组
res = rdd.groupBy(lambda x: x % 2).collect()
[(k, sorted(v)) for k,v in res]
# 按照模3结果来分组
res = rdd.groupBy(lambda x: x % 3).collect()
[(k, sorted(v)) for k,v in res]
rdd = sc.parallelize([('a', 1), ('a',1), ('b',1), ('b',1), ('b',1), ('c',1),('c',1),('c',1)])
rdd.collect()
# 分组后求值的和
rdd.groupByKey().mapValues(sum).collect()
from operator import add
# 累计求和
sc.parallelize([1, 2, 3, 4, 5]).reduce(add)
reduceByKey¶
功能与reduce函数一样。不过输入的数据为key value格式,按照key分组进行函数操作。
在WordCount例子中,使用reduceByKey来统计单词的个数。 链接:https://www.cnblogs.com/StitchSun/p/10535822.html
rdd = sc.parallelize([('a', 1), ('a',1), ('b',1), ('b',1), ('b',1), ('c',1),('c',1),('c',1)])
rdd.reduceByKey(add).collect()
aggregate()¶
函数原型:aggregate(zeroValue, seqOp, combOp)
aggregate函数操作比较复杂,有两个函数。seqOp函数是对每个分区的元素与zoroValue进行计算,
然后由combOp函数对所有分区的结果进行计算。
将初始值和第一个分区中的第一个元素传递给seq函数进行计算,然后将计算结果和第二个元素传递给seq函数,直到计算到最后一个值。
第二个分区中也是同理操作。
最后将初始值、所有分区的结果经过combine函数进行计算(先将前两个结果进行计算,将返回结果和下一个结果传给combine函数,以此类推),并返回最终结果。
data = sc.parallelize((1,2,3,4,5,6),2)
# 如果使用jupyter,打印结果在jupyter页面可以看到
def seq(a,b):
print('seqOp:'+str(a)+"\t"+str(b))
return min(a,b)
def combine(a,b):
print('comOp:'+str(a)+"\t"+str(b))
return a+b
# 例子解析:
# 先在每个分区中元素中两两操作,找最小的元素。
# 计算完成后,由combine函数计算两两分区结果的和
# 计算步骤1:初始值为3,与分区[1,2,3]元素一一进行seq最小值运算,得到结果为 1
# 计算步骤2:初始值为3,与分区[4,5,6]元素一一进行seq最小值运算,得到结果为 3
# 计算步骤3:初始值为3,与分区结果1,分区结果3进行combine相加运算,得到结果为 3 + 1 + 3, 结果为7
print(data.glom().collect())
data.aggregate(3,seq, combine)
aggregateByKey()¶
函数原型 aggregateByKey(zeroValue, seqFunc, combFunc, numPartitions=None, partitionFunc=<function portable_hash>)
在kv对的RDD中,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,
返回的结果作为一个新的kv对,然后再将结果按照key进行合并,
最后将每个分组的value传递给combine函数进行计算(先将前两个value进行计算,将返回结果和下一个value传给combine函数,以此类推),
将key与计算结果作为一个新的kv对输出
data = sc.parallelize([(1,3),(1,2),(1,4),(2,3)])
def seqFunc(a, b):
print("seqFunc:%s,%s" %(a,b))
return max(a, b) #取最大值
def combFunc(a, b):
print("combFunc:%s,%s" %(a ,b))
return a + b #累加起来
# aggregateByKey这个算子内部有分组
# 这里numPartitions值为4,将数据分为四个区,seqFunc计算结果为 (1,3),(1,3),(1,4)和(2,3)
# 然后按照key分组进行comFunc计算,得到结果[(1, 10), (2,3)]
data.aggregateByKey(3, seqFunc, combFunc, 4).collect()
tmp = [('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)]
x0 = sc.parallelize(tmp).sortBy(lambda x: x[0]).collect()
x1 = sc.parallelize(tmp).sortBy(lambda x: x[1]).collect()
print(x0)
print(x1)
sortByKey¶
函数原型 sortByKey(ascending=True, numPartitions=None, keyfunc=<function RDD.>)
根据key进行排序,输入的数据必须为key value格式
tmp = [('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)]
sc.parallelize(tmp).sortByKey().collect()
join()¶
两个数据集按照key内连接,返回数据集中有相同key的元素组成的新的数据集,
数据集A中的元素与数据集B中相同key元素一一组合,生成新的数据集
格式为(key, (value1, value2))
x = sc.parallelize([("a", 1), ("b", 4)])
y = sc.parallelize([("a", 2), ("a", 3)])
x.join(y).collect()
cogroup()¶
将多个RDD中同一个Key对应的Value组合到一起。如果RDD中没有对应的key,则会生成一个空值
x = sc.parallelize([("a", 1), ("b", 4)])
y = sc.parallelize([("a", 2)])
for k, v in x.cogroup(y).collect():
print(k, tuple(map(list, v)))
rdd = sc.parallelize([1, 2])
rdd2 = sc.parallelize([3, 4, 5])
rdd.cartesian(rdd2).collect()
coalesce()¶
按照给定的数量重新分区数据集
old = sc.parallelize([1, 2, 3, 4, 5], 3).glom().collect()
print(old)
sc.parallelize([1, 2, 3, 4, 5], 3).coalesce(1).glom().collect()
sc.parallelize([1, 2, 3, 4, 5], 3).coalesce(2).glom().collect()
pipe¶
对rdd数据集的每个元素,调用外部程序
# cat 文件内容
# 先在目录下创建一个for_test.txt文件,然后来读取文件内容
sc.parallelize(['1', '2', '', '3']).pipe('cat for_test.txt').collect()
repartition¶
数据集重新分区
# 创建默认为四个分区的数据集
rdd = sc.parallelize([1,2,3,4,5,6,7], 4)
print(rdd.glom().collect())
# 重新分为两个分区
rdd.repartition(2).glom().collect()
rePartitionAndSortWithinPartitions()¶
根据给定的分区程序对RDD进行重新分区,并在每个生成的分区内按键对记录进行排序。
这比调用重新分区,然后在每个分区内进行排序更有效率,因为它可以将排序压入洗牌机器。
应用场景:
- 如果需要重分区,并且想要对分区中的数据进行升序排序。
- 提高性能,替换repartition和sortBy
rdd = sc.parallelize([(0, 5), (3, 8), (2, 6), (0, 8), (3, 8), (1, 3)])
rdd2 = rdd.repartitionAndSortWithinPartitions(2, lambda x: x % 2, True)
rdd2.glom().collect()