PandasでJSONファイルを保存するときの注意点

個人的にはTensorFlowでモデリングするようになってから、numpyを触る頻度が高くなって、それに伴いpandasを触る機会が減っていった。
データの可視化もpyplotとかseaboneとかを使って、可視化の方法を調べながら模索している状況が増えていった。

しかし、先日副業の案件で学習データをサクッと可視化させようと思ったときに選んだのは、pandasだった。

やはりpandasは優秀かな

テキストデータを読み込んで、異常値を探すとかその場でフィルタリングしようかと思うと、
やはりpandasは楽だなと改めて思たった。(ただnumpy力が低いだけかもしれないが…) 例えば、とりあえずテキストに保存したデータセットをDataFrameに読み込んで、
データセットの概要を知りたいなーと思ったら、

df.describe()

とすれば、カラム毎の最大値・最小値、四分位数とかを出してくれるし、
histグラムで可視化したいなーとおもったら、

df.plot.hist(y=[<可視化するカラム名>], bins=<ビン数>)

とすれば、指定したカラムだけのヒストグラムが可視化できて、すごく楽だった。
分布はどのような形なのか、どの範囲で異常値を切り出すのかなど判断して
そのままDataFrameでフィルターすればいいので、短時間でデータセットのチェックと更新ができた。

DataFrameの保存には注意が必要

前提

前提として、データセットJSON形式で保存しており、 すでに実験するノートブックを用意してある、としてます。
なので、同じJSONの形式で保存することで実験のノートブックを再利用することが前提です。

問題点

通常、PythonでdictionaryをJSONとして保存した場合、

import json


dict_list = []

for i in range(10):
    dict_obj = {}
    dict_obj["single"] = i
    dict_obj["double"] = i * 2
    dict_obj["square"] = i * i
    dict_list.append(dict_obj)

with open('dict2json.json', 'w') as f:
    json.dump(dict_list, f)

のような処理になる。 そして、この場合テキストにはざっくりと

[{"single": 0, "double": 0, "square": 0}, {"single": 1, "double": 2, "square": 1}, {"single": 2, "double": 4, "square": 4}, {"single": 3, "double": 6, "square": 9}, {"single": 4, "double": 8, "square": 16}, {"single": 5, "double": 10, "square": 25}, {"single": 6, "double": 12, "square": 36}, {"single": 7, "double": 14, "square": 49}, {"single": 8, "double": 16, "square": 64}, {"single": 9, "double": 18, "square": 81}]

このような形式で保存されている。
いわゆる、key-valueが配列に格納されている形である。 なので、配列の1要素が1行(1レコード)のデータを表現している。

そして、何も意識せずにDataFrameをJSONファイルとして保存すると痛い目に遭った。 それが今回の教訓だった。

PandasでJSONを読み込むには、

import pandas as pd

df = pd.read_json('dict2json.json')

とすればよい。 そして、このDataFrameをJSONとして保存する場合、以下のように書けば良い。

df.to_json("df2json.json")

人によりけりだとは思うが、簡単で直感的だ。ただし、この場合保存されたファイルの中身は、

{"double":{"0":0,"1":2,"2":4,"3":6,"4":8,"5":10,"6":12,"7":14,"8":16,"9":18},"single":{"0":0,"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9},"square":{"0":0,"1":1,"2":4,"3":9,"4":16,"5":25,"6":36,"7":49,"8":64,"9":81}}

となる。 形としては、カラム毎に、 "index: value" の形で保存されている。

この場合、保存されたファイルを

with open('df2json.json') as f:
    json_data = json.load(f)

として読み込むと、1レコードずつとして読み込まれず、keyが想定していたものと異なる結果になる。

Key-Valueの形式で保存するには、

DataFrameをkey-valueの形式のJSONに保存するときには orient オプションに record を指定する必要があることに注意する。

df.to_json(filtered_data_filepath, orient='records')

そうすると、

[{"double":0,"single":0,"square":0},{"double":2,"single":1,"square":1},{"double":4,"single":2,"square":4},{"double":6,"single":3,"square":9},{"double":8,"single":4,"square":16},{"double":10,"single":5,"square":25},{"double":12,"single":6,"square":36},{"double":14,"single":7,"square":49},{"double":16,"single":8,"square":64},{"double":18,"single":9,"square":81}]

と保存された。