PythonでのCSV,JSONファイルの読み書き

オライリーの本「PythonとJavaScriptではじめるデータビジュアライゼーション」に、
pythonでのcsv,jsonファイルのいい感じの入出力方法がまとまっていたのでメモ。
Jupyter Notebookで動作確認したところ、エラーが出た箇所が少しあったので、
本のコードと少し異なっているところがあります。

本の公式サイト：https://www.oreilly.co.jp/books/9784873118086/#toc

# 動作環境
Windows 10 Pro 64bit
Python: 3.6.1
Anaconda: 4.4.0
Jupyter Notebook: 1.0.0

# 使用例データ
入出力のデータは以下を使用

```
nobel_winners = [
{ 'category': 'Physics',
  'name': 'Albert Einstein',
  'nationality': 'Swiss',
  'sex': 'male',
  'year': 1921},
{ 'category': 'Physics',
  'name': 'Paul Dirac',
  'nationality': 'British',
  'sex': 'male',
  'year': 1933},
{ 'category': 'Chemistry',
  'name': 'Marie Curie',
  'nationality': 'Polish',
  'sex': 'female',
  'year': 1911},
]
```

# コード例

## csvファイル読み書き
pythonで辞書をcsvファイルに書き込むのは以下。
nobel_winners[0].keys()で配列の第1要素を取り出し、辞書のキーを取得している。
キーをソートするため、sorted(cols)を使い、colsに代入。
本では、cols.sort()でソートしていたが、エラーが出たため以下のように書いている。

```py
cols = nobel_winners[0].keys()
cols = sorted(cols)

with open('data/nobel_winners.csv', 'w') as f:
    f.write(','.join(cols) + '\n')
    
    for o in nobel_winners:
        row = [str(o[col]) for col in cols]
        f.write(','.join(row) + '\n')
```

書き出したnobel_winners.csvを読み込むときは以下を実行する。

```py
with open('data/nobel_winners.csv', 'r') as f:
    for line in f.readlines():
        print(line, sep='')
```
```text:Out
category,name,nationality,sex,year

Physics,Albert Enistein,Swiss,male,1921

Physics,Paul Dirac,British,male,1933

Chemistry,Marie Curie,Polish,female,1911
```

## csvモジュールを使用しての読み書き
次にpythonのcsvモジュールを使用しての読み書きを行う。
openにnewline=''を指定しないとJupyter Notebookで実行したとき、
writer.writerowの下にさらにもう一つ改行が入ってしまったので入れている。

```py
import csv

with open('data/nobel_winners.csv', 'w', newline='') as f:
    fieldnames = nobel_winners[0].keys()
    fieldnames = sorted(fieldnames)
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    for w in nobel_winners:
        writer.writerow(w)
```

csvモジュールを使用してファイルを読み込む時は以下のようになる。

```py
import csv

with open('data/nobel_winners.csv', 'r') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row, sep='')
```
```text:Out
['category', 'name', 'nationality', 'sex', 'year']
['Physics', 'Albert Enistein', 'Swiss', 'male', '1921']
['Physics', 'Paul Dirac', 'British', 'male', '1933']
['Chemistry', 'Marie Curie', 'Polish', 'female', '1911']
```

行をPythonディクショナリに変換することでも、csvデータは取得できる。
そのように取得したのが以下になる。

```py
import csv

with open('data/nobel_winners.csv') as f:
    reader = csv.DictReader(f)
    nobel_winners = list(reader)

for w in nobel_winners:
    w['year'] = int(w['year'])
    
nobel_winners
```
```text:Out
[OrderedDict([('category', 'Physics'),
              ('name', 'Albert Enistein'),
              ('nationality', 'Swiss'),
              ('sex', 'male'),
              ('year', 1921)]),
 OrderedDict([('category', 'Physics'),
              ('name', 'Paul Dirac'),
              ('nationality', 'British'),
              ('sex', 'male'),
              ('year', 1933)]),
 OrderedDict([('category', 'Chemistry'),
              ('name', 'Marie Curie'),
              ('nationality', 'Polish'),
              ('sex', 'female'),
              ('year', 1911)])]
```

csvリーダーはcsvファイルから読み込むときにデータ型を推測せず、
すべてを文字列として扱うので、yearはintにキャストする必要がある。

## JSONファイル読み書き

Pythonディクショナリはjsonモジュールを使ってJSONファイルに保存できます。
保存の際はjsonモジュールのdumpメソッドを用います。

```py
import json

with open('data/nobel_winners.json', 'w') as f:
    json.dump(nobel_winners, f)
```

JSONファイルの読み込みについては、jsonモジュールの
loadメソッドを使うことでできます。

```py
import json

with open('data/nobel_winners.json') as f:
    nobel_winners = json.load(f)

nobel_winners
```
```text:Out
[{'category': 'Physics',
  'name': 'Albert Enistein',
  'nationality': 'Swiss',
  'sex': 'male',
  'year': 1921},
 {'category': 'Physics',
  'name': 'Paul Dirac',
  'nationality': 'British',
  'sex': 'male',
  'year': 1933},
 {'category': 'Chemistry',
  'name': 'Marie Curie',
  'nationality': 'Polish',
  'sex': 'female',
  'year': 1911}]
```

jsonモジュールの読み込みでは、csvとは異なりyearの整数型は
保たれたまま読み込まれるのでキャストは不要です。

## datetime型のJSONでのエンコード
datetime型を含むPythonデータをエンコードするには、
以下のようなカスタムエンコーダを作成する。

```py
import datetime
import json

class JSONDateTimeEncoder(json.JSONEncoder):  
    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return obj.isoformat()
        else:
            return json.JSONEncoder.default(self, obj)
    
def dumps(obj):
    return json.dumps(obj, cls=JSONDateTimeEncoder)

now_str = dumps({'time': datetime.datetime.now()})
now_str
```

```text:Out
'{"time": "2017-09-03T01:03:32.634095"}'
```

まず、カスタマイズした日付処理エンコーダを
作成するためにJSONEncoderをサブクラス化する。
この処理では、渡された引数objがdatetimeオブジェクトであれば、
日付や日時のisoformatを返すことを実行している。
json.dumpsメソッドにcls引数にカスタム日付エンコーダを設定する。

## 参考文献
- PythonとJavaScriptではじめるデータビジュアライゼーション https://www.oreilly.co.jp/books/9784873118086/#toc
- CSV ファイルの読み書き https://docs.python.jp/3/library/csv.html