web-dev-qa-db-fra.com

Collectez les lignes sous forme de liste avec le groupe par Apache spark

J'ai un cas d'utilisation particulier où j'ai plusieurs lignes pour le même client où chaque objet ligne ressemble:

root
 -c1: BigInt
 -c2: String
 -c3: Double
 -c4: Double
 -c5: Map[String, Int]

Maintenant, je dois faire le groupe par colonne c1 et collecter toutes les lignes sous forme de liste pour le même client comme:

c1, [Row1, Row3, Row4]
c2, [Row2, Row5]

J'ai essayé de faire de cette façon dataset.withColumn("combined", array("c1","c2","c3","c4","c5")).groupBy("c1").agg(collect_list("combined")) mais j'obtiens une exception:

Exception in thread "main" org.Apache.spark.sql.AnalysisException: cannot resolve 'array(`c1`, `c2`, `c3`, `c4`, `c5`)' due to data type mismatch: input to function array should all be the same type, but it's [bigint, string, double, double, map<string,map<string,double>>];;
7
Prateek Jain

Au lieu de array, vous pouvez utiliser la fonction struct pour combiner les colonnes et utiliser groupBy et collect_list fonction d'agrégation comme

import org.Apache.spark.sql.functions._
df.withColumn("combined", struct("c1","c2","c3","c4","c5"))
    .groupBy("c1").agg(collect_list("combined").as("combined_list"))
    .show(false)

de sorte que vous avez ensemble de données groupé avec schema as

root
 |-- c1: integer (nullable = false)
 |-- combined_list: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- c1: integer (nullable = false)
 |    |    |-- c2: string (nullable = true)
 |    |    |-- c3: string (nullable = true)
 |    |    |-- c4: string (nullable = true)
 |    |    |-- c5: map (nullable = true)
 |    |    |    |-- key: string
 |    |    |    |-- value: integer (valueContainsNull = false)

J'espère que la réponse est utile

8
Ramesh Maharjan

Si vous voulez que le résultat se compose de collections de Rows, envisagez de vous transformer en RDD comme suit:

import org.Apache.spark.sql.functions._
import org.Apache.spark.sql.Row

def df = Seq(
    (BigInt(10), "x", 1.0, 2.0, Map("a"->1, "b"->2)),
    (BigInt(10), "y", 3.0, 4.0, Map("c"->3)),
    (BigInt(20), "z", 5.0, 6.0, Map("d"->4, "e"->5))
  ).
  toDF("c1", "c2", "c3", "c4", "c5").
  // as[(BigInt, String, Double, Double, Map[String, Int])]

df.rdd.map(r => (r.getDecimal(0), r)).groupByKey.collect
// res1: Array[(Java.math.BigDecimal, Iterable[org.Apache.spark.sql.Row])] = Array(
//   (10,CompactBuffer([10,x,1.0,2.0,Map(a -> 1, b -> 2)], [10,y,3.0,4.0,Map(c -> 3)])),
//   (20,CompactBuffer([20,z,5.0,6.0,Map(d -> 4, e -> 5)]))
// )

Ou, si vous êtes bon avec des collections de lignes de type struct- dans un DataFrame, voici une approche alternative:

val cols = ds.columns

df.groupBy("c1").agg(collect_list(struct(cols.head, cols.tail: _*)).as("row_list")).
  show(false)
// +---+----------------------------------------------------------------+
// |c1 |row_list                                                        |
// +---+----------------------------------------------------------------+
// |20 |[[20,z,5.0,6.0,Map(d -> 4, e -> 5)]]                            |
// |10 |[[10,x,1.0,2.0,Map(a -> 1, b -> 2)], [10,y,3.0,4.0,Map(c -> 3)]]|
// +---+----------------------------------------------------------------+
0
Leo C