note.nkmk.me

PythonでCSVファイルを読み込み・書き込み(入力・出力)

Date: 2019-01-16 / tags: Python, CSV

Pythonの標準ライブラリのcsvモジュールはCSVファイルの読み込み・書き込み(新規作成・上書き保存・追記)のためのモジュール。

標準ライブラリなので追加でインストールする必要はない。

CSVファイルはカンマ区切りのテキストファイルなので、テキストファイルとして読み込んでカンマでの分割処理などを自分でしてもよいが、csvモジュールを使うとより簡単に扱える。

なお、モジュール名はcsvであり、本記事のタイトルや見出しもCSVとしているが、カンマ区切りに限らずTSV(タブ区切り)など任意の文字列で区切られたテキストファイル(CSV: Character Separated Value)が対象。

ここでは以下の項目について説明する。

  • CSVファイルの読み込み(入力): csv.reader
    • 基本的な使い方
      • 行・列・要素を取得
      • 文字列を数値に変換
    • 区切り文字(デリミタ)を指定: 引数delimiter
    • 引用符の扱い
    • 改行を含む場合
    • ヘッダーなどを含む場合
    • 辞書として読み込み: csv.DictReader
  • CSVファイルの書き込み(出力): csv.writer
    • 基本的な使い方
    • 既存のCSVファイルに追記
    • 区切り文字(デリミタ)を指定: 引数delimiter
    • 引用符の扱い
    • 改行を含む場合
    • ヘッダーなどを加える場合
    • 辞書を書き込み: csv.DictWriter
  • NumPyでのCSVファイルの読み書き
  • pandasでのCSVファイルの読み書き

最後に触れるが、CSVファイルから読み込んだデータの平均や合計を算出するなどといった処理を行う場合はpandasの使用をオススメする。

なお、すべての引数について触れているわけではないので詳細は公式ドキュメントを参照されたい。

スポンサーリンク

CSVファイルの読み込み(入力): csv.reader

CSVファイルの読み込み(入力)にはcsv.readerクラスを使う。

基本的な使い方

以下の内容のファイルを例とする。ここでは中身の説明のために単純にopen()でテキストファイルとして読み込んで表示している。open()についての詳細は以下の記事を参照。

import csv
import pprint

with open('data/src/sample.csv') as f:
    print(f.read())
# 11,12,13,14
# 21,22,23,24
# 31,32,33,34

なお、pprintは表示を見やすくするためにインポートしている。あとで使う。

コンストラクタcsv.reader()の第一引数にopen()で開いたファイルオブジェクトを指定する。csv.readerオブジェクトは行を反復処理するイテレータと見なせるため、for文で行ごとのデータを取得できる。

with open('data/src/sample.csv') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)
# ['11', '12', '13', '14']
# ['21', '22', '23', '24']
# ['31', '32', '33', '34']

上の結果から分かるように、各行の値を要素とするリストが順次返される。

二次元配列(リストのリスト)として取得したい場合はリスト内包表記を使う。

with open('data/src/sample.csv') as f:
    reader = csv.reader(f)
    l = [row for row in reader]

print(l)
# [['11', '12', '13', '14'], ['21', '22', '23', '24'], ['31', '32', '33', '34']]

行・列・要素を取得

二次元配列(リストのリスト)として取得した場合、0始まりの行番号をインデックス[]で指定すると行を取得できる。

print(l[1])
# ['21', '22', '23', '24']

さらに0始まりの列番号をインデックス[]で指定すると要素が取得できる。

print(l[1][1])
# 22

列を取得したい場合は転置(行と列を入れ替え)してからインデックス[]で指定する方法がある。

l_T = [list(x) for x in zip(*l)]
print(l_T)
# [['11', '21', '31'], ['12', '22', '32'], ['13', '23', '33'], ['14', '24', '34']]

print(l_T[1])
# ['12', '22', '32']

なお、後述のNumPyやpandasを使うとより簡単に範囲の抽出などが可能。

文字列を数値に変換

デフォルトでは各要素は文字列strとなる。

print(l[0][0])
# 11

print(type(l[0][0]))
# <class 'str'>

文字列を数値に変換したい場合はint()float()を使う。

文字列のリストを数値のリストに変換する場合はリスト内包表記を使う。

r = l[0]
print(r)
# ['11', '12', '13', '14']

print([int(v) for v in r])
# [11, 12, 13, 14]

二次元配列(リストのリスト)を一気に変換することも可能。

print([[int(v) for v in row] for row in l])
# [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]]

また、引数quotingcsv.QUOTE_NONNUMERICとすると、引用符(デフォルトではダブルクオーテーション")で囲まれていない要素が浮動小数点数floatに変換されて取得できる。とりあえず数値で取得したい場合はこちらのほうが簡単。floatに変換できない要素が引用符に囲まれていないとエラーとなるので注意。

with open('data/src/sample.csv') as f:
    reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
    l_f = [row for row in reader]

print(l_f)
# [[11.0, 12.0, 13.0, 14.0], [21.0, 22.0, 23.0, 24.0], [31.0, 32.0, 33.0, 34.0]]

print(l_f[0][0])
# 11.0

print(type(l_f[0][0]))
# <class 'float'>

区切り文字(デリミタ)を指定: 引数delimiter

csv.reader()はデフォルトでは区切り文字をカンマ,として扱う。引数delimiterに任意の文字列を区切り文字として指定できる。

以下のようなスペースで区切られたファイルを例とする。

with open('data/src/sample.txt') as f:
    print(f.read())
# 11 12 13 14
# 21 22 23 24
# 31 32 33 34

delimiter=' 'とするとスペースごとに要素として分割され、正しく取得できる。

with open('data/src/sample.txt') as f:
    reader = csv.reader(f, delimiter=' ')
    l = [row for row in reader]

print(l)
# [['11', '12', '13', '14'], ['21', '22', '23', '24'], ['31', '32', '33', '34']]

TSVファイル(タブ区切り)の場合はdelimiter='\t'とすればよい。

引用符の扱い

CSVファイルには、要素が引用符(ダブルクオーテーション"など)で囲まれたものがある。

以下のCSVファイルを例とする。

with open('data/src/sample_quote.csv') as f:
    print(f.read())
# 1,2,"3"
# "a,b,c",x,y

以下のように、デフォルトでは引用符自体は要素の値には含まれず、引用符で囲まれた部分の区切り文字も無視される。多くの場面ではこの動作で特に問題はないはず。

with open('data/src/sample_quote.csv') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)
# ['1', '2', '3']
# ['a,b,c', 'x', 'y']

引数quotingcsv.QUOTE_NONEとすると、引用符に対して特別な処理がされなくなる。引用符で囲まれた部分の区切り文字も要素の区切りとして扱われる。

with open('data/src/sample_quote.csv') as f:
    reader = csv.reader(f, quoting=csv.QUOTE_NONE)
    for row in reader:
        print(row)
# ['1', '2', '"3"']
# ['"a', 'b', 'c"', 'x', 'y']

デフォルトではダブルクオーテーション"が引用符として扱われるが、引数quotecharで任意の値を引用符として指定できる。シングルクオーテーション'を引用符としたい場合はquotechar="'"とすればよい。

改行を含む場合

引用符で囲まれた部分に改行を含む以下のCSVファイルを例とする。

with open('data/src/sample_linebreak.csv') as f:
    print(f.read())
# 1,2,"3"
# "a
# b",x,y

公式ドキュメントにあるように、改行コードが\r\nである環境(Windows)ではopen()の引数newline''としておいた方が安全。

newline='' が指定されない場合、クォートされたフィールド内の改行は適切に解釈されず、書き込み時に \r\n を行末に用いる処理系では余分な \r が追加されてしまいます。
csv --- CSV ファイルの読み書き — Python 3.7.2 ドキュメント

ヘッダーなどを含む場合

ヘッダーなどの見出し行・見出し列を含む以下のCSVファイルを例とする。

with open('data/src/sample_header_index.csv') as f:
    print(f.read())
# ,a,b,c,d
# ONE,11,12,13,14
# TWO,21,22,23,24
# THREE,31,32,33,34

これらの情報を特別に処理する機能はないため、データ部分のみを取り出したい場合はリスト内包表記とスライスを組み合わせるなどの操作が必要となる。

with open('data/src/sample_header_index.csv') as f:
    reader = csv.reader(f)
    l = [row for row in reader]

pprint.pprint(l)
# [['', 'a', 'b', 'c', 'd'],
#  ['ONE', '11', '12', '13', '14'],
#  ['TWO', '21', '22', '23', '24'],
#  ['THREE', '31', '32', '33', '34']]

print([row[1:] for row in l[1:]])
# [['11', '12', '13', '14'], ['21', '22', '23', '24'], ['31', '32', '33', '34']]

print([[int(v) for v in row[1:]] for row in l[1:]])
# [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]]

このようなファイルは後述のpandasを使うのが楽。

辞書として読み込み: csv.DictReader

csv.readerでは各行をリストとして読み込むが、csv.DictReaderでは各行を辞書(バージョン3.6以降はOrderedDict)として読み込む。

以下のCSVファイルを例とする。

with open('data/src/sample_header.csv') as f:
    print(f.read())
# a,b,c,d
# 11,12,13,14
# 21,22,23,24
# 31,32,33,34

デフォルトでは一行目の値がフィールド名として使われ、辞書のキーとなる。

with open('data/src/sample_header.csv') as f:
    reader = csv.DictReader(f)
    l = [row for row in reader]

pprint.pprint(l)
# [OrderedDict([('a', '11'), ('b', '12'), ('c', '13'), ('d', '14')]),
#  OrderedDict([('a', '21'), ('b', '22'), ('c', '23'), ('d', '24')]),
#  OrderedDict([('a', '31'), ('b', '32'), ('c', '33'), ('d', '34')])]

以下のようにフィールド名で列を指定して値を取得することが可能。

print(l[1])
# OrderedDict([('a', '21'), ('b', '22'), ('c', '23'), ('d', '24')])

print(l[1]['c'])
# 23

一行目にヘッダー(見出し行)がない場合は引数fieldnamesに指定する。

with open('data/src/sample.csv') as f:
    print(f.read())
# 11,12,13,14
# 21,22,23,24
# 31,32,33,34

with open('data/src/sample.csv') as f:
    reader = csv.DictReader(f, fieldnames=['a', 'b', 'c', 'd'], )
    for row in reader:
        print(row)
# OrderedDict([('a', '11'), ('b', '12'), ('c', '13'), ('d', '14')])
# OrderedDict([('a', '21'), ('b', '22'), ('c', '23'), ('d', '24')])
# OrderedDict([('a', '31'), ('b', '32'), ('c', '33'), ('d', '34')])

見出し列に対しては特別な処理は行われないので、余分な情報として除外したい場合は以下のようにpop()などを使う。

with open('data/src/sample_header_index.csv') as f:
    print(f.read())
# ,a,b,c,d
# ONE,11,12,13,14
# TWO,21,22,23,24
# THREE,31,32,33,34

with open('data/src/sample_header_index.csv') as f:
    reader = csv.DictReader(f)
    l = [row for row in reader]

pprint.pprint(l, width=100)
# [OrderedDict([('', 'ONE'), ('a', '11'), ('b', '12'), ('c', '13'), ('d', '14')]),
#  OrderedDict([('', 'TWO'), ('a', '21'), ('b', '22'), ('c', '23'), ('d', '24')]),
#  OrderedDict([('', 'THREE'), ('a', '31'), ('b', '32'), ('c', '33'), ('d', '34')])]

print([od.pop('') for od in l])
# ['ONE', 'TWO', 'THREE']

pprint.pprint(l)
# [OrderedDict([('a', '11'), ('b', '12'), ('c', '13'), ('d', '14')]),
#  OrderedDict([('a', '21'), ('b', '22'), ('c', '23'), ('d', '24')]),
#  OrderedDict([('a', '31'), ('b', '32'), ('c', '33'), ('d', '34')])]

CSVファイルの書き込み(出力): csv.writer

CSVファイルの書き込み(出力)にはcsv.writerクラスを使う。

基本的な使い方

コンストラクタcsv.writer()の第一引数にopen()で開いたファイルオブジェクトを指定する。

新規作成の場合、open()の第一引数に新たなファイルのパス、第二引数modeに書き込みモード'w'を指定する。既存のファイルに対して書き込みモードを'w'とすると上書きとなり、元の内容は削除される。追記する方法は後述。

writerow()メソッドで1行ずつ書き込む。引数はリスト。

with open('data/temp/sample_writer_row.csv', 'w') as f:
    writer = csv.writer(f)
    writer.writerow([0, 1, 2])
    writer.writerow(['a', 'b', 'c'])

以下のようなファイルが出力される。

with open('data/temp/sample_writer_row.csv') as f:
    print(f.read())
# 0,1,2
# a,b,c

writerows()メソッドを使うと二次元配列(リストのリスト)を一気に書き込める。

l = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]]
print(l)
# [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]]

with open('data/temp/sample_writer.csv', 'w') as f:
    writer = csv.writer(f)
    writer.writerows(l)

with open('data/temp/sample_writer.csv') as f:
    print(f.read())
# 11,12,13,14
# 21,22,23,24
# 31,32,33,34

既存のCSVファイルに追記

既存のCSVファイルに追記したい場合はopen()の追記モード'a'でファイルを開く。書き込み自体は新規作成の場合と同じくwriterow()writerows()。末尾に追記される。

with open('data/temp/sample_writer_row.csv', 'a') as f:
    writer = csv.writer(f)
    writer.writerow(['X', 'Y', 'Z'])

with open('data/temp/sample_writer_row.csv') as f:
    print(f.read())
# 0,1,2
# a,b,c
# X,Y,Z

区切り文字(デリミタ)を指定: 引数delimiter

これまでの例のように、デフォルトはカンマ区切りのファイルが出力される。引数delimiterで任意の区切り文字を指定可能。

TSVファイル(タブ区切り)として保存したい場合はdelimiter='\t'とすればよい。

with open('data/temp/sample_writer.tsv', 'w') as f:
    writer = csv.writer(f, delimiter='\t')
    writer.writerows(l)

with open('data/temp/sample_writer.tsv') as f:
    print(f.read())
# 11    12  13  14
# 21    22  23  24
# 31    32  33  34

引用符の扱い

デフォルトでは区切り文字(デリミタ)を含む要素などは引用符で囲まれて書き込まれる。

l = [[0, 1, 2], ['a,b,c', 'x', 'y']]

with open('data/temp/sample_writer_quote.csv', 'w') as f:
    writer = csv.writer(f)
    writer.writerows(l)

with open('data/temp/sample_writer_quote.csv') as f:
    print(f.read())
# 0,1,2
# "a,b,c",x,y

引数quotingで引用符の制御が可能。デフォルトはquoting=csv.QUOTE_MINIMALで、上の結果のように区切り文字のような特別な文字を含む要素のみを引用符で囲まれる。

quoting=csv.QUOTE_ALLとすると、すべての要素が引用符で囲まれる。

with open('data/temp/sample_writer_quote_all.csv', 'w') as f:
    writer = csv.writer(f, quoting=csv.QUOTE_ALL)
    writer.writerows(l)

with open('data/temp/sample_writer_quote_all.csv') as f:
    print(f.read())
# "0","1","2"
# "a,b,c","x","y"

quoting=csv.QUOTE_NONNUMERICとすると、数値でない要素が引用符で囲まれる。

with open('data/temp/sample_writer_quote_nonnumeric.csv', 'w') as f:
    writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)
    writer.writerows(l)

with open('data/temp/sample_writer_quote_nonnumeric.csv') as f:
    print(f.read())
# 0,1,2
# "a,b,c","x","y"

csv.QUOTE_NONEとすると、どの要素も引用符で囲まれない。この場合、引数escapecharでエスケープに使う文字を指定する必要がある。

with open('data/temp/sample_writer_quote_none.csv', 'w') as f:
    writer = csv.writer(f, quoting=csv.QUOTE_NONE, escapechar='\\')
    writer.writerows(l)

with open('data/temp/sample_writer_quote_none.csv') as f:
    print(f.read())
# 0,1,2
# a\,b\,c,x,y

デフォルトの引用符はダブルクオーテーション"。引数quotecharで指定可能。

with open('data/temp/sample_writer_quote_char.csv', 'w') as f:
    writer = csv.writer(f, quotechar="'")
    writer.writerows(l)

with open('data/temp/sample_writer_quote_char.csv') as f:
    print(f.read())
# 0,1,2
# 'a,b,c',x,y

改行を含む場合

要素の中に改行を含む場合、引用符で囲まれて書き込まれる。

l = [[0, 1, 2], ['a\nb', 'x', 'y']]

with open('data/temp/sample_writer_linebreak.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerows(l)

with open('data/temp/sample_writer_linebreak.csv') as f:
    print(f.read())
# 0,1,2
# "a
# b",x,y

公式ドキュメントにあるように、改行コードが\r\nである環境(Windows)ではopen()の引数newline''としておいた方が安全。

newline='' が指定されない場合、クォートされたフィールド内の改行は適切に解釈されず、書き込み時に \r\n を行末に用いる処理系では余分な \r が追加されてしまいます。
csv --- CSV ファイルの読み書き — Python 3.7.2 ドキュメント

ヘッダーなどを加える場合

csv.writerにはヘッダーなどの見出し行・列を加えて出力するための特別な機能はない。

見出し行はwriterow()で最初に書き込むだけでいいが、見出し列は各行に書き込むリストの先頭に要素を追加するといった処理が必要となる。

l = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]]
print(l)
# [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]]

header = ['', 'a', 'b', 'c', 'd']
index = ['ONE', 'TWO', 'THREE']

with open('data/temp/sample_writer_header_index.csv', 'w') as f:
    writer = csv.writer(f)
    writer.writerow(header)
    for i, row in zip(index, l):
        writer.writerow([i] + row)

with open('data/temp/sample_writer_header_index.csv') as f:
    print(f.read())
# ,a,b,c,d
# ONE,11,12,13,14
# TWO,21,22,23,24
# THREE,31,32,33,34

このような処理は後述のpandasを使うのが楽。

辞書を書き込み: csv.DictWriter

csv.writerでは各行にリストを書き込むが、csv.DictWriterでは各行に辞書を書き込む。

コンストラクタcsv.DictWriter()の第二引数fieldnamesに書き込む辞書のキーのリストを指定する。

writeheader()メソッドでfieldnamesがヘッダーとして書き込まれる。行の書き込みはwriterow()。引数に辞書を指定する。

d1 = {'a': 1, 'b': 2, 'c': 3}
d2 = {'a': 10, 'c': 30}

with open('data/temp/sample_dictwriter.csv', 'w') as f:
    writer = csv.DictWriter(f, ['a', 'b', 'c'])
    writer.writeheader()
    writer.writerow(d1)
    writer.writerow(d2)

with open('data/temp/sample_dictwriter.csv') as f:
    print(f.read())
# a,b,c
# 1,2,3
# 10,,30

辞書のリストをwriterows()で書き込むことも可能。

with open('data/temp/sample_dictwriter_list.csv', 'w') as f:
    writer = csv.DictWriter(f, ['a', 'b', 'c'])
    writer.writeheader()
    writer.writerows([d1, d2])

with open('data/temp/sample_dictwriter_list.csv') as f:
    print(f.read())
# a,b,c
# 1,2,3
# 10,,30

上の例のように、キーが存在しない辞書を書き込むとその要素はスキップされる(何も書き込まれず欠損値となる。

引数fieldnamesに指定した以外のキーを持つ辞書を書き込む場合、引数extrasaction'ignore'とするとそのキーの要素は無視される。

with open('data/temp/sample_dictwriter_ignore.csv', 'w') as f:
    writer = csv.DictWriter(f, ['a', 'c'], extrasaction='ignore')
    writer.writeheader()
    writer.writerows([d1, d2])

with open('data/temp/sample_dictwriter_ignore.csv') as f:
    print(f.read())
# a,c
# 1,3
# 10,30

デフォルトはextrasaction='raise'ValueErrorが送出されるので注意。

NumPyでのCSVファイルの読み書き

サードパーティライブラリNumPyを使うと配列をより柔軟に処理できる。NumPyの配列ndarrayとPython標準のリストを相互に変換することも可能。

CSVファイルの読み書きも簡単。詳細は以下の記事を参照。

ここでは簡単な例を示す。

CSVファイルから読み込み。

import numpy as np

with open('data/src/sample.csv') as f:
    print(f.read())
# 11,12,13,14
# 21,22,23,24
# 31,32,33,34

a = np.loadtxt('data/src/sample.csv', delimiter=',')

print(type(a))
# <class 'numpy.ndarray'>

print(a)
# [[11. 12. 13. 14.]
#  [21. 22. 23. 24.]
#  [31. 32. 33. 34.]]
source: csv_numpy.py

範囲を指定して抽出。

print(a[1:, :2])
# [[21. 22.]
#  [31. 32.]]
source: csv_numpy.py

平均や合計などを算出。

print(a.mean())
# 22.5

print(a.sum(axis=0))
# [63. 66. 69. 72.]
source: csv_numpy.py

pandasでのCSVファイルの読み書き

サードパーティライブラリpandasを使うと見出し行・列を含むデータや数値と文字列が混在しているデータなどをより柔軟に処理できる。pandasのDataFrameとPython標準のリストを相互に変換することも可能。

CSVファイルやExcelファイルの読み書きも簡単。詳細は以下の記事を参照。

ここでは簡単な例を示す。

CSVファイルから読み込み。

import pandas as pd

with open('data/src/sample_pandas_normal.csv') as f:
    print(f.read())
# name,age,state,point
# Alice,24,NY,64
# Bob,42,CA,92
# Charlie,18,CA,70
# Dave,68,TX,70
# Ellen,24,CA,88
# Frank,30,NY,57

df = pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0)

print(type(df))
# <class 'pandas.core.frame.DataFrame'>

print(df)
#          age state  point
# name                     
# Alice     24    NY     64
# Bob       42    CA     92
# Charlie   18    CA     70
# Dave      68    TX     70
# Ellen     24    CA     88
# Frank     30    NY     57

列や要素を選択して取得。

print(df['age'])
# name
# Alice      24
# Bob        42
# Charlie    18
# Dave       68
# Ellen      24
# Frank      30
# Name: age, dtype: int64

print(df.at['Bob', 'age'])
# 42

条件を指定して抽出。

print(df.query('state == "NY"'))
#        age state  point
# name                   
# Alice   24    NY     64
# Frank   30    NY     57

print(df.query('age > 30'))
#       age state  point
# name                  
# Bob    42    CA     92
# Dave   68    TX     70

要約統計量を算出。

print(df.describe())
#              age      point
# count   6.000000   6.000000
# mean   34.333333  73.500000
# std    18.392027  13.707662
# min    18.000000  57.000000
# 25%    24.000000  65.500000
# 50%    27.000000  70.000000
# 75%    39.000000  83.500000
# max    68.000000  92.000000

このように、CSVで保存するような表形式のデータ、特に数値の列や文字列の列が混在しているようなデータはpandasを使うと効率的に処理できる。そういった類のデータを扱うのであればpandasの導入をオススメする。

そのほかのpandas関連記事は以下から。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事