NumPyでCSVファイルを読み込み・書き込み(入力・出力)
NumPyではCSV(カンマ区切り)やTSV(タブ区切り)などのテキストファイルを配列ndarray
として読み込んだり、ndarray
をテキストファイルとして書き出したりできる。
ここでは以下の内容について説明する。なお、タイトルおよび見出しでは便宜上CSVとしているが、カンマ区切りに限らず任意の文字列で区切られたテキストファイル(CSV: Character Separated Value)を対象とする。
- CSVファイルを読み込み(入力):
np.loadtxt()
- 基本的な使い方
- 区切り文字(デリミタ)を指定: 引数
delimiter
- データ型を指定: 引数
dtype
- 読み込む行・列を指定: 引数
skiprows
,usecols
- 複雑なCSVファイルを読み込み(入力):
np.genfromtxt()
- 欠損値の処理
- 異なるデータ型の処理
- CSVファイルに書き込み(出力):
np.savetxt()
- 基本的な使い方
- フォーマットを指定: 引数
fmt
- 区切り文字(デリミタ)を指定: 引数
delimiter
- 出力できるのは一次元配列と二次元配列のみ
- pandasを利用した読み書き(入出力)
- 見出し行・列が付いたCSVファイルの処理
- 欠損値の処理
- 異なるデータ型の処理
最後に触れるようにヘッダー(見出し行)を含んでいたり、数値や文字列の列が混在しているようなファイルの読み書き(入出力)はpandasの方が便利。
また、他のアプリケーションで使う必要がなければテキストではなくNumPy独自のバイナリ形式で保存する方が楽。以下の記事を参照。
なお、すべての引数について触れているわけではないので詳細は公式ドキュメントを参照されたい。
CSVファイルを読み込み(入力): np.loadtxt()
基本的な使い方
任意の文字で区切られたテキストファイルをndarray
として読み込むにはnp.loadtxt()
を使う。
スペースで数値が区切られた以下のファイルを例とする。説明のため以降もファイルの中身をopen()
およびread()
で示す。open()
については以下の記事を参照。
import numpy as np
with open('data/src/sample.txt') as f:
print(f.read())
# 11 12 13 14
# 21 22 23 24
# 31 32 33 34
第一引数に読み込むファイルのパスを指定するとndarray
が返される。デフォルトではデータ型dtype
はfloat
(ビット数は環境依存)。
a = np.loadtxt('data/src/sample.txt')
print(type(a))
# <class 'numpy.ndarray'>
print(a)
# [[11. 12. 13. 14.]
# [21. 22. 23. 24.]
# [31. 32. 33. 34.]]
print(a.dtype)
# float64
区切り文字(デリミタ)を指定: 引数delimiter
カンマ区切りのファイル(CSVファイル)を例とする。
with open('data/src/sample.csv') as f:
print(f.read())
# 11,12,13,14
# 21,22,23,24
# 31,32,33,34
デフォルトではエラーとなり読み込めない。
# print(np.loadtxt('data/src/sample.csv'))
# ValueError: could not convert string to float: '11,12,13,14'
引数delimiter
に文字列でカンマ','
を指定する。
print(np.loadtxt('data/src/sample.csv', delimiter=','))
# [[11. 12. 13. 14.]
# [21. 22. 23. 24.]
# [31. 32. 33. 34.]]
引数delimiter
を省略した場合はデフォルト値のスペース' '
となり、上述のようなスペース区切りのファイルは読み込めるがそれ以外のファイルは読み込めないため、適宜指定する必要がある。
TSV(タブ区切り)の場合は'\t'
とすればよい。
データ型を指定: 引数dtype
上述のようにデフォルトではデータ型dtype
はfloat
(ビット数は環境依存)。引数dtype
に任意のデータ型を指定できる。
a = np.loadtxt('data/src/sample.csv', delimiter=',', dtype='int64')
print(a)
# [[11 12 13 14]
# [21 22 23 24]
# [31 32 33 34]]
print(a.dtype)
# int64
読み込む行・列を指定: 引数skiprows, usecols
不要なデータが含まれている場合は、引数skiprows
, usecols
で読み込む行・列を指定できる。
引数skiprows
は先頭から何行スキップして読み込むかを整数値で指定する。
引数usecols
は読み込む列をリストなどのシーケンスオブジェクトで指定する。一列のみ読み込む場合は整数値でもよい。
見出し行と見出し列を含む以下のファイルを例とする。
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
引数skiprows
, usecols
を指定すると文字列を除外した数値のみのデータが読み込める。
a = np.loadtxt('data/src/sample_header_index.csv', delimiter=',', dtype='int64',
skiprows=1, usecols=[1, 2, 3, 4])
print(a)
# [[11 12 13 14]
# [21 22 23 24]
# [31 32 33 34]]
なお、このようなファイルはpandasを使ったほうが楽。後述。
複雑なCSVファイルを読み込み(入力): np.genfromtxt()
np.genfromtxt()
を使うと、欠損値を含んでいたり複数の異なるデータ型を含んでいたりする、より複雑な構造のCSVファイルの読み込みが可能。
ただし、特に複数のデータ型を含むファイルはpandasを使ったほうが便利なので、ここでは簡単な紹介のみとする。詳細は以下の公式ドキュメントを参照。pandasについては後述。
欠損値の処理
以下のように値が欠損しているファイルを例とする。np.loadtxt()
だとエラーとなる。
with open('data/src/sample_nan.csv') as f:
print(f.read())
# 11,12,,14
# 21,,,24
# 31,32,33,34
# a = np.loadtxt('data/src/sample_nan.csv', delimiter=',')
# ValueError: could not convert string to float:
np.genfromtxt()
を使うと欠損値がnp.nan
として読み込まれる。
a = np.genfromtxt('data/src/sample_nan.csv', delimiter=',')
print(a)
# [[11. 12. nan 14.]
# [21. nan nan 24.]
# [31. 32. 33. 34.]]
print(a[0, 2])
# nan
print(type(a[0, 2]))
# <class 'numpy.float64'>
NumPyにおける欠損値の処理については以下の記事を参照。
- 関連記事: NumPyで欠損値np.nanを含む配列ndarrayの合計や平均を算出
- 関連記事: NumPyの配列ndarrayの欠損値np.nanを他の値に置換
- 関連記事: NumPyの配列ndarrayの欠損値np.nanを含む行や列を削除
異なるデータ型の処理
以下のように列ごとに異なるデータ型(文字列と数値)を持つファイルを例とする。
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
上の説明では省略したが、引数dtype
に適切な値を指定すればnp.loadtxt()
でもこのようなファイルを構造化配列(Structured array)として読み込むことが可能。
a = np.loadtxt('data/src/sample_pandas_normal.csv', delimiter=',', skiprows=1,
dtype={'names': ('name', 'age', 'state', 'point'),
'formats': ('<U7', '<i8', '<U2', '<i8')})
print(type(a))
# <class 'numpy.ndarray'>
print(a)
# [('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(a.dtype)
# [('name', '<U7'), ('age', '<i8'), ('state', '<U2'), ('point', '<i8')]
np.genfromtxt()
では、引数names
をTrue
、引数dtype
をNone
とすると、一行目の値がフィールド名で、列ごとに型が自動的に決定された構造化配列として読み込まれる。
a = np.genfromtxt('data/src/sample_pandas_normal.csv', delimiter=',',
names=True, dtype=None, encoding='utf-8')
print(type(a))
# <class 'numpy.ndarray'>
print(a)
# [('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(a.dtype)
# [('name', '<U7'), ('age', '<i8'), ('state', '<U2'), ('point', '<i8')]
構造化配列(Structured array)についての詳細は以下の公式ドキュメントを参照。
繰り返しになるが、このようなファイルはpandasを使ったほうが簡単。
CSVファイルに書き込み(出力): np.savetxt()
ndarray
を任意の文字列で区切られたテキストファイルとして書き込むにはnp.savetxt()
を使う。
以下のndarray
を例とする。
a = np.arange(6).reshape(2, 3)
print(a)
# [[0 1 2]
# [3 4 5]]
基本的な使い方
第一引数に出力先のファイルのパス、第二引数に元のndarray
を指定する。
np.savetxt('data/temp/np_savetxt.txt', a)
以下のような内容のファイルが作成される。
with open('data/temp/np_savetxt.txt') as f:
print(f.read())
# 0.000000000000000000e+00 1.000000000000000000e+00 2.000000000000000000e+00
# 3.000000000000000000e+00 4.000000000000000000e+00 5.000000000000000000e+00
フォーマットを指定: 引数fmt
引数fmt
で任意のフォーマットを指定できる。
小数点以下の桁数などを指定できるが、値が丸まった場合は当然そのままテキストとして保存されるので、あとから復元することはできなくなる。注意。
デフォルトは'%.18e'
で、上述のように小数点以下18桁の指数表記で書き込まれる。.
以降の数字は小数点以下の桁数、e
は指数表記を表す。
np.savetxt('data/temp/np_savetxt_5e.txt', a, fmt='%.5e')
with open('data/temp/np_savetxt_5e.txt') as f:
print(f.read())
# 0.00000e+00 1.00000e+00 2.00000e+00
# 3.00000e+00 4.00000e+00 5.00000e+00
なお、指数表記でもnp.loadtxt()
でそのまま読み込めるので、特にこだわりがなければデフォルトのフォーマットにしておけば問題ない。
print(np.loadtxt('data/temp/np_savetxt.txt'))
# [[0. 1. 2.]
# [3. 4. 5.]]
f
は小数。
np.savetxt('data/temp/np_savetxt_5f.txt', a, fmt='%.5f')
with open('data/temp/np_savetxt_5f.txt') as f:
print(f.read())
# 0.00000 1.00000 2.00000
# 3.00000 4.00000 5.00000
d
は整数。
np.savetxt('data/temp/np_savetxt_d.txt', a, fmt='%d')
with open('data/temp/np_savetxt_d.txt') as f:
print(f.read())
# 0 1 2
# 3 4 5
x
は16進数表記。ゼロ埋めも可能。04
は全体で4桁、残りを0
で埋める、という意味。説明のため10倍して保存する。
print(a * 10)
# [[ 0 10 20]
# [30 40 50]]
np.savetxt('data/temp/np_savetxt_x.txt', a * 10, fmt='%04x')
with open('data/temp/np_savetxt_x.txt') as f:
print(f.read())
# 0000 000a 0014
# 001e 0028 0032
16進数表記はnp.loadtxt()
ではそのまま読み込めないので再びNumPyで使うような場合は避けたほうがよい。
そのほかフォーマットの詳細は公式ドキュメントを参照。
区切り文字(デリミタ)を指定: 引数delimiter
np.loadtxt()
と同じく、np.savetxt()
でもデフォルトでは区切り文字(デリミタ)がスペース' '
となる。
引数delimiter
で任意の区切り文字を指定可能。
CSV(カンマ区切り)として保存したい場合はdelimiter=','
、TSV(タブ区切り)として保存したい場合はdelimiter='\t'
とすればよい。
np.savetxt('data/temp/np_savetxt.csv', a, delimiter=',', fmt='%d')
with open('data/temp/np_savetxt.csv') as f:
print(f.read())
# 0,1,2
# 3,4,5
np.savetxt('data/temp/np_savetxt.tsv', a, delimiter='\t', fmt='%d')
with open('data/temp/np_savetxt.tsv') as f:
print(f.read())
# 0 1 2
# 3 4 5
出力できるのは一次元配列と二次元配列のみ
np.savetxt()
で出力できるのは一次元配列と二次元配列のみ。三次元以上の配列はエラーとなる。
a_3d = np.arange(24).reshape(2, 3, 4)
print(a_3d)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
# np.savetxt('data/temp/np_savetxt_3d.txt', a_3d)
# ValueError: Expected 1D or 2D array, got 3D array instead
三次元以上の配列はflatten()
やreshape()
で二次元以下に変換すれば保存できる。
が、当然ながら、再び元のndarray
として使うにはloadtxt()
などで読み込んだあとでreshape()
で元の形状に戻す必要がある。元の形状の情報を別途保存する必要があるのであまり実用的ではないかもしれない。
バイナリファイル(npy
, npz
)で保存すればデータ型や形状がそのまま保持される。三次元以上の多次元配列もそのまま保存できるので、テキストファイルにこだわらなければそちらのほうが簡単。
pandasを利用した読み書き(入出力)
DataFrame
はpd.read_csv()
関数とDataFrame
のto_csv()
メソッドでCSVファイルの読み書きが可能。ヘッダー(見出し行)がついたデータなどをより簡単に処理できる。
NumPyのndarray
とpandasのDataFrame
は相互に変換可能なので、pandasを経由して処理できる。
ここではいくつかの例を簡単に紹介する。引数の設定などの詳細は以下の関連記事を参照。
また、pandasではExcelのファイルの読み書きもできる。
見出し行・列が付いたCSVファイルの処理
以下のCSVファイルを例とする。
import numpy as np
import pandas as pd
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
pd.read_csv()
では、デフォルトで1行目が見出し行、引数index_col
で指定した列が見出し列として読み込まれる。
df = pd.read_csv('data/src/sample_header_index.csv', index_col=0)
print(df)
# a b c d
# ONE 11 12 13 14
# TWO 21 22 23 24
# THREE 31 32 33 34
ndarray
として取得したい場合はDataFrame
のvalues
属性を使う。
a = df.values
print(a)
# [[11 12 13 14]
# [21 22 23 24]
# [31 32 33 34]]
print(type(a))
# <class 'numpy.ndarray'>
ndarray
に見出し行・列を付けて保存したい場合は、コンストラクタの引数index
, columns
を指定してDataFrame
を生成してからto_csv()
で書き込む。
a = np.arange(6).reshape(2, 3)
print(a)
# [[0 1 2]
# [3 4 5]]
df = pd.DataFrame(a, index=['ONE', 'TWO'], columns=['a', 'b', 'c'])
print(df)
# a b c
# ONE 0 1 2
# TWO 3 4 5
df.to_csv('data/temp/sample_pd.csv')
with open('data/temp/sample_pd.csv') as f:
print(f.read())
# ,a,b,c
# ONE,0,1,2
# TWO,3,4,5
欠損値の処理
以下のCSVファイルを例とする。
with open('data/src/sample_nan.csv') as f:
print(f.read())
# 11,12,,14
# 21,,,24
# 31,32,33,34
pd.read_csv
では特に何も設定しなくても欠損値はnan
として扱われる。なお、上述のように、デフォルトで1行目が見出し行(ヘッダー)として処理されるため、この例のように見出し行がない場合は引数header
をNone
とする。
df = pd.read_csv('data/src/sample_nan.csv', header=None)
print(df)
# 0 1 2 3
# 0 11 12.0 NaN 14
# 1 21 NaN NaN 24
# 2 31 32.0 33.0 34
pandasにおける欠損値の処理については以下の記事を参照。
異なるデータ型の処理
以下のCSVファイルを例とする。
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
DataFrame
はndarray
と異なり各列ごとにデータ型を持つ。pd.read_csv()
ではデフォルトでそれぞれの列のデータ型が推測され自動的に設定される。
df = pd.read_csv('data/src/sample_pandas_normal.csv')
print(df)
# name age state point
# 0 Alice 24 NY 64
# 1 Bob 42 CA 92
# 2 Charlie 18 CA 70
# 3 Dave 68 TX 70
# 4 Ellen 24 CA 88
# 5 Frank 30 NY 57
print(df.dtypes)
# name object
# age int64
# state object
# point int64
# dtype: object
pandasにおけるデータ型については以下の記事を参照。
DataFrame
のselect_dtypes()
メソッドで任意のデータ型の列を抽出できる。
print(df.select_dtypes('int'))
# age point
# 0 24 64
# 1 42 92
# 2 18 70
# 3 68 70
# 4 24 88
# 5 30 57
以下のように、文字列などの余分なデータを含んだCSVファイルから数値の列のみを抽出してndarray
として読み込むことが可能。
a = pd.read_csv('data/src/sample_pandas_normal.csv').select_dtypes('int').values
print(a)
# [[24 64]
# [42 92]
# [18 70]
# [68 70]
# [24 88]
# [30 57]]
print(type(a))
# <class 'numpy.ndarray'>
print(a.dtype)
# int64