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()
In [1]:
from pyspark.sql import SparkSession
import numpy as np
from pyspark import SparkContext

spark = SparkSession.Builder().getOrCreate()

sc = spark.sparkContext
In [2]:
rdd = sc.parallelize(range(10))
rdd.collect()
Out[2]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 

map(func)转换

map(func) 与python的map函数功能一样,都是对每个元素执行func函数的计算。

返回一个新的数据集,数据集的每个元素都是经过func函数处理的

我们这里以对每个元素乘以10计算示例

In [3]:
# 使用lambda 函数
temp = rdd.map(lambda x: x*10)
temp.collect()
Out[3]:
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
In [4]:
# 使用自定义函数
def multi_10(x):
    return x * 10

temp = rdd.map(multi_10)
temp.collect()
Out[4]:
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
 

flatMap(func)

类似于map(func), 但是不同的是map对每个元素处理完后返回与原数据集相同元素数量的数据集,而flatMap返回的元素数不一定和原数据集相同

In [5]:
rdd = sc.parallelize([[1,2],[2,3],[3,4]])

d = rdd.flatMap(lambda x: x)
d.collect()
Out[5]:
[1, 2, 2, 3, 3, 4]
 

mapPartitions(func)

mapPartitions是map的一个变种。

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

也就是把每个分区中的内容作为整体来处理的。

In [6]:
rdd = sc.parallelize([1,2,3,4,5], 3)
def f(iterator): 
    yield sum(iterator)
rdd.mapPartitions(f).collect()
Out[6]:
[1, 5, 9]
 

glom()函数就是要显示出RDD对象的分区情况

可以看到rdd分了三个区,每个区的数据为: [[1], [2, 3], [4, 5]]

所以上面的例子中mapPartitions计算sum的结果是三个,

每个分区求和结果是[1,5,9]

In [7]:
rdd.glom().collect()
Out[7]:
[[1], [2, 3], [4, 5]]
 

mapPartitionsWithIndex(func)

与mapPartition相比,mapPartitionWithIndex能够保留分区索引,函数的传入参数也是分区索引和iterator构成的键值对。

In [8]:
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())
 
[[(0, 1)], [(1, 5)], [(2, 9)]]
[[1], [5], [9]]
 

foreach(func)

在RDD上每个元素执行func运算,foreach与map的区别是:

map返回一个新的RDD,foreach直接在元素上应用func操作,

原RDD内容不变,无返回值

In [9]:
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)
 
None
 

foreachPartition(f)

与foreach一样,只是操作的对象为RDD的每个数据分区

In [10]:
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的元素组成新的数据集返回

In [11]:
# 选择偶数
d = rdd.filter(lambda x: x % 2 ==0)
d.collect()
Out[11]:
[2, 4]
In [12]:
# 整除3
def three(x):
    return x % 3 == 0

d = rdd.filter(three)
d.collect()
Out[12]:
[3]
 

sample()

sample()方法返回数据集的随机样本。

  • 第一个参数指定采样是否应该替换
  • 第二个参数定义返回数据的分数
  • 第三个参数是伪随机数产生器的种子
In [13]:
rdd.sample(False, 0.4, 40).collect()
Out[13]:
[3]
 

union

union(otherDataset)是数据合并,返回一个新的数据集,由原数据集和otherDataset联合而成。

In [14]:
rdd1 = rdd.map(lambda x: x + 10)
rdd.union(rdd1).collect()
Out[14]:
[1, 2, 3, 4, 5, 11, 12, 13, 14, 15]
 

intersection()

返回两个数据集的交集

In [15]:
rdd1 = sc.parallelize([1, 10, 2, 3, 4, 5])
rdd2 = sc.parallelize([1, 6, 2, 3, 7, 8])
rdd1.intersection(rdd2).collect()
Out[15]:
[1, 2, 3]
 

distinct()

返回数据集中不同值的列表,即去除数据集中重复的元素

In [16]:
rdd = sc.parallelize([1,2,3,4,1,2,3])
rdd.distinct().collect()
Out[16]:
[4, 1, 2, 3]
 

groupBy

给定一个分组条件,返回分组后的key value数据集

In [17]:
rdd = sc.parallelize(range(10))

# 按照模2结果来分组
res = rdd.groupBy(lambda x: x % 2).collect()
[(k, sorted(v)) for k,v in res]
Out[17]:
[(0, [0, 2, 4, 6, 8]), (1, [1, 3, 5, 7, 9])]
In [18]:
# 按照模3结果来分组
res = rdd.groupBy(lambda x: x % 3).collect()
[(k, sorted(v)) for k,v in res]
Out[18]:
[(0, [0, 3, 6, 9]), (1, [1, 4, 7]), (2, [2, 5, 8])]
 

groupByKey

数据按照key来分区,数据要求为key value格式。

返回的为key value格式,value为分组后的数据。

In [19]:
rdd = sc.parallelize([('a', 1), ('a',1), ('b',1), ('b',1), ('b',1), ('c',1),('c',1),('c',1)])
rdd.collect()
Out[19]:
[('a', 1),
 ('a', 1),
 ('b', 1),
 ('b', 1),
 ('b', 1),
 ('c', 1),
 ('c', 1),
 ('c', 1)]
In [20]:
# 分组后求值的和
rdd.groupByKey().mapValues(sum).collect()
Out[20]:
[('b', 3), ('c', 3), ('a', 2)]
 

reduce

与python中的reduce功能一样,对iter对象的元素前两个元素进行函数操作,得到的结果与第三个元素再进行函数操作,

以此类推,直到最后一个元素。

In [21]:
from operator import add
# 累计求和
sc.parallelize([1, 2, 3, 4, 5]).reduce(add)
Out[21]:
15
 

reduceByKey

功能与reduce函数一样。不过输入的数据为key value格式,按照key分组进行函数操作。

在WordCount例子中,使用reduceByKey来统计单词的个数。 链接:https://www.cnblogs.com/StitchSun/p/10535822.html

In [22]:
rdd = sc.parallelize([('a', 1), ('a',1), ('b',1), ('b',1), ('b',1), ('c',1),('c',1),('c',1)])
rdd.reduceByKey(add).collect()
Out[22]:
[('b', 3), ('c', 3), ('a', 2)]
 

aggregate()

函数原型:aggregate(zeroValue, seqOp, combOp)

aggregate函数操作比较复杂,有两个函数。seqOp函数是对每个分区的元素与zoroValue进行计算,

然后由combOp函数对所有分区的结果进行计算。

将初始值和第一个分区中的第一个元素传递给seq函数进行计算,然后将计算结果和第二个元素传递给seq函数,直到计算到最后一个值。

第二个分区中也是同理操作。

最后将初始值、所有分区的结果经过combine函数进行计算(先将前两个结果进行计算,将返回结果和下一个结果传给combine函数,以此类推),并返回最终结果。

http://www.cnblogs.com/LHWorldBlog/p/8215529.html

In [23]:
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)
 
[[1, 2, 3], [4, 5, 6]]
comOp:3	1
comOp:4	3
Out[23]:
7
 

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对输出

In [24]:
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()
Out[24]:
[(1, 10), (2, 3)]
 

sortBy

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

对集合元素排序,根据给定的函数进行排序操作

In [25]:
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)
 
[('1', 3), ('2', 5), ('a', 1), ('b', 2), ('d', 4)]
[('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)]
 

sortByKey

函数原型 sortByKey(ascending=True, numPartitions=None, keyfunc=<function RDD.>)

根据key进行排序,输入的数据必须为key value格式

In [26]:
tmp = [('a', 1), ('b', 2), ('1', 3), ('d', 4), ('2', 5)]
sc.parallelize(tmp).sortByKey().collect()
Out[26]:
[('1', 3), ('2', 5), ('a', 1), ('b', 2), ('d', 4)]
 

join()

两个数据集按照key内连接,返回数据集中有相同key的元素组成的新的数据集,

数据集A中的元素与数据集B中相同key元素一一组合,生成新的数据集

格式为(key, (value1, value2))

In [27]:
x = sc.parallelize([("a", 1), ("b", 4)])
y = sc.parallelize([("a", 2), ("a", 3)])

x.join(y).collect()
Out[27]:
[('a', (1, 2)), ('a', (1, 3))]
 

cogroup()

将多个RDD中同一个Key对应的Value组合到一起。如果RDD中没有对应的key,则会生成一个空值

In [28]:
x = sc.parallelize([("a", 1), ("b", 4)])
y = sc.parallelize([("a", 2)])
In [29]:
for k, v in x.cogroup(y).collect():
    print(k, tuple(map(list, v))) 
 
a ([1], [2])
b ([4], [])
 

cartesian()

笛卡尔集,是通过两个数据集计算而成的

数据集a的每个元素与数据集b的每个元素组合,形成新的数据对数据集

In [30]:
rdd = sc.parallelize([1, 2])
rdd2 = sc.parallelize([3, 4, 5])

rdd.cartesian(rdd2).collect()
Out[30]:
[(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)]
 

coalesce()

按照给定的数量重新分区数据集

In [31]:
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()
 
[[1], [2, 3], [4, 5]]
Out[31]:
[[1, 2, 3, 4, 5]]
In [32]:
sc.parallelize([1, 2, 3, 4, 5], 3).coalesce(2).glom().collect()
Out[32]:
[[1], [2, 3, 4, 5]]
 

pipe

对rdd数据集的每个元素,调用外部程序

In [33]:
# cat 文件内容
# 先在目录下创建一个for_test.txt文件,然后来读取文件内容
sc.parallelize(['1', '2', '', '3']).pipe('cat for_test.txt').collect()
Out[33]:
['for pyspark function test',
 'for pyspark function test',
 'for pyspark function test',
 'for pyspark function test']
 

repartition

数据集重新分区

In [34]:
# 创建默认为四个分区的数据集
rdd = sc.parallelize([1,2,3,4,5,6,7], 4)
print(rdd.glom().collect())

# 重新分为两个分区
rdd.repartition(2).glom().collect()
 
[[1], [2, 3], [4, 5], [6, 7]]
Out[34]:
[[1, 2, 3, 4, 5, 6, 7], []]
 

rePartitionAndSortWithinPartitions()

根据给定的分区程序对RDD进行重新分区,并在每个生成的分区内按键对记录进行排序。

这比调用重新分区,然后在每个分区内进行排序更有效率,因为它可以将排序压入洗牌机器。

应用场景:

  • 如果需要重分区,并且想要对分区中的数据进行升序排序。
  • 提高性能,替换repartition和sortBy
In [35]:
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()
Out[35]:
[[(0, 5), (0, 8), (2, 6)], [(1, 3), (3, 8), (3, 8)]]
posted @ 2019-04-04 17:49  MyStitch  阅读(2720)  评论(0编辑  收藏  举报