私は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.csvNB 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.csvS3パス
DariaWriters.writeSingleFileS3でこのメソッドを使用するには、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.writeSingleFileScalaのアプローチと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)}