Scala Spark DataFrame:orderBy之后的groupBy是否保持该顺序?

Scala Spark DataFrame:orderBy之后的groupBy是否保持该顺序?,scala,apache-spark,apache-spark-sql,spark-streaming,spark-dataframe,Scala,Apache Spark,Apache Spark Sql,Spark Streaming,Spark Dataframe,我有一个Spark 2.0数据帧示例,具有以下结构: id, hour, count id1, 0, 12 id1, 1, 55 .. id1, 23, 44 id2, 0, 12 id2, 1, 89 .. id2, 23, 34 etc. 它包含每个id的24个条目(一天中每小时一个条目),并使用orderBy函数按id、小时排序 我已经创建了一个聚合器groupConcat: def groupConcat(separator: String, columnToConcat: Int

我有一个Spark 2.0数据帧
示例
,具有以下结构:

id, hour, count
id1, 0, 12
id1, 1, 55
..
id1, 23, 44
id2, 0, 12
id2, 1, 89
..
id2, 23, 34
etc.
它包含每个id的24个条目(一天中每小时一个条目),并使用orderBy函数按id、小时排序

我已经创建了一个聚合器
groupConcat

  def groupConcat(separator: String, columnToConcat: Int) = new Aggregator[Row, String, String] with Serializable {
    override def zero: String = ""

    override def reduce(b: String, a: Row) = b + separator + a.get(columnToConcat)

    override def merge(b1: String, b2: String) = b1 + b2

    override def finish(b: String) = b.substring(1)

    override def bufferEncoder: Encoder[String] = Encoders.STRING

    override def outputEncoder: Encoder[String] = Encoders.STRING
  }.toColumn
它帮助我将列连接到字符串中以获得最终数据帧:

id, hourly_count
id1, 12:55:..:44
id2, 12:89:..:34
etc.
我的问题是,如果我使用
example.orderBy($“id”,$“hour”).groupBy(“id”).agg(groupConcat(“:”,2)作为“hourly_count”)
,这是否保证了小时计数将在各自的存储桶中正确排序

我读到RDD不一定如此(请参阅),但数据帧可能不同


如果没有,我如何解决它

简短的回答是肯定的,每小时计数将保持相同的顺序

总而言之,在分组之前进行排序是很重要的。此外,排序必须与实际需要排序的组+列相同

例如:

employees
    .sort("company_id", "department_id", "employee_role")
    .groupBy("company_id", "department_id")
    .agg(Aggregators.groupConcat(":", 2) as "count_per_role")

我有一个案例,订单并不总是保持不变:有时是,大部分是否定的

我的dataframe在Spark 1.6上有200个分区

df_group_sort = data.orderBy(times).groupBy(group_key).agg(
                                                  F.sort_array(F.collect_list(times)),
                                                  F.collect_list(times)
                                                           )
为了检查顺序,我比较了

F.sort_array(F.collect_list(times))

例如(左:排序数组(collect_list());右:collect_list())

左列始终是已排序的,而右列仅由已排序的块组成。
对于take()的不同执行,右列中块的顺序不同

顺序可能相同,也可能不同,这取决于分区的数量和数据的分布。我们可以使用rdd本身来解决这个问题

例如:

我将下面的示例数据保存在一个文件中,并将其加载到hdfs中

1,type1,300
2,type1,100
3,type2,400
4,type2,500
5,type1,400
6,type3,560
7,type2,200
8,type3,800
并执行以下命令:

sc.textFile("/spark_test/test.txt").map(x=>x.split(",")).filter(x=>x.length==3).groupBy(_(1)).mapValues(x=>x.toList.sortBy(_(2)).map(_(0)).mkString("~")).collect()
输出:

Array[(String, String)] = Array((type3,6~8), (type1,2~1~5), (type2,7~3~4))
Array[(String, String)] = Array((type1,2~1~5))
也就是说,我们按类型对数据进行分组,然后按价格进行排序,然后用“~”作为分隔符连接ID。 可以按如下方式断开上述命令:

val validData=sc.textFile("/spark_test/test.txt").map(x=>x.split(",")).filter(x=>x.length==3)

val groupedData=validData.groupBy(_(1))  //group data rdds

val sortedJoinedData=groupedData.mapValues(x=>{
   val list=x.toList
   val sortedList=list.sortBy(_(2))
   val idOnlyList=sortedList.map(_(0))
   idOnlyList.mkString("~")
}
)
sortedJoinedData.collect()
然后,我们可以使用命令获取特定的组

sortedJoinedData.filter(_._1=="type1").collect()
输出:

Array[(String, String)] = Array((type3,6~8), (type1,2~1~5), (type2,7~3~4))
Array[(String, String)] = Array((type1,2~1~5))

正如其他人指出的那样,orderBy之后的groupBy不能维持秩序。您要做的是使用一个窗口函数——按id分区并按小时排序。您可以在此基础上收集_列表,然后获取结果列表的最大值(最大值),因为它们是累积的(即,第一个小时在列表中只有它自己,第二个小时在列表中有2个元素,依此类推)

完整的示例代码:

import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window
import spark.implicits._

val data = Seq(( "id1", 0, 12),
  ("id1", 1, 55),
  ("id1", 23, 44),
  ("id2", 0, 12),
  ("id2", 1, 89),
  ("id2", 23, 34)).toDF("id", "hour", "count")

    val mergeList = udf{(strings: Seq[String]) => strings.mkString(":")}
    data.withColumn("collected", collect_list($"count")
                                                    .over(Window.partitionBy("id")
                                                                 .orderBy("hour")))
            .groupBy("id")
            .agg(max($"collected").as("collected"))
            .withColumn("hourly_count", mergeList($"collected"))
            .select("id", "hourly_count").show
这让我们置身于数据框架世界。我还简化了OP使用的UDF代码

输出:

+---+------------+
| id|hourly_count|
+---+------------+
|id1|    12:55:44|
|id2|    12:89:34|
+---+------------+

如果您想解决Java实现(Scala和Python应该类似):


不,在
groupByKey
中进行排序不一定会得到维护,但这在一个节点的内存中很难重现。如前所述,发生这种情况的最典型方式是当需要对
groupByKey
进行重新分区时。我在
排序
之后手动执行
重新分区
,成功地再现了这一点。然后我将结果传递到
groupByKey

case类编号(num:Int、group:Int、otherData:Int)
//将spark配置为“spark.sql.shuffle.partitions”=2或其他一些小数字
瓦尔五世=
(1至10万)
//使waaay的组数多于分区数。我添加了一个额外的整数,只是为了搞乱排序散列计算(也就是说,它不会是单调的,不确定是否需要)
.map(编号为u,Random.nextInt(300),Random.nextInt(1000000)).toDS()
//确保它们存储在少量分区中
.重新分配(2)
.sort($“num”)
//再次使用一个更大的数字重新分区,然后会有组,这样当需要合并时,您就可以使它们乱序。
.重新分配(200)
.groupByKey(u.group)
.地图组{
案例(g,nums)=>
nums//您只需在此处使用.sortBy(u.num)来修复问题
.map(u.num)
.mkString(“~”)
}
.collect()
//遍历连接的字符串。如果前面有号码的话
//比之前的数字小,你知道吗
//它坏了。
v、 zipWithIndex.map{case(r,i)=>
r、 split(“~”).map(uu.toInt).foldLeft(0){大小写(上一个,下一个)=>
如果(下一个<上一个){
println(数据集${i+1}***的“***下一个:${Next}小于${prev}”)
}
下一个
}
}

您是否有任何说明groupBy维持订购的参考资料?我在官方文件中找不到任何东西我没有官方文件,但我有这篇文章更好地解释了机制。评论也很有趣。有趣的是,就连Sean Owen自己也表示可能不会保留订单()有人看过我在6月7日添加的文章和评论吗,2017年?接受的答案指出,您需要按照您想要排序的列以及分组的列进行排序,即,
orderBy(times,group\u key)。groupBy(group\u key)
。你试过了吗?
example.orderBy(“hour”)
.groupBy(“id”)
.agg(functions.sort_array(
  functions.collect_list( 
     functions.struct(dataRow.col(“hour”),
                      dataRow.col(“count”))),false)
 .as(“hourly_count”));