web-dev-qa-db-fra.com

Comment ajouter une colonne persistante d'identifiants de lignes à Spark DataFrame?

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|
27
Kai

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|
+-----+-----+----+----+
3
Davos

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))

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.

2
Shantanu Sharma

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.

1
Chris T

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")
)
1
Sampad Desai