PythonでCSVファイルを読み込み・書き込み(入力・出力)
Pythonの標準ライブラリのcsvモジュールを使うと、CSVファイルの読み込み・書き込み(新規作成・上書き保存・追記)ができる。
csvというモジュール名だが、カンマ区切りに限らずタブ区切り(TSV)など任意の文字列で区切られたテキストファイル(CSV: Character Separated Value)を処理可能。
すべての引数について触れているわけではないので詳細は公式ドキュメントを参照されたい。
最後に触れるが、CSVファイルから読み込んだデータの平均や合計を算出するなどといった処理を行う場合はNumPyやpandasの使用をオススメする。
本記事のサンプルコードでは、csvモジュールと表示を見やすくするためのpprintモジュールを使う。
import csv
import pprint
CSVファイルの読み込み(入力): csv.reader()
CSVファイルの読み込み(入力)にはcsv.reader()
を使う。
基本的な使い方
以下の内容のファイルを例とする。ここでは単純にopen()
でテキストファイルとして読み込んで表示している。
with open('data/src/sample.csv') as f:
print(f.read())
# 11,12,13,14
# 21,22,23,24
# 31,32,33,34
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']
二次元配列(リストのリスト)として取得したい場合はリスト内包表記を使う。
- 関連記事: Pythonリスト内包表記の使い方
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()
を使う。
文字列のリストを数値のリストに変換する場合はリスト内包表記を使う。二次元配列(リストのリスト)全体をまとめて変換することもできる。
print(l[0])
# ['11', '12', '13', '14']
print([int(v) for v in l[0]])
# [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]]
また、引数quoting
をcsv.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']
引数quoting
をcsv.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 モジュールは独自 (universal) の改行処理を行うため、newline=''
を指定することは常に安全です。 csv --- CSV ファイルの読み書き — Python 3.11.4 ドキュメント
with open('data/src/sample_linebreak.csv', newline='') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# ['1', '2', '3']
# ['a\nb', 'x', 'y']
ヘッダーなどを含む場合
ヘッダーなどの見出し行・見出し列を含む以下の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
これらの情報を特別に処理する機能はないため、データ部分のみを取り出したい場合はリスト内包表記とスライスを組み合わせるなどの操作が必要となる。
- 関連記事: Pythonリスト内包表記の使い方
- 関連記事: Pythonのスライスによるリストや文字列の部分選択・代入
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()
は各行を辞書として読み込む。
以下の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)
# [{'a': '11', 'b': '12', 'c': '13', 'd': '14'},
# {'a': '21', 'b': '22', 'c': '23', 'd': '24'},
# {'a': '31', 'b': '32', 'c': '33', 'd': '34'}]
print(l[1])
# {'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)
# {'a': '11', 'b': '12', 'c': '13', 'd': '14'}
# {'a': '21', 'b': '22', 'c': '23', 'd': '24'}
# {'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)
# [{'': 'ONE', 'a': '11', 'b': '12', 'c': '13', 'd': '14'},
# {'': 'TWO', 'a': '21', 'b': '22', 'c': '23', 'd': '24'},
# {'': 'THREE', 'a': '31', 'b': '32', 'c': '33', 'd': '34'}]
print([od.pop('') for od in l])
# ['ONE', 'TWO', 'THREE']
pprint.pprint(l)
# [{'a': '11', 'b': '12', 'c': '13', 'd': '14'},
# {'a': '21', 'b': '22', 'c': '23', 'd': '24'},
# {'a': '31', 'b': '32', 'c': '33', 'd': '34'}]
CSVファイルの書き込み(出力): csv.writer()
CSVファイルの書き込み(出力)にはcsv.writer()
を使う。
基本的な使い方
csv.writer()
の第一引数にopen()
で開いたファイルオブジェクトを指定する。
新規作成の場合、open()
の第一引数に新たなファイルのパス、第二引数mode
に書き込みモード'w'
を指定する。既存のファイルに対して書き込みモードを'w'
とすると上書きとなり、元の内容は削除される。追記する方法は後述。
writerow()
メソッドで一行ずつ書き込む。引数にはリストを指定する。
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]]
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'
とすればよい。
l = [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]]
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 モジュールは独自 (universal) の改行処理を行うため、newline=''
を指定することは常に安全です。 csv --- CSV ファイルの読み書き — Python 3.11.4 ドキュメント
ヘッダーなどを加える場合
csv.writer
にはヘッダーなどの見出し行・列を加えて出力するための特別な機能はない。
見出し行はwriterow()
で最初に書き込むだけでいいが、見出し列は各行に書き込むリストの先頭に要素を追加するといった処理が必要となる。
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.]]
範囲を指定して抽出。
print(a[1:, :2])
# [[21. 22.]
# [31. 32.]]
平均や合計などを算出。
print(a.mean())
# 22.5
print(a.sum(axis=0))
# [63. 66. 69. 72.]
pandasでのCSVファイルの読み書き
サードパーティライブラリpandasを使うと見出し行・列を含むデータや数値と文字列が混在しているデータなどをより柔軟に処理できる。pandasのDataFrame
とPython標準のリストを相互に変換することも可能。
CSVファイルやExcelファイルの読み書きも簡単。詳細は以下の記事を参照。
- 関連記事: pandasでcsv/tsvファイル読み込み(read_csv, read_table)
- 関連記事: pandasでcsvファイルの書き出し・追記(to_csv)
- 関連記事: pandasでExcelファイル(xlsx, xls)の読み込み(read_excel)
- 関連記事: pandasでExcelファイル(xlsx, xls)の書き込み(to_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関連記事は以下から。
- 関連記事: pandas関連記事まとめ