प्रत्येक समूह की पहली पंक्ति का चयन कैसे करें?


144

मेरे पास निम्नानुसार एक DataFrame उत्पन्न है:

df.groupBy($"Hour", $"Category")
  .agg(sum($"value") as "TotalValue")
  .sort($"Hour".asc, $"TotalValue".desc))

परिणाम इस तरह दिखते हैं:

+----+--------+----------+
|Hour|Category|TotalValue|
+----+--------+----------+
|   0|   cat26|      30.9|
|   0|   cat13|      22.1|
|   0|   cat95|      19.6|
|   0|  cat105|       1.3|
|   1|   cat67|      28.5|
|   1|    cat4|      26.8|
|   1|   cat13|      12.6|
|   1|   cat23|       5.3|
|   2|   cat56|      39.6|
|   2|   cat40|      29.7|
|   2|  cat187|      27.9|
|   2|   cat68|       9.8|
|   3|    cat8|      35.6|
| ...|    ....|      ....|
+----+--------+----------+

जैसा कि आप देख सकते हैं, Hourबढ़ते क्रम में DataFrame का आदेश दिया जाता है, फिर TotalValueअवरोही क्रम में।

मैं प्रत्येक समूह की शीर्ष पंक्ति का चयन करना चाहूंगा, अर्थात

  • घंटे के समूह से == 0 चयन (0, cat26,30.9)
  • घंटे के समूह से == 1 चयन (1, cat67,28.5)
  • घंटे के समूह से == 2 चयन (2, cat56,39.6)
  • और इसी तरह

तो वांछित उत्पादन होगा:

+----+--------+----------+
|Hour|Category|TotalValue|
+----+--------+----------+
|   0|   cat26|      30.9|
|   1|   cat67|      28.5|
|   2|   cat56|      39.6|
|   3|    cat8|      35.6|
| ...|     ...|       ...|
+----+--------+----------+

यह संभव है कि प्रत्येक समूह की शीर्ष N पंक्तियों का चयन करने में सक्षम हो।

किसी भी सहायताको बहुत सराहा जाएगा।

जवाबों:


234

विंडो फ़ंक्शन :

कुछ इस तरह करना चाहिए ट्रिक:

import org.apache.spark.sql.functions.{row_number, max, broadcast}
import org.apache.spark.sql.expressions.Window

val df = sc.parallelize(Seq(
  (0,"cat26",30.9), (0,"cat13",22.1), (0,"cat95",19.6), (0,"cat105",1.3),
  (1,"cat67",28.5), (1,"cat4",26.8), (1,"cat13",12.6), (1,"cat23",5.3),
  (2,"cat56",39.6), (2,"cat40",29.7), (2,"cat187",27.9), (2,"cat68",9.8),
  (3,"cat8",35.6))).toDF("Hour", "Category", "TotalValue")

val w = Window.partitionBy($"hour").orderBy($"TotalValue".desc)

val dfTop = df.withColumn("rn", row_number.over(w)).where($"rn" === 1).drop("rn")

dfTop.show
// +----+--------+----------+
// |Hour|Category|TotalValue|
// +----+--------+----------+
// |   0|   cat26|      30.9|
// |   1|   cat67|      28.5|
// |   2|   cat56|      39.6|
// |   3|    cat8|      35.6|
// +----+--------+----------+

महत्वपूर्ण डेटा तिरछा होने की स्थिति में यह विधि अक्षम होगी।

सादे SQL एकत्रीकरण के बादjoin :

वैकल्पिक रूप से आप कुल डेटा फ्रेम के साथ जुड़ सकते हैं:

val dfMax = df.groupBy($"hour".as("max_hour")).agg(max($"TotalValue").as("max_value"))

val dfTopByJoin = df.join(broadcast(dfMax),
    ($"hour" === $"max_hour") && ($"TotalValue" === $"max_value"))
  .drop("max_hour")
  .drop("max_value")

dfTopByJoin.show

// +----+--------+----------+
// |Hour|Category|TotalValue|
// +----+--------+----------+
// |   0|   cat26|      30.9|
// |   1|   cat67|      28.5|
// |   2|   cat56|      39.6|
// |   3|    cat8|      35.6|
// +----+--------+----------+

यह डुप्लिकेट मान रखेगा (यदि समान मान के साथ प्रति घंटे एक से अधिक श्रेणी है)। आप इन्हें निम्नानुसार हटा सकते हैं:

dfTopByJoin
  .groupBy($"hour")
  .agg(
    first("category").alias("category"),
    first("TotalValue").alias("TotalValue"))

आदेश का उपयोग करनाstructs :

नीट, हालांकि बहुत अच्छी तरह से परीक्षण नहीं किया गया है, ट्रिक जिसमें जुड़ने या खिड़की के कार्यों की आवश्यकता नहीं है:

val dfTop = df.select($"Hour", struct($"TotalValue", $"Category").alias("vs"))
  .groupBy($"hour")
  .agg(max("vs").alias("vs"))
  .select($"Hour", $"vs.Category", $"vs.TotalValue")

dfTop.show
// +----+--------+----------+
// |Hour|Category|TotalValue|
// +----+--------+----------+
// |   0|   cat26|      30.9|
// |   1|   cat67|      28.5|
// |   2|   cat56|      39.6|
// |   3|    cat8|      35.6|
// +----+--------+----------+

डेटासेट API (स्पार्क 1.6+, 2.0+) के साथ:

स्पार्क 1.6 :

case class Record(Hour: Integer, Category: String, TotalValue: Double)

df.as[Record]
  .groupBy($"hour")
  .reduce((x, y) => if (x.TotalValue > y.TotalValue) x else y)
  .show

// +---+--------------+
// | _1|            _2|
// +---+--------------+
// |[0]|[0,cat26,30.9]|
// |[1]|[1,cat67,28.5]|
// |[2]|[2,cat56,39.6]|
// |[3]| [3,cat8,35.6]|
// +---+--------------+

स्पार्क 2.0 या बाद में :

df.as[Record]
  .groupByKey(_.Hour)
  .reduceGroups((x, y) => if (x.TotalValue > y.TotalValue) x else y)

अंतिम दो विधियाँ मैप साइड संयोजन का लाभ उठा सकती हैं और पूर्ण फेरबदल की आवश्यकता नहीं होती है, इसलिए अधिकांश समय विंडो फ़ंक्शन और जॉइन की तुलना में बेहतर प्रदर्शन का प्रदर्शन करना चाहिए। इन बेंत का उपयोग completedआउटपुट मोड में स्ट्रक्चर्ड स्ट्रीमिंग के साथ भी किया जा सकता है।

उपयोग न करें :

df.orderBy(...).groupBy(...).agg(first(...), ...)

यह काम (में विशेष रूप से लग सकता है localमोड), लेकिन यह अविश्वसनीय है (देखें चिंगारी से 16,207 करने के लिए, क्रेडिट Tzach ज़ोहर के लिए प्रासंगिक JIRA मुद्दे को जोड़ने , और चिंगारी से 30,335 )।

एक ही नोट पर लागू होता है

df.orderBy(...).dropDuplicates(...)

जो आंतरिक रूप से समतुल्य निष्पादन योजना का उपयोग करता है।


3
ऐसा लगता है कि चिंगारी 1.6 के बाद से यह पंक्ति_नंबर () के बजाय पंक्तिबद्ध है
एडम श्ज़ाकुचा

Df.orderBy (...) का उपयोग न करें के बारे में। gropBy (...)। किन परिस्थितियों में हम ऑर्डरबी (...) पर भरोसा कर सकते हैं? या अगर हम सुनिश्चित नहीं हो सकते हैं अगर ऑर्डरबी () सही परिणाम देने जा रहा है, तो हमारे पास क्या विकल्प हैं?
इग्नासियो अलोर्रे

मैं कुछ अनदेखी कर सकता हूं, लेकिन सामान्य तौर पर ग्रुपबाय से बचने की सिफारिश की जाती है , इसके बजाय कमबैक का उपयोग किया जाना चाहिए। इसके अलावा, आप एक पंक्ति सहेज रहे होंगे।
थॉमस

3
@Thomas groupBy / groupByKey से परहेज करते समय बस RDDs के साथ काम करते हैं, आप देखेंगे कि डेटासेट एपीआई में एक भी कम फ़ंक्शन नहीं है।
soote


16

स्पार्क 2.0.2 के लिए कई स्तंभों द्वारा समूहीकरण के साथ:

import org.apache.spark.sql.functions.row_number
import org.apache.spark.sql.expressions.Window

val w = Window.partitionBy($"col1", $"col2", $"col3").orderBy($"timestamp".desc)

val refined_df = df.withColumn("rn", row_number.over(w)).where($"rn" === 1).drop("rn")

8

यह शून्य323 के उत्तर के समान ही है लेकिन SQL क्वेरी तरीके से है।

यह मानते हुए कि डेटाफ्रेम बनाया और पंजीकृत है

df.createOrReplaceTempView("table")
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|0   |cat26   |30.9      |
//|0   |cat13   |22.1      |
//|0   |cat95   |19.6      |
//|0   |cat105  |1.3       |
//|1   |cat67   |28.5      |
//|1   |cat4    |26.8      |
//|1   |cat13   |12.6      |
//|1   |cat23   |5.3       |
//|2   |cat56   |39.6      |
//|2   |cat40   |29.7      |
//|2   |cat187  |27.9      |
//|2   |cat68   |9.8       |
//|3   |cat8    |35.6      |
//+----+--------+----------+

विंडो फ़ंक्शन:

sqlContext.sql("select Hour, Category, TotalValue from (select *, row_number() OVER (PARTITION BY Hour ORDER BY TotalValue DESC) as rn  FROM table) tmp where rn = 1").show(false)
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|1   |cat67   |28.5      |
//|3   |cat8    |35.6      |
//|2   |cat56   |39.6      |
//|0   |cat26   |30.9      |
//+----+--------+----------+

शामिल होने के बाद सादे SQL एकत्रीकरण:

sqlContext.sql("select Hour, first(Category) as Category, first(TotalValue) as TotalValue from " +
  "(select Hour, Category, TotalValue from table tmp1 " +
  "join " +
  "(select Hour as max_hour, max(TotalValue) as max_value from table group by Hour) tmp2 " +
  "on " +
  "tmp1.Hour = tmp2.max_hour and tmp1.TotalValue = tmp2.max_value) tmp3 " +
  "group by tmp3.Hour")
  .show(false)
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|1   |cat67   |28.5      |
//|3   |cat8    |35.6      |
//|2   |cat56   |39.6      |
//|0   |cat26   |30.9      |
//+----+--------+----------+

आदेशों पर आदेश का उपयोग करना:

sqlContext.sql("select Hour, vs.Category, vs.TotalValue from (select Hour, max(struct(TotalValue, Category)) as vs from table group by Hour)").show(false)
//+----+--------+----------+
//|Hour|Category|TotalValue|
//+----+--------+----------+
//|1   |cat67   |28.5      |
//|3   |cat8    |35.6      |
//|2   |cat56   |39.6      |
//|0   |cat26   |30.9      |
//+----+--------+----------+

डेटासेट तरीका और मूल उत्तर के समान नहीं है


2

पैटर्न कुंजी द्वारा समूह है => प्रत्येक समूह के लिए कुछ करें जैसे कि डेटाफ़्रेम में = = कम करें

मुझे लगा कि डेटाफ्रेम अमूर्त इस मामले में थोड़ा बोझिल है इसलिए मैंने आरडीडी कार्यक्षमता का उपयोग किया

 val rdd: RDD[Row] = originalDf
  .rdd
  .groupBy(row => row.getAs[String]("grouping_row"))
  .map(iterableTuple => {
    iterableTuple._2.reduce(reduceFunction)
  })

val productDf = sqlContext.createDataFrame(rdd, originalDf.schema)

1

नीचे दिया गया समाधान केवल एक GroupBy करता है और आपके डेटाफ्रेम की पंक्तियाँ निकालता है जिसमें एक शॉट में अधिकतमValue होता है। आगे जॉइन, या विंडोज के लिए कोई ज़रूरत नहीं है।

import org.apache.spark.sql.Row
import org.apache.spark.sql.catalyst.encoders.RowEncoder
import org.apache.spark.sql.DataFrame

//df is the dataframe with Day, Category, TotalValue

implicit val dfEnc = RowEncoder(df.schema)

val res: DataFrame = df.groupByKey{(r) => r.getInt(0)}.mapGroups[Row]{(day: Int, rows: Iterator[Row]) => i.maxBy{(r) => r.getDouble(2)}}

लेकिन यह सब कुछ पहले फेरबदल करता है। यह शायद ही एक सुधार है (शायद डेटा के आधार पर, विंडो फ़ंक्शन से भी बदतर नहीं है)।
एलपर टी।

आपके पास एक समूह पहले स्थान पर है, जो फेरबदल करेगा। यह विंडो फ़ंक्शन से भी बदतर नहीं है क्योंकि विंडो फ़ंक्शन में यह डेटाफ़्रेम में प्रत्येक एकल पंक्ति के लिए विंडो का मूल्यांकन करने वाला है।
एलघोटो

1

डेटाफ़्रेम एपी के साथ ऐसा करने का एक अच्छा तरीका इस तरह से argmax तर्क का उपयोग कर रहा है

  val df = Seq(
    (0,"cat26",30.9), (0,"cat13",22.1), (0,"cat95",19.6), (0,"cat105",1.3),
    (1,"cat67",28.5), (1,"cat4",26.8), (1,"cat13",12.6), (1,"cat23",5.3),
    (2,"cat56",39.6), (2,"cat40",29.7), (2,"cat187",27.9), (2,"cat68",9.8),
    (3,"cat8",35.6)).toDF("Hour", "Category", "TotalValue")

  df.groupBy($"Hour")
    .agg(max(struct($"TotalValue", $"Category")).as("argmax"))
    .select($"Hour", $"argmax.*").show

 +----+----------+--------+
 |Hour|TotalValue|Category|
 +----+----------+--------+
 |   1|      28.5|   cat67|
 |   3|      35.6|    cat8|
 |   2|      39.6|   cat56|
 |   0|      30.9|   cat26|
 +----+----------+--------+

0

यहाँ आप इस तरह कर सकते हैं -

   val data = df.groupBy("Hour").agg(first("Hour").as("_1"),first("Category").as("Category"),first("TotalValue").as("TotalValue")).drop("Hour")

data.withColumnRenamed("_1","Hour").show

-2

हम रैंक () विंडो फ़ंक्शन का उपयोग कर सकते हैं (जहां आप रैंक = 1 का चयन करेंगे) रैंक सिर्फ एक समूह की प्रत्येक पंक्ति के लिए एक नंबर जोड़ता है (इस मामले में यह घंटे होगा)

यहाँ एक उदाहरण है। ( https://github.com/jaceklaskowski/mastering-apache-spark-book/blob/master/spark-sql-functions.adoc#rank से )

val dataset = spark.range(9).withColumn("bucket", 'id % 3)

import org.apache.spark.sql.expressions.Window
val byBucket = Window.partitionBy('bucket).orderBy('id)

scala> dataset.withColumn("rank", rank over byBucket).show
+---+------+----+
| id|bucket|rank|
+---+------+----+
|  0|     0|   1|
|  3|     0|   2|
|  6|     0|   3|
|  1|     1|   1|
|  4|     1|   2|
|  7|     1|   3|
|  2|     2|   1|
|  5|     2|   2|
|  8|     2|   3|
+---+------+----+
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.