pandas to_csv()の使い方|indexなし・文字化け対策・保存先を解説

pandasで前処理や集計を終えたあと、「DataFrameをCSVに保存したいのに、ファイルがどこにあるか分からない」「開いたら左端に余計な列が増えた」「日本語が文字化けした」と困ることがあります。

DataFrameをCSVへ保存する基本形は、次の1行です。

df.to_csv("sales.csv", index=False)

ただし、正しく再利用できるCSVにするには、コードを書く前に次の順で決めることが大切です。

  1. どこへ保存するか
  2. インデックスを残すか
  3. どのアプリで開くかに応じて文字コードを選ぶ
  4. どの列を保存するか
  5. 同名ファイルを上書きしてよいか

この記事では、Google Colabで再現できる売上データを使い、保存されたCSVをもう一度読み込んで、行数・列名・日本語が正しいところまで確認します。

この記事でわかること

  • to_csv()でDataFrameをCSVへ保存する基本形
  • 保存先、indexencodingcolumnsの選び方
  • headersepで出力形式を変える方法
  • 余計なUnnamed: 0、文字化け、保存先不明、上書きの確認方法
  • read_csv()で読み込み直し、保存結果を検算する方法

to_csv()は、前処理・抽出・集計した結果を、共有や再利用のためにファイルへ書き出す処理です。データを整える前工程は、Pandasデータ前処理の基本で確認できます。

先に結論:保存先→index→文字コード→列→上書きの順に決める

確認すること 選ぶ基準 主な指定 注意点
保存先 Colab終了後も残すか 一時領域の相対パス/Google Driveの絶対パス Colabの一時領域はランタイム終了後に消える
インデックス 行ラベルに意味があるか 通常はindex=False 顧客IDなど意味のある値まで消さない
文字コード どのアプリで開くか UTF-8/utf-8-sig 受け渡し先の指定があれば従う
保存列 全列が必要か columns=[...] 列名の空白・表記違いに注意する
書き込み方法 同名ファイルを上書きしてよいか 通常保存/mode="x"mode="a" mode="a"は行の追記であり、値の更新ではない

最初は、実在する保存先へindex=Falseで保存し、読み込み直して確認するところから始めるのが安全です。

売上DataFrameを用意する

実行目的

同じデータを記事全体で使い、設定によってCSVの列や文字コードがどう変わるか比較します。商品IDは通常の列として保持し、DataFrameのインデックスは0から始まる連番です。

import pandas as pd
from pathlib import Path

# Colabの一時領域または現在の作業フォルダ内に保存用フォルダを作る
output_dir = Path("python_data_lab_output")
output_dir.mkdir(exist_ok=True)

df = pd.DataFrame({
    "商品ID": [101, 102, 103, 104],
    "商品": ["ノート", "ペン", "ファイル", "付箋"],
    "カテゴリ": ["文具", "文具", "収納", "文具"],
    "売上": [1200, 900, 1500, 600],
    "備考": ["重点商品", None, "在庫確認", "新商品"],
})

print(df)
print("行数・列数:", df.shape)
商品ID 商品 カテゴリ 売上 備考
0 101 ノート 文具 1200 重点商品
1 102 ペン 文具 900 None
2 103 ファイル 収納 1500 在庫確認
3 104 付箋 文具 600 新商品
行数・列数: (4, 5)

出力では4行5列なので、shape(4, 5)です。備考の2行目にあるNoneは欠損値ですが、今回は意味のある未記入として残します。保存のためだけに0や空文字へ置き換えません。

自分のデータでは、保存前に列名・データ型・欠損値をpandas info()の使い方で確認しておくと、保存後の変化を判断しやすくなります。

基本:indexなしでCSVへ保存する

実行目的

連番インデックスをCSVへ含めず、DataFrameの5列だけを保存します。ファイル名だけを指定した相対パスは、現在の作業場所を基準に解釈されます。ここでは作成済みのpython_data_lab_outputフォルダへ保存します。

basic_path = output_dir / "sales_basic.csv"
df.to_csv(basic_path, index=False)

print("保存先:", basic_path)
print("ファイルの存在:", basic_path.exists())
print("保存フォルダ内:", sorted(p.name for p in output_dir.iterdir()))
print("CSVの先頭3行:")
print("\n".join(basic_path.read_text(encoding="utf-8").splitlines()[:3]))
保存先: python_data_lab_output/sales_basic.csv
ファイルの存在: True
保存フォルダ内: ['sales_basic.csv']
CSVの先頭3行:
商品ID,商品,カテゴリ,売上,備考
101,ノート,文具,1200,重点商品
102,ペン,文具,900,

最初に見るのは、次の3点です。

  • ファイルの存在Trueになっている
  • 1行目が商品ID,商品,カテゴリ,売上,備考になっている
  • 左端に0、1のようなDataFrameの連番インデックスが追加されていない

to_csv()へ保存先を渡した場合、ファイルが作成されます。戻り値を新しいDataFrameとして使うメソッドではありません。

index=Trueとindex=Falseの違い

to_csv()では、indexの既定値がTrueです。指定を省略すると、DataFrameのインデックスもCSVへ出力されます。

指定 CSVの左端 選ぶ場面 注意点
index=Trueまたは省略 インデックスが入る 行ラベル自体に意味がある 名前のない連番は再読込時に余計な列になりやすい
index=False インデックスを入れない 0始まりの連番が単なる管理番号 意味のあるIDをインデックスにしている場合は失う
reset_index()後にindex=False 意味のあるインデックスを通常列として入れる IDを明示的な列名で残したい 列が重複しないか確認する
with_index_path = output_dir / "sales_with_index.csv"
without_index_path = output_dir / "sales_without_index.csv"

df.to_csv(with_index_path)  # index=Trueが既定値
df.to_csv(without_index_path, index=False)

with_index = pd.read_csv(with_index_path)
without_index = pd.read_csv(without_index_path)

print("indexを保存したCSVの列:", with_index.columns.tolist())
print("index=FalseのCSVの列:", without_index.columns.tolist())
print(with_index.head(2))
indexを保存したCSVの列: ['Unnamed: 0', '商品ID', '商品', 'カテゴリ', '売上', '備考']
index=FalseのCSVの列: ['商品ID', '商品', 'カテゴリ', '売上', '備考']
Unnamed: 0 商品ID 商品 カテゴリ 売上 備考
0 0 101 ノート 文具 1200 重点商品
1 1 102 ペン 文具 900 NaN

indexを省略したCSVを読み込むと、列一覧の先頭にUnnamed: 0が現れます。これは元のデータ列ではなく、列名のない連番インデックスが保存されたものです。

意味のあるインデックスは消さない

商品IDをインデックスにしたあとでindex=Falseにすると、CSVには商品IDが残りません。商品IDを再利用したい場合は、reset_index()で通常列へ戻してから保存します。

indexed_df = df.set_index("商品ID")

lost_id_path = output_dir / "sales_id_lost.csv"
kept_id_path = output_dir / "sales_id_kept.csv"

indexed_df.to_csv(lost_id_path, index=False)
indexed_df.reset_index().to_csv(kept_id_path, index=False)

print("index=FalseでIDを消した列:", pd.read_csv(lost_id_path).columns.tolist())
print("IDを通常列へ戻した列:", pd.read_csv(kept_id_path).columns.tolist())
index=FalseでIDを消した列: ['商品', 'カテゴリ', '売上', '備考']
IDを通常列へ戻した列: ['商品ID', '商品', 'カテゴリ', '売上', '備考']

注目するのは、前者には商品IDがなく、後者には残っている点です。index=Falseを機械的に付けるのではなく、現在のインデックスが単なる連番か、意味のある識別子かを先に確認してください。

日本語の文字化けを防ぐencodingの選び方

encodingは、CSVを利用する環境に合わせて選びます。

主な利用場面 候補 確認方法 注意点
Pythonや多くの現代的なツールで再利用 utf-8 同じencodingで読み込み直す アプリや読み込み方法によっては日本語表示を確認する
日本語をExcelで直接開いて確認 utf-8-sig 実際のExcel環境で開く Excelのバージョンや開き方でも挙動が異なる
受け渡し先から文字コード指定がある 指定された文字コード 相手先の手順で確認 環境を無視して固定しない

utf-8-sigはファイル先頭にBOMを付けたUTF-8です。日本語CSVをExcelで直接開く用途では有力な候補ですが、すべての用途で一律に選ぶ必要はありません。

utf8_path = output_dir / "sales_utf8.csv"
utf8_sig_path = output_dir / "sales_utf8_sig.csv"

df.to_csv(utf8_path, index=False, encoding="utf-8")
df.to_csv(utf8_sig_path, index=False, encoding="utf-8-sig")

print("UTF-8の先頭3バイト:", utf8_path.read_bytes()[:3])
print("UTF-8 with BOMの先頭3バイト:", utf8_sig_path.read_bytes()[:3])

checked_japanese = pd.read_csv(utf8_sig_path, encoding="utf-8-sig")
print("再読み込みした商品:", checked_japanese["商品"].tolist())
UTF-8の先頭3バイト: b'\xe5\x95\x86'
UTF-8 with BOMの先頭3バイト: b'\xef\xbb\xbf'
再読み込みした商品: ['ノート', 'ペン', 'ファイル', '付箋']

utf-8-sigのファイルでは、先頭3バイトがBOMを表すb'\xef\xbb\xbf'になります。再読み込み後の商品が「ノート」「ペン」「ファイル」「付箋」と表示されれば、指定した文字コードで日本語を読めています。

文字化けが起きたら、保存コードだけを見るのではなく、CSVをどのアプリで、どの文字コードとして開いたかも確認します。

必要な列だけcolumnsで保存する

実行目的

社内共有用として、商品カテゴリ売上だけを保存します。個人情報や作業用列を含むデータでは、保存前に共有範囲を確認することも重要です。

shared_columns = ["商品", "カテゴリ", "売上"]
shared_path = output_dir / "sales_shared.csv"

df.to_csv(
    shared_path,
    index=False,
    encoding="utf-8-sig",
    columns=shared_columns,
)

shared_df = pd.read_csv(shared_path, encoding="utf-8-sig")
print(shared_df)
print("保存した列:", shared_df.columns.tolist())
商品 カテゴリ 売上
0 ノート 文具 1200
1 ペン 文具 900
2 ファイル 収納 1500
3 付箋 文具 600
保存した列: ['商品', 'カテゴリ', '売上']

出力は4行3列です。商品ID備考が含まれず、列順もcolumnsで指定した順になっていることを確認します。

列名を間違えるとKeyErrorになります。自分のデータへ置き換える前に、df.columns.tolist()で実在する列名と前後の空白を確認してください。データ型を基準に列を選びたい場合は、pandas select_dtypes()の使い方へ進めます。

headerとsepは出力形式が必要なときだけ変更する

headerは列名を出力するか、sepは何の文字で列を区切るかを決めます。

  • 通常の単独CSVでは、列名を残す既定のheader=Trueを使う
  • 列名が不要という明確な仕様がある場合だけheader=Falseを使う
  • CSVは既定のカンマ区切り、TSVが必要な場合はsep="\t"を使う

タブ区切りでは、文字列"t"ではなく、タブ文字を表す"\t"を指定します。

no_header_path = output_dir / "sales_no_header.csv"
tsv_path = output_dir / "sales.tsv"

df[shared_columns].to_csv(no_header_path, index=False, header=False)
df[shared_columns].to_csv(tsv_path, index=False, sep="\t")

print("header=Falseの先頭行:")
print(no_header_path.read_text(encoding="utf-8").splitlines()[0])
print("TSVの先頭行(タブを見える形に変換):")
print(tsv_path.read_text(encoding="utf-8").splitlines()[0].replace("\t", " [TAB] "))
header=Falseの先頭行:
ノート,文具,1200
TSVの先頭行(タブを見える形に変換):
商品 [TAB] カテゴリ [TAB] 売上

header=Falseの先頭行は列名ではなく、最初の商品データになります。TSVの先頭行では、3つの列名がタブで区切られています。拡張子だけを.tsvへ変えても区切り文字は変わらないため、sepの指定とセットで確認します。

保存後はread_csv()で読み込み直して検算する

ファイルが存在するだけでは、必要な内容を正しく保存できたとは限りません。共有用CSVを読み込み直し、保存前後を同じ項目で比べます。

確認項目 保存前 再読み込み後 判断
行数 共有対象の4行 4行 一致するか
列数 指定した3列 3列 余計な列や欠落がないか
列名 商品・カテゴリ・売上 同じ3列 名前と順序が一致するか
日本語 商品名・カテゴリ名 同じ文字 文字化けしていないか
インデックス列 保存対象外 Unnamed: 0なし index=Falseが反映されたか
source_for_export = df[shared_columns]
loaded = pd.read_csv(shared_path, encoding="utf-8-sig")

check_result = pd.DataFrame({
    "確認項目": ["行数", "列数", "列名", "Unnamed: 0", "商品名"],
    "結果": [
        len(source_for_export) == len(loaded),
        source_for_export.shape[1] == loaded.shape[1],
        source_for_export.columns.tolist() == loaded.columns.tolist(),
        "Unnamed: 0" not in loaded.columns,
        source_for_export["商品"].tolist() == loaded["商品"].tolist(),
    ],
})

print(check_result)
print("再読み込み後の型:")
print(loaded.dtypes)
確認項目 結果
0 行数 True
1 列数 True
2 列名 True
3 Unnamed: 0 True
4 商品名 True
dtype
商品 o b j e c t
カテゴリ o b j e c t
売上 i n t 6 4

5項目がすべてTrueなら、今回の目的に必要な行・列・日本語・インデックス設定は一致しています。

ただし、CSVはDataFrameの型情報を完全に保存する形式ではありません。日付、カテゴリ型、先頭に0を含むコードなどは、再読み込み時に想定と異なる型になることがあります。ファイルを次の分析へ使う前に、dtypesinfo()を再確認してください。

よくある失敗を現象から確認・修正する

起きる現象 主な原因 確認する箇所 修正方針 修正後の確認
保存したファイルが見つからない 相対パスの基準を誤解 現在の作業場所、resolve()、ファイル一覧 保存先を明示する exists()True
存在しないフォルダへ保存できない 親フォルダがない 保存先の親フォルダ 先にフォルダを作る 目的のファイルがある
Unnamed: 0が増える 名前のないindexも保存した 保存時のindexと再読込後の列名 不要ならindex=Falseで保存し直す 列一覧から消える
日本語が文字化けする 保存時と読み手の文字コードが合わない encodingと開くアプリ 用途に合う文字コードで保存し直す 商品名を正しく読める
以前のファイルが消える 同名へ既定モードで上書き 保存先と同名ファイルの有無 別名またはmode="x" 意図したファイルが両方ある
追記の途中に列名が入る mode="a"でheaderも出力 追記前後の先頭・末尾 追記時だけheader=False 列名は先頭の1行だけ
追記後に列がずれる 列順・列数・区切りが不一致 既存CSVと追加DataFrameの列 列をそろえる。難しければ結合後に新規保存 再読込後の各値が正しい列にある

存在しない保存先フォルダを直す

起きる現象

ファイル名は正しいのに、存在しない親フォルダを含むパスへ保存しようとしてエラーになります。

原因と確認方法

to_csv()は、指定した親フォルダを自動では作りません。parent.exists()で確認します。

修正方法

必要なフォルダを作ってから保存します。

nested_path = output_dir / "reports" / "sales_report.csv"

print("修正前に親フォルダがあるか:", nested_path.parent.exists())
nested_path.parent.mkdir(parents=True, exist_ok=True)
df.to_csv(nested_path, index=False)

print("修正後にファイルがあるか:", nested_path.exists())
修正前に親フォルダがあるか: False
修正後にファイルがあるか: True

修正前の親フォルダはFalse、作成して保存した後のファイルはTrueになります。自分のデータでは、フォルダ名のスペルやGoogle Driveのマウント先も同時に確認してください。

上書きと追記はmodeで区別する

通常の保存は、同名ファイルがあると上書きします。既存ファイルを残すならmode="x"、既存CSVの末尾へ行を追加するならmode="a"を使います。mode="a"は値の更新や列の追加ではありません。

目的 mode 注意点
通常保存・上書き 既定の"w" 同名ファイルの内容は置き換わる
上書きを防いで新規保存 "x" 同名ファイルがあるとFileExistsError
既存CSVへ行を追記 "a" 列順をそろえ、通常はheader=False
protected_path = output_dir / "sales_protected.csv"
df.to_csv(protected_path, index=False)

try:
    df.to_csv(protected_path, index=False, mode="x")
except FileExistsError:
    print("同名ファイルがあるため、上書きを停止しました:", protected_path.name)

print("既存ファイルは残っているか:", protected_path.exists())
同名ファイルがあるため、上書きを停止しました: sales_protected.csv
既存ファイルは残っているか: True

FileExistsErrorが出たら、別名で保存するか、本当に上書きしてよいか判断します。続いて、列名と順序が同じデータを行方向へ追記する例を確認します。

append_path = output_dir / "sales_append.csv"
base = df[shared_columns].iloc[:3]
new_row = df[shared_columns].iloc[3:]

base.to_csv(append_path, index=False, encoding="utf-8-sig")
new_row.to_csv(
    append_path,
    index=False,
    encoding="utf-8-sig",
    mode="a",
    header=False,
)

appended = pd.read_csv(append_path, encoding="utf-8-sig")
print(appended)
print("追記後の行数:", len(appended))
商品 カテゴリ 売上
0 ノート 文具 1200
1 ペン 文具 900
2 ファイル 収納 1500
3 付箋 文具 600
追記後の行数: 4

追記後は4行で、列名は先頭の1回だけです。header=Falseを忘れると途中に列名が入ります。列構成が違う場合は追記せず、DataFrameを結合してから新しいCSVとして保存します。

Google Colabで保存するときの注意

この記事の相対パスは、Colabではランタイムの一時領域へ保存されます。ランタイム終了後も残したい場合は、Google Driveをマウントし、実在するDrive内フォルダの絶対パスを指定します。手元のPCへダウンロードする操作と、Driveへ保存する操作は別です。

Driveの接続、パスのコピー、CSVの再読み込みは、Google ColabでCSVを読み込む方法で詳しく確認してください。この記事ではto_csv()の設定と保存結果の検算に集中します。

自分のDataFrameへ置き換える前のチェックリスト

  • DataFrame名は正しいか
  • 保存する列名と順序をdf.columnsで確認したか
  • 現在のindexは単なる連番か、意味のあるIDか
  • 保存前の行数・列数をdf.shapeで記録したか
  • 欠損値を空欄として出力してよいか
  • 親フォルダは実在するか
  • 同名ファイルを上書きしてよいか
  • CSVを開くアプリと文字コードは合っているか
  • 追記する場合、既存CSVと列名・順序・区切り文字が同じか
  • 保存後にread_csv()で読み込み直したか

Excelから読み込んだデータをCSVへ変換する場合は、先にpandas read_excel()の使い方でシート・見出し・必要列が正しいか確認してください。

保存したCSVを次の分析へつなげる

CSV保存は分析の終点とは限りません。別のNotebookで読み込み直し、データの状態を確認してから集計へ進めます。

reused = pd.read_csv(shared_path, encoding="utf-8-sig")

print("再利用するデータ:")
print(reused)
print("カテゴリ別の売上合計:")
print(reused.groupby("カテゴリ", as_index=False)["売上"].sum())
再利用するデータ:
商品 カテゴリ 売上
0 ノート 文具 1200
1 ペン 文具 900
2 ファイル 収納 1500
3 付箋 文具 600
カテゴリ別の売上合計:
カテゴリ 売上
0 収納 1500
1 文具 2700

再読み込みしたデータから、カテゴリ別の売上合計を計算できました。保存後は、まず列・型・欠損を確認し、その後にpandas groupby・aggの使い方で複数列・複数集計へ進めます。

CSVを読み込み直したときに列名や型が想定と違う場合は、集計へ進む前に原因を修正してください。

まとめ

  • DataFrameのCSV保存にはto_csv()を使う
  • 最初に保存先を決め、index・文字コード・保存列・上書きの順で判断する
  • 連番インデックスが不要ならindex=Falseを使う
  • 意味のあるIDをインデックスにしている場合は、消す前に通常列へ戻すか判断する
  • 日本語の文字コードは開く環境で選び、Excelで直接開く用途ではutf-8-sigを候補にする
  • columnsで保存範囲を絞り、headersepは必要な仕様がある場合だけ変更する
  • 上書きを防ぐならmode="x"、行を追記する場合だけmode="a"を使う
  • 保存後はread_csv()で読み込み直し、行数・列名・日本語・不要列を検算する

最小コードを実行して終わりにせず、保存されたCSVを次の環境で正しく読めるかまで確認することが、失敗を防ぐポイントです。

関連記事

▲ ページトップへ戻る

to_csv()でindex=Falseを付けるのはなぜですか?

0から始まる連番インデックスがCSVへ追加され、再読み込み時にUnnamed: 0となるのを防ぐためです。ただし、顧客IDなど意味のあるインデックスは、通常列へ戻して残すか判断してください

CSVをExcelで開くと文字化けする場合はどうしますか?

保存時のencodingと、Excelがどの文字コードとして開いているかを確認します。日本語をExcelで直接開く用途ではencoding="utf-8-sig"を候補にし、実際の利用環境で表示を確認してください。受け渡し先に指定がある場合は、その指定を優先します。

to_csv()で保存したファイルはどこにありますか?

相対パスなら現在の作業場所を基準に保存されます。Path("ファイル名").resolve()で絶対パスを確認できます。Colabの一時領域はランタイム終了後に消えるため、残したい場合はGoogle Driveなどへ保存します。

CSVへ追記するときにヘッダーが重複するのはなぜですか?

mode="a"で追記するときも、既定では列名が出力されるためです。既存CSVと列構成を確認し、行を追記するコードではheader=Falseを指定します。

CSVへ保存するとデータ型や日付形式は保持されますか?

完全には保持されません。CSVは文字で表現されるため、再読み込み時に型が推定されます。先頭の0を持つコード、日付、カテゴリ型、欠損値などは、読み込み後にdtypesinfo()で確認してください。

Google Colab終了後もCSVを残すにはどうしますか?

Google DriveをマウントしてDrive内の実在パスへ保存するか、ランタイム終了前に端末へダウンロードします。Driveの接続とパス確認は関連記事を参照してください。

保存後に正しく出力できたか確認するにはどうしますか?

read_csv()で読み込み直し、保存前後のshape、列名と順序、日本語、欠損値、Unnamed: 0の有無を比べます。重要なデータでは数行だけでなく、行数や集計値も確認してください。

コメント

タイトルとURLをコピーしました