私の要件は、データフレームから上位N個のアイテムを取得することです。
私はこのDataFrameを持っています:
_val df = List(
("MA", "USA"),
("MA", "USA"),
("OH", "USA"),
("OH", "USA"),
("OH", "USA"),
("OH", "USA"),
("NY", "USA"),
("NY", "USA"),
("NY", "USA"),
("NY", "USA"),
("NY", "USA"),
("NY", "USA"),
("CT", "USA"),
("CT", "USA"),
("CT", "USA"),
("CT", "USA"),
("CT", "USA")).toDF("value", "country")
_
RDD[((Int, String), Long)]
colValCountにマップできました:読み取り:((colIdx、value)、count)
_((0,CT),5)
((0,MA),2)
((0,OH),4)
((0,NY),6)
((1,USA),17)
_
次に、各列インデックスの上位2つの項目を取得する必要があります。だから私の期待される出力はこれです:
_RDD[((Int, String), Long)]
((0,CT),5)
((0,NY),6)
((1,USA),17)
_
DataFrameでfreqItemsapiを使用してみましたが、時間がかかります。
どんな提案でも大歓迎です。
これを行う最も簡単な方法(自然なウィンドウ関数)は、SQLを作成することです。 SparkにはSQL構文が付属しており、SQLはこの問題に対する優れた表現力のあるツールです。
データフレームを一時テーブルとして登録し、グループ化してウィンドウを作成します。
spark.sql("""SELECT idx, value, ROW_NUMBER() OVER (PARTITION BY idx ORDER BY c DESC) as r
FROM (
SELECT idx, value, COUNT(*) as c
FROM (SELECT 0 as idx, value FROM df UNION ALL SELECT 1, country FROM df)
GROUP BY idx, value)
HAVING r <= 2""").show()
手続き型/ scalaアプローチのいずれかで、反復やループなしでウィンドウ関数を実行できるかどうかを確認したいのですが、Spark API。
ちなみに、含める列の数が任意である場合は、内部セクション(SELECT 0 as idx, value ... UNION ALL SELECT 1, country
など)列のリストを動的に使用します。
例えば:
_import org.Apache.spark.sql.functions._
df.select(lit(0).alias("index"), $"value")
.union(df.select(lit(1), $"country"))
.groupBy($"index", $"value")
.count
.orderBy($"count".desc)
.limit(3)
.show
// +-----+-----+-----+
// |index|value|count|
// +-----+-----+-----+
// | 1| USA| 17|
// | 0| NY| 6|
// | 0| CT| 5|
// +-----+-----+-----+
_
どこ:
_df.select(lit(0).alias("index"), $"value")
.union(df.select(lit(1), $"country"))
_
2つの列を作成しますDataFrame
:
_// +-----+-----+
// |index|value|
// +-----+-----+
// | 0| MA|
// | 0| MA|
// | 0| OH|
// | 0| OH|
// | 0| OH|
// | 0| OH|
// | 0| NY|
// | 0| NY|
// | 0| NY|
// | 0| NY|
// | 0| NY|
// | 0| NY|
// | 0| CT|
// | 0| CT|
// | 0| CT|
// | 0| CT|
// | 0| CT|
// | 1| USA|
// | 1| USA|
// | 1| USA|
// +-----+-----+
_
各列に具体的に2つの値が必要な場合:
_import org.Apache.spark.sql.DataFrame
def topN(df: DataFrame, key: String, n: Int) = {
df.select(
lit(df.columns.indexOf(key)).alias("index"),
col(key).alias("value"))
.groupBy("index", "value")
.count
.orderBy($"count")
.limit(n)
}
topN(df, "value", 2).union(topN(df, "country", 2)).show
// +-----+-----+-----+
// |index|value|count|
// +-----+-----+-----+
// | 0| MA| 2|
// | 0| OH| 4|
// | 1| USA| 17|
// +-----+-----+-----+
_
最後のRDDを考えると:
_val rdd =
sc.parallelize(
List(
((0, "CT"), 5),
((0, "MA"), 2),
((0, "OH"), 4),
((0, "NY"), 6),
((1, "USA"), 17)
))
rdd.filter(_._1._1 == 0).sortBy(-_._2).take(2).foreach(println)
> ((0,NY),6)
> ((0,CT),5)
rdd.filter(_._1._1 == 1).sortBy(-_._2).take(2).foreach(println)
> ((1,USA),17)
_
まず、指定された列インデックス(.filter(_._1._1 == 0)
)のアイテムを取得します。次に、アイテムを降順で並べ替えます(.sortBy(-_._2)
)。そして最後に、最初の2つの要素(.take(2)
)を取得します。これは、レコードのnbrが2未満の場合、1つの要素のみを取得します。
Sparkzで定義されているこのヘルパー関数を使用して、各単一パーティションをマップし、それらを組み合わせることができます。
package sparkz.utils
import scala.reflect.ClassTag
object TopElements {
def topN[T: ClassTag](elems: Iterable[T])(scoreFunc: T => Double, n: Int): List[T] =
elems.foldLeft((Set.empty[(T, Double)], Double.MaxValue)) {
case (accumulator@(topElems, minScore), elem) =>
val score = scoreFunc(elem)
if (topElems.size < n)
(topElems + (elem -> score), math.min(minScore, score))
else if (score > minScore) {
val newTopElems = topElems - topElems.minBy(_._2) + (elem -> score)
(newTopElems, newTopElems.map(_._2).min)
}
else accumulator
}
._1.toList.sortBy(_._2).reverse.map(_._1)
}
ソース: https://github.com/gm-spacagna/sparkz/blob/master/src/main/scala/sparkz/utils/TopN.scala
Spark SQLデータフレームを使用している場合、私の意見では、最良の(そして理解しやすいソリューション)は、次のようにコードを実行することです。
val test: Dataframe = df.select(col("col_name"))
test.show(5, false)
それがあなたを助けることを願っています:)