RAKSUL TechBlog

ラクスルグループのエンジニアが技術トピックを発信するブログです

Pandasでメモリを効率的に扱うテクニック5選

はじめに

ノバセル新卒2年目の田村(tamtam)です。

今回はPyconAPAC 2023でJoeun Park氏が話されていた「How much data can we cram into 16GB RAM with less budget?」を参考に、Pandasでメモリ使用量を抑え大規模なデータを扱うためのテクニック5選を紹介しようと思います!

データ処理に精通している方にとっては、これらの内容はおなじみのものかもしれません。しかし、復習の意味を込めて、ぜひご一読いただければ幸いです。

Joeun Park氏の登壇はYoutubeも公開されていますので興味ある方はご覧ください。 www.youtube.com

1.サンプリングをしデータの行数を減らす

サンプリングとは、データセットからランダムに行を選び出し、より小規模なデータセットを作成することを指します。

データセットのサイズを減らすことで、必要なメモリ量が減り、処理速度が向上します。

ここでは具体的なサンプリングを2つ紹介します。

ファイルレベルでサンプリングする

大量のデータが複数のファイルに分散されている場合に役立ちます。

データセット内のファイルをランダムに選択します。

import os
import random

# ファイルが格納されているディレクトリ
directory = "/path/to/your/directory"

# ディレクトリ内のファイルリストを取得
file_list = os.listdir(directory)

# サンプリングするファイルの数を設定
number_of_samples = 10  # 例として10個のファイルを選択

# ファイルリストからランダムにサンプルを選択
sampled_files = random.sample(file_list, min(number_of_samples, len(file_list)))

# サンプリングされたファイルのリストを出力
print(sampled_files)

ファイルのチャンクごとでサンプリングをする

特に大きなファイルやストリームデータを扱う際に有用です。

ファイル全体を一度に読み込むのではなく、小さなセグメント(チャンク)に分割して、それぞれからサンプルを取得します。

import pandas as pd

chunk_size = 10000  # 1回に読み込む行数
sample_fraction = 0.1  # サンプリングで保持する行の割合

# CSVファイルをチャンクで読み込み、各チャンクからランダムに行をサンプリング
df_sampled = pd.concat([chunk.sample(frac=sample_fraction) 
                        for chunk in pd.read_csv('data.csv', chunksize=chunk_size)])

# サンプリングされたデータフレームを使用
print(df_sampled.head())

2.サブセットを抽出しデータの列数を減らす

サブセットの抽出とはデータフレームから特定の列だけを選択することを指します。

不要な列を取り除くことで、使用するメモリ量を削減できます。

具体的なサブセット抽出の方法を2つ紹介します。

ファイル読み込み時にサブセット抽出をする

pandasのread_csvメソッドを使用する際にusecols引数を利用します。

こちらを利用することでファイル読み込み時に必要な列のみを抽出することができます。

import pandas as pd

# 読み込む列の名前を指定
columns_to_use = ['column1', 'column2', 'column3']

# 指定した列だけを読み込む
df = pd.read_csv('your_data.csv', usecols=columns_to_use)

usecols を使用する際は、CSVファイル内の列の名前または位置を正確に知っている必要があることに注意しましょう。

データフレーム読み込み後にサブセット抽出をする

下記の例では「列名を指定して抽出」する方法になります。

import pandas as pd

# データフレームの読み込み
df = pd.read_csv('your_data.csv')

# 特定の列を選択
columns_to_keep = ['column1', 'column2', 'column3']
subset_df = df[columns_to_keep]

他にもデータ型などの「特定の条件に基づいて抽出」、「不要な列を除外」といった抽出の方法があります。

3.データを分割して読み込む

大規模なデータセットを一度に全てメモリへ読み込むことは、メモリの制約により困難または不可能な場合があります。

データを分割して読み込むことで、小さなメモリフットプリントで処理が可能になります。

import pandas as pd

chunk_size = 10000  # 1チャンクあたりの行数
chunks = pd.read_csv('large_dataset.csv', chunksize=chunk_size)

for chunk in chunks:
    # ここで各チャンクに対して処理を行う
    # 例:基本的な統計を計算、データのクリーニング、ファイルへの保存等

4.扱うデータタイプを変える

よりメモリ効率の良いデータ型に変換することで、データを格納するのに必要なメモリ量を減らす方法になります。

Pandasでは、データフレームを読み込む際、多様なデータを扱うために、広範囲に渡る一般的なデータ型がデフォルトで使われます。しかし、これにより無駄なメモリ消費をすることがあります。

そのため、場合によってはデータ型をよりメモリ効率が良いものに変換した方が良いです。

ここでは代表的な型変換を3つ紹介します。

Numericな値の変換: データ範囲の縮小化

Numericな値(整数や浮動小数点)のデータの表現可能な範囲を狭くすることでメモリ消費を抑えます。

Pandasではto_numeric() 関数を使って自動的に最適な型に変換することができます。

# 整数型のダウンキャスト
df['int_column'] = pd.to_numeric(df['int_column'], downcast='integer')


# 浮動小数点型のダウンキャスト
df['float_column'] = pd.to_numeric(df['float_column'], downcast='float')

ダウンキャストは、精度の低下やオーバフローを引き起こす可能性があるため、データの範囲と精度を考慮して変換しましょう。

Pandasの describe() メソッドを使用すると数値データの基本統計量を確認できますので、必要に応じてデータ型を変更する前に確認しましょう。

文字列の変換: Categorical型の利用

Categorical型は特に重複する値が多い列で、文字列型よりもはるかに少ないメモリを消費します。

Pandasの Categorical 型は、データの一意の値を内部的に整数値で表現します。これにより、同じ文字列がデータセット内で繰り返し出現する場合、各インスタンスは整数参照として保存され、実際の文字列データは一度だけ保存されます。

一方通常の String 型では、各個の文字列が独立してメモリに保存されます。

import pandas as pd

# データフレームの読み込み
df = pd.read_csv('your_data.csv')

# 文字列型の列をCategorical型に変換
df['string_column'] = df['string_column'].astype('category')

データの多くが欠損値や0である列の変換: Sparse 型の利用

Sparse型は欠損値やゼロを効率的に格納し、メモリ使用量を大幅に削減することができます。

下記の例では、fill_value に0を指定しています。これは、非0値のみが効率的に格納され、0値は省略されることを意味します。

# ゼロが多い列をSparse型に変換
df['sparse_column'] = df['sparse_column'].astype(SparseDtype(float, fill_value=0))

5.Parquet形式で保存をする

Parquetとは、列指向のストレージフォーマットで、列ごとにデータを保存することが特徴です。

この特徴からParquetは列ごとにデータを圧縮し、効率的なストレージと高速な読み書きを実現します。

対照的に、CSVやテキスト形式は行指向であり、異なるデータタイプが混在するため、圧縮率が低くなりがちです。

Parquetファイルにはメタデータが含まれており、スキーマ情報やデータ型、統計情報などが記録されています。このメタデータを使用して、効率的なデータ読み込みや圧縮方式の選択が可能です。

Parquetファイルを扱う際には、Pandas以外に追加の(pyarrow または fastparquet)が必要になります。

import pandas as pd

# データフレームの作成
df = pd.read_csv('large_dataset.csv')

# Parquetファイルとして保存
df.to_parquet('large_dataset.parquet')

# Parquetファイルの読み込み
df = pd.read_parquet('large_dataset.parquet')

まとめ

この記事では、「How much data can we cram into 16GB RAM with less budget?」を参考に、Pandasでメモリを効率的に扱うテクニックを5つ紹介していきました。

  1. サンプリングをしデータの行数を減らす
  2. サブセットを抽出しデータの列数を減らす
  3. データを分割して読み込む
  4. 扱うデータタイプを変える
  5. Parquet形式で保存をする

ぜひ活用してデータ処理の助けになれれば幸いです!