ニュース

Pythonでメモリに載らない巨大データを扱う実践ガイド

メモリ不足は「悪」ではない。設計で回避できる

Pythonでデータ分析や機械学習をしていると、ファイルが大きすぎてメモリに載り切らないことに直面します。けれど、これは珍しいトラブルではありません。むしろ、現実のデータが大きく多様になっているからこその自然な課題です。大切なのは、「全部を一度に読み込まない」設計を取ること。本稿では、巨大データを安全かつ効率的に扱う実践テクニックを、すぐ使えるヒントとミニコードとともに整理します。

まずは状況把握:なぜメモリが足りなくなるのか

  • データ型が重い:object型の文字列やfloat64はコストが高い。適切な型にダウンキャストするだけで劇的に改善することがある。
  • 不要な列・行を読み込んでいる:最初から必要列だけ読み込み、早い段階でフィルタする。
  • 処理の途中で巨大な中間結果を作っている:逐次集約や遅延評価を使い、メモリのピークを押さえる。

王道1:ストリーミング処理とジェネレータ

ファイルを一行ずつ、あるいはチャンク(塊)で読み、逐次的に処理します。Pythonのジェネレータ(yield)は、メモリに全体を置かずにパイプラインを構成できる強力な手段です。圧縮ファイルはgzip.openで直接ストリーム処理できます。

王道2:pandasのチャンク処理

pandas.read_csvchunksizeを使えば、巨大CSVを分割して読み込めます。例えば、全体平均を求めたいときは各チャンクの合計と件数を持ち回り、最後に集約するだけです。

import pandas as pd

sum_x = 0.0
cnt = 0
for chunk in pd.read_csv("huge.csv", usecols=["x"], dtype={"x": "float32"}, chunksize=500_000):
    sum_x += chunk["x"].sum()
    cnt += len(chunk)
mean_x = sum_x / cnt
print(mean_x)

ポイントは、必要な列だけ読み込み、dtypeを軽量化すること。ついでにusecolsparse_datesで入出力コストも抑えましょう。

王道3:メモリマップ(numpy.memmap)

巨大な配列をディスク上に置いたまま、必要範囲だけをメモリにマッピングして扱えます。シーケンシャルアクセスやスライシングが中心の数値処理では非常に有効です。処理後はflush()で書き戻すのを忘れずに。

王道4:オンディスクな列指向フォーマット(HDF5 / Parquet)

  • HDF5(h5pyやPyTables):チャンク・圧縮・部分読み出しに強い。長期保管や再利用に向く。
  • Parquet(pyarrow/fastparquet):列指向で圧縮効率とスキャン性能が高い。pandasと相性がよく、分析基盤でも標準的。

「一度変換しておき、以後は必要列だけ高速に読む」戦略は、ワークフロー全体を安定させます。

王道5:Daskで“手元に収まらないDataFrame”を扱う

Dask DataFrameは、pandasライクなAPIで分割データを並行・遅延実行します。マシン1台でも、スレッドやプロセスでメモリ圧を逃がしながら処理でき、必要に応じてクラスタへスケールアウトも可能です。既存のpandasコードからの移行が比較的容易なのも魅力です。

王道6:スパース表現を恐れない

カテゴリのワンホットや高次元ベクトルは、ほとんどがゼロです。scipy.sparseのCSR/CSC行列へ変換すれば、メモリ消費と計算が大幅に軽くなります。対応アルゴリズム(例:線形モデル、近似手法)と組み合わせましょう。

王道7:インクリメンタル学習(partial_fit)

scikit-learnの一部の学習器(SGDClassifier/Regressor、MultinomialNB、BernoulliNB、MiniBatchKMeansなど)はpartial_fitに対応しています。データをチャンクで供給しながら逐次学習すれば、全体をメモリに載せる必要がありません。

王道8:外部ソートとサンプリング

  • 外部ソート:大きなファイルを小さく分割してソートし、heapq.mergeでマージ。ピークメモリを一定に保てます。
  • リザーバサンプリング:全体サイズが不明でも、等確率に代表標本を得られます。前処理や可視化のプロトタイピングに便利。

手元でできるメモリ削減の小技

  • ダウンキャスト:pd.to_numeric(..., downcast="integer")astype("float32")で節約。
  • カテゴリ化:文字列列はastype("category")で辞書化。結合やグループ化が速くなることも。
  • 早めの絞り込み:querylocで不要行を先に落とす。
  • 圧縮I/O:gzip/bz2/zipをそのまま読み、ディスクとネットワークのボトルネックを吸収。

ケーススタディ:巨大CSVから特徴量を作る

「ユーザーごとの合計購入額」を作る典型タスクを考えます。全件を読み込まず、チャンクごとに集約してディスクへ追記するのが安全です。

import pandas as pd
from pathlib import Path

out_path = Path("feat_user_amount.parquet")
first = True

for chunk in pd.read_csv("transactions.csv", usecols=["user_id", "amount"],
                         dtype={"user_id": "int64", "amount": "float32"},
                         chunksize=1_000_000):
    agg = chunk.groupby("user_id", as_index=False)["amount"].sum()
    agg.to_parquet(out_path, engine="pyarrow", index=False, compression="zstd",
                   partition_cols=None, append=not first)
    first = False

このように「小さく読み、小さくまとめ、オンディスクで持ち回る」ことで、メモリ不足を回避しながら確実に前へ進めます。

どの戦略を選ぶべき?クイックな指針

  • 一時的な一次分析:pandasのchunksize+ダウンキャスト+必要列のみ。
  • 繰り返し使うデータ基盤:Parquet/HDF5へ変換し、必要列を部分読み出し。
  • 長いパイプラインや学習:Daskで遅延実行、またはscikit-learnのpartial_fitでインクリメンタル学習。
  • 数値配列中心でランダムアクセス少なめ:numpy.memmap
  • 疎な高次元:scipy.sparse+対応モデル。

現場での安心感を上げる道具

扱うデータが大きいほど、I/Oとストレージは品質が重要です。高速な外付けSSDに置き場を分けるだけで、処理時間と安定性が大きく変わります。また、pandasや配列処理の基礎は一冊の定番書で体系的に押さえておくと、迷いが少なくなります。以下の書籍・デバイスは、今日の内容を実務に落とし込む際に心強い味方になります。

まとめ:小さく読み、賢く流す

巨大データを扱うコツは一貫しています。データを分割して流し、軽い型に整え、オンディスクのフォーマットや遅延実行を活用すること。これらはすべて、今日から既存コードに少しずつ取り入れられます。メモリ不足を「限界」ではなく「設計のヒント」と捉え、安心して次の一歩へ進みましょう。

関連記事
error: Content is protected !!