PySpark大数据分析(4):键值对操作

Spark的RDD支持键值对形式的数据,这种类型的RDD被称为pair RDD,并且拥有一些基于键值对的专属操作。Pair RDD往往用于聚合操作,比如将数据中的用户ID提取出来作为键值,然后对每个用户的行为进行聚合。这类操作在Spark中十分常用。

创建pair RDD

对于一个普通的RDD来说,使用map()函数就可以将它转变为pair RDD。

from pyspark import SparkConf, SparkContext

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

rdd = sc.parallelize(range(6))
pair_rdd = rdd.map(lambda x: (x % 2, x))
print(pair_rdd.collect())
# [(0, 0), (1, 1), (0, 2), (1, 3), (0, 4), (1, 5)]

Pair RDD的转化操作

Pair RDD可以直接使用普通RDD上的转化操作。但是需要注意,pair RDD操作的是二元组,而不是独立的元素。比如我们要对值进行过滤时,就需要在filter()中的函数里对值进行比较:

result = pair_rdd.filter(lambda x: x[1] > 3).collect()
print(result)
# [(0, 4), (1, 5)]

Spark提供了一些专门针对pair RDD的操作,比较常见的有reduceByKey()和foldByKey()。这两种操作与reduce()和fold()类似,区别在于reduceByKey()的操作是针对具有相同键的二元组进行归约,并且返回一个pair RDD,而不是返回一个值。因此reduceByKey()是一个转化操作。

result = pair_rdd.reduceByKey(lambda x, y: x + y).collect()
print(result)
# [(0, 6), (1, 9)]

combineByKey()也是非常常用的基于键进行聚合操作的函数,它通过combiner之间的运算完成聚合操作。combineByKey()接收三个函数作为参数,分别是createCombiner,mergeValue,以及mergeCombiners。

在每一个分区中,combineByKey()会遍历其中所有的元素。如果该元素在这个分区中还没出现过,combineByKey()会调用createCombiner()为这个新元素创建对应的累加器初始值。如果该元素在这个分区中已经出现过,combineByKey()会调用mergeValue()将该元素的值与累加器当前值合并。最后,所有分区的combiner还会通过mergeCombiners()的方法,将多个分区中具有相同键的combiner进行合并。

下面是一个使用combineByKey()基于键计算平均值的例子:

sum_count = pair_rdd.combineByKey(
    lambda x: (x, 1), 
    lambda x, y: (x[0] + y, x[1] + 1), 
    lambda x, y: (x[0] + y[0], x[1] + y[1])
)
print(sum_count.collect())
# [(0, (6, 3)), (1, (9, 3))]
result = sum_count.map(lambda kv: (kv[0], kv[1][0]/kv[1][1]))
print(result.collect())
# [(0, 2.0), (1, 3.0)]

combineByKey接收的3个函数都是针对Pair RDD的值进行操作的。首先为分区内每一个新元素进行初始化,因为要计算每个键对应的平均值,因此需要同时统计总和与个数,也就需要把原本的值x映射成元组(x, 1)的形式。如果遇到了该分区内出现过的键,那就将值加到对应元组的第一位,然后给用于计数的第二位加1。最后合并所有分区的结果,获得每个键对应的总和以及计数的元组。

我们还可以根据键对Pair RDD的数据进行分组,比如使用groupByKey()可以基于键将一个pair RDD中的数据分成多组,或者使用cogroup()对多个RDD中的数据进行分组。

连接操作用来将两个RDD中具有相同键的数据整合到一起。最常用的连接操作是join(),也就是内连接。只有当两个pair RDD都存在某个键,才会作为结果输出。如果某个键对应多个值的时候,输出的结果会包含每一种组合的情况。比如使用join()将用户的信息数据和购买过的商品数据进行拼接:

user_info = sc.parallelize([
    ('uid001', ('female', '29')), 
    ('uid002', ('male', '22')), 
    ('uid003', ('female', '28'))
])
act_log = sc.parallelize([
    ('uid001', 'item001'), 
    ('uid002', 'item001'), 
    ('uid001', 'item002'), 
    ('uid002', 'item003'),
    ('uid002', 'item004'),
    ('uid004', 'item004')
])
print(user_info.join(act_log).collect())
'''
[('uid001', (('female', '29'), 'item001')), 
 ('uid001', (('female', '29'), 'item002')), 
 ('uid002', (('male', '22'), 'item001')), 
 ('uid002', (('male', '22'), 'item003')),
 ('uid002', (('male', '22'), 'item004'))]
'''

如果不需要键在两个RDD中同时存在,可以使用leftOuterJoin()或者rightOuterJoin()进行连接,它们分别要求键必须存在于第一个RDD和第二个RDD中。如果在另一个RDD中不存在,则会在对应位置补充None。对于上面的例子,分别使用leftOuterJoin()和rightOuterJoin()的结果如下:

print(user_info.leftOuterJoin(act_log).collect())
'''
[('uid001', (('female', '29'), 'item001')),
 ('uid001', (('female', '29'), 'item002')),
 ('uid002', (('male', '22'), 'item001')),
 ('uid002', (('male', '22'), 'item003')),
 ('uid002', (('male', '22'), 'item004')),
 ('uid003', (('female', '28'), None))]
'''
print(user_info.rightOuterJoin(act_log).collect())
'''
[('uid001', (('female', '29'), 'item001')),
 ('uid001', (('female', '29'), 'item002')),
 ('uid002', (('male', '22'), 'item001')),
 ('uid002', (('male', '22'), 'item003')),
 ('uid002', (('male', '22'), 'item004')),
 ('uid004', (None, 'item004'))]
'''

如果键有定义好的顺序,则可以根据这个顺序对RDD中的数据排序。排好序的数据在之后的collect()和save()时也会保持该顺序。基于键的排序函数是sortByKey(),他接收一个ascending参数,默认为True,即升序排序。你也可以根据自定义的规则进行排序,sortByKey()的keyfunc参数就是用于接收自定义的排序方法。比如仅根据月和日对日期数据进行排序:

yyyy_mm_dd = sc.parallelize([('20180822', 'Python'), ('20190317', 'Scala'), ('20200630', 'Java')])
mmdd_sorted = yyyy_mm_dd.sortByKey(ascending=True, keyfunc=lambda x: x[4:])
print(mmdd_sorted.collect())
# [('20190317', 'Scala'), ('20200630', 'Java'), ('20180822', 'Python')]

Pair RDD的行动操作

所有基础RDD支持的行动操作在pair RDD上同样支持。除此之外,pair RDD还支持一些额外操作。比如countByKey()可以对每个键的元素个数进行分别计数。collectAsMap()可以返回一个字典形式的结果。lookup()可以返回给定键的所有值。

print(act_log.countByKey())
# defaultdict(<class 'int'>, {'uid001': 2, 'uid002': 3, 'uid004': 1})
print(act_log.collectAsMap())
# {'uid001': 'item002', 'uid002': 'item004', 'uid004': 'item004'}
print(act_log.lookup('uid002'))
# ['item001', 'item003', 'item004']

上一篇:PySpark大数据分析(3):使用Python操作RDD
下一篇:PySpark大数据分析(5):数据分区

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值