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

Modified: | Tags: 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']

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

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]]

また、引数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 モジュールは独自 (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

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

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.]]
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関連記事は以下から。

関連カテゴリー

関連記事