write.csv() はデータフレームを書き出す
データフレームを R から取り出し、他のプログラムでも開けるファイルにするのは 1 行で済みます。
write.csv(df, "results.csv", row.names = FALSE)
これが覚えるべき 1 行です。row.names = FALSE も含めて覚えてください(理由はすぐ説明します)。第 1 引数はデータフレーム、第 2 引数はファイル名で、この関数は何も返しません。仕事はファイルを作るという副作用がすべてです。
write.csv() が何を生成するのかは、ディスクに触れずに確認できます。ファイル名を期待する場所に任意のコネクションを渡せるからです。stdout() を渡せば、そのまま画面に表示されます。
これが CSV です。ヘッダー行が 1 行、その後は 1 行につき 1 レコード、文字列の値は引用符で囲まれ、数値は裸のままです。(引用は既定の quote = TRUE によるものです。主要な CSV リーダーはどれも対応しているので、そのままにしておきましょう。)実際のファイルに書き出す場合も、stdout() を "results.csv" に置き換えるだけの同じ呼び出しです。
row.names = FALSE を既定にすべき理由
どのデータフレームも行名を持っています。多くの場合は単なる番号 1、2、3… です。そして write.csv() の既定動作は、その行名をヘッダーが空の余分な最初の列としてファイルに書き込むことです。既定の出力がどうなるか、そしてそのファイルが R に戻ってきたとき何が起きるかを見てみましょう。
書き出した結果には "1"、"2" を保持する名前のない最初の列が増えており、読み戻すときに read.csv() は何らかの名前を付ける必要があったため X を発明しました。これが初心者を必ず悩ませる謎の X 列の正体です。データではなく、R の行番号がファイルに漏れ出したものです。エクスポートとインポートのサイクルを何度か繰り返すと、X、X.1、X.2… と溜まっていきます。
行名が本当に意味のある情報を持っていることはほとんどありません。持っている場合でも、その情報は正式な列に入れるべきです。ですから row.names = FALSE は反射的に付ける習慣にしてください。本来これが既定であるべきでしたが、後方互換性の理由でそうなっていません。習慣づけはあなた次第です。
ファイルが保存される場所
write.csv(df, "results.csv") は R の 作業ディレクトリ に書き出します。そこが想定した場所でなければ、ファイルは消えたように見えます。失敗したのではなく、getwd() が表示するフォルダに置かれているだけです。これは読み込みのときの落とし穴を鏡写しにしたもので、解決する 2 つの道具も同じです。
getwd() # the folder your file went to
write.csv(df, "C:/Users/ada/results/out.csv", row.names = FALSE) # or: remove all doubt
フルパス(スラッシュは Windows を含むすべてのプラットフォームで使えます)を使えば、スクリプトは曖昧でなくなります。作業ディレクトリの設定と管理については作業ディレクトリで扱います。
他の区切り文字のための write.table() と追記の問題
write.csv() は実際には汎用の write.table() にカンマ用の設定を施しただけの薄いラッパーです。別の形式が必要なときは write.table() を直接呼びます。最も多いのはタブ区切りです。
タブ区切りで引用符なし。多くの下流ツールが好む形式です。write.table() ではすべてを明示的に指定する必要がある点に注意してください。write.csv のカンマや引用の既定値は引き継がれません。
write.csv() が唯一拒否するのが 追記 です。append = TRUE を渡すと設定が無視された旨の警告が出ます。追記するとヘッダー行がファイルの途中に書き込まれてしまうからです。既存の CSV に本当に行を追加したい場合(実行ごとに結果を記録するなど)は、write.table() に降りてヘッダーを自分で抑制します。
write.table(new_rows, "log.csv", sep = ",",
append = TRUE, col.names = FALSE, row.names = FALSE)
tidyverse を使っているなら、readr::write_csv(df, "out.csv") が read_csv() の対になる関数です。出力は同じで、大きなデータフレームでは高速、そしてそもそも行名を書き出しません。覚えるべき引数が 1 つ減ります。
saveRDS() と readRDS(): 完全な往復
CSV は非可逆な形式です。保存されるのはテキストだけです。Date 列は文字列 "2026-08-07" になり、因子はラベルだけになって水準の順序は失われ、属性は消え、すべての型は読み込み時に推測し直されます(たいていは正しく、ときどきは間違って。先頭のゼロ、あなたのことです)。さらに CSV は 1 つの矩形の表しか保持できません。当てはめたモデル、リスト、dimnames 付きの行列には CSV 表現がそもそも存在しません。
ファイルが R のため のもの(今日きれいにしたデータを明日のセッションのために保存する、など)であれば、テキスト形式は完全に飛ばしましょう。
saveRDS(df, "clean_data.rds") # today
df <- readRDS("clean_data.rds") # tomorrow: identical object, types intact
saveRDS() は 1 つの R オブジェクトを、どんなオブジェクトでも正確にシリアライズします。readRDS() はそれをそのまま返します。因子は水準を保ち、日付は日付のまま、何も推測し直されません。データフレーム、データフレームのリスト、当てはめた回帰モデル、どれにでも使えます。.rds ファイルは圧縮されたバイナリなので、通常は CSV より小さくなります。唯一の代償は R 以外では開けないことですが、まさにそれが経験則の理由です。人や他のツールと共有するなら CSV、自分のために保存するなら RDS。
save() と load()、そして RDS がたいてい勝つ理由
もう一つ、より古い組み合わせにも出会うでしょう。save() は 複数の名前付きオブジェクト を 1 つの .RData ファイルに書き込み、load() はそれらを元の名前のままワークスペースに復元します。
save(df, model, params, file = "session.RData")
load("session.RData") # df, model, params silently appear
その便利さこそが問題です。変数名を決めるのはあなたではなく load() です。中身が見えないままオブジェクトを環境に植え付け、同名のものがあれば黙って上書きします。半年後、session.RData に何が入っているのか、読み込んでみるまで誰も思い出せません。
readRDS() のインターフェースは正直です。名前を選ぶのは あなた で(df <- readRDS(...))、背後で何かが上書きされることはなく、1 ファイルは 1 オブジェクトを意味します。複数のオブジェクトを 1 ファイルにまとめる必要が特にない限り、RDS を選びましょう。(まとめる必要がある場合は、オブジェクトを名前付きリストに入れてそのリストを saveRDS() すれば、驚きなしで同じ効果が得られます。)
この章のまとめ
write.csv(df, "out.csv", row.names = FALSE)— エクスポートはこれで全部です。そして、はい、いつでもrow.names = FALSEを付けます。- 読み戻したときの謎の
X列は行名がファイルに漏れたものです。既定では書き込まれますが、あなたは書き込むべきではありません。 - ファイルは作業ディレクトリに保存されます。
getwd()で「行方不明」のエクスポートを見つけられ、フルパスを使えばそもそも迷いません。 write.table()は他の区切り文字を扱い、(col.names = FALSEと併せて)本当の追記もできます。write.csv()は意図的に追記できません。saveRDS()/readRDS()はどんな R オブジェクトも正確に往復させます。型・因子・属性はそのままです。共有には CSV、自分用には RDS。save()/load()より RDS を選びましょう。名前が明示的で、黙って上書きされることもありません。
次は、インポートで痛い目に遭う前に独立したページに値するデータ型 — 日付です。
よくある質問
R でデータフレームを CSV にエクスポートするにはどうしますか?
write.csv(df, "output.csv", row.names = FALSE) と書きます。row.names = FALSE は重要です。これがないと R は内部の行番号を余分な最初の列として書き出し、そのファイルを再度読み込んだときに X という謎の列として現れます。
エクスポートした CSV を読み戻すと X という列があるのはなぜですか?
write.csv の既定値が row.names = TRUE だからです。行番号が名前のない最初の列として書き出され、read.csv が読み込む際にその列に X という名前を付けます。row.names = FALSE を指定して書き出せば、この幽霊のような列は消えます。
R の write.csv と saveRDS の違いは何ですか?
write.csv は誰でも開けるプレーンテキストの表を作りますが、保存されるのはテキストだけです。因子の水準、Date 型、属性、表形式でないオブジェクトは平坦化されるか失われ、読み込み時に型を推測し直す必要があります。一方 saveRDS は 1 つの R オブジェクトをそのまま保存し、readRDS は元どおりに返します。他人と共有するなら CSV、自分の R セッション用に保存するなら RDS です。
write.csv はファイルをどこに保存しますか?
フルパスを指定しない限り、R の現在の作業ディレクトリに保存されます。getwd() を実行すればその場所が分かります。ファイルが消えたように見えるときは、getwd() が表示するフォルダの中にあります。"C:/Users/ada/results/out.csv" のようにフルパスを渡せば曖昧さはなくなります。