web-dev-qa-db-fra.com

Comment enregistrer une étincelle DataFrame en tant que csv sur disque?

Par exemple, le résultat de ceci:

df.filter("project = 'en'").select("title","count").groupBy("title").sum()

retournerait un tableau.

Comment enregistrer une DataFrame spark en fichier csv sur disque?

8
Hello lad

Apache Spark ne prend pas en charge la sortie CSV native sur le disque. 

Vous avez quatre solutions disponibles:

  1. Vous pouvez convertir votre Dataframe en un RDD:

    def convertToReadableString(r : Row) = ???
    df.rdd.map{ convertToReadableString }.saveAsTextFile(filepath)
    

    Cela va créer un chemin de fichier de dossier. Sous le chemin du fichier, vous trouverez des fichiers de partitions (par exemple, partie-000 *).

    Ce que je fais habituellement si je veux ajouter toutes les partitions dans un gros fichier CSV, c’est 

    cat filePath/part* > mycsvfile.csv
    

    Certains utiliseront coalesce(1,false) pour créer une partition à partir du RDD. C’est généralement une mauvaise pratique, car elle peut submerger le conducteur en lui apportant toutes les données que vous collectez.

    Notez que df.rdd renverra un RDD[Row].

  2. Avec Spark <2, vous pouvez utiliser databricks spark-csv bibliothèque :

    • Spark 1.4+:

      df.write.format("com.databricks.spark.csv").save(filepath)
      
    • Spark 1.3:

      df.save(filepath,"com.databricks.spark.csv")
      
  3. Avec Spark 2.x, le package spark-csv n'est pas nécessaire car il est inclus dans Spark.

    df.write.format("csv").save(filepath)
    
  4. Vous pouvez convertir en trames de données Pandas locales et utiliser la méthode to_csv (PySpark uniquement).

Remarque: Les solutions 1, 2 et 3 génèrent des fichiers au format CSV (part-*) générés par l'API Hadoop sous-jacente que Spark appelle lorsque vous appelez save. Vous aurez un fichier part- par partition.

18
eliasah

J'ai eu le même problème où je devais enregistrer le contenu de la structure de données dans un fichier csv de nom que j'ai défini. df.write("csv").save("<my-path>") créait un répertoire que le fichier. Nous devons donc proposer les solutions suivantes. La majeure partie du code provient de la suivante dataframe-to-csv avec peu de modifications de la logique. 

def saveDfToCsv(df: DataFrame, tsvOutput: String, sep: String = ",", header: Boolean = false): Unit = {
    val tmpParquetDir = "Posts.tmp.parquet"

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

    val dir = new File(tmpParquetDir)
    val newFileRgex = tmpParquetDir + File.separatorChar + ".part-00000.*.csv"
    val tmpTsfFile = dir.listFiles.filter(_.toPath.toString.matches(newFileRgex))(0).toString
    (new File(tmpTsvFile)).renameTo(new File(tsvOutput))

    dir.listFiles.foreach( f => f.delete )
    dir.delete
    }
1
Jai Prakash

J'ai eu le même problème. J'avais besoin d'écrire un fichier csv sur le pilote alors que je me connectais au cluster en mode client.

Je voulais réutiliser le même code d'analyse CSV qu'Apache Spark pour éviter les erreurs potentielles.

J'ai vérifié le code spark-csv et ai trouvé le code responsable de la conversion de dataframe en raw csv RDD[String] dans com.databricks.spark.csv.CsvSchemaRDD.

Malheureusement, il est codé en dur avec sc.textFile et la fin de la méthode correspondante.

J'ai copié-collé ce code, supprimé les dernières lignes avec sc.textFile et renvoyé directement RDD à la place.

Mon code:

/*
  This is copypasta from com.databricks.spark.csv.CsvSchemaRDD
  Spark's code has perfect method converting Dataframe -> raw csv RDD[String]
  But in last lines of that method it's hardcoded against writing as text file -
  for our case we need RDD.
 */
object DataframeToRawCsvRDD {

  val defaultCsvFormat = com.databricks.spark.csv.defaultCsvFormat

  def apply(dataFrame: DataFrame, parameters: Map[String, String] = Map())
           (implicit ctx: ExecutionContext): RDD[String] = {
    val delimiter = parameters.getOrElse("delimiter", ",")
    val delimiterChar = if (delimiter.length == 1) {
      delimiter.charAt(0)
    } else {
      throw new Exception("Delimiter cannot be more than one character.")
    }

    val escape = parameters.getOrElse("escape", null)
    val escapeChar: Character = if (escape == null) {
      null
    } else if (escape.length == 1) {
      escape.charAt(0)
    } else {
      throw new Exception("Escape character cannot be more than one character.")
    }

    val quote = parameters.getOrElse("quote", "\"")
    val quoteChar: Character = if (quote == null) {
      null
    } else if (quote.length == 1) {
      quote.charAt(0)
    } else {
      throw new Exception("Quotation cannot be more than one character.")
    }

    val quoteModeString = parameters.getOrElse("quoteMode", "MINIMAL")
    val quoteMode: QuoteMode = if (quoteModeString == null) {
      null
    } else {
      QuoteMode.valueOf(quoteModeString.toUpperCase)
    }

    val nullValue = parameters.getOrElse("nullValue", "null")

    val csvFormat = defaultCsvFormat
      .withDelimiter(delimiterChar)
      .withQuote(quoteChar)
      .withEscape(escapeChar)
      .withQuoteMode(quoteMode)
      .withSkipHeaderRecord(false)
      .withNullString(nullValue)

    val generateHeader = parameters.getOrElse("header", "false").toBoolean
    val headerRdd = if (generateHeader) {
      ctx.sparkContext.parallelize(Seq(
        csvFormat.format(dataFrame.columns.map(_.asInstanceOf[AnyRef]): _*)
      ))
    } else {
      ctx.sparkContext.emptyRDD[String]
    }

    val rowsRdd = dataFrame.rdd.map(row => {
      csvFormat.format(row.toSeq.map(_.asInstanceOf[AnyRef]): _*)
    })

    headerRdd union rowsRdd
  }

}
0
Ajk