RDD算子: 指的是RDD对象中提供了非常多的具有特殊功能的函数, 我们一般将这样的函数称为算子(大白话: 指的RDD的API)
相关的算子的详细官方文档: https://spark.apache.org/docs/3.1.2/api/python/reference/pyspark.html#rdd-apis
3.1 RDD算子的分类
整个RDD算子, 共分为二大类: Transformation(转换算子) 和 Action(动作算子)
转换算子:
1- 所有的转换算子在执行完成后, 都会返回一个新的RDD
2- 所有的转换算子都是LAZY(惰性),并不会立即执行, 此时可以认为通过转换算子来定义RDD的计算规则
3- 转换算子必须遇到Action算子才会触发执行
动作算子:
1- 动作算子在执行后, 不会返回一个RDD, 要不然没有返回值, 要不就返回其他的
2- 动作算子都是立即执行, 一个动作算子就会产生一个Job执行任务,运行这个动作算子所依赖的所有的RDD
相关的转换算子:
相关的动作算子:
3.2 RDD的转换算子
值类型的算子:
- map算子:
- 格式: rdd.map(fn)
- 说明: 根据传入的函数, 对数据进行一对一的转换操作, 传入一行, 返回一行
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
需求: 请对每一个元素进行 +1 返回
rdd.map(lambda num: num + 1).collect()
结果:
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
- groupBy算子:
- 格式: groupBy(fn)
- 说明: 根据传入的函数对数据进行分组操作
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
需求: 请将数据分为奇数和偶数二部分
def jo(num):
if num % 2 == 0:
return 'o'
else:
return 'j'
rdd.groupBy(jo).collect()
结果:
[
('j', <pyspark.resultiterable.ResultIterable object at 0x7f1a1bc22490>),
('o', <pyspark.resultiterable.ResultIterable object at 0x7f1a1bc0a2b0>)
]
mapValues(list): 将 kv中value转换为list
rdd.groupBy(jo).mapValues(list).collect()
结果:
[
('j', [1, 3, 5, 7, 9]),
('o', [2, 4, 6, 8, 10])
]
思考: 上述的函数, 是否有简单的写法呢? 或者说直接使用lambda如何写呢?
rdd.groupBy(lambda num: 'o' if num % 2 == 0 else 'j' ).mapValues(list).collect()
结果:
[
('j', [1, 3, 5, 7, 9]),
('o', [2, 4, 6, 8, 10])
]
- filter算子
- 格式: filter(fn)
- 说明: 过滤算子, 可以根据函数中指定的过滤条件, 对数据进行过滤操作, 条件返回True表示保留, 返回False表示过滤掉
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
需求: 请将 <=3的数据过滤掉
rdd.filter(lambda num: num > 3).collect()
结果:
[4, 5, 6, 7, 8, 9, 10]
- flatMap算子:
- 格式: flatMap(fn)
- 说明: 在map算子的基础上, 在加入一个压扁的操作, 主要适用于一行中包含多个内容的操作, 实现一转多的操作
rdd = sc.parallelize(['张三 李四 王五 赵六','田七 周八 李九'])
需求: 将其转换为一个个的姓名
rdd.flatMap(lambda line: line.split(' ')).collect()
或者
rdd.flatMap(lambda line: line.split()).collect()
结果:
['张三', '李四', '王五', '赵六', '田七', '周八', '李九']
双值类型算子:
- union(并集) 和 intersection(交集)
- 格式: rdd1.union|intersection(rdd2)
rdd1 = sc.parallelize([3,1,5,7,9])
rdd2 = sc.parallelize([5,8,2,4,0])
结果:
并集:
rdd1.union(rdd2).collect()
结果:
[3, 1, 5, 7, 9, 5, 8, 2, 4, 0]
去重操作:
rdd1.union(rdd2).distinct().collect()
结果:
[8, 4, 0, 1, 5, 9, 2, 3, 7]
交集:
rdd1.intersection(rdd2).collect()
结果:
[5]
KV类型相关的算子:
- groupByKey算子:
- 格式: groupByKey()
- 说明: 根据key进行分组操作
rdd = sc.parallelize([('c01','张三'),('c02','李四'),('c02','王五'),('c03','赵六'),('c02','田七'),('c02','周八'),('c03','李九')])
需求: 根据班级分组统计
rdd.groupByKey().collect()
结果:
[
('c01', <pyspark.resultiterable.ResultIterable object at 0x7f1a1bc0af10>),
('c02', <pyspark.resultiterable.ResultIterable object at 0x7f1a1bc0af40>),
('c03', <pyspark.resultiterable.ResultIterable object at 0x7f1a1bc0afd0>)
]
rdd.groupByKey().mapValues(list).collect()
结果:
[
('c01', ['张三']),
('c02', ['李四', '王五', '田七', '周八']),
('c03', ['赵六', '李九'])]
rdd.groupByKey().mapValues(list).map(lambda kv: (kv[0],len(kv[1]))).collect()
结果:
[('c01', 1), ('c02', 4), ('c03', 2)]
- reduceByKey()
- 格式: reduceByKey(fn)
- 说明: 根据key进行分组, 将一个组内的value数据放置到一个列表中, 对这个列表基于 传入函数进行聚合计算操作
rdd = sc.parallelize([('c01','张三'),('c02','李四'),('c02','王五'),('c03','赵六'),('c02','田七'),('c02','周八'),('c03','李九')])
需求: 统计每个班级有多少个人
rdd.map(lambda kv:(kv[0],1)).reduceByKey(lambda agg,curr: agg + curr).collect()
结果:
[('c01', 1), ('c02', 4), ('c03', 2)]
如果不转为1:
rdd.reduceByKey(lambda agg,curr: agg + curr).collect()
结果:
[('c01', '张三'), ('c02', '李四王五田七周八'), ('c03', '赵六李九')]
- sortByKey()算子
- 格式: sortByKey(ascending = True|False)
- 说明: 根据key进行排序操作, 默认按照key进行升序排序, 如果需要倒序, 设置 ascending 为False
rdd = sc.parallelize([('c03','张三'),('c05','李四'),('c01','王五'),('c09','赵六'),('c02','田七'),('c07','周八'),('c06','李九')])
根据班级序号排序
rdd.sortByKey().collect()
结果:
[('c01', '王五'), ('c02', '田七'), ('c03', '张三'), ('c05', '李四'), ('c06', '李九'), ('c07', '周八'), ('c09', '赵六')]
rdd.sortByKey(ascending=False).collect()
结果:
[('c09', '赵六'), ('c07', '周八'), ('c06', '李九'), ('c05', '李四'), ('c03', '张三'), ('c02', '田七'), ('c01', '王五')]
思考:
rdd = sc.parallelize([('c03','张三'),('c05','李四'),('c011','王五'),('c09','赵六'),('c02','田七'),('c07','周八'),('c06','李九')])
rdd.sortByKey().collect()
结果: 字典序 由于key是字符串
[('c011', '王五'), ('c02', '田七'), ('c03', '张三'), ('c05', '李四'), ('c06', '李九'), ('c07', '周八'), ('c09', '赵六')]
- countByKey() 和 countByValue()
- 严格意义上应该是属于action算子
- 说明:
- countByKey() 根据key进行分组 统计每个分组下有多少个元素
- countByValue() 根据value进行分组, 统计相同value有多少个
rdd = sc.parallelize([('c01','张三'),('c02','李四'),('c02','王五'),('c03','赵六'),('c02','田七'),('c02','周八'),('c03','李九')])
rdd.countByKey()
defaultdict(<class 'int'>, {'c01': 1, 'c02': 4, 'c03': 2})
rdd.countByValue() 将列表找那个每一个元素, 作为一个整体来统计相同的value有多少个
defaultdict(<class 'int'>, {('c01', '张三'): 1, ('c02', '李四'): 1, ('c02', '王五'): 1, ('c03', '赵六'): 1, ('c02', '田七'): 1, ('c02', '周八'): 1, ('c03', '李九'): 1})
rdd = sc.parallelize([1,2,1,2,3,1,2,4])
rdd.countByValue()
defaultdict(<class 'int'>, {1: 3, 2: 3, 3: 1, 4: 1})
3.3 RDD的动作算子
-
collect() 算子
- 格式: collect()
- 作用: 收集各个分区的数据, 将数据汇总到一个大的列表返回
-
reduce() 算子
- 格式: reduce(fn)
- 作用: 根据传入的函数对数据进行聚合操作
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
rdd.reduce(lambda agg,curr: agg + curr)
结果: 55
- first()算子
- 格式: first()
- 说明: 获取第一个元素
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
rdd.first()
1
- take() 算子
- 格式: take(N)
- 说明: 获取前N个元素, 类似于limit操作
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
rdd.take(5)
结果
[1, 2, 3, 4, 5]
- top() 算子
- 格式: top(N, [fn])
- 说明: 对数据集进行倒序排序操作, 如果是kv类型, 默认是针对key进行排序, 获取前N个元素
- fn: 可以自定义排序, 根据谁来排序
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
rdd.top(3)
结果:
[10, 9, 8]
rdd = sc.parallelize([('c03','张三'),('c05','李四'),('c011','王五'),('c09','赵六'),('c02','田七'),('c07','周八'),('c06','李九')])
rdd.top(3)
结果:
[('c09', '赵六'), ('c07', '周八'), ('c06', '李九')]
rdd = sc.parallelize([('c03',5),('c05',9),('c011',2),('c09',6),('c02',80),('c07',12),('c06',10)])
rdd.top(3,lambda kv: kv[1])
结果:
[('c02', 80), ('c07', 12), ('c06', 10)]
- count()算子
- 格式: count()
- 说明: 统计多少个
rdd = sc.parallelize([('c03',5),('c05',9),('c011',2),('c09',6),('c02',80),('c07',12),('c06',10)])
rdd.count()
7
- foreach()算子
- 格式: foreach(fn)
- 说明: 对数据集进行遍历操作, 遍历后做什么, 取决于传入的函数
rdd = sc.parallelize([('c03',5),('c05',9),('c011',2),('c09',6),('c02',80),('c07',12),('c06',10)])
rdd.foreach(lambda kv: print(kv))
('c03', 5)
('c05', 9)
('c011', 2)
('c09', 6)
('c02', 80)
('c07', 12)
('c06', 10)
-
takeSample()算子
-
格式: takeSample(True|False, N,seed(种子值))
- 参数1: 是否允许重复采样
- 参数2: 采样多少个, 如果允许重复采样, 采样个数不限制, 否则最多等于本身数量个数
- 参数3: 设置种子值, 值可以随便写, 一旦写死了, 表示每次采样的内容也是固定的(可选的) 如果没有特殊需要, 一般不设置
-
作用: 数据抽样
-
rdd = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
rdd.takeSample(True,5)
[9, 9, 4, 8, 9]
rdd.takeSample(True,5)
[3, 8, 1, 3, 9]
rdd.takeSample(False,5)
[6, 1, 8, 7, 3]
rdd.takeSample(False,5)
[5, 7, 6, 3, 8]
rdd.takeSample(False,20)
[2, 10, 7, 5, 8, 9, 3, 4, 6, 1]
rdd.takeSample(False,5)
[8, 3, 10, 7, 9]
rdd.takeSample(False,5,2)
[6, 10, 4, 5, 7]
rdd.takeSample(False,5,2)
[6, 10, 4, 5, 7]
rdd.takeSample(False,5,2)
[6, 10, 4, 5, 7]
rdd.takeSample(False,3,2)
[6, 10, 4]