%pip install pandas pyarrow numpy tqdm dask graphviz
import sys
import numpy as np
import pandas as pd
pd.options.display.max_columns=9
pd.options.display.float_format = '{:,.4f}'.format
N = 100_000
COLS = 100
rng = np.random.default_rng()
vals = rng.random((N, COLS))
index = pd.date_range('2020-01-01', periods=N, freq='S')
df = pd.DataFrame(vals, index=index,
columns=(f'col{n+1}' for n in range(COLS)))
df
このデータを、CSV形式で保存します。
%%time
df.to_csv("100k_100.csv", index_label="date")
!ls -l 100k_100.csv
ファイルの出力に、14.3秒とかなりの時間がかかります。出力ファイルサイズは約200MBとなり、数値ひとつあたり20バイト近くも使っています。
%%time
df = pd.read_csv('100k_100.csv', index_col=0)
df[:3]
%%time
df.to_pickle('100k_100.pickle')
!ls -l 100k_100.pickle
%%time
df = pd.read_pickle("100k_100.pickle")
出力に 83ms、入力に57msと、CSVに比べればかなり高速です。ファイルサイズも80MBと、CSVの40%程度に抑えられています。Python以外の環境にデータを受け渡す必要がなければ、データ交換フォーマットとして利用を考慮しても良いでしょう。
ただし、Pickleは読み込み時にプログラムを実行してしまう可能性もあるフォーマットなため、一般的なデータ交換には適していません。信頼できるPickleのみを利用し、外部の組織などからのファイルは決して利用しないようにしましょう。
Apache Arrow は大規模なデータをメモリに読み込んで処理するためのプラットフォームで、高速なデータ転送やファイル入出力機能など、効率的なデータ処理に必要な機能を提供してくれます。PyArrowはAppache ArrowのPythonインターフェースで、NumPyやpandasと連携して、Apache Arrowを利用できるようになっています。
ここでは、PyArrowが提供するファイルフォーマットである、Parquet(パーケイ) を利用してみます。Parquetは、CSVと同じように、pandasのデータフレームから簡単に保存できます。
%%time
df.to_parquet('100k_100.parquet')
!ls -l 100k_100.pickle
%%time
df = pd.read_parquet('100k_100.parquet')
Parquetの出力に約1秒、入力に250ms。CSVよりはだいぶマシですが、Pickleよりは遅い、という結果になりました。
しかし、これだけではParquetの真価は計れません。Parquetは、一般的なデータファイルのように行単位にデータを格納するのではなく、カラム単位にデータを格納しています。こういったデータ形式をカラムナ形式といいます。
一般的な行単位のデータ形式に比べて、カラムナ形式は何が優れているのでしょうか?
これまでの例で見てきたような、カラムが100もあるようなデータを処理する場合、同時に100カラムのデータをすべて参照する、ということはどのぐらいあるでしょうか?ほとんどの場合は、同時に集計するのは一部のカラムだけで、すべてのデータを一度に読み込む必要はないでしょう。不要なデータまで読んでしまっては時間がかかりますし、メモリの使用量も増大してデータ処理が難しくなります。
Parquetでは、このような利用形態に合わせて、必要なカラムだけを選択して読み込めます。次の例は、先頭の2カラムだけを読み込んでいます。
%%time
pd.read_parquet('100k_100.parquet', columns=["col1", "col2"])
必要なカラムを制限することで、読み込み時間が 238ms->16ms と短縮されました。また、メモリの使用量も大きく削減されており、より多くの行を一度に処理できるようになっています。
df['year'] = df.index.year
df['month'] = df.index.month
df['day'] = df.index.day
df['hour'] = df.index.hour
df[:3]
%%time
df.to_parquet('partitioned/100k_100_1.parquet', index=True, partition_cols=["year", "month", "day", "hour"])
このParquetは、データをつぎのようなディレクトリに分割してディスクに格納します。
!tree partitioned
このようにパーティションを指定すれば、検索条件を指定して効率的にデータを絞り込むことができます。次の例は、2020年1月2日3時台のデータだけを取得します。
%%time
filters = [
[('year', '=', 2020), ('month', '=', 1), ('day', '=', 2), ('hour', '=', 3)]
]
df = pd.read_parquet('partitioned/100k_100_1.parquet', columns=["col1"], filters=filters)
df
先日、2020年7月24日に最初の安定バージョンとなる Apache Arrow 1.0がリリース され、これ以降、Parquetなどのファイルフォーマットも互換性が保証されるようになりました。
Apache Arrow/PyArrowは今回紹介した以外にも多くの機能が提供しています。この機会にぜひ調べてみてください。
Copyright © 2020 Atsuo Ishimoto
Powered by miyadaiku