web-dev-qa-db-de.com

Spark-CSV-Ausgabedateien mit einem einzigen Header zusammenführen

Ich möchte eine Datenverarbeitungspipeline in AWS erstellen, um die verarbeiteten Daten schließlich für Machine Learning zu verwenden.

Ich habe ein Scala-Skript, das Rohdaten aus S3 entnimmt, verarbeitet und mit Spark-CSV in HDFS oder sogar S3 schreibt. Ich denke, ich kann mehrere Dateien als Eingabe verwenden, wenn ich das AWS Machine Learning tool zum Trainieren eines Vorhersagemodells verwenden möchte. Wenn ich jedoch etwas anderes verwenden möchte, nehme ich an, dass es am besten ist, wenn ich eine einzelne CSV-Ausgabedatei erhalte.

Da ich reppartition (1) nor coalesce (1) zu Performancezwecken nicht verwenden möchte, habe ich derzeit hadoop fs -getmerge für manuelle Tests verwendet Da der Inhalt der Jobausgabedateien jedoch nur zusammengeführt wird, stößt ich auf ein kleines Problem. Ich brauche eine einzelne Kopfzeile in der Datendatei, um das Vorhersagemodell zu trainieren.

Wenn ich .option("header","true") für das spark-csv verwende, schreibt es die Header in jede Ausgabedatei, und nach dem Zusammenführen habe ich so viele Kopfzeilen in den Daten, wie Ausgabedateien vorhanden waren. Wenn die Header-Option jedoch "false" ist, werden keine Header hinzugefügt.

Jetzt habe ich eine Option gefunden, um die Dateien innerhalb des Scala-Skripts mit der Hadoop-API FileUtil.copyMerge zusammenzuführen. Ich habe dies in spark-Shell mit dem Code unten versucht. 

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

Diese Lösung verkettet die Dateien jedoch nur übereinander und verarbeitet keine Header. Wie bekomme ich eine Ausgabedatei mit nur einer Kopfzeile?

Ich habe sogar versucht, df.columns.mkString(",") als letztes Argument für copyMerge hinzuzufügen, aber dies fügte die Header noch mehrmals hinzu, nicht einmal.

19
V. Samma

sie können so herumlaufen.

  • 1.Erstellen Sie einen neuen DataFrame (headerDF) mit Headernamen.
  • 2.Nutzen Sie es mit dem DataFrame (dataDF), der die Daten enthält. 
  • 3.Ausgeben des vereinigten DataFrame mit der Option ("header", "false") auf die Festplatte.
  • 4.Menge Partitionsdateien (Part-0000 ** 0.csv) mit hadoop FileUtil

Auf diese Weise haben alle Partitionen keinen Header, außer dass der Inhalt einer einzelnen Partition eine Zeile mit Headernamen aus der headerDF hat. Wenn alle Partitionen zusammengefügt werden, befindet sich oben in der Datei ein einzelner Header. Beispielcode ist der folgende

  //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. Ausgabe des Headers mit dataframe.schema (Val header = dataDF.schema.fieldNames.reduce (_ + "," + _))
  2. erstellen Sie eine Datei mit dem Header auf Dsefs
  3. hängen Sie alle Partitionsdateien (ohne Header) an die Datei in # 2 mithilfe der hadoop Filesystem API an
1
Sam Jacob

Versuchen Sie, das Schema des Headers anzugeben, und lesen Sie die gesamte Datei aus dem Ordner. Verwenden Sie dazu die Option drop von Spark-csv. Das sollte Ihnen ermöglichen, alle Dateien in dem Ordner zu lesen, die nur die Kopfzeilen enthalten (weil Sie die fehlerhaften Dateien ablegen).

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

In header_DF haben Sie nur die Zeilen der Header, von denen Sie den Datenrahmen nach Ihren Wünschen umwandeln können.

0

So führen Sie Dateien in einem Ordner in einer Datei zusammen:

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

Wenn Sie alle Dateien in einer Datei zusammenführen möchten, aber immer noch im selben Ordner ( aber - dies bringt alle Daten zum Treiberknoten):

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

Eine andere Lösung wäre, Lösung Nr. 2 zu verwenden und dann die eine Datei im Ordner in einen anderen Pfad zu verschieben (mit dem Namen unserer CSV-Datei).

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