web-dev-qa-db-fra.com

Filtrage des lignes en fonction des valeurs de colonne dans scala spark dataframe

J'ai un dataframe (spark):

id  value 
3     0
3     1
3     0
4     1
4     0
4     0

Je veux créer un nouveau dataframe:

3 0
3 1
4 1

Nécessité de supprimer toutes les lignes après 1(value) pour chaque ID.J'ai essayé avec les fonctions de fenêtre dans spark dateframe (Scala). Mais impossible de trouver une solution. On dirait que je vais dans la mauvaise direction.

Je cherche une solution dans Scala.Merci

Sortie utilisant monotonically_increasing_id

 scala> val data = Seq((3,0),(3,1),(3,0),(4,1),(4,0),(4,0)).toDF("id", "value")
data: org.Apache.spark.sql.DataFrame = [id: int, value: int]

scala> val minIdx = dataWithIndex.filter($"value" === 1).groupBy($"id").agg(min($"idx")).toDF("r_id", "min_idx")
minIdx: org.Apache.spark.sql.DataFrame = [r_id: int, min_idx: bigint]

scala> dataWithIndex.join(minIdx,($"r_id" === $"id") && ($"idx" <= $"min_idx")).select($"id", $"value").show
+---+-----+
| id|value|
+---+-----+
|  3|    0|
|  3|    1|
|  4|    1|
+---+-----+

La solution ne fonctionnera pas si nous effectuons une transformation triée dans le cadre de données d'origine. Cette fois, le monotonically_increasing_id () est généré sur la base de l'original DF plutôt que sur le fichier DF.I trié qui manquait auparavant.

Toutes les suggestions sont les bienvenues. 

9
John

Une solution consiste à utiliser monotonically_increasing_id() et une auto-jointure:

val data = Seq((3,0),(3,1),(3,0),(4,1),(4,0),(4,0)).toDF("id", "value")
data.show
+---+-----+
| id|value|
+---+-----+
|  3|    0|
|  3|    1|
|  3|    0|
|  4|    1|
|  4|    0|
|  4|    0|
+---+-----+

Maintenant, nous générons une colonne nommée idx avec une Long croissante:

val dataWithIndex = data.withColumn("idx", monotonically_increasing_id())
// dataWithIndex.cache()

Nous obtenons maintenant la min(idx) pour chaque idvalue = 1:

val minIdx = dataWithIndex
               .filter($"value" === 1)
               .groupBy($"id")
               .agg(min($"idx"))
               .toDF("r_id", "min_idx")

Maintenant, nous joignons la min(idx) à la DataFrame originale:

dataWithIndex.join(
  minIdx,
  ($"r_id" === $"id") && ($"idx" <= $"min_idx")
).select($"id", $"value").show
+---+-----+
| id|value|
+---+-----+
|  3|    0|
|  3|    1|
|  4|    1|
+---+-----+

Remarque: monotonically_increasing_id() génère sa valeur en fonction de la partition de la ligne. Cette valeur peut changer chaque fois que dataWithIndex est réévalué. Dans mon code ci-dessus, en raison d'une évaluation paresseuse, ce n'est que lorsque j'appelle la dernière variable (show) que monotonically_increasing_id() est évalué.

Si vous voulez forcer la valeur à rester la même, par exemple pour pouvoir utiliser show afin d'évaluer les étapes ci-dessus, décommentez cette ligne ci-dessus:

//  dataWithIndex.cache()
8
David Griffin

Bonjour, j'ai trouvé la solution en utilisant Window et Self Join.

val data = Seq((3,0,2),(3,1,3),(3,0,1),(4,1,6),(4,0,5),(4,0,4),(1,0,7),(1,1,8),(1,0,9),(2,1,10),(2,0,11),(2,0,12)).toDF("id", "value","sorted")

data.show

scala> data.show
+---+-----+------+
| id|value|sorted|
+---+-----+------+
|  3|    0|     2|
|  3|    1|     3|
|  3|    0|     1|
|  4|    1|     6|
|  4|    0|     5|
|  4|    0|     4|
|  1|    0|     7|
|  1|    1|     8|
|  1|    0|     9|
|  2|    1|    10|
|  2|    0|    11|
|  2|    0|    12|
+---+-----+------+




val sort_df=data.sort($"sorted")

scala> sort_df.show
+---+-----+------+
| id|value|sorted|
+---+-----+------+
|  3|    0|     1|
|  3|    0|     2|
|  3|    1|     3|
|  4|    0|     4|
|  4|    0|     5|
|  4|    1|     6|
|  1|    0|     7|
|  1|    1|     8|
|  1|    0|     9|
|  2|    1|    10|
|  2|    0|    11|
|  2|    0|    12|
+---+-----+------+



var window=Window.partitionBy("id").orderBy("$sorted")

 val sort_idx=sort_df.select($"*",rowNumber.over(window).as("count_index"))

val minIdx=sort_idx.filter($"value"===1).groupBy("id").agg(min("count_index")).toDF("idx","min_idx")

val result_id=sort_idx.join(minIdx,($"id"===$"idx") &&($"count_index" <= $"min_idx"))

result_id.show

+---+-----+------+-----------+---+-------+
| id|value|sorted|count_index|idx|min_idx|
+---+-----+------+-----------+---+-------+
|  1|    0|     7|          1|  1|      2|
|  1|    1|     8|          2|  1|      2|
|  2|    1|    10|          1|  2|      1|
|  3|    0|     1|          1|  3|      3|
|  3|    0|     2|          2|  3|      3|
|  3|    1|     3|          3|  3|      3|
|  4|    0|     4|          1|  4|      3|
|  4|    0|     5|          2|  4|      3|
|  4|    1|     6|          3|  4|      3|
+---+-----+------+-----------+---+-------+

Toujours à la recherche de solutions plus optimisées.Merci

1
John
use isin method and filter as below:

val data = Seq((3,0,2),(3,1,3),(3,0,1),(4,1,6),(4,0,5),(4,0,4),(1,0,7),(1,1,8),(1,0,9),(2,1,10),(2,0,11),(2,0,12)).toDF("id", "value","sorted")
val idFilter = List(1, 2)
 data.filter($"id".isin(idFilter:_*)).show
+---+-----+------+
| id|value|sorted|
+---+-----+------+
|  1|    0|     7|
|  1|    1|     8|
|  1|    0|     9|
|  2|    1|    10|
|  2|    0|    11|
|  2|    0|    12|
+---+-----+------+

Ex: filter based on val
val valFilter = List(0)
data.filter($"value".isin(valFilter:_*)).show
+---+-----+------+
| id|value|sorted|
+---+-----+------+
|  3|    0|     2|
|  3|    0|     1|
|  4|    0|     5|
|  4|    0|     4|
|  1|    0|     7|
|  1|    0|     9|
|  2|    0|    11|
|  2|    0|    12|
+---+-----+------+
0
mputha

Vous pouvez simplement utiliser groupBy comme ceci

val df2 = df1.groupBy("id","value").count().select("id","value")

Ici votre df1 est

id  value 
3     0
3     1
3     0
4     1
4     0
4     0

Et le dataframe résultant est df2 qui est votre sortie attendue comme ceci

id  value 
3     0
3     1
4     1
4     0
0
Abu Shoeb