はじめに
みずほリサーチ&テクノロジーズ株式会社の@fujineです。
本記事ではpandas 2.0
を対象に、CSV
ファイルの入力関数である read_csvの全49個(!)の引数をじっくり解説 いたします。具体的には、
- 各引数には、どんな効果や(公式ドキュメントにも記載されていない)制約があるのか?
- 引数を工夫することで、処理時間やメモリ消費量などのパフォーマンスが具体的にどれだけ改善されるのか?
-
pandas
のver2.0では、それ以前のバージョンからどう変化したのか? - 多くの引数を保守しやすく管理するにはどうしたらいいか?
を体系的に整理・検証することを目指します。
新入社員/若手社員向けのレクチャーや、これまで「何となく」使っていた引数を「効果的に」使えるようになるためのノウハウ集としてご活用下さい!
read_csv
の引数が49個もある理由
「ただCSV
を読み込むだけなのに、なぜそんなに多くの引数が必要なのか?」と気になった方も多いでしょう。
CSV(Comma-Separated Values)フォーマットは60年以上前の1960年代頃から使われ始めたらしく、現在でも身近なデータフォーマットの1つです。当時は国際標準が存在しなかったため、CSVの普及とともに、OSやアプリケーション、国や地域、ベンダーの違いによって微妙に異なる独自の記法が多数誕生しました。2005年10月にようやくRFC4180にて国際標準化されましたが、従来の独自記法は今も根強く存在しています。read_csv
の引数が他の関数と比べて異様に多いのは、これらの独自記法を可能な限り引数で制御しようとした結果となります。
そして、これだけ引数の数が多いとどの引数が重要なのか分かりにくく、初学者がいきなりread_csv
を使いこなすのはハードルが高い です。更に、pandas
のバージョンアップによって各引数が新規追加・仕様変更・非推奨/廃止になることがあり、使い慣れた経験者であっても 引数の変化を適時検証するのはやはり大変 です。
そこで、read_csv
を使いこなすための手引きを目指し、本記事を執筆いたしました。
検証環境
GoogleCloud VMのn2d-highmem-4 インスタンス(4vCPU, 32GBメモリ)で検証します。Linux OSのバージョンは以下の通りです。
$ cat /proc/version
Linux version 4.19.0-23-cloud-amd64 (debian-kernel@lists.debian.org) (gcc version 8.3.0 (Debian 8.3.0-6)) #1 SMP Debian 4.19.269-1 (2022-12-20)
Pythonはver3.11.0
、pandas
はver2.0.0
を使用します。ver2.0.0
は2023/4/3にメジャーアップデートしたばかりで、read_csv
に対しても重要な変更がいくつか行われました。
pip
コマンドでpandas
をインストールします。他のパッケージは任意です。本記事では以下用途で使用します。
-
pyarrow
:pyarrow
のデータ型として利用 -
s3fs
:AWS S3
に保管されたオブジェクトへのアクセス用 -
chardet
: ファイルの文字コード推定用 -
pyyaml
:yaml
ファイルの入出力用 -
memory_profiler
: メモリ消費量の計測用
$ pip install -q pandas==2.0.0 pyarrow s3fs chardet pyyaml memory_profiler
pandas
をインポートし、DataFrameの表示オプションを設定します。
import pandas as pd
pd.set_option('display.unicode.east_asian_width', True)
pd.set_option('display.width', 150)
検証のためのサンプルファイルを作成します。中身は、日本の気象データを模したものです。
$ cat sample.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
引数の解説
以下より、read_csv
の各引数を用途別に解説していきます。
基本
filepath_or_buffer
CSVファイルのパスを指定します。全引数の中で唯一の必須引数です。位置引数でもあるため、filepath_or_buffer='xxx.csv'
と指定しなくてもファイルパスとして認識されます。
最も一般的な指定方法は、ファイルパスを文字列で指定する方法でしょう。
df = pd.read_csv('sample.csv')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
file-likeオブジェクトにも対応しており、標準ライブラリのpathlib.Pathやos.pathオブジェクトも読み込めます。
from pathlib import Path
p = Path('sample.csv')
df = pd.read_csv(p)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
io.StringIOにも対応しています。CSVデータがメモリ上にある場合、StringIO
を使えば以下のようにメモリ上からダイレクトにDataFrame
へ変換できるので便利です。
from io import StringIO
with StringIO("""
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
""") as f:
df = pd.read_csv(f)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
ローカル上のファイルだけでなく、URL
やクラウドストレージ(S3
、GCS
など)上にあるリモートファイルも読み込むことができます。
以下は、国土交通省が公開している日本標準時基準の台風発生数データです。カスタムヘッダーが必要な場合は、後述するstorage_options
を別途指定します。
url = 'https://www.data.jma.go.jp/yoho/typhoon/statistics/generation/generation_j.csv'
df = pd.read_csv(url, encoding='shift-jis')
print(df)
年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月 年間
0 1951 NaN 1.0 1.0 2.0 1.0 1.0 3.0 3 2 4 1.0 2.0 21
1 1952 NaN NaN NaN NaN NaN 3.0 3.0 5 3 6 3.0 4.0 27
2 1953 NaN 1.0 NaN NaN 1.0 2.0 1.0 6 3 5 3.0 1.0 23
3 1954 NaN NaN 1.0 NaN 1.0 NaN 1.0 5 5 4 3.0 1.0 21
4 1955 1.0 1.0 1.0 1.0 NaN 2.0 7.0 6 4 3 1.0 1.0 28
.. ... ... ... ... ... ... ... ... ... ... ... ... ... ...
67 2018 1.0 1.0 1.0 NaN NaN 4.0 5.0 9 4 1 3.0 NaN 29
68 2019 1.0 1.0 NaN NaN NaN 1.0 4.0 5 6 4 6.0 1.0 29
69 2020 NaN NaN NaN NaN 1.0 1.0 NaN 8 3 6 3.0 1.0 23
70 2021 NaN 1.0 NaN 1.0 1.0 2.0 3.0 4 4 4 1.0 1.0 22
71 2022 NaN NaN NaN 2.0 NaN 1.0 3.0 5 7 5 1.0 1.0 25
[72 rows x 14 columns]
storage_options
リモートファイルへのアクセスに必要な情報(ホストの接続情報や、HTTP/HTTPSリクエスト時に設定するカスタムヘッダーなど)を辞書で設定します。
以下は、AWS S3
で公開されている「Amazonレビューのサンプルデータセット」オブジェクトにアクセスする例です。storage_options
には、オブジェクトに匿名でアクセスするための{'anon': True}
というオプションを付与しています。
url = 's3://amazon-reviews-pds/tsv/sample_us.tsv'
df = pd.read_csv(url, sep='\t', storage_options={'anon': True})
print(df.head(1).T)
0
marketplace US
customer_id 18778586
review_id RDIJS7QYB6XNR
product_id B00EDBY7X8
product_parent 122952789
product_title Monopoly Junior Board Game
product_category Toys
star_rating 5
helpful_votes 0
total_votes 0
vine N
verified_purchase Y
review_headline Five Stars
review_body Excellent!!!
review_date 2015-08-31
pandasの公式ドキュメントにて、各クラウド接続に必要な依存パッケージが公開されています。上記以外に指定可能なオプションは、記載パッケージの公式ドキュメントをご参照下さい。
sep / delimiter
区切り文字を指定します(delimiter
はsep
のエイリアスです)。デフォルトは,
です。
sep
には任意の一文字や正規表現を指定できるため、以下のようにカンマ以外の区切り文字にも対応できます。
$ cat sample_sep.csv
Date;Area;MinTemp;MaxTemp;WindSpeed;Rainfall
2023/4/1;東京;10.3;23.3;2.6;null
2023/4/1;大阪;10.4;25.1;2.3;null
2023/4/2;東京;11.1;19.0;3.2;0
2023/4/2;大阪;10.9;23.3;4.4;null
df = pd.read_csv('sample_sep.csv', sep=';')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
sep='\t'
と指定すれば、カンマの代わりにタブ(\t
)で区切られたTSV
ファイルを読み込めます(TSV
ファイル専用のpandas.read_table関数でも同じ結果が得られます)。
$ cat sample_sep.tsv
Date Area MinTemp MaxTemp WindSpeed Rainfall
2023/4/1 東京 10.3 23.3 2.6 null
2023/4/1 大阪 10.4 25.1 2.3 null
2023/4/2 東京 11.1 19.0 3.2 0
2023/4/2 大阪 10.9 23.3 4.4 null
df = pd.read_csv('sample_sep.tsv', sep='\t')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
sep=None
とすると、データから区切り文字を自動推定してくれます。自動推定するには、engine='python'
という引数(後述)も同時に指定します。
df = pd.read_csv('sample_sep.csv', sep=None, engine='python')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
delim_whitespace
スペースを区切り文字とするかどうかを指定します。デフォルトはFalse
(スペースで区切らない)です。
True
にするとsep='\s+'
と等価となり、各フィールドの値がスペースで区切られて読み込まれます。
$ cat sample_space.csv
Date Area MinTemp MaxTemp WindSpeed Rainfall
2023/4/1 東京 10.3 23.3 2.6 null
2023/4/1 大阪 10.4 25.1 2.3 null
2023/4/2 東京 11.1 19.0 3.2 0
2023/4/2 大阪 10.9 23.3 4.4 null
df = pd.read_csv('sample_space.csv', delim_whitespace=True)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
delim_whitespace
とsep
は、どちらか一方のみを指定します。両方を同時に指定するとValueError
が送出されます。
try:
df = pd.read_csv('sample_space.csv', delim_whitespace=True, sep=None)
except Exception as e:
print(repr(e))
ValueError('Specified a delimiter with both sep and delim_whitespace=True; you can only specify one.')
カラムとインデックスの指定
header
ヘッダーの行番号(0始まり)を指定します。デフォルトはheader='infer'
となっており、自動的に推定されます。
以下ファイルのように、ヘッダー領域にコメント行などが含まれている場合を想定します。
$ cat sample_header.csv
# comment
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
header
引数にヘッダーの行番号を指定します。1以上の値を指定した場合、それより上の行はスキップされます。
# 0行目のコメントをスキップし、1行目をヘッダーとして読み込む
df = pd.read_csv('sample_header.csv', header=1)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
マルチヘッダーの場合は、ヘッダー行番号のリストを指定することで、カラムがマルチインデックスとなります。
$ cat sample_multiheader.csv
# comment
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
# comment
年月日,観測地点,最低気温,最高気温,風速,降水量
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
# 0行目と2行目をスキップし、1行目と3行目をマルチヘッダーとして読み込む
df = pd.read_csv('sample_multiheader.csv', header=[1, 3])
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
年月日 観測地点 最低気温 最高気温 風速 降水量
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
ヘッダーが無い場合はheader=None
とします。カラム名には連番が自動付与されますが、後述するnames
引数で任意のカラム名で上書きできます。
$ cat sample_noheader.csv
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
df = pd.read_csv('sample_noheader.csv', header=None)
print(df)
0 1 2 3 4 5
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
names
カラム名を明示的に指定します。
ヘッダーがあるCSV
ファイルの場合は、header
でヘッダー行番号を明示的に指定し、names
に別名のリストを与えます。
names = ['年月日', '観測値', '最低気温', '最高気温', '風速', '降水量']
df = pd.read_csv('sample.csv', header=0, names=names)
print(df)
年月日 観測値 最低気温 最高気温 風速 降水量
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
ヘッダー無しファイルの場合は、以下のようにheader=None
とした上でカラム名を指定します。
df = pd.read_csv('sample_noheader.csv', header=None, names=names)
print(df)
年月日 観測値 最低気温 最高気温 風速 降水量
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
なお、データのフィールド数とlen(names)
が一致しない場合、エラーとならず、以下挙動となります。
len(names)
がレコードのフィールド数よりも多い場合、各フィールドの値はnames
の 左側 から順に紐づけられ、余ったカラムの値は全て欠損値となります。
df = pd.read_csv('sample_noheader.csv', header=None,
names=names + ['Extra'])
print(df)
年月日 観測値 最低気温 最高気温 風速 降水量 Extra
0 2023/4/1 東京 10.3 23.3 2.6 NaN NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0 NaN
3 2023/4/2 大阪 10.9 23.3 4.4 NaN NaN
len(names)
がカラム数よりも少ない場合、各フィールドの値はnames
の 右側 から順に紐づけられ、余ったデータはインデックスとなります。
df = pd.read_csv('sample_noheader.csv', header=None,
names=names[:-1])
print(df)
年月日 観測値 最低気温 最高気温 風速
2023/4/1 東京 10.3 23.3 2.6 NaN
2023/4/1 大阪 10.4 25.1 2.3 NaN
2023/4/2 東京 11.1 19.0 3.2 0.0
2023/4/2 大阪 10.9 23.3 4.4 NaN
「names
を指定するとカラムとデータの値がずれてしまう」といった事象が起きたら、データのフィールド数とlen(names)
が一致しているか確認しましょう!
index_col
インデックスとするカラムを指定します。デフォルトはNone
です。
カラム名もしくは列番号を指定すると、そのカラムがインデックスとなります。
df = pd.read_csv('sample.csv', index_col='Date')
print(df)
Area MinTemp MaxTemp WindSpeed Rainfall
Date
2023/4/1 東京 10.3 23.3 2.6 NaN
2023/4/1 大阪 10.4 25.1 2.3 NaN
2023/4/2 東京 11.1 19.0 3.2 0.0
2023/4/2 大阪 10.9 23.3 4.4 NaN
index_col
にリストを指定すると、マルチインデックスとなります。
df = pd.read_csv('sample.csv', index_col=['Date', 'Area'])
print(df)
MinTemp MaxTemp WindSpeed Rainfall
Date Area
2023/4/1 東京 10.3 23.3 2.6 NaN
大阪 10.4 25.1 2.3 NaN
2023/4/2 東京 11.1 19.0 3.2 0.0
大阪 10.9 23.3 4.4 NaN
index_col=False
とすると、カラムのインデックス化を無効化できます。
例えば、以下のようにヘッダー以外のレコード末尾にカンマが付与されたトリッキーなCSVがあるとします。
$ cat sample_malformed.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null,
2023/4/1,大阪,10.4,25.1,2.3,null,
2023/4/2,東京,11.1,19.0,3.2,0,
2023/4/2,大阪,10.9,23.3,4.4,null,
そのまま読み込むと、レコードのフィールド数(7)とヘッダーのカラム数(6)が一致しないため、左のDate
列が自動的にインデックスとなってしまいます。
df = pd.read_csv('sample_malformed.csv')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
2023/4/1 東京 10.3 23.3 2.6 NaN NaN
2023/4/1 大阪 10.4 25.1 2.3 NaN NaN
2023/4/2 東京 11.1 19.0 3.2 0.0 NaN
2023/4/2 大阪 10.9 23.3 4.4 NaN NaN
index_col=False
とすることでDate
カラムはインデックス化されず、正しく読み込むことができます。
df = pd.read_csv('sample_malformed.csv', index_col=False)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
「CSVファイルを読み込むと、なぜか自動的にカラムがインデックスになってしまう」という事象が起きた際は、フィールド数とカラム数が一致しているか確認しましょう!
usecols
読み込むカラムを、列番号かカラム名のリストで指定します。デフォルトはNone
で、全てのカラムが読み込まれます。
読み込みたいカラムが限定的な場合、usecols
で絞り込むことでメモリ消費量を抑える効果があります。
usecols=['Date', 'Area', 'MinTemp', 'MaxTemp']
df = pd.read_csv('sample.csv', usecols=usecols)
print(df)
Date Area MinTemp MaxTemp
0 2023/4/1 東京 10.3 23.3
1 2023/4/1 大阪 10.4 25.1
2 2023/4/2 東京 11.1 19.0
3 2023/4/2 大阪 10.9 23.3
usecols
に関数を指定すると、関数がTrue
(もしくはTrue
と評価されるオブジェクト)を返すカラムのみが読み込まれます。
カラム数が数百~と大量にあると、1つずつ明示的に指定するのは大変です。カラム名にパターンがある場合は、以下のように関数と正規表現を組合わせた方がコードがスッキリします。
$ cat sample_manycolumns.csv
a_1,a_2,a_3,b_1,b_2,b_3,c_1,c_2,c_3,d_1,d_2,d_3
1,2,3,4,5,6,7,8,9,10,11,12
1,2,3,4,5,6,7,8,9,10,11,12
1,2,3,4,5,6,7,8,9,10,11,12
import re
# カラム名の1文字目がa/c/dのいずれか、3文字目が2/3のいずれかのカラムのみ読み込む
usecols = lambda column: re.search('[a|c|d]_[2|3]', column)
df = pd.read_csv('sample_manycolumns.csv', usecols=usecols)
print(df)
a_2 a_3 c_2 c_3 d_2 d_3
0 2 3 8 9 11 12
1 2 3 8 9 11 12
2 2 3 8 9 11 12
squeeze / prefix / mangle_dupe_cols
これらの引数はver1.5時点で非推奨となっています。同様の処理を行いたい場合は、以下の代替手段を参考にして下さい。
引数名 | 効果 | 代替手段 |
---|---|---|
squeeze |
カラムが1列の場合、Series として読み込む |
pandas.DataFrame.squeezeを使用 |
prefix |
カラム名に接頭辞を追加 | pandas.DataFrame.add_prefixを使用 |
mangle_dupe_cols |
重複するカラム名の末尾に連番を付与 | 新しい引数を検討中(GH#47718) |
データ変換
dtype
カラムのデータ型を指定します。デフォルトはNone
で、データから自動的に推定されます。
適切なデータ型を明示的に指定することで、メモリ消費量を抑える効果があります。
カラム名をキー、データ型を値とする辞書をdtype
に指定すると、カラム毎に指定したデータ型に変換されます。データ型には
- Pythonのintやfloat、str、boolクラスなど
-
Numpy
のデータ型(np.int8やnp.float32など) -
Pandas
のデータ型(CategoricalDtypeなど)
といった、様々なデータ型を指定することができます。
import numpy as np
dtype={'MinTemp': np.float32, 'MaxTemp': np.float32}
df = pd.read_csv('sample.csv', dtype=dtype)
print(df.dtypes)
Date object
Area object
MinTemp float32
MaxTemp float32
WindSpeed float64
Rainfall float64
dtype: object
dtype
に単一のデータ型を指定すると、全カラムが同じデータ型に変換されます。
df = pd.read_csv('sample.csv', dtype=str)
print(df.dtypes)
Date object
Area object
MinTemp object
MaxTemp object
WindSpeed object
Rainfall object
dtype: object
変換できない値が含まれているとValueError
が送出され、変換に失敗した列番号がエラーメッセージに出力されます。
try:
df = pd.read_csv('sample.csv', dtype=bool)
except Exception as e:
print(repr(e))
ValueError('cannot safely convert passed user dtype of bool for object dtyped data in column 0')
dtype_backend
データ型のバックエンドを指定します。ver2.0で新たに追加された実験的な引数です。
通常はnumpy
のデータ型が採用されますが、新たにnumpy_nullable
とpyarrow
の2つを指定できるようになりました。
まずは、バックエンドをnumpy_nullable
で読み込んでみます。
df = pd.read_csv('sample.csv', dtype_backend='numpy_nullable')
print(df)
print('-' * 60)
print(df.dtypes)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 <NA>
1 2023/4/1 大阪 10.4 25.1 2.3 <NA>
2 2023/4/2 東京 11.1 19.0 3.2 0
3 2023/4/2 大阪 10.9 23.3 4.4 <NA>
------------------------------------------------------------
Date string[python]
Area string[python]
MinTemp Float64
MaxTemp Float64
WindSpeed Float64
Rainfall Int64
dtype: object
欠損値がNaN
ではなく<NA>
となりました。<NA>
とは、pandas
のver1.0から採用された新しい欠損値です。
df.dtypes
でデータ型を確認すると、整数カラムと浮動小数点数カラムのデータ型が従来のint64
/float64
ではなく、Int64
/Float64
(いずれも先頭が大文字)となっています。Int64(Int64Dtype)やFloat64(Float64Dtype)は、どちらも<NA>
をサポートする新しいデータ型です。
続いて、バックエンドをpyarrow
で読み込んでみます。
df = pd.read_csv('sample.csv', dtype_backend='pyarrow')
print(df)
print('-' * 60)
print(df.dtypes)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 <NA>
1 2023/4/1 大阪 10.4 25.1 2.3 <NA>
2 2023/4/2 東京 11.1 19.0 3.2 0
3 2023/4/2 大阪 10.9 23.3 4.4 <NA>
------------------------------------------------------------
Date string[pyarrow]
Area string[pyarrow]
MinTemp double[pyarrow]
MaxTemp double[pyarrow]
WindSpeed double[pyarrow]
Rainfall int64[pyarrow]
dtype: object
欠損値が<NA>
なのはnumpy_nullable
と同じですが、データ型は全てpyarrow
データ型のラッパーであるpandas.ArrowDtypeになりました。
pyarrowでは他にも、
- 日付を32bitで表現するdate32
- 任意の有効桁数を指定可能なdecimal128
- バイナリデータに特化したbinary
など、pandas
やnumpy
には無い多様なデータ型を多数提供しています。pandas
を使いつつpyarrow
のデータ型も活用したい、というシーンに有効です。
なお、dtype_backend
とdtype
を同時に指定した場合、dtype
で指定したデータ型が優先されます。
df = pd.read_csv('sample.csv', dtype_backend='pyarrow',
dtype={'Area': 'category', 'MinTemp': np.float32})
print(df)
print('-' * 60)
print(df.dtypes)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 <NA>
1 2023/4/1 大阪 10.4 25.1 2.3 <NA>
2 2023/4/2 東京 11.1 19.0 3.2 0
3 2023/4/2 大阪 10.9 23.3 4.4 <NA>
------------------------------------------------------------
Date string[pyarrow]
Area category
MinTemp float32
MaxTemp double[pyarrow]
WindSpeed double[pyarrow]
Rainfall int64[pyarrow]
dtype: object
engine
CSVパーサーエンジンの種類を選択できます。現在サポートされているエンジンはc
、python
、pyarrow
の3種類です。
各エンジンの特徴は以下の通りです。明示的に指定しない場合はc
が適用されます。
エンジン | メリット | デメリット |
---|---|---|
c |
高速 | 区切り文字(sep )を自動推定できないskipfooter やon_bad_lines 等の他引数と併用できない |
python |
全引数をサポート | 他パーサーより低速 |
pyarrow |
高速 マルチスレッド対応 |
実験段階であり、多数引数が実装中(GH38872) |
ただし、engine='c'
と明示的に指定しても、c
エンジンが処理できないデータや引数が含まれていた場合は以下のようにParserWarning
が送出され、python
エンジンにフォールバックすることがあります。
df = pd.read_csv('sample_sep.csv', sep=None)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
/tmp/ipykernel_6667/3161112102.py:1: ParserWarning: Falling back to the 'python' engine because the 'c' engine does not support sep=None with delim_whitespace=False; you can avoid this warning by specifying engine='python'.
df = pd.read_csv('sample_sep.csv', sep=None)
各エンジンのパフォーマンス検証結果は、次章をご参照下さい。
converters
カラム毎に個別の変換処理を指定することができます。デフォルトはNone
です。
変換には、キーをカラム名(もしくは列番号)、値を変換関数とする辞書を指定します。
# 最低気温と最高気温を、摂氏(℃)から華氏(℉)に変換
c2f = lambda c: float(c) * 1.8 + 32
df = pd.read_csv('sample.csv', converters={'MinTemp': c2f, 'MinTemp': c2f})
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 50.54 23.3 2.6 NaN
1 2023/4/1 大阪 50.72 25.1 2.3 NaN
2 2023/4/2 東京 51.98 19.0 3.2 0.0
3 2023/4/2 大阪 51.62 23.3 4.4 NaN
converters
とdtype
で同じカラムを同時指定した場合、converters
が優先され、dtype
は無視されます(ParserWarning
が送出されます)。
df = pd.read_csv('sample.csv', dtype={'MinTemp': bool},
converters={'MinTemp': c2f, 'MinTemp': c2f})
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 50.54 23.3 2.6 NaN
1 2023/4/1 大阪 50.72 25.1 2.3 NaN
2 2023/4/2 東京 51.98 19.0 3.2 0.0
3 2023/4/2 大阪 51.62 23.3 4.4 NaN
/tmp/ipykernel_6667/259361885.py:1: ParserWarning: Both a converter and dtype were specified for column MinTemp - only the converter will be used.
df = pd.read_csv('sample.csv', dtype={'MinTemp': bool},
true_values / false_values
True/False
に置換する値を文字列のリストで指定します。どちらもデフォルトはNone
です。
この置換処理は、置換後のカラムがbool
にキャスト可能な場合のみ行われます。
df = pd.read_csv('sample.csv', true_values=['大阪'],
false_values=['東京'])
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 False 10.3 23.3 2.6 NaN
1 2023/4/1 True 10.4 25.1 2.3 NaN
2 2023/4/2 False 11.1 19.0 3.2 0.0
3 2023/4/2 True 10.9 23.3 4.4 NaN
以下のように、置換後のカラムTrue/False
以外の値が残ってしまう場合、置換処理は行われません。
df = pd.read_csv('sample.csv', true_values=['東京'])
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
skipinitialspace
区切り文字の末尾がスペースの場合、スペースをスキップするかどうかを指定します。デフォルトはFalse
(スキップしない)です。
以下のように、カンマの直後にスペースが挿入されているCSVファイルを用意します。
$ cat sample_initialspace.csv
Date, Area, MinTemp, MaxTemp, WindSpeed, Rainfall
2023/4/1, 東京, 10.3, 23.3, 2.6, null
2023/4/1, 大阪, 10.4, 25.1, 2.3, null
2023/4/2, 東京, 11.1, 19.0, 3.2, 0
2023/4/2, 大阪, 10.9, 23.3, 4.4, null
デフォルト(skipinitialspace=False
)では、カラムや値の先頭にスペースがそのまま付与されてしまいます。
df = pd.read_csv('sample_initialspace.csv')
print(list(df.columns))
['Date', ' Area', ' MinTemp', ' MaxTemp', ' WindSpeed', ' Rainfall']
skipinitialspace=True
と指定することで、先頭のスペースを除去することができます。
df = pd.read_csv('sample_initialspace.csv', skipinitialspace=True)
print(list(df.columns))
['Date', 'Area', 'MinTemp', 'MaxTemp', 'WindSpeed', 'Rainfall']
skiprows
読み飛ばす行番号(0始まり)を指定します。デフォルトはNone
です。
整数のリストを指定すると、リストで指定された行番号のみがスキップされます。
df = pd.read_csv('sample.csv', skiprows=[1])
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 大阪 10.4 25.1 2.3 NaN
1 2023/4/2 東京 11.1 19.0 3.2 0.0
2 2023/4/2 大阪 10.9 23.3 4.4 NaN
整数N
を指定すると、ファイルの先頭N
行(ヘッダー行を含む)がスキップされます。
df = pd.read_csv('sample.csv', skiprows=2, header=None)
print(df)
0 1 2 3 4 5
0 2023/4/1 大阪 10.4 25.1 2.3 NaN
1 2023/4/2 東京 11.1 19.0 3.2 0.0
2 2023/4/2 大阪 10.9 23.3 4.4 NaN
skiprows
に関数を指定すると、関数がTrue
(もしくはTrue
と評価されるオブジェクト)を返すレコードのみが読み込まれます。
以下は、ファイルの偶数行のみを読み込む例です。
skiprows = lambda n: n % 2
df = pd.read_csv('sample.csv', skiprows=skiprows)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 大阪 10.4 25.1 2.3 NaN
1 2023/4/2 大阪 10.9 23.3 4.4 NaN
skiprows
とheader
を同時に指定する場合、skiprows
⇒header
の順に実行される点に注意が必要です。この場合、header
で指定する行番号は、ファイルの行番号ではなく、skiprows
によりスキップされた後の行番号を指定 します。
sample_multiheader.csv
のデータ内容を再掲の上、skiprows
とheader
を同時に指定してみます。
$ cat sample_multiheader.csv
# comment
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
# comment
年月日,観測地点,最低気温,最高気温,風速,降水量
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
df = pd.read_csv('sample_multiheader.csv',
skiprows=[0, 2], header=[0, 1])
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
年月日 観測地点 最低気温 最高気温 風速 降水量
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
skipfooter
フッター行数を整数で指定します。デフォルトは0
です。
指定すると、ファイル末尾のフッターレコードがスキップされます。skipfooter
は、engine='python'
でのみ有効です。
$ cat sample_footer.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
# footer
df = pd.read_csv('sample_footer.csv', skipfooter=1,
engine='python')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
nrows
読み込むレコード数(ヘッダー行数を除く)を指定します。
データ分析等で大きなCSVファイルの中身をざっと確認したい時、最初にnrows
でファイルの先頭部分のみを読み込んでデータ内容を確認するのに重宝します。
df = pd.read_csv('sample.csv', nrows=2)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
low_memory
データ型の推定方法を指定します。
デフォルトはTrue
で、ファイルを部分的に読み込みながらデータ型を推定します。一度に全データを使用しないため、メモリ消費量を抑える効果がありますが、推定されたデータ型と異なる値が後から読み込まれる可能性があります(その場合、以下のようにDtypeWarning
が送出されます)。
# 最初の100万行は数字、末尾の1行だけ文字列のデータを生成して読み込む
with StringIO('\n'.join('x' + '0'*1000000 + 'a')) as f:
df = pd.read_csv(f)
print(df.dtypes)
x object
dtype: object
/tmp/ipykernel_6667/2567239896.py:3: DtypeWarning: Columns (0) have mixed types. Specify dtype option on import or set low_memory=False.
df = pd.read_csv(f)
一方、low_memory=False
とすると、データ全体を読み込んでからデータ型が推定されます。メモリ消費量は増加しますが、正確なデータ型を特定することが可能です。
with StringIO('\n'.join('x' + '0'*1000000 + 'a')) as f:
df = pd.read_csv(f, low_memory=False)
print(df.dtypes)
x object
dtype: object
memory_map
ファイルオブジェクトをメモリに直接マッピングするかどうかを指定します。デフォルトはFalse
で、filepath_or_buffer
引数にファイルパスが指定された場合にのみ有効です(io.StringIO
等のメモリ上にあるデータは対象外)。
True
に設定するとディスクI/Oのオーバーヘッドがなくなり、パフォーマンスが向上することがあります。
効果を確認するため、Amazon Customer Reviews Datasetから日本語のAmazonレビューデータをダウンロードして使用します。データ件数は約260万行です。
$ wget -q https://s3.amazonaws.com/amazon-reviews-pds/tsv/amazon_reviews_multilingual_JP_v1_00.tsv.gz
$ gzip -df amazon_reviews_multilingual_JP_v1_00.tsv.gz
$ wc -l amazon_reviews_multilingual_JP_v1_00.tsv
262432 amazon_reviews_multilingual_JP_v1_00.tsv
読み込み時間を計測すると、平均値ではmemory_map=True
が若干早いようですが、標準偏差も含めると明確な変化は見られませんでした。
filename = 'amazon_reviews_multilingual_JP_v1_00.tsv'
%timeit pd.read_csv(filename, sep='\t', encoding='utf-8', memory_map=False)
%timeit pd.read_csv(filename, sep='\t', encoding='utf-8', memory_map=True)
2.59 s ± 226 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
2.47 s ± 37.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
欠損値(NA/NaN
)のハンドリング
na_values
欠損値とみなす値を指定します。デフォルトはNone
です。
pandasでは以下の文字列群が「欠損値と見なす値」として事前定義されています。本記事ではこれを「欠損値リスト」と呼ぶことにします。
print(pd._libs.parsers.STR_NA_VALUES)
{'', 'nan', 'NaN', '<NA>', '-1.#QNAN', '-NaN', 'n/a', 'NA', 'null', 'NULL', 'N/A', '#N/A N/A', '#N/A', '1.#IND', '-1.#IND', '#NA', '1.#QNAN', '-nan', 'None'}
na_values
に単一の値を指定すると、指定値が欠損値に置換されます。
df = pd.read_csv('sample.csv', na_values=0.0)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 NaN
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
リストを指定すると、リスト内の値が欠損値に置換されます。
df = pd.read_csv('sample.csv', na_values=['東京', 0.0])
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 NaN 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 NaN 11.1 19.0 3.2 NaN
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
辞書を指定すると、カラム毎に異なる欠損値を指定することができます。
df = pd.read_csv('sample.csv',
na_values={'Area': '東京', 'MinTemp': [10.4, 10.9]})
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 NaN 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 NaN 25.1 2.3 NaN
2 2023/4/2 NaN 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 NaN 23.3 4.4 NaN
keep_default_na
欠損値リストを使用するかどうかを指定します。デフォルトはTrue
(使用する)です。
ほとんどの値が欠損値のファイルを作成して検証します。
$ cat sample_nan.csv
A,B,C,D,E
1,2,3,4,5
,N/A,NULL,-nan,None
1.#IND,NaN,-1.#QNAN,<NA>,null
-NaN,#N/A N/A,1.#QNAN,-1.#IND,#N/A
NA,nan,#NA,n/a,
デフォルト(keep_default_na=True, na_values=None
)では、欠損値リストの値のみが欠損値に自動変換されます。
df = pd.read_csv('sample_nan.csv')
print(df)
A B C D E
0 1.0 2.0 3.0 4.0 5.0
1 NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN
na_values
引数を指定すると、指定した値も併せて欠損値に置換されます。
df = pd.read_csv('sample_nan.csv', na_values=[2, 4])
print(df)
A B C D E
0 1.0 NaN 3.0 NaN 5.0
1 NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN NaN
keep_default_na=False
にすると、欠損値リストの値は欠損値に置換されません。
df = pd.read_csv('sample_nan.csv', keep_default_na=False)
print(df)
A B C D E
0 1 2 3 4 5
1 N/A NULL -nan None
2 1.#IND NaN -1.#QNAN <NA> null
3 -NaN #N/A N/A 1.#QNAN -1.#IND #N/A
4 NA nan #NA n/a
keep_default_na=False
の状態でna_values
を指定すると、指定値のみが欠損値に置換されます。
df = pd.read_csv('sample_nan.csv', keep_default_na=False,
na_values=[2, 4])
print(df)
A B C D E
0 1 NaN 3 NaN 5
1 N/A NULL -nan None
2 1.#IND NaN -1.#QNAN <NA> null
3 -NaN #N/A N/A 1.#QNAN -1.#IND #N/A
4 NA nan #NA n/a
na_filter
欠損値の置換処理を有効化するかどうかを指定します。デフォルトはTrue
(有効)です。
False
にすると置換処理は無効化されます。
無効化するとファイル読み込みのパフォーマンスが向上しますので、前処理済みデータのように「欠損値が含まれていないことが担保されている」CSVファイルには効果的です。
df = pd.read_csv('sample_nan.csv', na_filter=False)
print(df)
A B C D E
0 1 2 3 4 5
1 N/A NULL -nan None
2 1.#IND NaN -1.#QNAN <NA> null
3 -NaN #N/A N/A 1.#QNAN -1.#IND #N/A
4 NA nan #NA n/a
なお、na_filter=False
にすると、keep_default_na
とna_values
は無視されます。警告も出力されません。
df = pd.read_csv('sample_nan.csv', na_filter=False,
keep_default_na=True, na_values=[2, 4])
print(df)
A B C D E
0 1 2 3 4 5
1 N/A NULL -nan None
2 1.#IND NaN -1.#QNAN <NA> null
3 -NaN #N/A N/A 1.#QNAN -1.#IND #N/A
4 NA nan #NA n/a
verbose
欠損値の処理に要した時間が出力されます。デフォルトはFalse
です。
CSV
ファイルの読み込みが遅い場合にverbose=True
とすることで、欠損値処理のオーバーヘッドをデバッグするのに役立ちそうです。
df = pd.read_csv('sample_nan.csv', verbose=True)
Tokenization took: 0.01 ms
Type conversion took: 0.25 ms
Parser memory cleanup took: 0.00 ms
skip_blank_lines
空行をスキップするかどうかを指定します。デフォルトはTrue
(スキップする)です。
空行を含む以下ファイルで検証します。
$ cat sample_blankline.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
df = pd.read_csv('sample_blankline.csv')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
False
とすると、空行は全て欠損値として扱われます。
df = pd.read_csv('sample_blankline.csv', skip_blank_lines=False)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 NaN NaN NaN NaN NaN NaN
1 2023/4/1 東京 10.3 23.3 2.6 NaN
2 2023/4/1 大阪 10.4 25.1 2.3 NaN
3 NaN NaN NaN NaN NaN NaN
4 2023/4/2 東京 11.1 19.0 3.2 0.0
5 2023/4/2 大阪 10.9 23.3 4.4 NaN
日付・時刻のハンドリング
CSV
ファイルの文字列から日付・時刻に変換する引数を解説します。
様々なフォーマットで日付・時刻を表現した以下サンプルファイルで検証します。各カラムは以下の通りです。
-
iso8601
: 国際規格であるISO8601による表記 -
year
/month
/day
/time
: 年/月/日/時刻を各カラムに分割 -
dayfirst
: イギリス式と呼ばれる、DD/MM/YYYY
(先頭が日)による表記 -
japanese
: 日本語による表記
$ cat sample_datetime.csv
iso8601,year,month,day,time,dayfirst,japanese
2023-04-01T00:00:00,2023,5,1,00:00:00,1/6/2023 00:00:00,2023年7月1日 00時00分00秒
2023-04-02T12:34:56,2023,5,2,12:34:56,2/6/2023 12:34:56,2023年7月2日 12時34分56秒
2023-04-03T23:59:59,2023,5,3,23:59:59,3/6/2023 23:59:59,2023年7月3日 23時59分59秒
デフォルトの引数では、どのカラムもdatetime
型ではなく、object
もしくはint64
型で読み込まれます。
df = pd.read_csv('sample_datetime.csv')
print(df)
iso8601 year month day time dayfirst japanese
0 2023-04-01T00:00:00 2023 5 1 00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-04-02T12:34:56 2023 5 2 12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-04-03T23:59:59 2023 5 3 23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
parse_dates
日付とするカラムを指定します。デフォルトはNone
です。
カラム名か列番号のリストを指定すると、該当カラムがnumpy.datetime64に変換されます。
df = pd.read_csv('sample_datetime.csv', parse_dates=['iso8601'])
print(df)
iso8601 year month day time dayfirst japanese
0 2023-04-01 00:00:00 2023 5 1 00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-04-02 12:34:56 2023 5 2 12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-04-03 23:59:59 2023 5 3 23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
日付列をindex_col
で指定してparse_dates=True
とすると、インデックスがdatetime
に変換されます。
df = pd.read_csv('sample_datetime.csv', index_col='iso8601', parse_dates=True)
print(df)
year month day time dayfirst japanese
iso8601
2023-04-01 00:00:00 2023 5 1 00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
2023-04-02 12:34:56 2023 5 2 12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2023-04-03 23:59:59 2023 5 3 23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
2重リストで指定すると、複数のカラムを単一のdatetime
カラムに変換できます。
year
/month
/day
の各カラムを1つの日付に変換するには、以下のようにします。変換後のカラム名は、元のカラム名を_
で結合したものです。
df = pd.read_csv('sample_datetime.csv', parse_dates=[['year', 'month', 'day']])
print(df)
year_month_day iso8601 time dayfirst japanese
0 2023-05-01 2023-04-01T00:00:00 00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-05-02 2023-04-02T12:34:56 12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-05-03 2023-04-03T23:59:59 23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
複数カラムの変換結果は、DataFrame
の0カラム目に挿入されます。入力ファイルのカラム順と一致しなくなりますのでご注意下さい。
日付と時刻を結合することも可能です。
df = pd.read_csv('sample_datetime.csv', parse_dates=[['year', 'month', 'day', 'time']])
print(df)
year_month_day_time iso8601 dayfirst japanese
0 2023-05-01 00:00:00 2023-04-01T00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-05-02 12:34:56 2023-04-02T12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-05-03 23:59:59 2023-04-03T23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
parse_dates
が日付推定できなかった場合、dateutil.parser.parse
パーサーにフォールバックして再度推定されます。
df = pd.read_csv('sample_datetime.csv', parse_dates=[['year', 'month']])
print(df)
year_month iso8601 day time dayfirst japanese
0 2023-05-01 2023-04-01T00:00:00 1 00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-05-01 2023-04-02T12:34:56 2 12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-05-01 2023-04-03T23:59:59 3 23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
/tmp/ipykernel_6667/2411069377.py:1: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
df = pd.read_csv('sample_datetime.csv', parse_dates=[['year', 'month']])
辞書を指定すれば、変換後のカラム名を任意の名前に変更できます。
df = pd.read_csv('sample_datetime.csv',
parse_dates={'Date': ['year', 'month', 'day'],
'Datetime': ['year', 'month', 'day', 'time']})
print(df)
Date Datetime iso8601 dayfirst japanese
0 2023-05-01 2023-05-01 00:00:00 2023-04-01T00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-05-02 2023-05-02 12:34:56 2023-04-02T12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-05-03 2023-05-03 23:59:59 2023-04-03T23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
infer_datetime_format / date_parser
どちらの引数もver2.0で非推奨となりました。代わりに、同バージョンで新たに追加されたdate_format
引数を使用して下さい。
keep_date_col
parse_dates
で日付変換する場合、変換前のカラムを残すかどうかを指定します。デフォルトはFalse
(残さない)です。
True
に設定すると、変換前のカラムは削除されません。
df = pd.read_csv('sample_datetime.csv', parse_dates=[['year', 'month', 'day']],
keep_date_col=True)
print(df)
year_month_day iso8601 year month day time dayfirst japanese
0 2023-05-01 2023-04-01T00:00:00 2023 5 1 00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-05-02 2023-04-02T12:34:56 2023 5 2 12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-05-03 2023-04-03T23:59:59 2023 5 3 23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
dayfirst
DD/MM/YYYY
な日付フォーマットの変換有無を指定します。デフォルトはFalse
(変換しない)です。
サンプルデータのdayfirst
カラムを日付変換すると、デフォルトではMM/DD/YYYY
で変換されてしまい、月と日が逆転してしまいます。
df = pd.read_csv('sample_datetime.csv', parse_dates=['dayfirst'])
print(df)
iso8601 year month day time dayfirst japanese
0 2023-04-01T00:00:00 2023 5 1 00:00:00 2023-01-06 00:00:00 2023年7月1日 00時00分00秒
1 2023-04-02T12:34:56 2023 5 2 12:34:56 2023-02-06 12:34:56 2023年7月2日 12時34分56秒
2 2023-04-03T23:59:59 2023 5 3 23:59:59 2023-03-06 23:59:59 2023年7月3日 23時59分59秒
True
に指定することで、正しい日付で変換されます。
df = pd.read_csv('sample_datetime.csv', parse_dates=['dayfirst'], dayfirst=True)
print(df)
iso8601 year month day time dayfirst japanese
0 2023-04-01T00:00:00 2023 5 1 00:00:00 2023-06-01 00:00:00 2023年7月1日 00時00分00秒
1 2023-04-02T12:34:56 2023 5 2 12:34:56 2023-06-02 12:34:56 2023年7月2日 12時34分56秒
2 2023-04-03T23:59:59 2023 5 3 23:59:59 2023-06-03 23:59:59 2023年7月3日 23時59分59秒
date_format
日付フォーマットを文字列で指定します。ver2.0で新たに追加されました。デフォルトはNone
です。
指定したフォーマットに従って日付変換が実行されます。この引数は、parse_dates
と併せて使用します。
日本語で表記されたjapanese
カラムも、正しい日付で変換できます。
df = pd.read_csv('sample_datetime.csv', parse_dates=['japanese'],
date_format='%Y年%m月%d日 %H時%M分%S秒')
print(df)
iso8601 year month day time dayfirst japanese
0 2023-04-01T00:00:00 2023 5 1 00:00:00 1/6/2023 00:00:00 2023-07-01 00:00:00
1 2023-04-02T12:34:56 2023 5 2 12:34:56 2/6/2023 12:34:56 2023-07-02 12:34:56
2 2023-04-03T23:59:59 2023 5 3 23:59:59 3/6/2023 23:59:59 2023-07-03 23:59:59
date_format
には任意の書式の他、ISO8601
やmixed
といった指定も可能です。
ここまで解説した引数を活用し、サンプルデータの全カラムをdatetime
に変換するには以下のようにします。dayfirst=True
が全てのカラムに適用されてしまうため、datetime1
とdatetime2
には明示的にISO8601
と指定しています。
df = pd.read_csv('sample_datetime.csv', dayfirst=True,
parse_dates={
'datetime1': ['iso8601'],
'datetime2': ['year', 'month', 'day', 'time'],
'datetime3': ['dayfirst'],
'datetime4': ['japanese']
},
date_format={
'datetime1': 'ISO8601',
'datetime2': 'ISO8601',
'datetime4': '%Y年%m月%d日 %H時%M分%S秒'
})
print(df)
datetime1 datetime2 datetime3 datetime4
0 2023-04-01 00:00:00 2023-05-01 00:00:00 2023-06-01 00:00:00 2023-07-01 00:00:00
1 2023-04-02 12:34:56 2023-05-02 12:34:56 2023-06-02 12:34:56 2023-07-02 12:34:56
2 2023-04-03 23:59:59 2023-05-03 23:59:59 2023-06-03 23:59:59 2023-07-03 23:59:59
cache_dates
日付の変換結果をキャッシュするかどうかを指定します。デフォルトはTrue
(キャッシュする)です。
False
でキャッシュを無効化できます。
df = pd.read_csv('sample_datetime.csv', parse_dates=['iso8601'], cache_dates=False)
print(df)
iso8601 year month day time dayfirst japanese
0 2023-04-01 00:00:00 2023 5 1 00:00:00 1/6/2023 00:00:00 2023年7月1日 00時00分00秒
1 2023-04-02 12:34:56 2023 5 2 12:34:56 2/6/2023 12:34:56 2023年7月2日 12時34分56秒
2 2023-04-03 23:59:59 2023 5 3 23:59:59 3/6/2023 23:59:59 2023年7月3日 23時59分59秒
重複する日付がファイルに多数含まれている場合、キャッシュされた変換結果を再利用することで、読み込み時間を短縮できます。キャッシュ効果の検証結果は次章をご参照下さい。
イテレーション
CSV
ファイルの分割読み込みを指定します。
最初の気象サンプルデータを再掲します。
$ cat sample.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
iterator / chunksize
CSVファイルの分割読み込みを指定します。iterator
のデフォルトはFalse
(分割しない)です。
True
に設定すると、ファイル全体を一度に読み込まず、代わりにTextFileReader
というイテレータを返します。
reader = pd.read_csv('sample.csv', iterator=True)
print(type(reader))
<class 'pandas.io.parsers.readers.TextFileReader'>
ループ処理と組み合わせることで、TextFileReader
からDataFrame
を順次取得することができます。chunksize
に整数を指定することで、指定行数ずつ順に読み込まれます。
reader = pd.read_csv('sample.csv', iterator=True, chunksize=2)
for df in reader:
print(df)
print('-'*60)
reader.close()
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
------------------------------------------------------------
Date Area MinTemp MaxTemp WindSpeed Rainfall
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
------------------------------------------------------------
コンテキストマネージャーも使用できます。イテレーション処理の成功・失敗に関わらずTextFileReader
を必ずcloseしたい場合に便利です。
with pd.read_csv('sample.csv', iterator=True, chunksize=3) as reader:
for df in reader:
print(df)
print('-'*60)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
------------------------------------------------------------
Date Area MinTemp MaxTemp WindSpeed Rainfall
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
------------------------------------------------------------
読み込む行数を固定せず変動させたい時は、TextFileReader
のget_chunk
という関数を使います。get_chunk
に指定した行数が順次読み込まれます。
reader = pd.read_csv('sample.csv', iterator=True)
print(reader.get_chunk(1))
print('-'*60)
print(reader.get_chunk(3))
reader.close()
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
------------------------------------------------------------
Date Area MinTemp MaxTemp WindSpeed Rainfall
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
このように、iterator
やchunksize
は一度にメモリに収まらないような巨大なCSVファイルを分割処理したいときに便利です!
圧縮フォーマット、クォート、文字コードなど
CSV
ファイルの圧縮フォーマットや文字コード、クォート文字のパース方法などを指定します。
compression
ファイルの圧縮フォーマットを指定します。対応済みの圧縮フォーマットは以下の通りです。Zstdはver1.4、Tarはver1.5でそれぞれ追加されました。
print(pd.io.common._supported_compressions)
{'xz', 'bz2', 'tar', 'gzip', 'zip', 'zstd'}
compression
のデフォルトはinfer
です。ファイル拡張子から自動的に圧縮フォーマットが推定され、オンザフライで解凍して読み込まれます。
$ gzip -kf sample.csv
df = pd.read_csv('sample.csv.gz')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
compression='gzip'
のように明示的に指定すると、ファイル拡張子に関係なく指定された圧縮フォーマットで解凍されて読み込まれます。None
とすれば、解凍せずにテキストファイルとして読み込まれます。
df = pd.read_csv('sample.csv.gz', compression='gzip')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
thousands
桁区切りの文字を指定します。以下のファイルで検証します。
$ cat sample_thousands.csv
product,price
pizza,"2,980"
pc,"238,000"
car,"4,540,000"
thousands
を指定しない場合、クォーテーション内のカンマは文字列として扱われるため、price
カラムのデータ型はobject
型となります。
df = pd.read_csv('sample_thousands.csv')
print(df.dtypes)
product object
price object
dtype: object
thousands=','
と指定することで、price
カラムを数値として読み込むことができます。
df = pd.read_csv('sample_thousands.csv', thousands=',')
print(df.dtypes)
product object
price int64
dtype: object
decimal
小数点とする文字を指定します。デフォルトは.
です。
国際的には、小数点にはピリオドとカンマのいずれかを使用する国が多いようです(Wikipedia(小数点)より引用)。
小数点として点(point on the line)を用いるか、コンマ(comma on the line)を用いるかは、国、地域、文化によってまちまちである。ごく大まかには、イギリス、米国、日本、中国、インドでは「点」を用い、フランス、ドイツ、イタリア、スペイン、ロシアでは「コンマ」を用いる。以下では、国際度量衡総会での呼び方にならって、「点」を用いる方式を「イギリス式(British practice)」、「コンマ」を用いる方式を「フランス式(French practice)」と呼称する。
以下、カンマを小数点とするフランス式のファイルで検証します。
$ cat sample_french.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,"10,3","23,3","2,6",
2023/4/1,大阪,"10,4","25,1","2,3",
2023/4/2,東京,"11,1","19,0","3,2","0,0"
2023/4/2,大阪,"10,9","23,3","4,4",
df = pd.read_csv('sample_french.csv', decimal=',')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
float_precision
浮動小数点数のコンバーターを指定します。デフォルトはNone
です。
コンバーターにはNone
(通常)、high
(高精度)、legacy
(低精度)、round_trip
(ラウンドトリップ)のいずれかを指定できます。
桁数の多い浮動小数点数のファイルを準備し、各コンバーターで読み込んでみます。
$ cat sample_precision.csv
value
3.141592653589793238462643383279502884
df_none = pd.read_csv('sample_precision.csv', float_precision=None)
df_high = pd.read_csv('sample_precision.csv', float_precision='high')
df_legacy = pd.read_csv('sample_precision.csv', float_precision='legacy')
df_round_trip = pd.read_csv('sample_precision.csv', float_precision='round_trip')
ファイルと同じ値で差分すると、None
とhigh
では丸め誤差による僅かな値が現れたのに対し、legacy
とround_trip
は正確に0
となりました。
python
とpandas
はどちらもfloat
に倍精度浮動小数点数(64bit)を採用しているため、丸め誤差が影響を与えるようなユースケースは少ないかと思いますが、厳密な算術結果が求められる場面ではround_trip
が有益となりそうです。
value = 3.141592653589793238462643383279502884
print(df_none - value)
print('-'*15)
print(df_high - value)
print('-'*15)
print(df_legacy - value)
print('-'*15)
print(df_round_trip - value)
value
0 -4.440892e-16
---------------
value
0 -4.440892e-16
---------------
value
0 0.0
---------------
value
0 0.0
lineterminator
改行コード(任意の1文字)を指定します。デフォルトは実行環境に依存します。
MacOS9以前で使用されていたCR
改行コードのファイルを用意します。
$ cat -A sample_cr.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall^M2023/4/1,M-fM-^]M-1M-dM-:M-,,10.3,23.3,2.6,null^M2023/4/1,M-eM-$M-'M-iM-^XM-*,10.4,25.1,2.3,null^M2023/4/2,M-fM-^]M-1M-dM-:M-,,11.1,19.0,3.2,0^M2023/4/2,M-eM-$M-'M-iM-^XM-*,10.9,23.3,4.4,null^M
lineterminator='\r'
とすることで、LF
やCRLF
以外の改行コードでも読み込むことができます。
df = pd.read_csv('sample_cr.csv', lineterminator='\r')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
lineterminator
はengine='c'
でのみ有効です。他のエンジンを明示的に指定するとValueError
が送出されます。
try:
df = pd.read_csv('sample_cr.csv', lineterminator='\r',
engine='python')
except Exception as e:
print(repr(e))
ValueError('Custom line terminators not supported in python parser (yet)')
quotechar
クォートに使用する文字を指定します。デフォルトは"
で、任意の1文字を指定できます。
各フィールドをシングルクォーテーションで囲ったファイルで検証します。
$ cat sample_singlequote.csv
'Date','Area','MinTemp','MaxTemp','WindSpeed','Rainfall'
'2023/4/1','東京','10.3','23.3','2.6',''
'2023/4/1','大阪','10.4','25.1','2.3',''
'2023/4/2','東京','11.1','19.0','3.2','0.0'
'2023/4/2','大阪','10.9','23.3','4.4',''
quotechar
を指定せずに読み込むと、シングルクォーテーションも値の一部として読み込まれます。
df = pd.read_csv('sample_singlequote.csv')
print(df)
'Date' 'Area' 'MinTemp' 'MaxTemp' 'WindSpeed' 'Rainfall'
0 '2023/4/1' '東京' '10.3' '23.3' '2.6' ''
1 '2023/4/1' '大阪' '10.4' '25.1' '2.3' ''
2 '2023/4/2' '東京' '11.1' '19.0' '3.2' '0.0'
3 '2023/4/2' '大阪' '10.9' '23.3' '4.4' ''
quotechar='\''
とすることで、シングルクォートがクォート文字として適切に処理されました。
df = pd.read_csv('sample_singlequote.csv', quotechar='\'')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
quoting
クォートの動作を指定します。デフォルトはcsv.QUOTE_MINIMAL(0)
です。
quoting
には以下4つのパターンがあり、0~3の整数、もしくはcsvモジュールの各定数名で指定します。
指定値 | クォートされる対象 |
---|---|
0 もしくは csv.QUOTE_MINIMAL |
sep 、quotechar 、lineterminator などの文字列を含む値のみ |
1 もしくは csv.QUOTE_ALL | 全ての値 |
2 もしくは csv.QUOTE_NONNUMERIC | 数値以外の値 |
3 もしくは csvQUOTE_NONE | クォートしない |
カンマを含む文字列をダブルクォートで囲ったファイルで検証します。
$ cat sample_quoting.csv
year,title,famous_scene
1596,Romeo and Juliet,"O Romeo, Romeo! wherefore art thou Romeo?"
1599,Julius Caesar,"And you, Brutus!"
1601,Hamlet,"To be, or not to be: that is the question."
quoting=0(csv.QUOTE_MINIMAL)
の場合、各カラムは適切に読み込まれています。quoting=1(csv.QUOTE_ALL)
でも同じ結果となります。
df = pd.read_csv('sample_quoting.csv', quoting=0)
print(df)
year title famous_scene
0 1596 Romeo and Juliet O Romeo, Romeo! wherefore art thou Romeo?
1 1599 Julius Caesar And you, Brutus!
2 1601 Hamlet To be, or not to be: that is the question.
quoting=2(csv.QUOTE_NONNUMERIC)
とすると、year
カラムがfloat
型になります。これはcsv.QUOTE_NONNUMERICの以下仕様に沿っています。
reader に対しては、クオートされていない全てのフィールドを float 型に変換するよう指示します。
df = pd.read_csv('sample_quoting.csv', quoting=2)
print(df)
year title famous_scene
0 1596.0 Romeo and Juliet O Romeo, Romeo! wherefore art thou Romeo?
1 1599.0 Julius Caesar And you, Brutus!
2 1601.0 Hamlet To be, or not to be: that is the question.
quoting=3(csvQUOTE_NONE)
では、各カラムはクォート処理されないため、カラムと値の関係がずれてしまいます。また、ダブルクォーテーションも値に含まれたままです。
df = pd.read_csv('sample_quoting.csv', quoting=3)
print(df)
year title famous_scene
1596 Romeo and Juliet "O Romeo Romeo! wherefore art thou Romeo?"
1599 Julius Caesar "And you Brutus!"
1601 Hamlet "To be or not to be: that is the question."
doublequote
連続した2つのクォートを、単一のクォートとみなすかどうかを指定します。デフォルトはTrue
です。
famous_scene
カラムの前後を3つのダブルクォートで囲ったファイルで検証します。
$ cat sample_doublequote.csv
year,title,famous_scene
1596,Romeo and Juliet,"""O Romeo, Romeo! wherefore art thou Romeo?"""
1599,Julius Caesar,"""And you, Brutus!"""
1601,Hamlet,"""To be, or not to be: that is the question."""
デフォルトのdoublequote=True
では、famous_scene
カラムの前後2つのダブルクォートが単一のダブルクォートと認識され、期待通りの結果が得られます。
df = pd.read_csv('sample_doublequote.csv', doublequote=True)
print(df)
year title famous_scene
0 1596 Romeo and Juliet "O Romeo, Romeo! wherefore art thou Romeo?"
1 1599 Julius Caesar "And you, Brutus!"
2 1601 Hamlet "To be, or not to be: that is the question."
doublequote=False
とすると、以下では"""O Romeo Romeo! wherefore art thou Romeo?"""
が""
と"O Romeo Romeo! wherefore art thou Romeo?"""
の2カラムとして読み込まれてしまい、カラムと値がずれてしまいます。
df = pd.read_csv('sample_doublequote.csv', doublequote=False)
print(df)
year title famous_scene
1596 Romeo and Juliet "O Romeo Romeo! wherefore art thou Romeo?"""
1599 Julius Caesar "And you Brutus!"""
1601 Hamlet "To be or not to be: that is the question."""
escapechar
エスケープする特殊文字(1文字)を指定します。デフォルトはNone
です。
一例として、escapechar=' '
とするとスペースがエスケープされ、空白を除去した文字列として読み込まれます。
df = pd.read_csv('sample_quoting.csv', escapechar=' ')
print(df)
year title famous_scene
0 1596 RomeoandJuliet ORomeo,Romeo!whereforeartthouRomeo?
1 1599 JuliusCaesar Andyou,Brutus!
2 1601 Hamlet Tobe,ornottobe:thatisthequestion.
comment
コメント行の開始文字(1文字)を指定します。デフォルトはNone
です。
指定すると、その開始文字より右側のフィールドはスキップされます。コメントの開始文字が共通している場合は、header
やskipfotter
を個別に指定するよりもcomment
を指定した方が楽です。
$ cat sample_comment.csv
# header
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall # comment
2023/4/1,東京,10.3,23.3,2.6,null # comment
2023/4/1,大阪,10.4,25.1,2.3,null # comment
2023/4/2,東京,11.1,19.0,3.2,0 # comment
2023/4/2,大阪,10.9,23.3,4.4,null # comment
# footer
df = pd.read_csv('sample_header.csv', comment='#')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
ファイル内のコメント開始文字が共通している場合は、header
やskipfotter
を個別に指定するよりもcomment
で指定した方が楽です。
encoding
CSVファイルの文字コードを指定します。デフォルトはNone
です。
sample.csv
を、Windowsで使用されているCP932
という文字コードに変換したファイルで検証します。
$ iconv -f UTF-8 -t CP932 sample.csv > sample_cp932.csv
$ cat sample_cp932.csv | iconv -f CP932
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall
2023/4/1,東京,10.3,23.3,2.6,null
2023/4/1,大阪,10.4,25.1,2.3,null
2023/4/2,東京,11.1,19.0,3.2,0
2023/4/2,大阪,10.9,23.3,4.4,null
上記ファイルをcp932
で読み込みます。encoding
に指定可能な文字コードの一覧は、Python公式ドキュメントの標準エンコーディングをご参照下さい。
df = pd.read_csv('sample_cp932.csv', encoding='cp932')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
ファイルの文字コードが不明な場合、chardetというPythonパッケージでファイルの文字コードを推定することができます。推定結果には、encoding
に文字コード名、confidence
に確信度が出力されます。
あくまで推定値ですが、UTF
/JIS
/EUC
といった文字コードの大体の種類を特定するのに役立ちます。
import chardet
for filename in ('sample.csv', 'sample_cp932.csv'):
with open(filename, 'rb') as f:
res = chardet.detect(f.read())
print(f'{filename} : {res}')
sample.csv : {'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}
sample_cp932.csv : {'encoding': 'Windows-1254', 'confidence': 0.3922507857706182, 'language': 'Turkish'}
encoding_errors
エンコードエラーが発生した場合の挙動を指定します。デフォルトはstrict
で、UnicodeDecodeError
が送出されます。
try:
df = pd.read_csv('sample_sjis.csv', encoding='utf-8')
except UnicodeDecodeError as e:
print(repr(e))
UnicodeDecodeError('utf-8', b'\x93\x8c\x8b\x9e', 0, 1, 'invalid start byte')
encoding_errors='ignore'
とすると、エンコードに失敗した文字はスキップされます。
df = pd.read_csv('sample_sjis.csv', encoding='utf-8', encoding_errors='ignore')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 10.3 23.3 2.6 NaN
1 2023/4/1 10.4 25.1 2.3 NaN
2 2023/4/2 11.1 19.0 3.2 0.0
3 2023/4/2 10.9 23.3 4.4 NaN
encoding_errors='replace'
とすると、エンコードに失敗した文字は�
というUnicodeのREPLACEMENT CHARACTER (U+FFFD)に置換されます。
df = pd.read_csv('sample_sjis.csv', encoding_errors='replace')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 ���� 10.3 23.3 2.6 NaN
1 2023/4/1 ��� 10.4 25.1 2.3 NaN
2 2023/4/2 ���� 11.1 19.0 3.2 0.0
3 2023/4/2 ��� 10.9 23.3 4.4 NaN
encoding_errors='backslashreplace'
とすると、エンコードに失敗した文字はバックスラッシュによるエスケープシーケンスに置換されます。
df = pd.read_csv('sample_sjis.csv', encoding_errors='backslashreplace')
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 \x93\x8c\x8b\x9e 10.3 23.3 2.6 NaN
1 2023/4/1 \x91\xe5\x8d\xe3 10.4 25.1 2.3 NaN
2 2023/4/2 \x93\x8c\x8b\x9e 11.1 19.0 3.2 0.0
3 2023/4/2 \x91\xe5\x8d\xe3 10.9 23.3 4.4 NaN
dialect
CSVの細かな記法を指定します。デフォルトはNone
です。
本記事の冒頭で述べた通り、CSVは国際標準化される前から広く利用されてきたため、区切り文字やクォートの有無など、微妙な記法の違いが存在します。Pythonにはそれらの記法を定義できるcsv.Dialectというクラスが存在し、read_csv
にも同クラスを適用することができます。
検証用に、csv.Dialect
で定義されているexcel
、excel-tab
、unix
の各記法でCSVファイルを生成してみます。
import csv
with open('sample.csv') as f:
reader = csv.DictReader(f)
rows = [row for row in reader]
fieldnames = reader.fieldnames
for dialect in ('excel', 'excel-tab', 'unix'):
with open(f'sample_{dialect}.csv', 'w') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, dialect=dialect)
writer.writeheader()
writer.writerows(rows)
生成結果を比較してみましょう。
dialect='excel'
では、改行コードがCRLF
(レコード末尾の^M$
)であり、クォートは必須ではないようです。
$ cat -A sample_excel.csv
Date,Area,MinTemp,MaxTemp,WindSpeed,Rainfall^M$
2023/4/1,M-fM-^]M-1M-dM-:M-,,10.3,23.3,2.6,null^M$
2023/4/1,M-eM-$M-'M-iM-^XM-*,10.4,25.1,2.3,null^M$
2023/4/2,M-fM-^]M-1M-dM-:M-,,11.1,19.0,3.2,0^M$
2023/4/2,M-eM-$M-'M-iM-^XM-*,10.9,23.3,4.4,null^M$
dialect='excel-tab'
では、区切り文字がタブ(^I
)となっており、それ以外はexcel
と同じです。
$ cat -A sample_excel-tab.csv
Date^IArea^IMinTemp^IMaxTemp^IWindSpeed^IRainfall^M$
2023/4/1^IM-fM-^]M-1M-dM-:M-,^I10.3^I23.3^I2.6^Inull^M$
2023/4/1^IM-eM-$M-'M-iM-^XM-*^I10.4^I25.1^I2.3^Inull^M$
2023/4/2^IM-fM-^]M-1M-dM-:M-,^I11.1^I19.0^I3.2^I0^M$
2023/4/2^IM-eM-$M-'M-iM-^XM-*^I10.9^I23.3^I4.4^Inull^M$
最後のdialect='unix'
では、改行コードがLF
(レコード末尾の$
)であり、全フィールドがクォート処理されています。
$ cat -A sample_unix.csv
"Date","Area","MinTemp","MaxTemp","WindSpeed","Rainfall"$
"2023/4/1","M-fM-^]M-1M-dM-:M-,","10.3","23.3","2.6","null"$
"2023/4/1","M-eM-$M-'M-iM-^XM-*","10.4","25.1","2.3","null"$
"2023/4/2","M-fM-^]M-1M-dM-:M-,","11.1","19.0","3.2","0"$
"2023/4/2","M-eM-$M-'M-iM-^XM-*","10.9","23.3","4.4","null"$
excel
とunix
はそれぞれ改行コードが異なっていますが、read_csv
は代表的な記法をデフォルトで認識できるため、どちらも特別な指定なく読み込むことができます。
df_excel = pd.read_csv('sample_excel.csv')
df_unix = pd.read_csv('sample_unix.csv')
# 2つのdataframeを比較(中身が異なる時のみ、AssertionErrorが送出)
pd.testing.assert_frame_equal(df_excel, df_unix)
print(df_excel)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
excel-tab
はタブ区切りのためsep='\t'
でも読み込めますが、以下のようにdialect
のクラスを直接指定することもできます。
df = pd.read_csv('sample_excel-tab.csv', dialect=csv.excel_tab)
print(df)
Date Area MinTemp MaxTemp WindSpeed Rainfall
0 2023/4/1 東京 10.3 23.3 2.6 NaN
1 2023/4/1 大阪 10.4 25.1 2.3 NaN
2 2023/4/2 東京 11.1 19.0 3.2 0.0
3 2023/4/2 大阪 10.9 23.3 4.4 NaN
もし今後、csv.dialect
クラスにも定義されていない独自のCSV記法を扱う場面があれば、dialect
引数が役立ちそうです。
エラーハンドリング
フィールド数がヘッダーのカラム数よりも多いレコード(以下、不正レコードと呼称)の扱いを指定します。デフォルトはerror
です。
不正レコードを含んだ以下ファイルで検証します。末尾2行が不正レコードです。
$ cat sample_badlines.csv
X,Y,Z
1,2,3
4,5,6,7
8,9,10,11,12
error_bad_lines / warn_bad_lines
どちらもver1.3で非推奨となり、ver2.0で削除されました。代わりに、ver1.3で追加されたon_bad_lines
引数を使用して下さい。
on_bad_lines
不正レコードに対する処理方法を指定します。
デフォルトはerror
で、不正レコードが検知されると以下のようにParserError
が送出され、読み込みが失敗します。
try:
df = pd.read_csv('sample_badlines.csv')
except Exception as e:
print(repr(e))
ParserError('Error tokenizing data. C error: Expected 3 fields in line 3, saw 4\n')
on_bad_lines='warn'
にすると、不正レコードは警告が出力されてスキップされ、正常なレコードのみ読み込むことができます。
df = pd.read_csv('sample_badlines.csv', on_bad_lines='warn')
print(df)
X Y Z
0 1 2 3
Skipping line 3: expected 3 fields, saw 4
Skipping line 4: expected 3 fields, saw 5
on_bad_lines='skip'
にすると、不正レコードは警告無しでスキップされ、正常なレコードのみ読み込むことができます。
df = pd.read_csv('sample_badlines.csv', on_bad_lines='skip')
print(df)
X Y Z
0 1 2 3
on_bad_lines
には関数も指定できます。関数の引数には、sep
で区切られた文字列のリストが入力されます。関数がNone
を返すとその行はスキップされ、関数が文字列のリストを返すとその返り値が読み込み結果となります。関数はengine='python'
でのみ使用可能です。
# カラム数とフィールド数が不一致の場合、レコードの末尾3フィールドを返す
on_bad_lines = lambda line: line[-3:]
df = pd.read_csv('sample_badlines.csv', on_bad_lines=on_bad_lines,
engine='python')
print(df)
X Y Z
0 1 2 3
1 5 6 7
2 10 11 12
Kaggle等のデータ分析コンペでは少数の不正レコードをskip
で無視することがよくありますが、基幹業務では通常、検知された不正レコードを保存して後処理する必要があります。
以下のように関数内で不正レコードをリストに退避しておけば、正常なレコードのみを読み込みつつ、不正レコードに後処理を実行することが可能です。リスト退避以外にも、ファイルやログに出力するのにも使えます。
bad_lines = []
on_bad_lines = lambda line: bad_lines.append(line)
df = pd.read_csv('sample_badlines.csv', on_bad_lines=on_bad_lines,
engine='python')
print(df)
print('-' * 15)
print(f'{len(bad_lines)} bad lines found.')
print(bad_lines)
X Y Z
0 1 2 3
---------------
2 bad lines found.
[['4', '5', '6', '7'], ['8', '9', '10', '11', '12']]
パフォーマンス比較
前章では、各引数の効果や制約を中心に検証しました。
本章では以下5つについて、実行時間とメモリ消費量がどれだけ違うのかパフォーマンスを比較評価していきます。
-
engine
に指定するパーサーエンジンを、python
、c
、pyarrow
で比較 -
dtype
の指定有無で比較 - 数値や文字列の前処理において、読み込みと同時に
converters
で変換するパターンと、読み込み後に変換するパターンを比較 - 日付変換において、読み込みと同時に
parse_dates
で変換するパターンと、読み込み後にpandas.to_datetimeで変換するパターンを比較 - 日付変換における
cache_dates
の指定有無を比較
計測用に、データ型が異なる5カラム × 5,000万行のサンプルデータを作成します。
from string import ascii_lowercase
N = 5 * 10**7
df = pd.DataFrame(index=pd.RangeIndex(0, N))
df['int'] = np.random.randint(low=0, high=256, size=N, dtype=np.uint8)
df['float'] = np.random.rand(N).astype(np.float32)
df['bool'] = df['float'] > 0.5
df['string'] = np.random.choice(list(ascii_lowercase), size=N)
df['datetime'] = pd.date_range(start='2023/4/1', periods=N, freq='min')
print(df.head())
filename = 'sample_large.csv'
df.to_csv(filename, index=False)
del df
int float bool string datetime
0 147 0.309425 False w 2023-04-01 00:00:00
1 164 0.531861 True j 2023-04-01 00:01:00
2 235 0.292943 False s 2023-04-01 00:02:00
3 159 0.271452 False g 2023-04-01 00:03:00
4 179 0.840848 True e 2023-04-01 00:04:00
メモリ計測用にインストールしたmemory_profiler
のnotebook拡張機能を有効化します。
%load_ext memory_profiler
1. engine
のパフォーマンス比較
python
、c
、pyarrow
の3つのパーサーエンジンで比較します。
読み込み時間では、pyarrow
エンジンが圧倒的に速い結果となりました。c
エンジンの約5倍、python
エンジンの約20倍です!pyarrow
はマルチスレッドに対応している唯一のエンジンであるため、vCPU数が多い環境ほど読み込み時間の短縮が期待できます。
%timeit pd.read_csv(filename, engine='python')
%timeit pd.read_csv(filename, engine='c')
%timeit pd.read_csv(filename, engine='pyarrow')
1min 49s ± 2.42 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
27.6 s ± 707 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
5.35 s ± 667 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
メモリ消費量(increment
をご参照)も、pyarrow
が最も省メモリでした。python
エンジンの約1/4しか消費しないのは驚きです!
pyarrow
エンジンでは併用可能な引数が少ないという制約があるものの、利用できるシーンでは積極的に使った方が良さそうです。
%memit pd.read_csv(filename, engine='python')
%memit pd.read_csv(filename, engine='c')
%memit pd.read_csv(filename, engine='pyarrow')
peak memory: 25604.86 MiB, increment: 19105.46 MiB
peak memory: 8164.46 MiB, increment: 7480.59 MiB
peak memory: 7794.48 MiB, increment: 5771.98 MiB
2. dtype
によるデータ型の指定
各カラムのデータ型をdtype
で指定した場合の効果を比較します。
実行時間では、dtype
を指定しない方が高速でした。string
やdatetime
の変換処理で時間を要したものと思われます。
dtype={'int': np.uint8, 'float': np.float32, 'bool': bool,
'string': pd.CategoricalDtype(list(ascii_lowercase))}
%timeit pd.read_csv(filename)
%timeit pd.read_csv(filename, dtype=dtype, parse_dates=['datetime'])
26.2 s ± 816 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
35.4 s ± 928 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
メモリ消費量では、dtype
指定ありの方が省メモリでした。
%memit pd.read_csv(filename)
%memit pd.read_csv(filename, dtype=dtype, parse_dates=['datetime'])
peak memory: 7680.60 MiB, increment: 6955.95 MiB
peak memory: 6393.80 MiB, increment: 5670.64 MiB
読み込み後のDataFrame
のメモリサイズ(MB)をカラム毎に比較し、dtype
指定によるメモリ削減率(reduce
)を算出してみます。
結果は、bool
型を除く全てのカラムで削減効果が得られました。特に、datetime
カラムは89%、string
カラムは98%と劇的に減っています!値のレンジや重複度合いに応じて適切なデータ型を用いることで、読み取り後のメモリサイズを大きく削減できることがあることが分かりました。
usage = pd.DataFrame()
# カラム毎のメモリサイズを取得
usage['default[MB]'] = pd.read_csv(filename).memory_usage(index=False, deep=True)
usage['dtype[MB]'] = (pd.read_csv(filename, dtype=dtype, parse_dates=['datetime'])
.memory_usage(index=False, deep=True))
# メモリサイズの単位をMBに変換し、削減率を算出
usage /= 10**6
usage['reduce[%]'] = (1 - usage['dtype[MB]'] / usage['default[MB]']) * 100
print(usage.round(1))
default[MB] dtype[MB] reduce[%]
int 400.0 50.0 87.5
float 400.0 200.0 50.0
bool 50.0 50.0 0.0
string 2900.0 50.0 98.3
datetime 3800.0 400.0 89.5
3. converters
による数値/文字列の変換
数値変換
サンプルデータからfloat
列だけを読み込み、平方根を計算する例で比較します。
実行時間では、後者(読み込み後に数値演算)がパフォーマンスに優れていました。前者ではconverters
が各要素を1つずつ処理するのに対し、後者ではnumpy
の配列演算により高速化が行われた、と推測されます。メモリ消費量では大きな差異は見られませんでした。
converters={'float': lambda f: np.sqrt(float(f))}
%timeit pd.read_csv(filename, usecols=['float'], converters=converters)
%timeit np.sqrt(pd.read_csv(filename, usecols=['float']))
%memit pd.read_csv(filename, usecols=['float'], converters=converters)
%memit np.sqrt(pd.read_csv(filename, usecols=['float']))
56.6 s ± 76.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
10.2 s ± 625 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
peak memory: 928.48 MiB, increment: 450.10 MiB
peak memory: 973.07 MiB, increment: 489.69 MiB
文字列変換
サンプルデータからstring
列だけを読み込み、3桁0埋め(a
⇒00a
)する例で比較します。
実行時間は意外にもconverters
を使用した方が2割ほど速く、メモリ消費量はどちらも同程度という結果となりました。簡単な文字列処理であれば、converters
を使用しても大きなボトルネックにはならなさそうです。メモリ消費量はどちらも同程度でした。
converters={'string': lambda s: s.zfill(3)}
%timeit pd.read_csv(filename, usecols=['string'], converters=converters)
%timeit pd.read_csv(filename, usecols=['string'])['string'].str.zfill(3)
%memit pd.read_csv(filename, usecols=['string'], converters=converters)
%memit pd.read_csv(filename, usecols=['string'])['string'].str.zfill(3)
15.1 s ± 583 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
18.9 s ± 838 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
peak memory: 4526.77 MiB, increment: 3794.86 MiB
peak memory: 4546.05 MiB, increment: 3606.62 MiB
4. parse_dates
とto_datetime
による日付・時刻変換
サンプルデータからdatetime
列だけを読み込み、parse_dates
による変換と、読み込み後にpandas.to_datetimeで変換するパターンで比較します。
結果は、実行時間・メモリ消費量とも近しい結果となりました。どちらを使っても良さそうですが、to_datetime
は変換エラー時の挙動やタイムゾーン⇒UTCへの変換といった便利機能が多数実装されているため、より高度な変換要件がある場合はto_datetime
が便利です。
%timeit pd.read_csv(filename, usecols=['datetime'], parse_dates=['datetime'])
%timeit pd.to_datetime(pd.read_csv(filename, usecols=['datetime'])['datetime'])
%memit pd.read_csv(filename, usecols=['datetime'], parse_dates=['datetime'])
%memit pd.to_datetime(pd.read_csv(filename, usecols=['datetime'])['datetime'])
31.6 s ± 870 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
31.7 s ± 1.13 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
peak memory: 6057.39 MiB, increment: 5322.44 MiB
peak memory: 5676.00 MiB, increment: 4941.04 MiB
5. cache_dates
によるキャッシュ効果
cache_dates
によるキャッシュ効果を、3つの日付フォーマットで比較検証します。
事前に、重複した日付を含む5,000万件のDataFrame
を生成し、各日付フォーマットでCSV出力します。
# 5万件のユニークな日付を1000回複製
dates = pd.Series(pd.date_range('2023/4/1', periods=50000).repeat(1000), name='Date')
# ISO、dayfirst、日本語の3フォーマットでそれぞれ保存
dates.to_csv('dates_isoformat.csv', index=False)
dates.dt.strftime('%d/%m/%Y').to_csv('dates_dayfirst.csv', index=False)
dates.dt.strftime('%Y年%m月%d日').to_csv('dates_japan.csv', index=False)
del dates
ISO8601フォーマットでは、実行時間が3割ほど短縮されました。pandasのユーザーガイドにもparse_datesはISO8601に最適化されている
との記載があるため、このフォーマットであればキャッシュが無くても十分に高速なようです。
filename = 'dates_isoformat.csv'
%timeit pd.read_csv(filename, parse_dates=['Date'], cache_dates=False)
%timeit pd.read_csv(filename, parse_dates=['Date'])
%memit pd.read_csv(filename, parse_dates=['Date'], cache_dates=False)
%memit pd.read_csv(filename, parse_dates=['Date'])
11.4 s ± 883 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
8.06 s ± 564 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
peak memory: 2270.68 MiB, increment: 1525.89 MiB
peak memory: 1986.41 MiB, increment: 1241.62 MiB
dayfirst
フォーマットでは、実行時間が9割近くも大幅削減されました!
filename = 'dates_dayfirst.csv'
%timeit pd.read_csv(filename, parse_dates=['Date'], dayfirst=True, cache_dates=False)
%timeit pd.read_csv(filename, parse_dates=['Date'], dayfirst=True)
%memit pd.read_csv(filename, parse_dates=['Date'], dayfirst=True, cache_dates=False)
%memit pd.read_csv(filename, parse_dates=['Date'], dayfirst=True)
1min 29s ± 870 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
8.18 s ± 564 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
peak memory: 2269.60 MiB, increment: 1525.80 MiB
peak memory: 1989.43 MiB, increment: 1245.62 MiB
日本語のフォーマットでも、約8割ほど実行時間が削減されました。
filename = 'dates_japan.csv'
%timeit pd.read_csv(filename, parse_dates=['Date'], date_format='%Y年%m月%d日', \
cache_dates=False)
%timeit pd.read_csv(filename, parse_dates=['Date'], date_format='%Y年%m月%d日')
%memit pd.read_csv(filename, parse_dates=['Date'], date_format='%Y年%m月%d日', \
cache_dates=False)
%memit pd.read_csv(filename, parse_dates=['Date'], date_format='%Y年%m月%d日')
1min 31s ± 1.1 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
11.2 s ± 660 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
peak memory: 2216.78 MiB, increment: 1525.82 MiB
peak memory: 1835.72 MiB, increment: 1144.76 MiB
以上の結果から、どの日付フォーマットでもキャッシュは有効であり、変換処理に時間が掛かるほどキャッシュ効果が大きいことが確認できました。
また、キャッシュしない方がなぜかメモリ消費量が増えることも分かりました(原因は不明)。キャッシュはデフォルトで有効化されているため、あえて無効化する必要は無さそうです。
その他テクニック
最後となる本章では、「各引数をベタ書きせず、コレクションとして定義するテクニック」をご紹介します。read_csv
と各引数を別々に実装・管理することで、
- 引数を別のモジュールやファイルで管理し、プログラムを修正せずに引数の値だけを変更できるようにしたい
- スキーマが同じ(もしくは近しい)複数のCSVファイルを読み込む際に、引数を共通利用したい
といったニーズに応えることができます。本章では、dict
、dataclass
、yaml
、toml
の4種類で引数を定義し、それぞれの特徴やメリットを確認していきます。
説明にあたり、国土交通省が公開している日本標準時基準の台風発生数のCSVデータを使用します。先頭5行と末尾5行のファイル内容は以下となります。
$ wget -q -O typhoon.csv https://www.data.jma.go.jp/yoho/typhoon/statistics/generation/generation_j.csv
$ (head -5;tail -5) < typhoon.csv | iconv -f SJIS
年,1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月,年間
1951,,1,1,2,1,1,3,3,2,4,1,2,21
1952,,,,,,3,3,5,3,6,3,4,27
1953,,1,,,1,2,1,6,3,5,3,1,23
1954,,,1,,1,,1,5,5,4,3,1,21
2018,1,1,1,,,4,5,9,4,1,3,,29
2019,1,1,,,,1,4,5,6,4,6,1,29
2020,,,,,1,1,,8,3,6,3,1,23
2021,,1,,1,1,2,3,4,4,4,1,1,22
2022,,,,2,,1,3,5,7,5,1,1,25
今回は以下要件でCSVを読み込むことにします。
- 文字コードは
SHIFT-JIS
形式とする -
年
と1月
~12月
の計13カラムのみを読み込む(年間
カラムはスキップ) -
年
カラムをインデックスに指定する - ヘッダー行を除いた先頭5行のみを読み込む
- 各カラムのデータ型は、欠損値を許容する整数型とする
要件の実装例は以下となります。合計6つの引数を直接指定しましたが、以降では各引数を様々な方法で定義してみます。
df = pd.read_csv('typhoon.csv',
encoding='shift-jis',
usecols='年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月'.split(),
index_col='年',
nrows=5,
dtype=pd.UInt16Dtype())
print(df)
1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
年
1951 <NA> 1 1 2 1 1 3 3 2 4 1 2
1952 <NA> <NA> <NA> <NA> <NA> 3 3 5 3 6 3 4
1953 <NA> 1 <NA> <NA> 1 2 1 6 3 5 3 1
1954 <NA> <NA> 1 <NA> 1 <NA> 1 5 5 4 3 1
1955 1 1 1 1 <NA> 2 7 6 4 3 1 1
引数をdict
で定義
最もシンプルなパターンです。dict
では位置引数は指定できないため、ファイル名もfilepath_or_buffer
というキー名で指定します。
あとはread_csv(**params)
とするだけで、全ての引数が展開されます。引数の定義と読み込み処理を分離することで、コードの見通しがとても良くなりました。
dict
を使うメリットは2点です。
- 組み込み関数のため、ライブラリの追加インストールが不要
- ミュータブル/イミュータブルを問わず、任意のオブジェクトを定義できる
params = dict(filepath_or_buffer='typhoon.csv',
encoding='shift-jis',
usecols='年 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月'.split(),
index_col='年',
nrows=5,
dtype=pd.UInt16Dtype())
df = pd.read_csv(**params)
print(df)
1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
年
1951 <NA> 1 1 2 1 1 3 3 2 4 1 2
1952 <NA> <NA> <NA> <NA> <NA> 3 3 5 3 6 3 4
1953 <NA> 1 <NA> <NA> 1 2 1 6 3 5 3 1
1954 <NA> <NA> 1 <NA> 1 <NA> 1 5 5 4 3 1
1955 1 1 1 1 <NA> 2 7 6 4 3 1 1
引数をdataclasses
で定義
dataclassesは、Python3.7から実装されたデータ定義用の標準ライブラリです。引数用のクラスを定義し、asdict
で辞書に変換することで引数を展開します。
ここでは、usecols
の値をリストではなくタプルで定義しています。fieldを使えばリストも定義可能ですが、usecols
にはタプルも指定できることと、dataclasses
のフィールドはイミュータブルの方がが望ましいとの考えから、今回はタプルとしました。
dataclasses
を使用するメリットは2点です。
- 外部パッケージが不要
- 引数のアノテーションや型チェックができる
from dataclasses import dataclass, asdict
@dataclass
class Params:
filepath_or_buffer: str = 'typhoon.csv'
encoding: str = 'shift-jis'
usecols: tuple = ('年', '1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月')
index_col: str = '年'
nrows: int = 5
dtype: pd.core.arrays.integer.IntegerDtype = pd.UInt16Dtype()
df = pd.read_csv(**asdict(Params()))
print(df)
1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
年
1951 <NA> 1 1 2 1 1 3 3 2 4 1 2
1952 <NA> <NA> <NA> <NA> <NA> 3 3 5 3 6 3 4
1953 <NA> 1 <NA> <NA> 1 2 1 6 3 5 3 1
1954 <NA> <NA> 1 <NA> 1 <NA> 1 5 5 4 3 1
1955 1 1 1 1 <NA> 2 7 6 4 3 1 1
引数をyaml
で定義
メジャーな設定ファイルフォーマットの1つであるyamlで定義するパターンです。python
では、pyyaml等の外部パッケージを使用して読み書きします。
ポイントはdtype
の指定方法です。dict
やdataclasses
のようにpd.UInt16Dtypes()
といったpythonオブジェクトを直接定義できないため、同じデータ型を意味するUInt16
という文字列に置き換えて定義します。
yaml
で定義するメリットは2点です。
- プログラミング言語に依存しない、メジャーな設定ファイルフォーマットで定義できる
- 引数をファイルで管理できる
$ cat params.yaml
filepath_or_buffer: typhoon.csv
encoding: shift-jis
index_col: 年
nrows: 5
usecols:
- 年
- 1月
- 2月
- 3月
- 4月
- 5月
- 6月
- 7月
- 8月
- 9月
- 10月
- 11月
- 12月
dtype: UInt16
import yaml
with open('params.yaml') as f:
params = yaml.safe_load(f)
df = pd.read_csv(**params)
print(df)
1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
年
1951 <NA> 1 1 2 1 1 3 3 2 4 1 2
1952 <NA> <NA> <NA> <NA> <NA> 3 3 5 3 6 3 4
1953 <NA> 1 <NA> <NA> 1 2 1 6 3 5 3 1
1954 <NA> <NA> 1 <NA> 1 <NA> 1 5 5 4 3 1
1955 1 1 1 1 <NA> 2 7 6 4 3 1 1
引数をtoml
で定義
tomlも注目を集めている設定ファイルフォーマットの1つです。python3.11でtomllib
が標準ライブラリに追加され、toml
ファイルの読み込みが出来るようになりました。
params.toml
に引数を定義して、tomllib.load()
で読み込みます。バイナリモード(rb
)で読み込んでいるのはtomllib
の仕様によるものです。
toml
を使用するメリットは3点です。
- 外部パッケージが不要(python 3.11以降)
- プログラミング言語に依存しない、メジャーな設定ファイルフォーマットで定義できる
- 引数をファイルで管理できる
$ cat params.toml
filepath_or_buffer = "typhoon.csv"
encoding = "shift-jis"
index_col = "年"
nrows = 5
usecols = ["年", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]
dtype = "UInt16"
import tomllib
with open('params.toml', 'rb') as f:
params = tomllib.load(f)
df = pd.read_csv(**params)
print(df)
1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
年
1951 <NA> 1 1 2 1 1 3 3 2 4 1 2
1952 <NA> <NA> <NA> <NA> <NA> 3 3 5 3 6 3 4
1953 <NA> 1 <NA> <NA> 1 2 1 6 3 5 3 1
1954 <NA> <NA> 1 <NA> 1 <NA> 1 5 5 4 3 1
1955 1 1 1 1 <NA> 2 7 6 4 3 1 1
まとめ
本記事では、pandas.read_csv
関数における大量の引数について、その効果や制約、パフォーマンス、各引数の管理方法をご紹介しました。
世の中にはCSV
よりもずっと高効率なデータフォーマットが多数ありますが、既存システムとの互換性といった諸事情から、CSV
は今後もしばらくは使い続けられるでしょう。本記事を通じて、そんなCSV
の取り扱いが少しでも便利になれば幸いです。
長文にお付き合いいただき、ありがとうございました。