web-dev-qa-db-ja.com

SparkおよびSparkSQL:ウィンドウ関数を模倣する方法は?

説明

与えられたデータフレームdf

id |       date
---------------
 1 | 2015-09-01
 2 | 2015-09-01
 1 | 2015-09-03
 1 | 2015-09-04
 2 | 2015-09-04

実行中のカウンターまたはインデックスを作成したいのですが、

  • 同じIDでグループ化され、
  • そのグループの日付でソート、

したがって、

id |       date |  counter
--------------------------
 1 | 2015-09-01 |        1
 1 | 2015-09-03 |        2
 1 | 2015-09-04 |        3
 2 | 2015-09-01 |        1
 2 | 2015-09-04 |        2

これは、ウィンドウ関数で実現できるものです。

val w = Window.partitionBy("id").orderBy("date")
val resultDF = df.select( df("id"), rowNumber().over(w) )

残念ながら、Spark 1.4.1は、通常のデータフレームのウィンドウ関数をサポートしていません。

org.Apache.spark.sql.AnalysisException: Could not resolve window function 'row_number'. Note that, using window functions currently requires a HiveContext;

質問

  • ウィンドウ関数を使用せずに、現在のSpark 1.4.1で上記の計算を実行するにはどうすればよいですか?
  • 通常のデータフレームのウィンドウ関数はいつSparkでサポートされますか?

ありがとう!

10
Martin Senne

これはRDDで行うことができます。個人的には、RDD用のAPIの方がはるかに理にかなっていると思います。データをデータフレームのように「フラット」にする必要はありません。

val df = sqlContext.sql("select 1, '2015-09-01'"
    ).unionAll(sqlContext.sql("select 2, '2015-09-01'")
    ).unionAll(sqlContext.sql("select 1, '2015-09-03'")
    ).unionAll(sqlContext.sql("select 1, '2015-09-04'")
    ).unionAll(sqlContext.sql("select 2, '2015-09-04'"))

// dataframe as an RDD (of Row objects)
df.rdd 
  // grouping by the first column of the row
  .groupBy(r => r(0)) 
  // map each group - an Iterable[Row] - to a list and sort by the second column
  .map(g => g._2.toList.sortBy(row => row(1).toString))     
  .collect()

上記の結果は次のようになります。

Array[List[org.Apache.spark.sql.Row]] = 
Array(
  List([1,2015-09-01], [1,2015-09-03], [1,2015-09-04]), 
  List([2,2015-09-01], [2,2015-09-04]))

'group'内の位置も必要な場合は、zipWithIndexを使用できます。

df.rdd.groupBy(r => r(0)).map(g => 
    g._2.toList.sortBy(row => row(1).toString).zipWithIndex).collect()

Array[List[(org.Apache.spark.sql.Row, Int)]] = Array(
  List(([1,2015-09-01],0), ([1,2015-09-03],1), ([1,2015-09-04],2)),
  List(([2,2015-09-01],0), ([2,2015-09-04],1)))

could FlatMapを使用してこれをRowオブジェクトの単純なリスト/配列にフラット化しますが、「グループ」で何かを実行する必要がある場合は、それは良い考えではありません。

このようにRDDを使用することの欠点は、DataFrameからRDDに変換してから元に戻すのが面倒なことです。

6
Kirk Broadhurst

ローカルのHiveContextにもDataFramesを使用できます。そうしないという非常に正当な理由がない限り、とにかくそれはおそらく良い考えです。これは、spark-ShellおよびSQLContextシェルで使用可能なデフォルトのpysparkであり(現時点では、sparkRはプレーンなSQLContextを使用しているようです)、そのパーサーは Spark SQLおよびDataFrameガイド が推奨します。

import org.Apache.spark.{SparkContext, SparkConf}
import org.Apache.spark.sql.Hive.HiveContext
import org.Apache.spark.sql.expressions.Window
import org.Apache.spark.sql.functions.rowNumber

object HiveContextTest {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("Hive Context")
    val sc = new SparkContext(conf)
    val sqlContext = new HiveContext(sc)
    import sqlContext.implicits._

    val df = sc.parallelize(
        ("foo", 1) :: ("foo", 2) :: ("bar", 1) :: ("bar", 2) :: Nil
    ).toDF("k", "v")

    val w = Window.partitionBy($"k").orderBy($"v")
    df.select($"k", $"v", rowNumber.over(w).alias("rn")).show
  }
}
7
zero323

Sparkバージョン(> =)1.5がある場合は、DataFrameのウィンドウ関数が最適です。ただし、実際に古いバージョン(1.4.1など)で立ち往生している場合は、これを解決するためのハッキーな方法は次のとおりです

val df = sc.parallelize((1, "2015-09-01") :: (2, "2015-09-01") :: (1, "2015-09-03") :: (1, "2015-09-04") :: (1, "2015-09-04") :: Nil)
           .toDF("id", "date")

val dfDuplicate = df.selecExpr("id as idDup", "date as dateDup")
val dfWithCounter = df.join(dfDuplicate,$"id"===$"idDup")
                      .where($"date"<=$"dateDup")
                      .groupBy($"id", $"date")
                      .agg($"id", $"date", count($"idDup").as("counter"))
                      .select($"id",$"date",$"counter")

dfWithCounter.showを実行すると

あなたが得るでしょう:

+---+----------+-------+                                                        
| id|      date|counter|
+---+----------+-------+
|  1|2015-09-01|      1|
|  1|2015-09-04|      3|
|  1|2015-09-03|      2|
|  2|2015-09-01|      1|
|  2|2015-09-04|      2|
+---+----------+-------+

dateはソートされていませんが、counterは正しいことに注意してください。また、counterステートメントで<=>=に変更することにより、whereの順序を変更することもできます。

3
Sayon M