Python 计算每个键的唯一值的有效方法
我有一个成员列表,其中有许多属性,其中两个是名称和ID。我希望在RDD中得到一个元组列表。元组将包含作为第一个元素的Python 计算每个键的唯一值的有效方法,python,apache-spark,pyspark,Python,Apache Spark,Pyspark,我有一个成员列表,其中有许多属性,其中两个是名称和ID。我希望在RDD中得到一个元组列表。元组将包含作为第一个元素的ID,以及作为第二个元素的与ID相关联的unique名称计数 e、 例如:ID, 以下是我为完成此任务而编写的代码: IDnametuple = members.map(lambda a: (a.ID, a.name)) # extract only ID and name idnamelist = IDnametuple.groupByKey()
ID
,以及作为第二个元素的与ID相关联的unique
名称计数
e、 例如:ID,
以下是我为完成此任务而编写的代码:
IDnametuple = members.map(lambda a: (a.ID, a.name)) # extract only ID and name
idnamelist = IDnametuple.groupByKey() # group the IDs together
idnameunique_count = (idnamelist
# set(tup[1]) should extract unique elements,
# and len should tell the number of them
.map(lambda tup: (tup[0], len(set(tup[1])))))
它非常慢,而且比为每个成员计算唯一属性的类似操作慢得多
有没有更快的方法?据我所知,我尝试使用尽可能多的内置程序,这是加快速度的正确方法 没有任何细节,我们只能猜测,但显而易见的选择是
groupByKey
。如果每个id都与大量名称相关联,那么由于大量的洗牌,它可能会非常昂贵。最简单的改进是aggregateByKey
或combineByKey
:
create\u combiner=set
def合并_值(acc,x):
附件增补(x)
返回acc
def合并器(acc1、acc2):
acc1.更新(acc2)
返回acc1
id_name_unique_count=(id_name_tuple#保持一致的命名约定
.combineByKey(创建合并器、合并值、合并合并器)
.mapValues(len))
如果唯一值的预期数量较大,则您可能更愿意替换近似值的精确方法。一种可能的方法是使用Bloom filter来跟踪唯一值,而不是set
有关groupByKey
vsaggregateByKey
(reduceebykey
,combineByKey
)的更多信息,请参阅:
- 在
- 没有任何细节,我们只能猜测,但显而易见的选择是
groupByKey
。如果每个id都与大量名称相关联,那么由于大量的洗牌,它可能会非常昂贵。最简单的改进是aggregateByKey
或combineByKey
:
create\u combiner=set
def合并_值(acc,x):
附件增补(x)
返回acc
def合并器(acc1、acc2):
acc1.更新(acc2)
返回acc1
id_name_unique_count=(id_name_tuple#保持一致的命名约定
.combineByKey(创建合并器、合并值、合并合并器)
.mapValues(len))
如果唯一值的预期数量较大,则您可能更愿意替换近似值的精确方法。一种可能的方法是使用Bloom filter来跟踪唯一值,而不是set
有关groupByKey
vsaggregateByKey
(reduceebykey
,combineByKey
)的更多信息,请参阅:
- 在
from operator import add
IDnametuple = sc.parallelize([(0, "a"),(0, "a"),(0, "b"),(1, "a"),(1, "b"),(1, "c"),(2, "z")])
idnameunique_count = (IDnametuple.distinct()
.map(lambda idName : (idName[0], 1))
.reduceByKey(add))
因此
idnameunique\u count.collect()
返回[(0,2)、(1,3)、(2,1)]
,其中(0,a”)
仅计数一次。正如@zero323所提到的,这里的键将groupByKey
替换为reduceByKey
,以避免创建中间名称列表。您所需要的只是名称计数,这是一个小得多的对象,它可能是一个巨大的列表。您的版本还使用set()
在闭包代码中顺序消除重复项,而distinct
作为分布式并行RDD转换执行 这基本上是计算不同键值对的字数示例:
from operator import add
IDnametuple = sc.parallelize([(0, "a"),(0, "a"),(0, "b"),(1, "a"),(1, "b"),(1, "c"),(2, "z")])
idnameunique_count = (IDnametuple.distinct()
.map(lambda idName : (idName[0], 1))
.reduceByKey(add))
因此
idnameunique\u count.collect()
返回[(0,2)、(1,3)、(2,1)]
,其中(0,a”)
仅计数一次。正如@zero323所提到的,这里的键将groupByKey
替换为reduceByKey
,以避免创建中间名称列表。您所需要的只是名称计数,这是一个小得多的对象,它可能是一个巨大的列表。您的版本还使用set()
在闭包代码中顺序消除重复项,而distinct
作为分布式并行RDD转换执行 这种方法有一个问题——它必须洗牌两次。一次获取不同的
值,一次获取reduceByKey
。关于并行性。。。除非键的数量与可用核心的数量相当,否则它与在分组数据上使用集合时完全相同。通过使用集合作为累加器,您只能处理键的唯一值集合位于单个工作进程内存中的数据集。在我的解决方案中,您没有这个限制,因为您只为每个键存储一个数字,如“避免GroupByKey”中所述。您的解决方案基本上是使用集合重新实现groupByKey。我不理解你对密钥和核心之间关系的评论。这就是限制。要执行distinct
,您必须reduceByKey
,只有在重复数较大时才会执行。在这种情况下,combineByKey
应该已经减少了数据量。否则,第一个潜在的故障点是洗牌和存储((k,v),null)
对,这些对必须放入内存中。接下来,您将执行另一次洗牌,这通常需要再次洗牌大部分数据。这是你通过计算得到的部分。关于键核关系。默认情况下,Spark中的每个操作都是按分区顺序进行的。因此,如果散列值的数量足够大,足以填满所有分区,那么就无法提高并行性。关键是这些((k,v),对)不需要放入单个工作进程的内存中,因为它们可以驻留在多个分区中,而这些分区可以托管在多个工作进程中。如果你没有足够的分区,你可以重新分区,你不需要在内存中同时有所有的分区,你也可以添加更多的工人,这就是