私はhttps://github.com/databricks/spark-csvを使用しています。単一のCSVを書き込もうとしていますが、できません。フォルダを作成しています。
パスやファイル名などのパラメーターを取り、そのCSVファイルを書き込むScala関数が必要です。
私はhttps://github.com/databricks/spark-csvを使用しています。単一のCSVを書き込もうとしていますが、できません。フォルダを作成しています。
パスやファイル名などのパラメーターを取り、そのCSVファイルを書き込むScala関数が必要です。
回答:
各パーティションは個別に保存されるため、複数のファイルを含むフォルダーを作成しています。単一の出力ファイル(まだフォルダー内にある)repartition
が必要な場合、次のことができます(上流のデータが大きいが、シャッフルが必要な場合に推奨されます)。
df
.repartition(1)
.write.format("com.databricks.spark.csv")
.option("header", "true")
.save("mydata.csv")
またはcoalesce
:
df
.coalesce(1)
.write.format("com.databricks.spark.csv")
.option("header", "true")
.save("mydata.csv")
保存前のデータフレーム:
すべてのデータがに書き込まれmydata.csv/part-00000
ます。このオプションを使用する前に、何が起こっているのか、およびすべてのデータを1人のワーカーに転送する場合のコストを理解しておいてください。レプリケーションで分散ファイルシステムを使用する場合、データは複数回転送されます。最初に単一のワーカーにフェッチされ、その後ストレージノードに分散されます。
または、コードをそのままにして、cat
またはHDFSgetmerge
などの汎用ツールを使用して、後ですべての部分をマージすることもできます。
coalesce(1)
非常に高価であり、通常は実用的ではないという単純な結果です。
HDFSでSparkを実行している場合、私はcsvファイルを通常どおりに書き込み、HDFSを利用してマージを行うことで問題を解決してきました。私はSpark(1.6)で直接それをやっています:
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), true, hadoopConfig, null)
// the "true" setting deletes the source files once they are merged into the new output
}
val newData = << create your dataframe >>
val outputfile = "/user/feeds/project/outputs/subject"
var filename = "myinsights"
var outputFileName = outputfile + "/temp_" + filename
var mergedFileName = outputfile + "/merged_" + filename
var mergeFindGlob = outputFileName
newData.write
.format("com.databricks.spark.csv")
.option("header", "false")
.mode("overwrite")
.save(outputFileName)
merge(mergeFindGlob, mergedFileName )
newData.unpersist()
どこでこのトリックを学んだか思い出せませんが、あなたのために役立つかもしれません。
私はここでのゲームに少し遅れるかもしれませんが、小さなデータセットを使用しcoalesce(1)
たりrepartition(1)
機能したりする可能性がありますが、大きなデータセットはすべて1つのノードの1つのパーティションにスローされます。これは、OOMエラーをスローするか、遅くとも処理が遅くなる可能性があります。
FileUtil.copyMerge()
Hadoop APIの関数を使用することを強くお勧めします。これにより、出力が1つのファイルにマージされます。
編集 -これは事実上、エグゼキューターノードではなくドライバーにデータをもたらします。Coalesce()
単一のエグゼキューターがドライバーよりも多くのRAMを使用する場合は問題ありません。
編集2:copyMerge()
Hadoop 3.0で削除されます。最新バージョンでの作業方法の詳細については、次のスタックオーバーフローの記事を参照してください。Hadoop 3.0でCopyMergeを行う方法は?
Databricksを使用していて、すべてのデータを1つのワーカーのRAMに収めることができる(したがってを使用できる.coalesce(1)
)場合は、dbfsを使用して、結果のCSVファイルを見つけて移動できます。
val fileprefix= "/mnt/aws/path/file-prefix"
dataset
.coalesce(1)
.write
//.mode("overwrite") // I usually don't use this, but you may want to.
.option("header", "true")
.option("delimiter","\t")
.csv(fileprefix+".tmp")
val partition_path = dbutils.fs.ls(fileprefix+".tmp/")
.filter(file=>file.name.endsWith(".csv"))(0).path
dbutils.fs.cp(partition_path,fileprefix+".tab")
dbutils.fs.rm(fileprefix+".tmp",recurse=true)
ファイルがワーカーのRAMに収まらない場合は、FileUtils.copyMerge()を使用するchaotic3quilibriumの提案を検討する ことをお勧めします。私はこれを行っていないので、S3などで可能かどうかはまだわかりません。
この回答は、この質問に対する以前の回答と、提供されたコードスニペットの私自身のテストに基づいています。私はもともとそれをDatabricksに投稿し、ここで再公開しています。
私が見つけたdbfsのrmの再帰オプションに関する最良のドキュメントは、Databricksフォーラムにあります。
Minkymorganから変更されたS3で機能するソリューション。
元のディレクトリを削除する場合 も、一時パーティションディレクトリパス(最終パスとは異なる名前のパス)srcPath
をdestPath
指定し、単一の最終csv / txtを[ 指定] として渡すだけdeleteSource
です。
/**
* Merges multiple partitions of spark text file output into single file.
* @param srcPath source directory of partitioned files
* @param dstPath output path of individual path
* @param deleteSource whether or not to delete source directory after merging
* @param spark sparkSession
*/
def mergeTextFiles(srcPath: String, dstPath: String, deleteSource: Boolean): Unit = {
import org.apache.hadoop.fs.FileUtil
import java.net.URI
val config = spark.sparkContext.hadoopConfiguration
val fs: FileSystem = FileSystem.get(new URI(srcPath), config)
FileUtil.copyMerge(
fs, new Path(srcPath), fs, new Path(dstPath), deleteSource, config, null
)
}
スパークのdf.write()
APIは、指定されたパス内の複数の部品ファイルを作成します...唯一の単一部品ファイルの使用スパーク書き込みを強制するdf.coalesce(1).write.csv(...)
代わりに、df.repartition(1).write.csv(...)
再分割が広い変態参照のことであるのに対し、合体としては狭い変換で配分()COALESCE対() -スパーク
df.coalesce(1).write.csv(filepath,header=True)
1つのpart-0001-...-c000.csv
ファイルを使用して、指定されたファイルパスにフォルダーを作成します
cat filepath/part-0001-...-c000.csv > filename_you_want.csv
ユーザーフレンドリーなファイル名にする
df.toPandas().to_csv(path)
これを使用して、単一のcsvを優先ファイル名で書き込むこともできます
保存する前に1つのパーティションにrepartition / coalesce(フォルダを取得しますが、その中に1つのパートファイルが含まれます)
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs._
import org.apache.spark.sql.{DataFrame,SaveMode,SparkSession}
import org.apache.spark.sql.functions._
私は以下のアプローチを使用して解決しました(hdfsはファイル名を変更します):-
手順1:-(データフレームを作成してHDFSに書き込む)
df.coalesce(1).write.format("csv").option("header", "false").mode(SaveMode.Overwrite).save("/hdfsfolder/blah/")
ステップ2:-(Hadoop構成の作成)
val hadoopConfig = new Configuration()
val hdfs = FileSystem.get(hadoopConfig)
Step3:-(hdfsフォルダーパスでパスを取得)
val pathFiles = new Path("/hdfsfolder/blah/")
Step4:-(hdfsフォルダーからSparkファイル名を取得)
val fileNames = hdfs.listFiles(pathFiles, false)
println(fileNames)
setp5:-(すべてのファイル名を保存してリストに追加するためのscala可変リストを作成します)
var fileNamesList = scala.collection.mutable.MutableList[String]()
while (fileNames.hasNext) {
fileNamesList += fileNames.next().getPath.getName
}
println(fileNamesList)
ステップ6:-(ファイル名scalaリストから_SUCESSファイルの順序をフィルターします)
// get files name which are not _SUCCESS
val partFileName = fileNamesList.filterNot(filenames => filenames == "_SUCCESS")
ステップ7:-(スカラリストを文字列に変換し、目的のファイル名をhdfsフォルダ文字列に追加してから、名前の変更を適用します)
val partFileSourcePath = new Path("/yourhdfsfolder/"+ partFileName.mkString(""))
val desiredCsvTargetPath = new Path(/yourhdfsfolder/+ "op_"+ ".csv")
hdfs.rename(partFileSourcePath , desiredCsvTargetPath)
私はこれをPythonで使用して単一のファイルを取得しています:
df.toPandas().to_csv("/tmp/my.csv", sep=',', header=True, index=False)
この回答は、受け入れられた回答を拡張し、より多くのコンテキストを提供し、マシンのSpark Shellで実行できるコードスニペットを提供します。
受け入れられた回答に関するより多くのコンテキスト
受け入れられた答えは、サンプルコードが単一のmydata.csv
ファイルを出力するという印象を与えるかもしれませんが、そうではありません。デモしてみましょう:
val df = Seq("one", "two", "three").toDF("num")
df
.repartition(1)
.write.csv(sys.env("HOME")+ "/Documents/tmp/mydata.csv")
出力されるものは次のとおりです。
Documents/
tmp/
mydata.csv/
_SUCCESS
part-00000-b3700504-e58b-4552-880b-e7b52c60157e-c000.csv
NB mydata.csv
は承認された回答のフォルダです-ファイルではありません!
特定の名前の単一ファイルを出力する方法
spark-dariaを使用して、単一のmydata.csv
ファイルを書き出すことができます。
import com.github.mrpowers.spark.daria.sql.DariaWriters
DariaWriters.writeSingleFile(
df = df,
format = "csv",
sc = spark.sparkContext,
tmpFolder = sys.env("HOME") + "/Documents/better/staging",
filename = sys.env("HOME") + "/Documents/better/mydata.csv"
)
これにより、ファイルは次のように出力されます。
Documents/
better/
mydata.csv
S3パス
DariaWriters.writeSingleFile
S3でこのメソッドを使用するには、s3aパスを渡す必要があります。
DariaWriters.writeSingleFile(
df = df,
format = "csv",
sc = spark.sparkContext,
tmpFolder = "s3a://bucket/data/src",
filename = "s3a://bucket/data/dest/my_cool_file.csv"
)
詳細はこちらをご覧ください。
copyMergeの回避
copyMergeはHadoop 3から削除されました。ここで説明するように、DariaWriters.writeSingleFile
実装ではを使用します。 Spark 3は引き続きHadoop 2を使用していたため、copyMergeの実装は2020年に機能します。SparkがHadoop 3にアップグレードされる時期はわかりませんが、SparkがHadoopをアップグレードするときにコードが破損するcopyMergeアプローチは避けた方がよいでしょう。fs.rename
ソースコード
DariaWriters
実装を検査したい場合は、spark-dariaソースコードでオブジェクトを探します。
PySparkの実装
PySparkを使用して単一のファイルを書き出す方が簡単です。デフォルトで単一のファイルとして書き出されるPandas DataFrameにDataFrameを変換できるためです。
from pathlib import Path
home = str(Path.home())
data = [
("jellyfish", "JALYF"),
("li", "L"),
("luisa", "LAS"),
(None, None)
]
df = spark.createDataFrame(data, ["word", "expected"])
df.toPandas().to_csv(home + "/Documents/tmp/mydata-from-pyspark.csv", sep=',', header=True, index=False)
制限事項
DariaWriters.writeSingleFile
Scalaのアプローチとdf.toPandas()
、小さなデータセットのためのPythonのアプローチだけの仕事。巨大なデータセットを単一のファイルとして書き出すことはできません。データを並行して書き込むことができないため、データを単一のファイルとして書き込むことは、パフォーマンスの観点から最適ではありません。
Listbufferを使用すると、データを単一のファイルに保存できます。
import java.io.FileWriter
import org.apache.spark.sql.SparkSession
import scala.collection.mutable.ListBuffer
val text = spark.read.textFile("filepath")
var data = ListBuffer[String]()
for(line:String <- text.collect()){
data += line
}
val writer = new FileWriter("filepath")
data.foreach(line => writer.write(line.toString+"\n"))
writer.close()
Javaを使用するもう1つの方法があります。
import java.io._
def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit)
{
val p = new java.io.PrintWriter(f);
try { op(p) }
finally { p.close() }
}
printToFile(new File("C:/TEMP/df.csv")) { p => df.collect().foreach(p.println)}