jsonlの読み込み
まとめ
- jsonlファイルで中身がバカでかいやつは一気に読み込むとメモリエラーとなるのでchunkを使用するとよい。
- jsonの中身は辞書型みたいな感じでデータ格納されてるっぽい。
- バカでかいファイルの読み込みは一度parquet形式で保存して次回からそれを読み込むと速い。
本文
jsonもあんまりよくわかってませんが、jsonlが出てきたのでメモ。
pandasでJSON文字列・ファイルを読み込み(read_json) | note.nkmk.me
まずは上記サイト上の「JSON Lines(.jsonl)を読み込み」を参考にファイルを読み込んでみましょう。
%%time test = pd.read_json(DATA / "test.jsonl", orient= "record", lines= True) print(type(test)) display(test.head())
dataframe形式で返ってきました。ただこれは読み込む先のjsonlファイル上の問題ですが、events列が以下の通りlistとなっていてさらにその中身がdictとなっていて扱いずらそうな。
print(test.iloc[0,1]) print(type(test.iloc[0,1])) print(type(test.iloc[0,1][0]))
そこで、他の人がやっていたchunkを使用して読み込むパターンをやってみましょう。ドキュメントを見る感じchuncksize
はjsonlを読み込むときのみに使用できるみたい。ただ何を表しているのかいまいちわからなかったので、手を動かしながらやってみます。
pandas.read_json — pandas 1.5.1 documentation
%%time cnt = 0 CHUNKSIZE = 10000 test = pd.read_json(DATA / "test.jsonl", lines= True, chunksize= CHUNKSIZE) print(type(test)) for chunk in test: cnt += 1 if cnt > 1: continue display(chunk) print(type(chunk)) print(cnt)
うーん、まず返してくるのはドキュメントの通りJsonReaderという形式です。for文とかで一行ずつ取り出してみるとchuncksize
を使用しなかったときのようなdataframe形式で取得できました。1chunkあたりのデータ数がちょうどCHUNKSIZE
と一致しているので、chucksize
は1回?あたりに読み込む行数みたいなイメージでしょうか。また、今回の場合はfor文168回回しているので、全データ数は10000*168=1,680,000行くらいでしょうか。大きいですね。
結局なぜこのchuncksize
を使用してJsonReader形式のデータを返しているのかとう話ですが、おそらく返ってくる値がdictとかでめんどくさいのを読み込みの段階でうまく扱えるからですかね?読み込む早さはchuncksize
使用している方が読み込んだ後にfor文回しているので遅かったです(ちなむと読み込み自体は以下の通りJsonReader形式で読み込んだ方が速かったです。)そもそもjsonオブジェクトって項目名と値を:で結んだりして表現するみたいなので、辞書型みたいで複雑になるのはあるあるなのでしょうか。また、それらをそのままの形式で一気に抽出してからいじるより、サクッとjsonreader形式で抽出してからfor文で辞書型の中身をいじった方が早かったりするんですかね?
%%time CHUNKSIZE = 10000 test = pd.read_json(DATA / "test.jsonl", lines= True, chunksize= CHUNKSIZE)
jsonreader + for文でaid
項目のみ抽出したとき(30s)
%%time CHUNKSIZE = 10000 test = pd.read_json(DATA / "test.jsonl", lines= True, chunksize= CHUNKSIZE) print(type(test)) for chunk in test: for session_id, events in chunk.values: last_aid_list.append([session_id, events[-1]["aid"]])
lines=Trueで抽出してからapplyで加工したとき(23s)
%%time test = pd.read_json(DATA / "test.jsonl", orient= "record", lines= True) test["aid"] = test["events"].apply(lambda x: x[-1]["aid"])
時間測ってみたもののむしろapplyで加工した方が速かった。。うーん、jsonreaderで読み込むことにどういうメリットがあるんだろう。。
上記までもそれなりのデータ数がでしたが、さらに大きいデータになると一気にloadしようとする方はメモリエラー出た一方でchunck
は読み込めました。メモリ削減の効果はありそうです。ただ、これだけのデータ数になると読み込みにとにかく時間がかかる。。8分読み込んでもまだ。。
%%time def read_data(path, is_train): ''' data読み込み ''' # # lines 読み込み # # データ数大きいとメモリ不足 # df = pd.read_json(DATA / path, orient= "record", lines= True) # df["aid"] = df["events"].apply(lambda x: x[-1]["aid"]) # df["ts"] = df["events"].apply(lambda x: x[-1]["ts"]) # df["type"] = df["events"].apply(lambda x: x[-1]["type"]) # jsonreader 読み込み last_aid_list = [] CHUNKSIZE = 10000 json_reader = pd.read_json(DATA / path, lines= True, chunksize= CHUNKSIZE) for chunk in json_reader: for session_id, events in chunk.values: last_aid_list.append([session_id, events[-1]["aid"]]) # del json_reader,chunk del chunk gc.collect() df = pd.DataFrame(last_aid_list) del last_aid_list gc.collect() return df train = read_data("train.jsonl", is_train= True)
ということで、parquet formatで早く読み込むというnotebookがあったのでこちらを使わせて頂く。parquetに一度保存して次回からはparquetを読み込むという方法。確かにこれなら読み込みにかかる時間がだいぶ減った。にしても5 / 124ファイルのデータ数が22,547,356行とは。。ひいい。
%%time def read_data(path, is_train, is_save= True): ''' data読み込み ''' # =================== # read existing parquet files files = sorted(glob('../input/otto-chunk-data-inparquet-format/train_parquet/*'))[0:5] # files = sorted(glob('../input/otto-chunk-data-inparquet-format/train_parquet/*')) dfs = [] for path in tqdm(files): dfs.append(pd.read_parquet(path)) dfs = pd.concat(dfs).reset_index(drop=True) return dfs train = read_data("train.jsonl", is_train= True)
おしまい。