Spark:GroupBy之后的TOPN

时间:2016-03-10 13:44:25

标签: scala apache-spark rdd

我有一个RDD P 映射到类:

case class MyRating(userId:Int, itemId:Int, rating:Double)

我有兴趣为每个用户找到TopN条目,即GroupBy userId ,并在每个组建的组中,根据最高评级过滤掉TopN(例如10个)条目。

我做了以下事情:

val A : RDD[((Int), Iterable[MyRating])] = P.keyBy(r => (r.userId)).groupByKey
val B : RDD[((Int), List[MyRating])] = key.mapValues(iter => iter.toList.sortBy(_.rating, false))
val C = values.groupByKey.take(10)

在groupByKey只留下10个密钥(用户)后,明确应用.take(10),并且不会过滤掉每个用户的前10个评级。

我们如何在groupBy之后应用.take(N),以便它对值的某些部分而不是键本身起作用?

5 个答案:

答案 0 :(得分:3)

一种天真的方法是采用 n 值:

B.mapValues(_.take(n))

但是如果你只需要很小的值子集,那么最好使用例如aggregateByKey并在运行时删除过时的记录而不是对所有内容进行分组。你可能想在实践​​中想要更高效的东西(你可以查看top / takeOrdered的Spark实现),但你可以从这样的东西开始:

import scala.math.Ordering
import scala.collection.mutable.PriorityQueue

implicit val ord = Ordering.by[MyRating, Double](_.rating)

val pairs = rdd.keyBy(_.userId)
pairs.aggregateByKey(new scala.collection.mutable.PriorityQueue[MyRating]())(
  (acc, x) => {
    acc.enqueue(x)
    acc.take(n)
  },
  (acc1, acc2) => (acc1 ++ acc2).take(n)
)

请注意,由于SI-7568,上述代码段需要Scala 2.11+。

答案 1 :(得分:3)

如果我理解正确,你需要做的是: 按用户ID分组RDD,然后为每个(id,list)元组返回id并将列表排序并修剪为10个元素

P
  .groupBy(_.userId)  
  .map{ case (key, it) => 
    (key, it.toList.sortBy(mr => -mr.rating).take(10)) 
  }

答案 2 :(得分:1)

你非常接近,但你需要在A到B的映射中取得前N个条目。例如,如果你想从列表中获取前2个MyRating项,下面的代码就可以了。 B将是一个RDD,其中包含每个userId的前2个MyRating的列表。 (另外,sortBy函数只需将评级设为负值即可。)

case class MyRating(userId:Int, itemId:Int, rating:Double)

val plist:List[MyRating] = List(MyRating(1,0,1),MyRating(1,1,5),MyRating(1,2,7),MyRating(1,3,9),MyRating(1,4,10),MyRating(2,5,1),MyRating(2,6,5),MyRating(2,6,7))
val P: org.apache.spark.rdd.RDD[MyRating] = sc.parallelize(plist)

val A : RDD[((Int), Iterable[MyRating])] = P.keyBy(r => (r.userId)).groupByKey
val TOPCOUNT = 2
val B : RDD[((Int), List[MyRating])] = A.mapValues(iter => iter.toList.sortBy(- _.rating).take(TOPCOUNT))

答案 3 :(得分:1)

以下是使用{0}建议的val A : RDD[(Int, MyRating)] = P.keyBy(r => r.userId) val B = A.aggregateByKey(List[MyRating]())( (l, r) => (l :+ r).sortBy(-_.rating).take(10), (l1, l2) => (l1 ++ l2).sortBy(-_.rating).take(10)) 的示例:

groupBy

使用此方法的好处是您不会在执行程序之间混洗大量数据。如果单个用户的评级分布在多个节点上,aggregateByKey需要将用户的所有评级发送给同一个执行者,而Unable to find field "email" (Capybara::ElementNotFound) ./features/step_definitions/microsites/quiz/microsites_quiz_entrant_sign_in_facebook_steps.rb:13:in `/^I log in with my facebook email and password quiz$/' ./features/support/database_cleaner.rb:11:in `block in <top (required)>' features/microsites/quiz/microsites_quiz_entrant_sign_in_facebook.feature:7:in `And I log in with my facebook email and password quiz' Unable to find field "username_or_email" (Capybara::ElementNotFound) ./features/step_definitions/microsites/quiz/microsites_quiz_entrant_sign_in_twitter_steps.rb:13:in `/^I log in with my twitter email and password quiz$/' ./features/support/database_cleaner.rb:11:in `block in <top (required)>' features/microsites/quiz/microsites_quiz_entrant_sign_in_twitter.feature:7:in `And I log in with my twitter email and password quiz' Failing Scenarios: cucumber features/microsites/quiz/microsites_quiz_entrant_sign_in_facebook.feature:3 # Scenario: Viewing quiz entrant sign in with facebook cucumber features/microsites/quiz/microsites_quiz_entrant_sign_in_twitter.feature:3 # Scenario: Viewing quiz entrant sign in with twitter 首先在每个执行者上建立一个前N列表,然后只有那些列表被混洗和组合。

这对您是否有益取决于数据的分布。如果你没有比最后的顶级更多的评分,那么你就不会获得太多的收益(尤其是我的天真实施,对每个单独的评级进行排序)。但是,如果每个遗嘱执行人的评级数量级更大,您可以赢得很多。

答案 4 :(得分:0)

我找到了相关的帖子:Spark: Get top N by key

复制@jbochi的推荐:

从1.4版开始,存在使用MLLib执行此操作的内置方法:https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/mllib/rdd/MLPairRDDFunctions.scala

val scores = sc.parallelize(Array(
      ("a", 1),  
      ("a", 2), 
      ("a", 3), 
      ("b", 3), 
      ("b", 1), 
      ("a", 4),  
      ("b", 4), 
      ("b", 2)
))
import org.apache.spark.mllib.rdd.MLPairRDDFunctions.fromPairRDD
scores.topByKey(2) // Where the keys are a and b