Pythonの基本文法に慣れてきた頃、手元の表データをコードで扱ってみたくなりませんか。
売上の一覧、アンケートの結果、会員名簿。仕事で扱うデータの多くは、行と列でできた表の形をしています。
その表をプログラムから読み書きするとき、いちばんよく登場するのがcsvという形式です。Pythonには、これを扱うためのcsvモジュールが最初から入っています。
この記事では、csvモジュールの基本を順番に追いかけます。あわせて、初心者がほぼ必ずぶつかる文字化けと空行の問題も片づけていきましょう。
csvファイルは、実はただのテキストファイル¶
csvはComma-Separated Valuesの略で、値をカンマで区切って並べただけの形式です。Excelのアイコンで表示されることが多いので誤解されがちですが、中身はメモ帳でも開ける普通のテキストにすぎません。
たとえば、次のような3行のテキストがcsvファイルの正体です。
name,age,city
佐藤,28,東京
鈴木,34,大阪
1行目が見出しで、2行目からがデータ。たったこれだけの決まりごとで、表を表現しています。
構造がシンプルなぶん、どんなツールでも読み書きできます。だからこそ、システム同士がデータを受け渡すときの共通語のように使われ続けているわけです。
この形式にはRFC 4180という仕様書もあります。カンマや改行を含む値はダブルクォートで囲む、といった取り決めがそこに書かれています。
なぜsplitではなくcsvモジュールを使うのか¶
カンマで区切るだけなら、文字列をsplitすれば済みそうに見えます。私も駆け出しの頃は本気でそう考えていました。
ところが、値そのものにカンマが入っていると一気に破綻します。住所や商品名では、まったく珍しくないケースです。
name,memo
佐藤,"東京都,港区"
この2行目をsplitで割ると要素が3つに分かれ、列がずれます。ダブルクォートで囲まれた中身は区切らない、という判断をしてくれるのがcsvモジュールの価値です。
私は10年ほどエンジニアとして開発に関わってきましたが、この列ずれが原因の障害は何度も見てきました。自前のsplitで書かれた読み込み処理は、たいてい数か月後に事故を起こします。
まずは1行ずつ読んでみる¶
読み込みの基本はcsv.readerです。ファイルを開いて渡すと、1行ずつリストとして取り出せます。
先ほどの3行のファイルをmembers.csvという名前で保存して、次のコードを動かしてみましょう。
import csv
with open("members.csv", encoding="utf-8", newline="") as f:
reader = csv.reader(f)
header = next(reader)
print(header)
for row in reader:
print(row)
実行すると、次のように表示されます。
['name', 'age', 'city']
['佐藤', '28', '東京']
['鈴木', '34', '大阪']
next関数で見出しの1行だけを先に取り出し、残りをforで回しているのがポイントです。こうしておくと、見出しがデータに紛れ込みません。
openにwith文を使っているのは、処理が終わったあとにファイルを確実に閉じるためです。この書き方の仕組みが気になる方は、こちらもあわせてどうぞ。【関連記事】pythonのコンテキストマネージャーって何?詳しく解説します!
見出し付きのデータはDictReaderが読みやすい¶
readerで読むと、各行はリストになります。つまり、名前を取り出すにはrow[0]と書くことになります。
列が5つ、10つと増えてくると、この番号がだんだん苦痛になってきます。そこで登場するのがcsv.DictReaderです。
import csv
with open("members.csv", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["city"])
DictReaderは1行目を見出しとして自動で読み取り、各行を辞書にして返してくれます。row[0]ではなくrow["name"]と書けるので、あとから読み返したときの意味がはっきりします。
列の順番が変わっても壊れないのも利点です。実務でもらうcsvは、途中で列が1つ増えることが本当によくあります。
数字は文字列のまま返ってくる¶
ここで初心者がつまずきやすい点をひとつ。csvモジュールは、読み取った値を勝手に数値へ変換しません。
公式ドキュメントにも、行はリストとして返され、自動的な型変換は行われないと明記されています。年齢の28は、数値の28ではなく文字列の28として届くわけです。
そのため、合計を出したいときは自分でintへ変換します。
import csv
total = 0
with open("members.csv", encoding="utf-8", newline="") as f:
for row in csv.DictReader(f):
total += int(row["age"])
print(total)
これを忘れると、足し算のつもりが文字列の連結になったり、そもそもエラーで止まったりします。csvから読んだ値はすべて文字列、と覚えておくと安全です。
csvファイルへ書き出す¶
読めるようになったら、次は書き出しです。writerowsを使えば、複数行をまとめて出力できます。
辞書のリストをそのまま書きたい場合は、DictWriterが便利です。
import csv
rows = [
{"name": "田中", "age": 41, "city": "名古屋"},
{"name": "高橋", "age": 25, "city": "福岡"},
]
with open("output.csv", "w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age", "city"])
writer.writeheader()
writer.writerows(rows)
fieldnamesで列の順番を決め、writeheaderで見出し行を出します。あとはwriterowsに渡すだけです。
出力先のパスを組み立てるときは、文字列の連結よりpathlibのほうが安全に書けます。【関連記事】Pythonのpathlibとは?ファイルパス操作を初心者向けに解説
newline="" を忘れるとどうなるか¶
上のコードで、openにnewline=""を渡しているのに気づいたでしょうか。これは、おまじないではなく明確な理由があります。
公式ドキュメントは、ファイルオブジェクトを渡すときはnewline=""を指定して開くべきだと述べています。指定しないと、クォートで囲まれた中の改行が正しく解釈されず、さらにWindowsのように改行が\r\nの環境では余分な\rが足されてしまうためです。
その結果どうなるか。書き出したファイルを開くと、1行おきに空行が入った見た目になります。
読むときも書くときも、csvを扱うopenにはnewline=""を付ける。これを習慣にしておくだけで、原因のわかりにくい不具合をひとつ確実に避けられます。
Excelで開くと文字化けするときの直し方¶
もうひとつの定番のつまずきが文字化けです。Pythonで書き出したcsvをExcelで開いたら、日本語が意味不明な記号の羅列になっていた。よくある光景です。
原因は、ファイル側の文字コードと、Excelが想定する文字コードの食い違いにあります。ExcelはBOMという3バイトの目印がないと、そのファイルをUTF-8だと判断してくれないことがあります。
Pythonでは、encodingにutf-8-sigを指定するとこのBOMを自動で付けてくれます。先ほどの書き出しコードでutf-8-sigを使っていたのは、そのためです。
逆に、古い業務システムからもらったファイルはcp932で保存されていることがあります。用途ごとの目安を表にまとめました。
| encodingの指定 | どんなときに使うか |
|---|---|
| utf-8 | Pythonや他のシステムとやり取りする、いちばん標準的な指定 |
| utf-8-sig | 書き出したファイルを誰かにExcelで開いてもらうとき |
| cp932 | Windowsの古いシステムからもらったファイルを読むとき |
読み込みでUnicodeDecodeErrorが出たときは、まずcp932を試してみてください。私の経験では、日本の業務データに限れば、これで8割方は解決します。
タブ区切りや別の区切り文字にも対応できる¶
カンマではなくタブで区切られたtsvというファイルを渡されることもあります。この場合も、csvモジュールがそのまま使えます。
delimiterという引数に区切り文字を指定するだけです。
import csv
with open("data.tsv", encoding="utf-8", newline="") as f:
for row in csv.reader(f, delimiter="\t"):
print(row)
セミコロン区切りのファイルも、同じ要領で読めます。区切り文字が違うだけで別のライブラリを探す必要はありません。
なお、データの受け渡しにはjson形式もよく使われます。階層のあるデータならこちらのほうが向いています。【関連記事】PythonでJSONデータの扱いをマスター!API連携に必須のjsonモジュールの使い方
pandasとどう使い分けるか¶
表データと聞くと、pandasを思い浮かべた方もいるかもしれません。どちらを使えばいいのか、迷うところです。
大まかな違いを表にしました。
| 観点 | csvモジュール | pandas |
|---|---|---|
| インストール | 不要(標準ライブラリ) | 必要 |
| 得意なこと | 1行ずつの読み書き、加工しながらの出力 | 集計、並べ替え、欠損値の処理 |
| メモリの使い方 | 1行分ずつ処理できる | 表全体を読み込む |
| 学習コスト | 低い | やや高い |
集計や分析をしたいならpandasが圧倒的に速く書けます。一方、数百万行のログを1行ずつ処理したい場面では、メモリに全部載せないcsvモジュールが有利です。
私自身、日々のちょっとした変換作業はいまだにcsvモジュールで済ませています。追加のインストールが要らないので、他の人の環境でもそのまま動くからです。
pandasのほうが気になる方は、こちらの入門記事から始めてみてください。【関連記事】pandas入門 データ処理をやってみよう
つまずきポイントをまとめて確認する¶
最後に、この記事で扱った落とし穴を一覧にしておきます。困ったときの確認表として使ってください。
| 症状 | 原因 | 対処 |
|---|---|---|
| 1行おきに空行が入る | newlineを指定していない | openにnewline=""を渡す |
| Excelで日本語が崩れる | BOMがない | 書き出しでutf-8-sigを使う |
| UnicodeDecodeErrorが出る | 文字コードが違う | 読み込みでcp932を試す |
| 数値の計算がおかしい | 値が文字列のまま | intやfloatへ変換する |
| 列がずれる | splitで自作している | csv.readerに任せる |
どれも一度ぶつかれば忘れないものばかりです。逆に言えば、知らないと必ず一度は時間を溶かします。
csvが読み書きできるようになると、日々の集計や転記をPythonに任せられるようになります。事務作業の自動化がどれだけ効くのかは、こちらでも紹介しています。【関連記事】非エンジニアがPythonを学ぶべき「コスパ最強」の理由。事務作業を秒で終わらせるには?
まずは手元にある小さなcsvを1つ、readerで読んで表示するところから始めてみましょう。動いた瞬間から、できることが一気に広がります。
ここまでお読みいただきありがとうございました。