PySpark大数据分析(3):使用Python操作RDD

使用Python编写Spark代码

在Python中使用Spark,首先需要导入PySpark,然后创建一个SparkConf对象配置你的应用,再基于这个SparkConf创建一个SparkContext。以创建一个名为’MyApp’的本地应用为例:

from pyspark import SparkConf, SparkContext


conf = SparkConf().setMaster('local').setAppName('MyApp')
sc = SparkContext(conf=conf)

也可以通过SparkSession实现之前命令行中的PySpark编程,比如找出’Sepal Width’大于3的数据:

from pyspark import SparkConf, SparkContext
from pyspark.sql import SparkSession


conf = SparkConf().setMaster('local').setAppName('MyApp')
sc = SparkContext(conf=conf)
spark = SparkSession(sc)

iris = spark.read.csv('/Users/data/iris.csv', header=True)
iris.filter(iris['Sepal Width'] > 3.0).show(5)

展示结果为:

+------------+-----------+------------+-----------+-----------+
|Sepal Length|Sepal Width|Petal Length|Petal Width|      Label|
+------------+-----------+------------+-----------+-----------+
|         5.1|        3.5|         1.4|        0.2|Iris-setosa|
|         4.7|        3.2|         1.3|        0.2|Iris-setosa|
|         4.6|        3.1|         1.5|        0.2|Iris-setosa|
|         5.0|        3.6|         1.4|        0.2|Iris-setosa|
|         5.4|        3.9|         1.7|        0.4|Iris-setosa|
+------------+-----------+------------+-----------+-----------+
only showing top 5 rows

通过SparkSession读取的数据是以DataFrame的形式存储的,如果想使用RDD需要进行转换:

print(type(iris))
# 
rdd = iris.rdd
print(rdd.first())
# Row(Sepal Length='5.1', Sepal Width='3.5', Petal Length='1.4', Petal Width='0.2', Label='Iris-setosa')

向Spark传递函数

像Python中的filter()函数一样,我们可以将一个自定义的函数传递给RDD的filter(),从而让它按照我们定义的方式进行过滤。当函数比较简单时,可以使用匿名函数lambda传递;

from pyspark import SparkConf, SparkContext

conf = SparkConf().setMaster('local').setAppName('MyApp')
sc = SparkContext(conf=conf)

even_numbers = rdd.filter(lambda x: x % 2 == 0).collect()
print(even_numbers)  # [0, 2, 4, 6, 8, 10]

如果函数较为复杂,则可以通过def关键字定义后将函数传入filter()中:

def find_prime_number(num):
    if num <= 1:
        return None
    
    for i in range(2, num):
        if num % i == 0:
            return None
    
    return num

rdd = sc.parallelize(range(12))
prime_numbers = rdd.filter(find_prime_number).collect()
print(prime_numbers)  # [2, 3, 5, 7, 11]

在Python中,如果被传递的函数是某个对象的成员,或包含了对某个对象的引用,那这个对象本身也会被传递到工作节点上。一方面,传输一个对象很可能开销巨大;同时,Python很可能因为无法将传递的对象序列化传输而报错。比如下面这种情况:

class PrimeNumChecker:
    def __init__(self, prime_num_list):
        self.prime_num_list = prime_num_list

    def is_prime(self, num):
        return num in self.prime_num_list

    def find_prime_numbers(self, rdd):
        return rdd.filter(self.is_prime)

checker = PrimeNumChecker(prime_num_list=[7, 11, 13, 17, 19])
checker.find_prime_numbers(rdd).collect()

传递self.is_prime时,整个checker都会被传递到工作节点上。如果需要传递某个变量,最好将它先放在一个局部变量中,再传递这个局部变量:

class PrimeNumChecker:
    def __init__(self, prime_num_list):
        self.prime_num_list = prime_num_list

    def is_prime(self, num):
        return num in self.prime_num_list

    def find_prime_numbers(self, rdd):
        prime_nums = self.prime_num_list
        return rdd.filter(lambda x: x in prime_nums)

常用操作

转化操作

filter

最常见的转化操作就是filter(),它根据过滤条件将原RDD中符合条件的元素转化为新的RDD。比如筛选出含有数字1的元素:

print(rdd.filter(lambda x: '1' in str(x)).collect())
# [1, 10, 11]

map

另一个常用操作是map(),他对原RDD中的每个元素执行指定操作,并存入新的RDD中。比如对每个元素求平方:

print(rdd.map(lambda x: x ** 2).collect())
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]

flatMap

当某一操作返回值为多个值时,可以使用flatMap(),它对原RDD中的每个元素执行指定操作,并将这些元素所返回的所有值存入一个新的RDD中。比如分词操作:

sentences = sc.parallelize(['Python Spark', 'Scala Spark', 'Java Spark'])
words = sentences.flatMap(lambda x: x.split())
print(words.collect())
# ['Python', 'Spark', 'Scala', 'Spark', 'Java', 'Spark']

distinct

使用distinct()可以对RDD元素去重,但是由于distinct()需要对所有数据进行混洗(shuffle),因此开销非常大。

words1 = words.distinct()
print(words1.collect())
# ['Python', 'Spark', 'Scala', 'Java']

union

union()返回两个RDD的所有元素。由于RDD本身允许包含重复元素,这里的union操作也不是集合操作,会保留所有重复的元素。

words2 = sc.parallelize(['Python', 'Apache', 'RDD', 'Python'])
print(words1.union(words2).collect())
# ['Python', 'Spark', 'Scala', 'Java', 'Python', 'Apache', 'RDD', 'Python']

intersection

intersection()返回两个RDD的共有元素。与distinct()一样,intersection()也会导致数据混洗,因此开销很大。但是同时也会移除重复的元素。

print(words1.intersection(words2).collect())
# ['Python']

subtract

这又是一个需要混洗的操作,subtract()会返回原RDD中不存在于传入的RDD中的元素,相当于一个RDD减去它与另一个RDD的交集部分。

print(words1.subtract(words2).collect())
# ['Spark', 'Java', 'Scala']

cartesian

cartesian()是用来计算笛卡尔积的操作,可以返回两个RDD中的元素两两组合的全部情况。大数据量的情况下,笛卡尔积操作的开销巨大。

print(words1.cartesian(words2).collect())
# [('Python', 'Python'), ('Python', 'Apache'), ('Python', 'RDD'), ('Python', 'Python'), ('Spark', 'Python'), ('Spark', 'Apache'), ('Spark', 'RDD'), ('Spark', 'Python'), ('Scala', 'Python'), ('Scala', 'Apache'), ('Scala', 'RDD'), ('Scala', 'Python'), ('Java', 'Python'), ('Java', 'Apache'), ('Java', 'RDD'), ('Java', 'Python')]

行动操作

reduce

reduce()接受一个函数,该函数会对两个同类型的元素进行操作,之后遍历RDD中的元素,依次对它们执行这个接收到的函数,从而实现并行整合RDD中的所有数据。比如构建一个求和函数:

print(rdd.reduce(lambda x, y: x + y))
# 66

在这个reduce()中,传入的函数是将两个值x和y相加。于是reduce()会遍历RDD中的所有元素,然后对他们进行求和运算,得到的结果再求和,从而计算出从0到11的和。

fold

fold()与reduce()功能一致,但需要提供初始值,作为每个分区第一次调用时的结果。为了避免对影响计算结果,这个初始值应当是这个运算的不变元。比如加法不变元是0,乘法不变元是1,因此不同运算对应的初始值也应当不同。

rdd.fold(0, lambda x, y: x + y)
# 66

aggregate

无论是reduce()还是fold(),都要求返回值与处理的RDD元素的类型一致,而aggregate()则不需要。aggregate()接收的函数可以处理不同类型元素,比如当我们计算一个RDD中元素的平均值时,输入的参数是RDD中的某一个元素,输出则是一个包含元素和以及元素个数的元组,这是reduce()无法直接处理的。与fold()类似,aggregate()也需要传入每个分区首次调用时的初始值。下面是使用aggregate()求平均值的代码:

total, cnt = rdd.aggregate(
    (0, 0),
    lambda sum_and_cnt, value: (sum_and_cnt[0] + value, sum_and_cnt[1] + 1),
    lambda x, y: (x[0] + y[0], x[1] + y[1])
)
print(total / cnt)
# 5.5

其中第一个lambda函数用来分别统计元素和以及元素个数,第二个lambda将各个节点的累加器进行合并。

collect, take, top, takeSample

foreach

count, countByValue

持久化

由于RDD是惰性求值的,因此当我们需要多次使用一个RDD时,为了避免重复的计算,我们需要对这个RDD的数据进行持久化磁盘上或缓存在内存中。对于需要持久化的RDD,只需要调用persist()就可以完成该操作。在pyspark.StorageLevel中,有不同的持久化等级,可以根据需要为persist()指定具体的持久化级别。

from pyspark import StorageLevel


words1.persist(storageLevel=StorageLevel.MEMORY_ONLY)

持久化操作本身不会触发强制求值,你可以在行动操作之前就调用它。如果缓存的数据太多了,Spark会根据LRU策略清除掉不常用的分区移除,之后如果再次被用到就需要重新计算。你也可以使用unpersist()手动释放一些不需要的分区。

上一篇:PySpark大数据分析(2):RDD操作
下一篇:PySpark大数据分析(4):键值对操作

你可能感兴趣的:(大数据处理,大数据,数据分析,Spark,Python,RDD)