Dataframe 如何分组并获取Spark数据帧中的中值num

Dataframe 如何分组并获取Spark数据帧中的中值num,dataframe,apache-spark,apache-spark-sql,Dataframe,Apache Spark,Apache Spark Sql,我现在有一个这样的数据帧 +------------+----------+ | A | B | +------------+----------+ |aaaaaaaaaaaa|11 | |aaaaaaaaaaaa|44 | |bbbbbbbbbbbb|22 | |aaaaaaaaaaaa|33 | +------------+----------+ 我想得到每列A中B列的中值 +------------+

我现在有一个这样的数据帧

+------------+----------+
|         A  |    B     |
+------------+----------+
|aaaaaaaaaaaa|11        |
|aaaaaaaaaaaa|44        |
|bbbbbbbbbbbb|22        |
|aaaaaaaaaaaa|33        |
+------------+----------+
我想得到每列A中B列的中值

+------------+----------+
|         A  |    B     |
+------------+----------+
|aaaaaaaaaaaa|33        |
|bbbbbbbbbbbb|22        |
+------------+----------+
我怎么做?感谢您回答这个问题。

您可以使用、groupBy和来实现它。下面是Scala中的代码示例:

import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.expressions.UserDefinedFunction
import org.apache.spark.sql.functions._

def median[T: Numeric](xs: IndexedSeq[T]): Double = {
    if (xs.isEmpty) 0.0
    else {
      // There is faster algorithms (O(N), n-th order statistics) for finding percentiles,
      // but let's go with this one for simplicity - O(NlogN)
      val sorted = xs.sorted
      if (sorted.length % 2 == 1) implicitly[Numeric[T]].toDouble(sorted(sorted.length / 2))
      else {
        // [1, 2]
        val a = sorted(sorted.length / 2)
        val b = sorted(sorted.length / 2 - 1)
        implicitly[Numeric[T]].toDouble(implicitly[Numeric[T]].plus(a, b)) / 2
      }
    }
}

/// .... 

// This is important to make `toDF` visible!
import spark.sqlContext.implicits._

val medianUDF: UserDefinedFunction = udf[Double, IndexedSeq[Int]](median[Int])
val df: DataFrame = Seq(("aaaaaaaaaaaa", 11), ("aaaaaaaaaaaa", 44), ("bbbbbbbbbbbb", 22), ("aaaaaaaaaaaa", 33))
  .toDF("A", "B")
df.show()
//  +------------+---+
//  |           A|  B|
//  +------------+---+
//  |aaaaaaaaaaaa| 11|
//  |aaaaaaaaaaaa| 44|
//  |bbbbbbbbbbbb| 22|
//  |aaaaaaaaaaaa| 33|
//  +------------+---+

// Using UDF as aggregation function. Input for this UDF is indexed sequence - result from collect_list
df.groupBy(col("A"))
  .agg(medianUDF(collect_list(col("B"))).as("median"))
  .show()
//    +------------+------+
//    |           A|median|
//    +------------+------+
//    |bbbbbbbbbbbb|  22.0|
//    |aaaaaaaaaaaa|  33.0|
//    +------------+------+

下面是一种并行计算所有中间值的方法(当然是近似计算)

让我们从收集所有可能的密钥开始:

//生成数据(顺便说一句,请在下次提问时提供该代码)
val df=序列((“aaaaaaaa”,11),(“aaaaaaaaaa”,44),
(“bbbbbbbbbb”,22),(“aaaaaaaaaa”,33))
.toDF(“A”、“B”)
val cols=df.select(“A”).distinct.collect.map(u.getAs[String](0))
假设
cols
具有大小
N
。 一种方法是迭代
cols
并通过N个单独的作业计算中值。另一个答案提供了代码

然而,可以使用一个pivot并行计算所有中间值(因此只有一个作业)

val精度=1e-3
val中位数=df
//索引是人为的,它只是用来单独处理每一行
.带列(“索引”,单调递增ID)
.groupBy(“索引”)
.pivot(“A”).agg(第一('B))
.stat.approxQuantile(cols,数组(0.5),精度)
val result=cols.index.map(i=>cols(i)->中间值(i)(0)).toMap
只有几把钥匙可能不值得,如果你有更多的钥匙可能会很有趣

编辑 第一种解决方案在原始数据帧中每行保留一行,我认为它将一直工作。如果您有许多键,那么使用这样的窗口计算索引可能会很有趣

.withColumn("index", row_number() over Window.partitionBy("A").orderBy("B"))

但如果每个键有数百万行,则不要使用windows。它可能非常慢,甚至会使您的工作崩溃。

但是,如果每个组有数十亿行,并且使用聚合,它将只生成两行,其中包含这些数组列中的所有值,这些数组列称为UDF?我不知道它是否能正常工作parallel@chlebek,很可能Spark将能够处理10亿个整数(1*10^9*4字节~4 GB),但在更大范围内,它将失败。其中一种方法是使用,但在需要确保dataframe只包含该键的值之前