Scala Spark DataFrame:orderBy之后的groupBy是否保持该顺序?
我有一个Spark 2.0数据帧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
示例
,具有以下结构:
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”));