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回ではなく複数回追加されました。
このように歩き回ることができます。
この方法では、単一のパーティションのコンテンツに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)
ヘッダーのスキーマを指定し、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にはヘッダーの行のみが含まれるため、必要な方法でデータフレームを変換できます。
フォルダ内のファイルを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
}