Cette question n'est pas nouvelle, mais je trouve un comportement surprenant dans Spark. J'ai besoin d'ajouter une colonne d'identifiants de lignes à un DataFrame. J'ai utilisé la méthode DataFrame monotonically_increasing_id () et cela me donne un col supplémentaire d'ID de lignes uniques (qui ne sont PAS consécutifs mais sont uniques).
Le problème que je rencontre est que, lorsque je filtre le DataFrame, les ID de ligne du DataFrame résultant sont réaffectés. Les deux DataFrames sont illustrés ci-dessous.
le premier est le DataFrame initial avec les ID de ligne ajoutés comme suit:
df.withColumn("rowId", monotonically_increasing_id())
le deuxième DataFrame est celui obtenu après filtrage sur la colonne P via df.filter(col("P"))
.
Le problème est illustré par l'ID de ligne pour custId 169, qui était 5 dans le DataFrame initial, mais après filtrage, cet ID de ligne (5) a été réaffecté à custmId 773 lorsque custId 169 a été filtré! Je ne sais pas pourquoi c'est le comportement par défaut.
Je voudrais que la rowIds
soit "collante"; si je supprime des lignes du DataFrame, je ne veux pas que leurs identifiants soient "réutilisés", je les veux aussi accompagnés de leurs lignes. Est-il possible de faire ça? Je ne vois aucun indicateur pour demander ce comportement à la méthode monotonically_increasing_id
.
+---------+--------------------+-------+
| custId | features| P |rowId|
+---------+--------------------+-------+
|806 |[50,5074,...| true| 0|
|832 |[45,120,1...| true| 1|
|216 |[6691,272...| true| 2|
|926 |[120,1788...| true| 3|
|875 |[54,120,1...| true| 4|
|169 |[19406,21...| false| 5|
after filtering on P:
+---------+--------------------+-------+
| custId| features| P |rowId|
+---------+--------------------+-------+
| 806|[50,5074,...| true| 0|
| 832|[45,120,1...| true| 1|
| 216|[6691,272...| true| 2|
| 926|[120,1788...| true| 3|
| 875|[54,120,1...| true| 4|
| 773|[3136,317...| true| 5|
Je ne pouvais pas reproduire cela. J'utilise Spark 2.0, alors le comportement a peut-être changé ou je ne fais pas la même chose que vous.
val df = Seq(("one", 1,true),("two", 2,false),("three", 3,true),("four", 4,true))
.toDF("name", "value","flag")
.withColumn("rowd", monotonically_increasing_id())
df.show
val df2 = df.filter(col("flag")=== true)
df2.show
df: org.Apache.spark.sql.DataFrame = [name: string, value: int ... 2 more fields]
+-----+-----+-----+----+
| name|value| flag|rowd|
+-----+-----+-----+----+
| one| 1| true| 0|
| two| 2|false| 1|
|three| 3| true| 2|
| four| 4| true| 3|
+-----+-----+-----+----+
df2: org.Apache.spark.sql.Dataset[org.Apache.spark.sql.Row] = [name: string, value: int ... 2 more fields]
+-----+-----+----+----+
| name|value|flag|rowd|
+-----+-----+----+----+
| one| 1|true| 0|
|three| 3|true| 2|
| four| 4|true| 3|
+-----+-----+----+----+
Je travaillais récemment sur un problème similaire. Bien que monotonically_increasing_id()
soit très rapide, il n’est pas fiable et ne vous donnera pas des numéros de lignes consécutifs, mais uniquement des entiers uniques.
Créer une partition Windows puis utiliser row_number().over(some_windows_partition)
prend énormément de temps.
Jusqu'à présent, la meilleure solution consiste à compresser avec index puis à reconvertir le fichier compressé en image de données d'origine, avec le nouveau schéma comprenant la colonne d'index.
Essaye ça:
from pyspark.sql import Row
from pyspark.sql.types import StructType, StructField, LongType
new_schema = StructType(**original_dataframe**.schema.fields[:] + [StructField("index", LongType(), False)])
zipped_rdd = **original_dataframe**.rdd.zipWithIndex()
indexed = (zipped_rdd.map(lambda ri: row_with_index(*list(ri[0]) + [ri[1]])).toDF(new_schema))
Où original_dataframe
est la dataframe
vous devez ajouter un index et row_with_index
est le nouveau schéma avec l'index de colonne que vous pouvez écrire en tant que
row_with_index = Row(
"calendar_date"
,"year_week_number"
,"year_period_number"
,"realization"
,"index"
)
Ici, calendar_date
, year_week_number
, year_period_number
et realization
étaient les colonnes de ma dataframe
originale. Vous pouvez remplacer les noms par les noms de vos colonnes. L'index est le nouveau nom de colonne que vous avez dû ajouter pour les numéros de ligne.
Ce processus est largement plus efficace et plus fluide par rapport à la méthode row_number().over(some_windows_partition)
.
J'espère que cela t'aides.
Pour contourner l'évaluation changeante de monotonically_increasing_id (), vous pouvez essayer d'écrire le cadre de données sur le disque et de relire. Ensuite, la colonne id est désormais simplement un champ de données en cours de lecture, plutôt que calculé de manière dynamique à un moment donné du pipeline. Bien que ce soit une solution assez laide, cela a fonctionné quand j'ai fait un test rapide.
Cela a fonctionné pour moi. Création d'une autre colonne d'identité et utilisation de la fonction de fenêtre row_number
import org.Apache.spark.sql.functions.{row_number}
import org.Apache.spark.sql.expressions.Window
val df1: DataFrame = df.withColumn("Id",lit(1))
df1
.select(
...,
row_number()
.over(Window
.partitionBy("Id"
.orderBy(col("...").desc))
)
.alias("Row_Nbr")
)