প্রতিটি গ্রুপের প্রথম সারিটি কীভাবে নির্বাচন করবেন?


143

আমার নিম্নলিখিত হিসাবে ডেটাফ্রেম উত্পন্ন হয়েছে:

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ক্রমবর্ধমান ক্রমে অর্ডার করা হয়েছে, তার পরে একটি উতরিত ক্রম দ্বারা TotalValue

আমি প্রতিটি গ্রুপের শীর্ষ সারিটি নির্বাচন করতে চাই ie

  • আওয়ারের গ্রুপ থেকে == 0 নির্বাচন করুন (0, বিড়াল 26,30.9)
  • আওয়ারের গ্রুপ থেকে == 1 টি নির্বাচন করুন (1, বিড়াল 67,28.5)
  • আওয়ারের গ্রুপ থেকে == 2 টি নির্বাচন করুন (2, ক্যাট 576,39.6)
  • ইত্যাদি

সুতরাং কাঙ্ক্ষিত আউটপুট হবে:

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

পাশাপাশি প্রতিটি গ্রুপের শীর্ষ এন সারিগুলি নির্বাচন করতে সক্ষম হতে পারে।

কোন সাহায্যের অত্যন্ত প্রশংসা করা হয়।

উত্তর:


231

উইন্ডো ফাংশন :

এর মতো কিছুতে কৌশলটি করা উচিত:

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|
// +----+--------+----------+

উল্লেখযোগ্য ডেটা স্কিউর ক্ষেত্রে এই পদ্ধতিটি অকার্যকর হবে।

সমতলে এসকিউএল সমষ্টি অনুসরণ করে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মোড) কিন্তু এটা উপরে ভরসা করা যায় (দেখুন স্ফুলিঙ্গ-16207 করতে, ক্রেডিট Tzach সোহরের জন্য প্রাসঙ্গিক জির ইস্যু লিঙ্ক , এবং স্পার্ক-30335 )।

একই নোট প্রযোজ্য

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

যা অভ্যন্তরীণভাবে সমতুল্য প্রয়োগের পরিকল্পনা ব্যবহার করে।


3
দেখে মনে হচ্ছে স্পার্ক 1.6 সাল থেকে এটি রো-নাম্বার পরিবর্তে সারি_নম্বার ()
অ্যাডাম স্যাজাউচা

Df.orderBy (...)। GropBy (...) ব্যবহার করবেন না সম্পর্কে। কোন পরিস্থিতিতে আমরা অর্ডারবি (...) এর উপর নির্ভর করতে পারি? বা যদি আমরা নিশ্চিত হতে পারি না যে অর্ডারবাই () সঠিক ফলাফল দিচ্ছে, তবে আমাদের কী বিকল্প রয়েছে?
Ignacio Alorre

আমি হয়ত কিছু উপেক্ষা করছি তবে সাধারণভাবে এটি গ্রুপবাইকে এড়াতে বাঞ্ছনীয় , পরিবর্তে হ্রাসবাইক ব্যবহার করা উচিত। এছাড়াও, আপনি একটি লাইন সংরক্ষণ করা হবে।
থমাস

3
@ থমাস আরডিডিগুলির সাথে কথা বলার সময় গ্রুপবাই / গ্রুপবাইকি এড়িয়ে চলেছেন, আপনি খেয়াল করবেন যে ডেটাসেট এপিআইয়ের কোনও হ্রাস-বাইকির কার্যকারিতাও নেই।
soote


16

একাধিক কলাম দ্বারা গ্রুপিং সহ স্পার্ক ২.০.২ এর জন্য:

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 এর উত্তরের ঠিক একই তবে এসকিউএল কোয়েরি পদ্ধতিতে।

ধরে নিই যে ডেটাফ্রেম তৈরি করা হয়েছে এবং হিসাবে নিবন্ধিত হয়েছে

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      |
//+----+--------+----------+

সাফল্যের সাথে এসকিউএল সমষ্টিটি এর পরে যোগদান:

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

প্যাটার্নটি কী দ্বারা গ্রুপ করা হয় => প্রতিটি গ্রুপে কিছু করুন যেমন হ্রাস করুন => ডেটা ফ্রেমে ফিরে যান

আমি ভেবেছিলাম এই ক্ষেত্রে ডেটাফ্রেম বিমূর্তিটি কিছুটা জটিল so তাই আমি আরডিডি কার্যকারিতাটি ব্যবহার করেছি

 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

নীচের সমাধানটি কেবল একটি গোষ্ঠী দ্বারা তৈরি করে এবং আপনার ডেটাফ্রেমের সারিগুলি বের করে যার মধ্যে একটি শটে সর্বোচ্চ মান রয়েছে। আর যোগস বা উইন্ডোজের দরকার নেই।

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

ডেটাফ্রেম এপিআই দিয়ে এটি করার একটি দুর্দান্ত উপায় আরগম্যাক্স যুক্তি ব্যবহার করে

  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-funitions.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|
+---+------+----+
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.