如何在磁盘上将火花DataFrame保存为csv?

时间:2015-10-16 15:39:36

标签: scala apache-spark apache-spark-sql

例如,结果如下:

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

将返回一个数组。

如何将火花DataFrame保存为磁盘上的csv文件?

4 个答案:

答案 0 :(得分:20)

Apache Spark不支持磁盘上的本机CSV输出。

您有四种可用的解决方案:

  1. 您可以将Dataframe转换为RDD:

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

    这将创建一个文件夹文件路径。在文件路径下,您将找到分区文件(例如part-000 *)

    如果我想将所有分区附加到一个大的CSV中,我通常会这样做

    cat filePath/part* > mycsvfile.csv
    

    有些人会使用 coalesce(1,false) 从RDD创建一个分区。它通常是一种糟糕的做法,因为它可能会把你收集的所有数据都拉到它上面而压倒了驱动程序。

    请注意,df.rdd将返回RDD[Row]

  2. 使用 Spark< 2 ,您可以使用databricks spark-csv library

    • Spark 1.4 +:

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

      df.save(filepath,"com.databricks.spark.csv")
      
  3. 使用 Spark 2.x ,不需要spark-csv包,因为它已包含在Spark中。

    df.write.format("csv").save(filepath)
    
  4. 您可以转换为本地Pandas数据框并使用to_csv方法(仅限PySpark)。

  5. 注意:解决方案1,2和3将导致由您调用part-*时Spark调用的基础Hadoop API生成的CSV格式文件(save)。每个分区将有一个part-个文件。

答案 1 :(得分:1)

我有类似的问题,我必须将数据帧的内容保存到我定义的名称的csv文件。 df.write("csv").save("<my-path>")正在创建目录而不是文件。所以必须提出以下解决方案。 大多数代码取自以下dataframe-to-csv,对逻辑几乎没有修改。

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
    }

答案 2 :(得分:1)

将数据帧作为cv写入磁盘类似于从csv读取。如果希望将结果作为一个文件,则可以使用合并。

df.coalesce(1)
      .write
      .option("header","true")
      .option("sep",",")
      .mode("overwrite")
      .csv("output/path")

如果结果是数组,则应使用特定于语言的解决方案,而不是spark dataframe api。因为所有这些结果都会返回驱动程序机器。

答案 3 :(得分:0)

我有类似的问题。当我在客户端模式下连接到集群时,我需要在驱动程序上写下csv文件。

我想重用与Apache Spark相同的CSV解析代码,以避免潜在的错误。

我检查了spark-csv代码并找到了负责将数据框转换为RDD[String]中的原始csv com.databricks.spark.csv.CsvSchemaRDD的代码。

可悲的是,它是sc.textFile的硬编码和相关方法的结束。

我复制粘贴了该代码并删除了sc.textFile的最后一行并直接返回了RDD。

我的代码:

/*
  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
  }

}