Scala 在Spark中的groupby之后跨列收集最常出现的唯一值

Scala 在Spark中的groupby之后跨列收集最常出现的唯一值,scala,apache-spark,apache-spark-sql,Scala,Apache Spark,Apache Spark Sql,我有以下数据帧 val input=Seq((“ZZ”、“a”、“a”、“b”、“b”), (“ZZ”、“a”、“b”、“c”、“d”), (“YY”,“b”,“e”,空,“f”), (“YY”,“b”,“b”,空,“f”), (“XX”,“j”,“i”,“h”,空)) .toDF(“主要”、“价值1”、“价值2”、“价值3”、“价值4”) input.show() +----+------+------+------+------+ |主要|值1 |值2 |值3 |值4| +----+----

我有以下数据帧

val input=Seq((“ZZ”、“a”、“a”、“b”、“b”),
(“ZZ”、“a”、“b”、“c”、“d”),
(“YY”,“b”,“e”,空,“f”),
(“YY”,“b”,“b”,空,“f”),
(“XX”,“j”,“i”,“h”,空))
.toDF(“主要”、“价值1”、“价值2”、“价值3”、“价值4”)
input.show()
+----+------+------+------+------+
|主要|值1 |值2 |值3 |值4|
+----+------+------+------+------+
|ZZ | a | a | b | b|
|ZZ | a | b | c | d|
|YY | b | e | null | f|
|YY | b | b | null | f|
|XX | j | i | h |空|
+----+------+------+------+------+
我需要按
main
列进行分组,并从其余列中为每个
main
值选择两个最常出现的值

我做了以下几件事

val newdf = input.select('main,array('value1,'value2,'value3,'value4).alias("values"))
val newdf2 = newdf.groupBy('main).agg(collect_set('values).alias("values"))
val newdf3 = newdf2.select('main, flatten($"values").alias("values"))
以下面的形式获取数据

+----+--------------------+
|main|              values|
+----+--------------------+
|  ZZ|[a, a, b, b, a, b...|
|  YY|[b, e,, f, b, b,, f]|
|  XX|          [j, i, h,]|
+----+--------------------+
现在,我需要从列表中选择出现次数最多的两项作为两列。不知道怎么做

因此,在这种情况下,预期输出应该是

+----+------+------+
|main|value1|value2|
+----+------+------+
|  ZZ|     a|     b|
|  YY|     b|     f|
|  XX|     j|     i|
+----+------+------+
null
不应计数,只有在没有其他值可填充时,最终值才应为
null

这是做事情的最好方式吗?有更好的方法吗?

您可以使用从数组中选择最常出现的两个值

input.withColumn(“值”、数组(“value1”、“value2”、“value3”、“value4”))
.groupBy(“主”).agg(展平(收集列表(“值”)).as(“值”))
.withColumn(“max”,maxUdf(“值))/(1)
.cache()/(2)
.withColumn(“value1”,“max.getItem(0))
.withColumn(“值2”,“最大获取项(1))
.drop(“值”、“最大值”)
.show(假)
maxUdf
定义为

def getMax[T](array: Seq[T]) = {
  array
    .filter(_ != null) //remove null values
    .groupBy(identity).mapValues(_.length) //count occurences of each value
    .toSeq.sortWith(_._2 > _._2) //sort (3)
    .map(_._1).take(2) //return the two (or one) most common values
}
val maxUdf = udf(getMax[String] _)
备注:

  • 在这里使用udf意味着,包含单个值
    main
    的所有条目的整个数组必须装入一个Spark executor的内存中
  • 此处需要
    缓存
    ,否则将调用udf两次,一次用于
    value1
    ,一次用于
    value2
  • 这里的
    sortWith
    是,但是如果两个元素的出现次数相同,则可能需要添加一些额外的逻辑来处理这种情况(例如主值
    XX
    i
    j
    h

  • 这是我没有udf的尝试

    import org.apache.spark.sql.expressions.Window
    val w = Window.partitionBy('main).orderBy('count.desc)
    
    newdf3.withColumn("values", explode('values))
      .groupBy('main, 'values).agg(count('values).as("count"))
      .filter("values is not null")
      .withColumn("target", concat(lit("value"), lit(row_number().over(w))))
      .filter("target < 'value3'")
      .groupBy('main).pivot('target).agg(first('values)).show
    
    +----+------+------+
    |main|value1|value2|
    +----+------+------+
    |  ZZ|     a|     b|
    |  YY|     b|     f|
    |  XX|     j|  null|
    +----+------+------+
    
    import org.apache.spark.sql.expressions.Window
    val w=Window.partitionBy('main.).orderBy('count.desc)
    newdf3.withColumn(“值”,explode(“值))
    .groupBy('main',value.).agg(count('value.).as('count'))
    .filter(“值不为空”)
    .withColumn(“target”,concat(lit(“value”),lit(row_number(),over(w)))
    .filter(“目标<'value3'))
    .groupBy('main).pivot('target).agg(first('values)).show
    +----+------+------+
    |主|值1 |值2|
    +----+------+------+
    |ZZ | a | b|
    |YY | b | f|
    |XX | j |空|
    +----+------+------+
    
    最后一行的值为空,因为我以这种方式修改了您的数据帧

    +----+--------------------+
    |main|              values|
    +----+--------------------+
    |  ZZ|[a, a, b, b, a, b...|
    |  YY|[b, e,, f, b, b,, f]|
    |  XX|              [j,,,]| <- For null test
    +----+--------------------+
    
    +----+--------------------+
    |主值|
    +----+--------------------+
    |ZZ |[a,a,b,b,a,b|
    |YY |[b,e,f,b,b,f]|
    |XX |[j,,,]|