web-dev-qa-db-fra.com

Fusionnez les fichiers CSV de sortie Spark avec un seul en-tête

Je souhaite créer un pipeline de traitement de données dans AWS pour éventuellement utiliser les données traitées pour Machine Learning.

J'ai un script Scala qui prend des données brutes de S3, les traite et les écrit sur HDFS ou même sur S3 avec Spark-CSV. Je pense que je peux utiliser plusieurs fichiers comme entrée si je souhaite utiliser l'outil AWS Machine Learning pour la formation d'un modèle de prévision. Mais si je veux utiliser autre chose, je suppose qu'il est préférable que je reçoive un seul fichier de sortie CSV.

Actuellement, comme je ne souhaite pas utiliser repartition (1) ni coalesce (1) à des fins de performances, j’ai utilisé hadoop fs -getmerge pour les tests manuels, mais il ne fait que fusionner le contenu des fichiers de sortie du travail, je rencontre un petit problème. J'ai besoin de une seule rangée d'en-têtes dans le fichier de données pour former le modèle de prédiction.

Si j'utilise .option("header","true") pour spark-csv, les en-têtes sont écrits dans chaque fichier de sortie et, après la fusion, j'ai autant de lignes d'en-tête dans les données qu'il y a de fichiers de sortie. Mais si l'option d'en-tête est false, elle n'ajoute aucun en-tête.

Maintenant, j'ai trouvé une option pour fusionner les fichiers dans le script Scala avec l'API Hadoop FileUtil.copyMerge . J'ai essayé ceci dans spark-Shell avec le code ci-dessous. 

import org.Apache.hadoop.fs.FileUtil
import org.Apache.hadoop.fs.FileSystem;
import org.Apache.hadoop.conf.Configuration;
import org.Apache.hadoop.fs.Path;
val configuration = new Configuration();
val fs = FileSystem.get(configuration);
FileUtil.copyMerge(fs, new Path("smallheaders"), fs, new Path("/home/hadoop/smallheaders2"), false, configuration, "")

Mais cette solution ne fait que concaténer les fichiers les uns sur les autres et ne gère pas les en-têtes. Comment puis-je obtenir un fichier de sortie avec une seule ligne d'en-têtes?

J'ai même essayé d'ajouter df.columns.mkString(",") comme dernier argument de copyMerge, mais cela a ajouté les en-têtes toujours plusieurs fois, pas une fois.

19
V. Samma

vous pouvez marcher comme ça.

  • 1.Créez un nouveau DataFrame (headerDF) contenant les noms d'en-tête.
  • 2.Union avec le DataFrame (dataDF) contenant les données. 
  • 3.Outputez l'unité DataFrame d'union sur le disque avec l'option ("en-tête", "faux") .
  • Fichiers de partition 4.merge (part-0000 ** 0.csv) en utilisant hadoop FileUtil

De cette manière, toutes les partitions n’ont pas d’en-tête, à la différence que le contenu d’une partition unique contient une rangée de noms d’en-tête du headerDF. Lorsque toutes les partitions sont fusionnées, un en-tête unique apparaît en haut du fichier. Les exemples de code sont les suivants

  //dataFrame is the data to save on disk
  //cast types of all columns to String
  val dataDF = dataFrame.select(dataFrame.columns.map(c => dataFrame.col(c).cast("string")): _*)

  //create a new data frame containing only header names
  import scala.collection.JavaConverters._
  val headerDF = sparkSession.createDataFrame(List(Row.fromSeq(dataDF.columns.toSeq)).asJava, dataDF.schema)

  //merge header names with data
  headerDF.union(dataDF).write.mode(SaveMode.Overwrite).option("header", "false").csv(outputFolder)

  //use hadoop FileUtil to merge all partition csv files into a single file
  val fs = FileSystem.get(sparkSession.sparkContext.hadoopConfiguration)
  FileUtil.copyMerge(fs, new Path(outputFolder), fs, new Path("/folder/target.csv"), true, spark.sparkContext.hadoopConfiguration, null)
3
Kang
  1. Générez l'en-tête à l'aide de dataframe.schema (Val header = dataDF.schema.fieldNames.reduce (_ + "," + _))
  2. créer un fichier avec l'en-tête sur dsefs
  3. ajoute tous les fichiers de partition (sans en-tête) au fichier de n ° 2 en utilisant l'API hadoop Filesystem
1
Sam Jacob

Essayez de spécifier le schéma de l'en-tête et de lire tous les fichiers du dossier en utilisant l'option drop malformée de spark-csv. Cela devrait vous permettre de lire tous les fichiers du dossier en ne conservant que les en-têtes (car vous déposez le fichier mal formé) ..__

val headerSchema = List(
  StructField("example1", StringType, true),
  StructField("example2", StringType, true),
  StructField("example3", StringType, true)
)

val header_DF =sqlCtx.read
  .option("delimiter", ",")
  .option("header", "false")
  .option("mode","DROPMALFORMED")
  .option("inferSchema","false")
  .schema(StructType(headerSchema))
  .format("com.databricks.spark.csv")
  .load("folder containg the files")

Dans header_DF, vous n’avez que les lignes des en-têtes; vous pouvez ainsi transformer le cadre de données à votre guise.

0
eugenio calabrese

Pour fusionner des fichiers d’un dossier dans un seul fichier:

import org.Apache.hadoop.conf.Configuration
import org.Apache.hadoop.fs._

def merge(srcPath: String, dstPath: String): Unit =  {
  val hadoopConfig = new Configuration()
  val hdfs = FileSystem.get(hadoopConfig)
  FileUtil.copyMerge(hdfs, new Path(srcPath), hdfs, new Path(dstPath), false, hadoopConfig, null)
}

Si vous souhaitez fusionner tous les fichiers dans un seul fichier, mais toujours dans le même dossier ( mais cela amène toutes les données sur le nœud du pilote):

dataFrame
      .coalesce(1)
      .write
      .format("com.databricks.spark.csv")
      .option("header", "true")
      .save(out)

Une autre solution consisterait à utiliser la solution n ° 2, puis à déplacer le fichier dans le dossier vers un autre chemin (avec le nom de notre fichier CSV).

def df2csv(df: DataFrame, fileName: String, sep: String = ",", header: Boolean = false): Unit = {
    val tmpDir = "tmpDir"

    df.repartition(1)
      .write
      .format("com.databricks.spark.csv")
      .option("header", header.toString)
      .option("delimiter", sep)
      .save(tmpDir)

    val dir = new File(tmpDir)
    val tmpCsvFile = tmpDir + File.separatorChar + "part-00000"
    (new File(tmpCsvFile)).renameTo(new File(fileName))

    dir.listFiles.foreach( f => f.delete )
    dir.delete
}
0
belka