web-dev-qa-db-ja.com

マージSpark単一ヘッダーのCSVファイルを出力

AWSでデータ処理パイプラインを作成して、最終的には処理されたデータを機械学習に使用したいと考えています。

私はScalaスクリプトを使用してS3から生データを取得し、それを処理してHDFSまたはS3にSpark-CSVで書き込みます。複数のファイルを使用できると思いますAWS Machine Learning予測モデルをトレーニングするためのツールを使用する場合は入力として使用しますが、何か他のものを使用する場合は、単一のCSV出力ファイルを受信するのが最適だと思います。

現在、私はrepartition(1)coalesce(1)もパフォーマンスの目的で使用したくないので、hadoop fs -getmergeを使用しています。手動テスト用ですが、ジョブ出力ファイルの内容をマージするだけなので、小さな問題が発生しています。予測モデルをトレーニングするために、データファイルにヘッダーの1行が必要です。

Spark-csvに.option("header","true")を使用すると、すべての出力ファイルにヘッダーが書き込まれ、マージした後、出力ファイルと同じ数のヘッダー行がデータに含まれます。ただし、ヘッダーオプションがfalseの場合、ヘッダーは追加されません。

ScalaスクリプトとHadoop API _FileUtil.copyMerge_ )内のファイルをマージするオプションを見つけました。これを_spark-Shell_でコードで試しました未満。

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

ただし、このソリューションでは、ファイルを互いに連結するだけで、ヘッダーは処理しません。 ヘッダーが1行だけの出力ファイルを取得するにはどうすればよいですか?

copyMergeの最後の引数としてdf.columns.mkString(",")を追加してみましたが、これによりヘッダーが1回ではなく複数回追加されました。

24
V. Samma

このように歩き回ることができます。

  • 1.ヘッダー名を含む新しいDataFrame(headerDF)を作成します。
  • 2.データを含むDataFrame(dataDF)と結合します。
  • 3. union-ed DataFrameをoption( "header"、 "false")でディスクに出力します。
  • 4. hadoop FileUtilを使用してパーティションファイル(part-0000 ** 0.csv)をマージする

この方法では、単一のパーティションのコンテンツにheaderDFからのヘッダー名の行が含まれる場合を除いて、すべてのパーティションにヘッダーはありません。すべてのパーティションがマージされると、ファイルの上部に単一のヘッダーがあります。サンプルコードは次のとおりです

  //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. Dataframe.schemaを使用してヘッダーを出力します(val header = dataDF.schema.fieldNames.reduce(_ + "、" + _))
  2. dsefsにヘッダーを含むファイルを作成する
  3. hadoop Filesystem APIを使用して#2のファイルにすべてのパーティションファイル(ヘッダーなし)を追加します
1
Sam Jacob

ヘッダーのスキーマを指定し、spark-csvの不正な形式のオプションドロップを使用してフォルダーからすべてのファイルを読み取ってください。これにより、ヘッダーのみを保持してフォルダー内のすべてのファイルを読み取ることができます(不正な形式をドロップしたため)。例:

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

Header_DFにはヘッダーの行のみが含まれるため、必要な方法でデータフレームを変換できます。

0

フォルダ内のファイルを1つのファイルにマージするには:

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

すべてのファイルを1つのファイルにマージしたいが、同じフォルダー内にある場合(butこれにより、すべてのデータがドライバーノードに送られます):

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

別のソリューションは、ソリューション#2を使用してから、フォルダー内の1つのファイルを別のパス(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