web-dev-qa-db-fra.com

SPARK DataFrame: comment diviser efficacement la trame de données pour chaque groupe en fonction des mêmes valeurs de colonne

J'ai un DataFrame généré comme suit:

df.groupBy($"Hour", $"Category")
  .agg(sum($"value").alias("TotalValue"))
  .sort($"Hour".asc,$"TotalValue".desc))

Les résultats ressemblent à:

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

Je voudrais créer de nouveaux cadres de données basés sur chaque valeur unique de col("Hour"), c'est-à-dire.

  • pour le groupe de Hour == 0
  • pour le groupe de Hour == 1
  • pour le groupe de Hour == 2 et ainsi de suite ...

La sortie souhaitée serait donc:

df0 as:

+----+--------+----------+
|Hour|Category|TotalValue|
+----+--------+----------+
|   0|   cat26|      30.9|
|   0|   cat13|      22.1|
|   0|   cat95|      19.6|
|   0|  cat105|       1.3|
+----+--------+----------+

df1 as:
+----+--------+----------+
|Hour|Category|TotalValue|
+----+--------+----------+
|   1|   cat67|      28.5|
|   1|    cat4|      26.8|
|   1|   cat13|      12.6|
|   1|   cat23|       5.3|
+----+--------+----------+

et de même,

df2 as:

+----+--------+----------+
|Hour|Category|TotalValue|
+----+--------+----------+
|   2|   cat56|      39.6|
|   2|   cat40|      29.7|
|   2|  cat187|      27.9|
|   2|   cat68|       9.8|
+----+--------+----------+

Toute aide est grandement appréciée.

EDIT 1:

Ce que j'ai essayé:

df.foreach(
  row => splitHour(row)
  )

def splitHour(row: Row) ={
    val Hour=row.getAs[Long]("Hour")

    val HourDF= sparkSession.createDataFrame(List((s"$Hour",1)))

    val hdf=HourDF.withColumnRenamed("_1","Hour_unique").drop("_2")

    val mydf: DataFrame =df.join(hdf,df("Hour")===hdf("Hour_unique"))

    mydf.write.mode("overwrite").parquet(s"/home/dev/shaishave/etc/myparquet/$Hour/")
  }

PROBLÈME AVEC CETTE STRATÉGIE:

Cela a pris 8 heures quand il a été exécuté sur une trame de données df qui avait plus de 1 million de lignes et spark a été donné environ 10 Go RAM sur un seul noeud. Ainsi, join s'avère très inefficace.

Attention: je dois écrire chaque dataframe mydf en tant que parquet qui a un schéma imbriqué qui doit être maintenu (pas aplati).

8
shubham rajput

Comme indiqué dans mes commentaires, une approche potentiellement facile à ce problème serait d'utiliser:

df.write.partitionBy("hour").saveAsTable("myparquet")

Comme indiqué, la structure des dossiers serait myparquet/hour=1, myparquet/hour=2, ..., myparquet/hour=24 Par opposition à myparquet/1, myparquet/2,. .., myparquet/24.

Pour modifier la structure des dossiers, vous pouvez

  1. Utilisez potentiellement le paramètre de configuration Hive hcat.dynamic.partitioning.custom.pattern Dans un HiveContext explicite; plus d'informations sur HCatalog DynamicPartitions .
  2. Une autre approche serait de changer le système de fichiers directement après avoir exécuté la commande df.write.partitionBy.saveAsTable(...) avec quelque chose comme for f in *; do mv $f ${f/${f:0:5}/} ; done Qui supprimerait le texte Hour= Du nom du dossier.

Il est important de noter qu'en modifiant le modèle de dénomination des dossiers, lorsque vous exécutez spark.read.parquet(...) dans ce dossier, Spark ne comprendra pas automatiquement les partitions dynamiques car il manque les informations de partitionKey (c'est-à-dire Hour).

7
Denny Lee
2
O. Gindele