Pandasで大規模データを扱う時にメモリ消費を抑える手法を紹介
Pandasでデータが大きすぎて読み込めない場合、効率的にデータを扱うためのいくつかの方法があります。本記事では、巨大データを効果的に読み込むためのテクニックを解説します。具体的には、必要な列だけを選択して読み込む方法、データをチャンクごとに分割して処理する方法、さらにはデータの型を明示的に指定してメモリを節約する手法について解説します。
はじめに
Pandasで大規模データを扱う場合、メモリ不足でデータが読み込めないことがあります。
ここでは、大規模データを処理する場合に、メモリ使用量を抑えるためのテクニックをいくつか紹介します。また、実際にテクニックを使った場合にどの程度メモリ消費を抑えることができたかについて実際のコードを提示しながら解説したいと思います。
データ処理に慣れている人にはお馴染みの手法ですが、kaggleなどのコンペでも有用な方法ですので覚えておくと良いと思います。
メモリ使用量を抑える方法
列を指定して読み込む
列数(カラム数)は大量だけど、データ分析等に必要な列はその一部な場合、必要な列だけを読み出すことでメモリ使用量を抑えることが可能です。
以下は、具体的なコードの例です。下記のコードでは、large_data.csv
のcolumn1~3
だけを指定して読み込んでいます。
import pandas as pd
df = pd.read_csv('large_data.csv', usecols=['column1', 'column2', 'column3'])
データを分割して読み込む
行ごとに処理を行う場合や、全行のデータなくても処理できる場合、データを塊(チャンク)ごとに読み込んで処理を行うことでメモリ消費量を抑えることが可能です。
以下は、具体的なコードの例です。下記のコードでは、chunk_size=1000
として、10,000行単位で読み込みます。なお、for
文では、例えば「各行に対して処理(統計量計算など)したデータを保存する」「条件に一致するデータだけを抽出する」や、「加工してファイルへ保存する」などの処理を行います。
import pandas as pd
chunk_size = 10000
for chunk in pd.read_csv('large_data.csv', chunksize=chunk_size):
# 各チャンクに対して処理を行う
# 例: 必要なデータをフィルタリング・統計計算、データクリーニング、ファイルへの保存など
データ型を指定してデータ量を減らす
Pandasでは、型を指定しないとデフォルトでは整数はint64
、浮動小数点はfloat64
で読み込まれます。pandasでは、整数はint8, int16, int32, int64
, 浮動小数点はfloat32, float64
を指定することができるので、データの範囲に応じて型を指定することでメモリ消費量を抑えることが可能です。
例えば、年齢はint8
(0~255)の範囲で十分なので、int64
からint8
に変えればデータ量を1/8にできます。
以下は、具体的なコードの例です。下記のコードでは、column1, column2
をそれぞれint32, float32
で読み込んでいます。
df = pd.read_csv('large_data.csv', dtype={'column1': 'int32', 'column2': 'float32'})
実践
実際にダミーデータを作成して、上記のテクニックでどの程度メモリ消費量を抑えることができるか確認してみます。
ダミーデータを作成する
下記はダミーデータの作成コードです。ダミーデータは10万行×100列の0~10,000
の範囲の乱数データです。各列はcolumn1~column100
というラベルが付けられています。
このデータのメモリ使用量は76.29MBです
import pandas as pd
import random
cols = 100
rows = 100_000
datas = {"column"+str(idx+1): [random.randint(0, 10000) for _ in range(rows)] for idx in range(cols)}
df = pd.DataFrame(datas)
df.to_csv("table.csv", index=False)
memory_usage = df.memory_usage(deep=True).sum()
print(f'DataFrame memory usage: {memory_usage/1024/1024 : .2f} Mbytes')
# DataFrame memory usage: 76.29 Mbytes
そのまま読み込む
まず、保存したダミーデータをそのまま読み込んでみました。
読み込んだデータのメモリ使用量は76.29MBでした
df = pd.read_csv("table.csv")
memory_usage = df.memory_usage(deep=True).sum()
print(f'DataFrame memory usage: {memory_usage/1024/1024 : .2f} Mbytes')
# DataFrame memory usage: 76.29 Mbytes
カラム(列)を指定して読み込む
次に列を指定して読み込んでみました。読み込みは10列に1列です。
読み込んだデータのメモリ使用量は7.63MBでした。読み込むカラム数を1/10にしたので、メモリ使用量もちょうど1/10になっています。
sel = ['column'+str(idx+1) for idx in range(0, cols, 10)]
df = pd.read_csv("table.csv", usecols=sel)
memory_usage = df.memory_usage(deep=True).sum()
print(f'DataFrame memory usage: {memory_usage/1024/1024 : .2f} Mbytes')
# DataFrame memory usage: 7.63 Mbytes
データの型を指定して読み込む
データの型を指定して読み込んでみました。このデータは0~10000
の範囲なので、16bit(int16
)で収まる範囲です。
読み込んだデータのメモリ使用量は19.07MBでした。int64
からint16
にしたので、メモリ消費量も1/4になりました。
types = {'column'+str(idx+1): 'int16' for idx in range(cols)}
df = pd.read_csv("table.csv", dtype=types)
memory_usage = df.memory_usage(deep=True).sum()
print(f'DataFrame memory usage: {memory_usage/1024/1024 : .2f} Mbytes')
# DataFrame memory usage: 19.07 Mbytes
データ型指定とカラム(列)指定を併用
データ型と列指定は併用することも可能です。
読み込んだデータのメモリ使用量は1.91MBでした。int64
からint16
と1/4、列数も100から10の1/10したので、メモリ消費量も1/40になりました。
sel = ['column'+str(idx+1) for idx in range(0, cols, 10)]
types = {'column'+str(idx+1): 'int16' for idx in range(0, cols, 10)}
df = pd.read_csv("table.csv", usecols=sel, dtype=types)
memory_usage = df.memory_usage(deep=True).sum()
print(f'DataFrame memory usage: {memory_usage/1024/1024 : .2f} Mbytes')
# DataFrame memory usage: 1.91 Mbytes
分割読み込み(参考)
最後にチャンク読み込みの例です。具体的なチャンクに分割するコードは以下のようになります。
chunk_size = 10000
for i, chunk in enumerate(pd.read_csv('table.csv', chunksize=chunk_size)):
print(i, len(chunk))
print(chunk)
まとめ
以上、Pandasのデータ読み込みでメモリ消費量を抑える方法について解説しました。メモリ消費量を抑えるように加工したデータはparquet
などのフォーマットで保存しておけば、データの型などを指定することなく読み込むことが可能です。