【保存版】Pandas2.0のread_csv関数の全引数、パフォーマンス、活用テクニックを完全解説する！

# はじめに
みずほリサーチ&テクノロジーズ株式会社の@fujineです。

本記事では`pandas 2.0`を対象に、`CSV`ファイルの入力関数である **[read_csv](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html)の全49個（！）の引数をじっくり解説** いたします。具体的には、

- 各引数には、どんな効果や（公式ドキュメントにも記載されていない）制約があるのか？
- 引数を工夫することで、処理時間やメモリ消費量などのパフォーマンスが具体的にどれだけ改善されるのか？
- `pandas`のver2.0では、それ以前のバージョンからどう変化したのか？
- 多くの引数を保守しやすく管理するにはどうしたらいいか？

を体系的に整理・検証することを目指します。

**新入社員/若手社員向けのレクチャーや、これまで「何となく」使っていた引数を「効果的に」使えるようになるためのノウハウ集としてご活用下さい！**

# `read_csv`の引数が49個もある理由
「ただ`CSV`を読み込むだけなのに、なぜそんなに多くの引数が必要なのか？」と気になった方も多いでしょう。

CSV(**C**omma-**S**eparated **V**alues)フォーマットは60年以上前の1960年代頃から使われ始めたらしく、現在でも身近なデータフォーマットの1つです。当時は国際標準が存在しなかったため、CSVの普及とともに、OSやアプリケーション、国や地域、ベンダーの違いによって微妙に異なる独自の記法が多数誕生しました。2005年10月にようやく[RFC4180](https://tools.ietf.org/html/rfc4180.html)にて国際標準化されましたが、**従来の独自記法は今も根強く存在しています**。`read_csv`の引数が他の関数と比べて異様に多いのは、これらの独自記法を可能な限り引数で制御しようとした結果となります。

そして、これだけ引数の数が多いと**どの引数が重要なのか分かりにくく、初学者がいきなり`read_csv`を使いこなすのはハードルが高い** です。更に、`pandas`のバージョンアップによって各引数が新規追加・仕様変更・非推奨/廃止になることがあり、使い慣れた経験者であっても **引数の変化を適時検証するのはやはり大変** です。

そこで、`read_csv`を使いこなすための手引きを目指し、本記事を執筆いたしました。

# 検証環境
GoogleCloud VMの[n2d-highmem-4 ](https://cloud.google.com/compute/docs/general-purpose-machines?hl=ja#n2d-high-mem)インスタンス（4vCPU, 32GBメモリ）で検証します。Linux OSのバージョンは以下の通りです。


```sh
$ 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にメジャーアップデートしたばかり](https://pandas.pydata.org/docs/whatsnew/v2.0.0.html)で、`read_csv`に対しても重要な変更がいくつか行われました。

`pip`コマンドで`pandas`をインストールします。他のパッケージは任意です。本記事では以下用途で使用します。
- `pyarrow`: `pyarrow`のデータ型として利用
- `s3fs`: `AWS S3`に保管されたオブジェクトへのアクセス用
- `chardet`: ファイルの文字コード推定用
- `pyyaml`: `yaml`ファイルの入出力用
- `memory_profiler`: メモリ消費量の計測用


```sh
$ pip install -q pandas==2.0.0 pyarrow s3fs chardet pyyaml memory_profiler
```

`pandas`をインポートし、DataFrameの表示オプションを設定します。


```python
import pandas as pd

pd.set_option('display.unicode.east_asian_width', True)
pd.set_option('display.width', 150)
```

検証のためのサンプルファイルを作成します。中身は、日本の気象データを模したものです。


```sh
$ 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'`と指定しなくてもファイルパスとして認識されます。

最も一般的な指定方法は、ファイルパスを文字列で指定する方法でしょう。


```python
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オブジェクト](https://docs.python.org/ja/3/glossary.html#term-file-like-object)にも対応しており、標準ライブラリの[pathlib.Path](https://docs.python.org/ja/3/library/pathlib.html)や[os.path](https://docs.python.org/ja/3/library/os.path.html)オブジェクトも読み込めます。


```python
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](https://docs.python.org/ja/3/library/io.html#io.StringIO)にも対応しています。CSVデータがメモリ上にある場合、`StringIO`を使えば以下のようにメモリ上からダイレクトに`DataFrame`へ変換できるので便利です。


```python
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`など）上にあるリモートファイルも読み込むことができます。

以下は、国土交通省が公開している[日本標準時基準の台風発生数データ](https://data.e-gov.go.jp/data/dataset/mlit_20140919_0747/resource/7b8ced63-2453-4c85-91d9-3299674c01b6)です。カスタムヘッダーが必要な場合は、後述する`storage_options`を別途指定します。


```python
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}`というオプションを付与しています。


```python
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


:::note info

pandasの公式ドキュメントにて、[各クラウド接続に必要な依存パッケージ](https://pandas.pydata.org/docs/getting_started/install.html#access-data-in-the-cloud)が公開されています。上記以外に指定可能なオプションは、記載パッケージの公式ドキュメントをご参照下さい。

:::

### sep / delimiter
区切り文字を指定します（`delimiter`は`sep`のエイリアスです）。デフォルトは`,`です。

`sep`には任意の一文字や正規表現を指定できるため、以下のようにカンマ以外の区切り文字にも対応できます。


```sh
$ 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



```python
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](https://pandas.pydata.org/docs/reference/api/pandas.read_table.html)関数でも同じ結果が得られます）。


```sh
$ 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



```python
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'`という引数（後述）も同時に指定します。


```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+'`と等価となり、各フィールドの値がスペースで区切られて読み込まれます。


```sh
$ 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



```python
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`が送出されます。


```python
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'`となっており、自動的に推定されます。

以下ファイルのように、ヘッダー領域にコメント行などが含まれている場合を想定します。


```sh
$ 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以上の値を指定した場合、それより上の行はスキップされます。


```python
# 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


マルチヘッダーの場合は、ヘッダー行番号のリストを指定することで、カラムがマルチインデックスとなります。


```sh
$ 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



```python
# 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`引数で任意のカラム名で上書きできます。


```sh
$ 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



```python
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`に別名のリストを与えます。


```python
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`とした上でカラム名を指定します。


```python
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


:::note warn

なお、データのフィールド数と`len(names)`が一致しない場合、エラーとならず、以下挙動となります。

:::

`len(names)`がレコードのフィールド数よりも多い場合、各フィールドの値は`names`の **左側** から順に紐づけられ、余ったカラムの値は全て欠損値となります。


```python
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`の **右側** から順に紐づけられ、余ったデータはインデックスとなります。


```python
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


:::note info

「`names`を指定するとカラムとデータの値がずれてしまう」といった事象が起きたら、データのフィールド数と`len(names)`が一致しているか確認しましょう！

:::

### index_col
インデックスとするカラムを指定します。デフォルトは`None`です。

カラム名もしくは列番号を指定すると、そのカラムがインデックスとなります。


```python
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`にリストを指定すると、マルチインデックスとなります。


```python
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があるとします。


```sh
$ 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`列が自動的にインデックスとなってしまいます。


```python
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`カラムはインデックス化されず、正しく読み込むことができます。


```python
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


:::note info

「CSVファイルを読み込むと、なぜか自動的にカラムがインデックスになってしまう」という事象が起きた際は、フィールド数とカラム数が一致しているか確認しましょう！

:::

### usecols
読み込むカラムを、列番号かカラム名のリストで指定します。デフォルトは`None`で、全てのカラムが読み込まれます。

:::note info

読み込みたいカラムが限定的な場合、`usecols`で絞り込むことでメモリ消費量を抑える効果があります。

:::


```python
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`と評価されるオブジェクト）を返すカラムのみが読み込まれます。

:::note info

カラム数が数百～と大量にあると、1つずつ明示的に指定するのは大変です。カラム名にパターンがある場合は、以下のように関数と正規表現を組合わせた方がコードがスッキリします。

:::


```sh
$ 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



```python
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
:::note alert

これらの引数はver1.5時点で非推奨となっています。同様の処理を行いたい場合は、以下の代替手段を参考にして下さい。

:::

|引数名|効果|代替手段|
|---|---|---|
|`squeeze`|カラムが1列の場合、`Series`として読み込む|[pandas.DataFrame.squeeze](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.squeeze.html)を使用|
|`prefix`|カラム名に接頭辞を追加|[pandas.DataFrame.add_prefix](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.add_prefix.html)を使用|
|`mangle_dupe_cols`|重複するカラム名の末尾に連番を付与|新しい引数を検討中[(GH#47718)](https://github.com/pandas-dev/pandas/issues/47718)|

## データ変換

### dtype
カラムのデータ型を指定します。デフォルトは`None`で、データから自動的に推定されます。

:::note info

適切なデータ型を明示的に指定することで、メモリ消費量を抑える効果があります。

:::

カラム名をキー、データ型を値とする辞書を`dtype`に指定すると、カラム毎に指定したデータ型に変換されます。データ型には
- Pythonの[intやfloat](https://docs.python.org/ja/3/library/stdtypes.html#typesnumeric)、[str](https://docs.python.org/ja/3/library/stdtypes.html#text-sequence-type-str)、[bool](https://docs.python.org/ja/3/library/functions.html#bool)クラスなど
- `Numpy`のデータ型（[np.int8やnp.float32](https://numpy.org/doc/stable/user/basics.types.html)など）
- `Pandas`のデータ型（[CategoricalDtype](https://pandas.pydata.org/docs/reference/api/pandas.CategoricalDtype.html)など）

といった、様々なデータ型を指定することができます。


```python
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`に単一のデータ型を指定すると、全カラムが同じデータ型に変換されます。


```python
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`が送出され、変換に失敗した列番号がエラーメッセージに出力されます。


```python
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`で読み込んでみます。


```python
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から採用された新しい欠損値](https://pandas.pydata.org/docs/user_guide/missing_data.html#missing-data-na)です。

`df.dtypes`でデータ型を確認すると、整数カラムと浮動小数点数カラムのデータ型が従来の`int64`/`float64`ではなく、`Int64`/`Float64`（いずれも先頭が大文字）となっています。[Int64(Int64Dtype)](https://pandas.pydata.org/docs/dev/reference/api/pandas.Int64Dtype.html)や[Float64(Float64Dtype)](https://pandas.pydata.org/docs/dev/reference/api/pandas.Float64Dtype.html)は、どちらも`<NA>`をサポートする新しいデータ型です。

続いて、バックエンドを`pyarrow`で読み込んでみます。


```python
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](https://pandas.pydata.org/docs/dev/reference/api/pandas.ArrowDtype.html)になりました。

:::note info

pyarrowでは他にも、
- 日付を32bitで表現する[date32](https://arrow.apache.org/docs/python/generated/pyarrow.date32.html)
- 任意の有効桁数を指定可能な[decimal128](https://arrow.apache.org/docs/python/generated/pyarrow.decimal128.html)
- バイナリデータに特化した[binary](https://arrow.apache.org/docs/python/generated/pyarrow.binary.html)

など、`pandas`や`numpy`には無い[多様なデータ型を多数提供](https://arrow.apache.org/docs/python/api/datatypes.html#factory-functions)しています。`pandas`を使いつつ`pyarrow`のデータ型も活用したい、というシーンに有効です。

:::

なお、`dtype_backend`と`dtype`を同時に指定した場合、`dtype`で指定したデータ型が優先されます。


```python
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`)を自動推定できない<br>`skipfooter`や`on_bad_lines`等の他引数と併用できない|
|`python`|全引数をサポート|他パーサーより低速|
|`pyarrow`|高速<br>マルチスレッド対応|実験段階であり、多数引数が実装中([GH38872](https://github.com/pandas-dev/pandas/issues/38872))|

ただし、`engine='c'`と明示的に指定しても、`c`エンジンが処理できないデータや引数が含まれていた場合は以下のように`ParserWarning`が送出され、`python`エンジンにフォールバックすることがあります。


```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`です。

変換には、キーをカラム名（もしくは列番号）、値を変換関数とする辞書を指定します。


```python
# 最低気温と最高気温を、摂氏（℃）から華氏（℉）に変換
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`が送出されます）。


```python
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`にキャスト可能な場合のみ行われます。


```python
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`以外の値が残ってしまう場合、置換処理は行われません。


```python
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ファイルを用意します。


```sh
$ 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`）では、カラムや値の先頭にスペースがそのまま付与されてしまいます。


```python
df = pd.read_csv('sample_initialspace.csv')
print(list(df.columns))
```

    ['Date', ' Area', ' MinTemp', ' MaxTemp', ' WindSpeed', ' Rainfall']


`skipinitialspace=True`と指定することで、先頭のスペースを除去することができます。


```python
df = pd.read_csv('sample_initialspace.csv', skipinitialspace=True)
print(list(df.columns))
```

    ['Date', 'Area', 'MinTemp', 'MaxTemp', 'WindSpeed', 'Rainfall']


### skiprows
読み飛ばす行番号(0始まり)を指定します。デフォルトは`None`です。

整数のリストを指定すると、リストで指定された行番号のみがスキップされます。


```python
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`行（ヘッダー行を含む）がスキップされます。


```python
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`と評価されるオブジェクト）を返すレコードのみが読み込まれます。

以下は、ファイルの偶数行のみを読み込む例です。


```python
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


:::note warn

`skiprows`と`header`を同時に指定する場合、`skiprows`⇒`header`の順に実行される点に注意が必要です。この場合、`header`で指定する行番号は、**ファイルの行番号ではなく、`skiprows`によりスキップされた後の行番号を指定** します。

:::

`sample_multiheader.csv`のデータ内容を再掲の上、`skiprows`と`header`を同時に指定してみます。


```sh
$ 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



```python
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'`でのみ有効です。


```sh
$ 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



```python
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
読み込むレコード数（ヘッダー行数を除く）を指定します。

:::note info

データ分析等で大きなCSVファイルの中身をざっと確認したい時、最初に`nrows`でファイルの先頭部分のみを読み込んでデータ内容を確認するのに重宝します。

:::


```python
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`が送出されます）。


```python
# 最初の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`とすると、データ全体を読み込んでからデータ型が推定されます。メモリ消費量は増加しますが、正確なデータ型を特定することが可能です。


```python
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`等のメモリ上にあるデータは対象外）。

:::note info

`True`に設定するとディスクI/Oのオーバーヘッドがなくなり、パフォーマンスが向上することがあります。

:::

効果を確認するため、[Amazon Customer Reviews Dataset](https://s3.amazonaws.com/amazon-reviews-pds/readme.html)から日本語のAmazonレビューデータをダウンロードして使用します。データ件数は約260万行です。


```sh
$ 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`が若干早いようですが、標準偏差も含めると明確な変化は見られませんでした。


```python
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では以下の文字列群が「欠損値と見なす値」として事前定義されています。本記事ではこれを「欠損値リスト」と呼ぶことにします。


```python
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`に単一の値を指定すると、指定値が欠損値に置換されます。


```python
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


リストを指定すると、リスト内の値が欠損値に置換されます。


```python
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


辞書を指定すると、カラム毎に異なる欠損値を指定することができます。


```python
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`（使用する）です。

ほとんどの値が欠損値のファイルを作成して検証します。


```sh
$ 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`）では、欠損値リストの値のみが欠損値に自動変換されます。


```python
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`引数を指定すると、指定した値も併せて欠損値に置換されます。


```python
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`にすると、欠損値リストの値は欠損値に置換されません。


```python
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`を指定すると、指定値のみが欠損値に置換されます。


```python
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`にすると置換処理は無効化されます。

:::note info

無効化するとファイル読み込みのパフォーマンスが向上しますので、前処理済みデータのように「欠損値が含まれていないことが担保されている」CSVファイルには効果的です。

:::


```python
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`は無視されます。警告も出力されません。


```python
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`とすることで、欠損値処理のオーバーヘッドをデバッグするのに役立ちそうです。


```python
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`（スキップする）です。

空行を含む以下ファイルで検証します。


```sh
$ 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



```python
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`とすると、空行は全て欠損値として扱われます。


```python
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](https://ja.wikipedia.org/wiki/ISO_8601)による表記
- `year`/`month`/`day`/`time`: 年/月/日/時刻を各カラムに分割
- `dayfirst`: イギリス式と呼ばれる、`DD/MM/YYYY`（先頭が日）による表記
- `japanese`: 日本語による表記


```sh
$ 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`型で読み込まれます。


```python
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](https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.datetime64)に変換されます。


```python
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`に変換されます。


```python
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つの日付に変換するには、以下のようにします。変換後のカラム名は、元のカラム名を`_`で結合したものです。


```python
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秒


:::note warn

複数カラムの変換結果は、`DataFrame`の**0カラム目**に挿入されます。入力ファイルのカラム順と一致しなくなりますのでご注意下さい。

:::

日付と時刻を結合することも可能です。


```python
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`パーサーにフォールバックして再度推定されます。


```python
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']])


辞書を指定すれば、変換後のカラム名を任意の名前に変更できます。


```python
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

:::note alert

どちらの引数もver2.0で非推奨となりました。代わりに、同バージョンで新たに追加された`date_format`引数を使用して下さい。

:::

### keep_date_col
`parse_dates`で日付変換する場合、変換前のカラムを残すかどうかを指定します。デフォルトは`False`（残さない）です。

`True`に設定すると、変換前のカラムは削除されません。


```python
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`で変換されてしまい、月と日が逆転してしまいます。


```python
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`に指定することで、正しい日付で変換されます。


```python
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`カラムも、正しい日付で変換できます。


```python
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`と指定しています。


```python
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`でキャッシュを無効化できます。


```python
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秒


:::note info

重複する日付がファイルに多数含まれている場合、キャッシュされた変換結果を再利用することで、読み込み時間を短縮できます。キャッシュ効果の検証結果は次章をご参照下さい。

:::

## イテレーション
`CSV`ファイルの分割読み込みを指定します。

最初の気象サンプルデータを再掲します。


```sh
$ 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`というイテレータを返します。


```python
reader = pd.read_csv('sample.csv', iterator=True)
print(type(reader))
```

    <class 'pandas.io.parsers.readers.TextFileReader'>


ループ処理と組み合わせることで、`TextFileReader`から`DataFrame`を順次取得することができます。`chunksize`に整数を指定することで、指定行数ずつ順に読み込まれます。


```python
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したい場合に便利です。


```python
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`に指定した行数が順次読み込まれます。


```python
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


:::note info

このように、`iterator`や`chunksize`は一度にメモリに収まらないような巨大なCSVファイルを分割処理したいときに便利です！

:::

## 圧縮フォーマット、クォート、文字コードなど
`CSV`ファイルの圧縮フォーマットや文字コード、クォート文字のパース方法などを指定します。

### compression
ファイルの圧縮フォーマットを指定します。対応済みの圧縮フォーマットは以下の通りです。Zstdは[ver1.4](https://pandas.pydata.org/docs/whatsnew/v1.4.0.html#other-enhancements)、Tarは[ver1.5](https://pandas.pydata.org/docs/whatsnew/v1.5.0.html#reading-directly-from-tar-archives)でそれぞれ追加されました。


```python
print(pd.io.common._supported_compressions)
```

    {'xz', 'bz2', 'tar', 'gzip', 'zip', 'zstd'}


`compression`のデフォルトは`infer`です。ファイル拡張子から自動的に圧縮フォーマットが推定され、オンザフライで解凍して読み込まれます。


```sh
$ gzip -kf sample.csv
```


```python
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`とすれば、解凍せずにテキストファイルとして読み込まれます。


```python
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
桁区切りの文字を指定します。以下のファイルで検証します。


```sh
$ cat sample_thousands.csv
```

    product,price
    pizza,"2,980"
    pc,"238,000"
    car,"4,540,000"


`thousands`を指定しない場合、クォーテーション内のカンマは文字列として扱われるため、`price`カラムのデータ型は`object`型となります。


```python
df = pd.read_csv('sample_thousands.csv')
print(df.dtypes)
```

    product    object
    price      object
    dtype: object


`thousands=','`と指定することで、`price`カラムを数値として読み込むことができます。


```python
df = pd.read_csv('sample_thousands.csv', thousands=',')
print(df.dtypes)
```

    product    object
    price       int64
    dtype: object


### decimal
小数点とする文字を指定します。デフォルトは`.`です。

国際的には、小数点にはピリオドとカンマのいずれかを使用する国が多いようです（[Wikipedia(小数点)](https://ja.wikipedia.org/wiki/%E5%B0%8F%E6%95%B0%E7%82%B9)より引用）。
> 小数点として点（point on the line）を用いるか、コンマ（comma on the line）を用いるかは、国、地域、文化によってまちまちである。ごく大まかには、イギリス、米国、日本、中国、インドでは「点」を用い、フランス、ドイツ、イタリア、スペイン、ロシアでは「コンマ」を用いる。以下では、国際度量衡総会での呼び方にならって、「点」を用いる方式を「イギリス式（British practice）」、「コンマ」を用いる方式を「フランス式（French practice）」と呼称する。

以下、カンマを小数点とするフランス式のファイルで検証します。


```sh
$ 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",



```python
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`（ラウンドトリップ）のいずれかを指定できます。

桁数の多い浮動小数点数のファイルを準備し、各コンバーターで読み込んでみます。


```sh
$ cat sample_precision.csv
```

    value
    3.141592653589793238462643383279502884



```python
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`が有益となりそうです。


```python
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`改行コードのファイルを用意します。


```sh
$ 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`以外の改行コードでも読み込むことができます。


```python
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`が送出されます。


```python
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文字を指定できます。

各フィールドをシングルクォーテーションで囲ったファイルで検証します。


```sh
$ 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`を指定せずに読み込むと、シングルクォーテーションも値の一部として読み込まれます。


```python
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='\''`とすることで、シングルクォートがクォート文字として適切に処理されました。


```python
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](https://docs.python.org/ja/3/library/csv.html#csv.QUOTE_MINIMAL)|`sep`、`quotechar`、`lineterminator`などの文字列を含む値のみ|
|1 もしくは [csv.QUOTE_ALL](https://docs.python.org/ja/3/library/csv.html#csv.QUOTE_ALL)|全ての値|
|2 もしくは [csv.QUOTE_NONNUMERIC](https://docs.python.org/ja/3/library/csv.html#csv.QUOTE_NONNUMERIC)|数値以外の値|
|3 もしくは [csvQUOTE_NONE](https://docs.python.org/ja/3/library/csv.html#csv.QUOTE_NONE)|クォートしない|

カンマを含む文字列をダブルクォートで囲ったファイルで検証します。


```sh
$ 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)`でも同じ結果となります。


```python
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](https://docs.python.org/ja/3/library/csv.html#csv.QUOTE_NONNUMERIC)の以下仕様に沿っています。
> reader に対しては、クオートされていない全てのフィールドを float 型に変換するよう指示します。


```python
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)`では、各カラムはクォート処理されないため、カラムと値の関係がずれてしまいます。また、ダブルクォーテーションも値に含まれたままです。


```python
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つのダブルクォートで囲ったファイルで検証します。


```sh
$ 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つのダブルクォートが単一のダブルクォートと認識され、期待通りの結果が得られます。


```python
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カラムとして読み込まれてしまい、カラムと値がずれてしまいます。


```python
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=' '`とするとスペースがエスケープされ、空白を除去した文字列として読み込まれます。


```python
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`を指定した方が楽です。


```sh
$ 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



```python
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


:::note info 

ファイル内のコメント開始文字が共通している場合は、`header`や`skipfotter`を個別に指定するよりも`comment`で指定した方が楽です。

:::

### encoding
CSVファイルの文字コードを指定します。デフォルトは`None`です。

`sample.csv`を、Windowsで使用されている`CP932`という文字コードに変換したファイルで検証します。


```sh
$ 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公式ドキュメントの[標準エンコーディング](https://docs.python.org/ja/3/library/codecs.html#standard-encodings)をご参照下さい。


```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](https://github.com/chardet/chardet)というPythonパッケージでファイルの文字コードを推定することができます。推定結果には、`encoding`に文字コード名、`confidence`に確信度が出力されます。

あくまで推定値ですが、`UTF`/`JIS`/`EUC`といった文字コードの大体の種類を特定するのに役立ちます。


```python
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`が送出されます。


```python
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'`とすると、エンコードに失敗した文字はスキップされます。


```python
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)](https://ja.wikipedia.org/wiki/%E7%89%B9%E6%AE%8A%E7%94%A8%E9%80%94%E6%96%87%E5%AD%97_(Unicode%E3%81%AE%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF))に置換されます。


```python
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'`とすると、エンコードに失敗した文字はバックスラッシュによるエスケープシーケンスに置換されます。


```python
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](https://docs.python.org/ja/3/library/csv.html#csv.Dialect)というクラスが存在し、`read_csv`にも同クラスを適用することができます。

検証用に、`csv.Dialect`で定義されている`excel`、`excel-tab`、`unix`の各記法でCSVファイルを生成してみます。


```python
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$`）であり、クォートは必須ではないようです。


```sh
$ 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`と同じです。


```sh
$ 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`（レコード末尾の`$`）であり、全フィールドがクォート処理されています。


```sh
$ 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`は代表的な記法をデフォルトで認識できるため、どちらも特別な指定なく読み込むことができます。


```python
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`のクラスを直接指定することもできます。


```python
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


:::note info

もし今後、`csv.dialect`クラスにも定義されていない独自のCSV記法を扱う場面があれば、`dialect`引数が役立ちそうです。

:::

## エラーハンドリング
フィールド数がヘッダーのカラム数よりも多いレコード（以下、不正レコードと呼称）の扱いを指定します。デフォルトは`error`です。

不正レコードを含んだ以下ファイルで検証します。末尾2行が不正レコードです。


```sh
$ 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
:::note alert

どちらもver1.3で非推奨となり、ver2.0で削除されました。代わりに、ver1.3で追加された`on_bad_lines`引数を使用して下さい。

:::

### on_bad_lines
不正レコードに対する処理方法を指定します。

デフォルトは`error`で、不正レコードが検知されると以下のように`ParserError`が送出され、読み込みが失敗します。


```python
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'`にすると、不正レコードは警告が出力されてスキップされ、正常なレコードのみ読み込むことができます。


```python
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'`にすると、不正レコードは警告無しでスキップされ、正常なレコードのみ読み込むことができます。


```python
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'`でのみ使用可能です。


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


:::note info

Kaggle等のデータ分析コンペでは少数の不正レコードを`skip`で無視することがよくありますが、基幹業務では通常、検知された不正レコードを保存して後処理する必要があります。

以下のように関数内で不正レコードをリストに退避しておけば、**正常なレコードのみを読み込みつつ、不正レコードに後処理を実行することが可能**です。リスト退避以外にも、ファイルやログに出力するのにも使えます。

:::


```python
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](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html)で変換するパターンを比較
- 日付変換における`cache_dates`の指定有無を比較

計測用に、データ型が異なる5カラム × 5,000万行のサンプルデータを作成します。


```python
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拡張機能を有効化します。


```python
%load_ext memory_profiler
```

## 1. `engine`のパフォーマンス比較
`python`、`c`、`pyarrow`の3つのパーサーエンジンで比較します。

読み込み時間では、`pyarrow`エンジンが圧倒的に速い結果となりました。`c`エンジンの約5倍、`python`エンジンの約20倍です！`pyarrow`はマルチスレッドに対応している唯一のエンジンであるため、vCPU数が多い環境ほど読み込み時間の短縮が期待できます。


```python
%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`エンジンでは併用可能な引数が少ないという制約があるものの、利用できるシーンでは積極的に使った方が良さそうです。


```python
%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`の変換処理で時間を要したものと思われます。


```python
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`指定ありの方が省メモリでした。


```python
%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%と劇的に減っています！値のレンジや重複度合いに応じて適切なデータ型を用いることで、読み取り後のメモリサイズを大きく削減できることがあることが分かりました。


```python
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`の配列演算により高速化が行われた、と推測されます。メモリ消費量では大きな差異は見られませんでした。


```python
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`を使用しても大きなボトルネックにはならなさそうです。メモリ消費量はどちらも同程度でした。


```python
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](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html)で変換するパターンで比較します。

結果は、実行時間・メモリ消費量とも近しい結果となりました。どちらを使っても良さそうですが、`to_datetime`は変換エラー時の挙動やタイムゾーン⇒UTCへの変換といった便利機能が多数実装されているため、より高度な変換要件がある場合は`to_datetime`が便利です。


```python
%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出力します。


```python
# 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のユーザーガイド](https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#datetime-handling)にも`parse_datesはISO8601に最適化されている`との記載があるため、このフォーマットであればキャッシュが無くても十分に高速なようです。


```python
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割近くも大幅削減されました！


```python
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割ほど実行時間が削減されました。


```python
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データ](https://data.e-gov.go.jp/data/dataset/mlit_20140919_0747/resource/7b8ced63-2453-4c85-91d9-3299674c01b6)を使用します。先頭5行と末尾5行のファイル内容は以下となります。


```sh
$ 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つの引数を直接指定しましたが、以降では各引数を様々な方法で定義してみます。


```python
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点です。
- 組み込み関数のため、ライブラリの追加インストールが不要
- ミュータブル/イミュータブルを問わず、任意のオブジェクトを定義できる


```python
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](https://docs.python.org/ja/3/library/dataclasses.html)は、Python3.7から実装されたデータ定義用の標準ライブラリです。引数用のクラスを定義し、`asdict`で辞書に変換することで引数を展開します。

ここでは、`usecols`の値をリストではなくタプルで定義しています。[field](https://docs.python.org/ja/3/library/dataclasses.html#dataclasses.field)を使えばリストも定義可能ですが、`usecols`にはタプルも指定できることと、`dataclasses`のフィールドはイミュータブルの方がが望ましいとの考えから、今回はタプルとしました。

`dataclasses`を使用するメリットは2点です。
- 外部パッケージが不要
- 引数のアノテーションや型チェックができる


```python
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](https://yaml.org/)で定義するパターンです。`python`では、[pyyaml](https://pyyaml.org/wiki/PyYAMLDocumentation)等の外部パッケージを使用して読み書きします。

ポイントは`dtype`の指定方法です。`dict`や`dataclasses`のように`pd.UInt16Dtypes()`といったpythonオブジェクトを直接定義できないため、同じデータ型を意味する`UInt16`という文字列に置き換えて定義します。

`yaml`で定義するメリットは2点です。
- プログラミング言語に依存しない、メジャーな設定ファイルフォーマットで定義できる
- 引数をファイルで管理できる


```sh
$ 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



```python
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](https://toml.io/ja/latest)も注目を集めている設定ファイルフォーマットの1つです。python3.11で`tomllib`が標準ライブラリに追加され、`toml`ファイルの読み込みが出来るようになりました。

`params.toml`に引数を定義して、`tomllib.load()`で読み込みます。バイナリモード（`rb`）で読み込んでいるのは`tomllib`の仕様によるものです。

`toml`を使用するメリットは3点です。
- 外部パッケージが不要(python 3.11以降)
- プログラミング言語に依存しない、メジャーな設定ファイルフォーマットで定義できる
- 引数をファイルで管理できる


```sh
$ 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"



```python
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`の取り扱いが少しでも便利になれば幸いです。

長文にお付き合いいただき、ありがとうございました。