【高速化】dataframeの処理速度をupさせるためのコツ3選

今回はDataFrameの処理速度を

向上させるためのコツについて解説します

この記事を読むべき方・・・

  • Pythonでデータ分析や機械学習を行うエンジニア
  • 大量データを扱い、処理速度に課題を感じているユーザー
  • Pandasを使いこなしたいと考えている初級〜中級者

コツ1: ベクトル化を使った効率的なデータ処理

ベクトル化:ループを使わずに全ての要素に対して一度に操作を行うことです

for文などを使用せずに、行や列に対して

一括処理を行うことで処理速度を向上させます

おへんじ
iterrowsなどは一行ずつ処理を行うから
処理速度が遅いよー

今回は例として

iterrowsを使用したコードと

ベクトル化を使用したコードを比較します

タイムの計測には%%timeitを使用します

Before: iterrowsを使用したコード

itterowsを使用して

A列とB列の値を足し合わせた,C列を作成します

import pandas as pd
import numpy as np
import time

# データの準備(A, B列にそれぞれランダムな値がある10万行のデータフレームを作成)
df = pd.DataFrame({'A': np.random.rand(100000), 'B': np.random.rand(100000)})

# データの確認
print(df)
    A       	B
0	0.055674	0.321138
1	0.246434	0.251720
2	0.635473	0.411995
3	0.517269	0.957901
4	0.382983	0.708736
...	...	...
99995	0.185137	0.689433
99996	0.552369	0.492906
99997	0.208750	0.157748
99998	0.296905	0.114558
99999	0.991895	0.257504

# iterrowsを使用した処理
%%timeit
df['C'] = np.nan
for index, row in df.iterrows():
    df.at[index, 'C'] = row['A'] + row['B']

# 処理時間
26.8 s ± 641 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

After: ベクトル化を使用したコード

iterrowsと同様の処理を

ベクトル化を使用して行います

# ベクトル化を使用した処理
%%timeit
df['C'] = df['A'] + df['B']

# 処理時間 1.33 ms ± 423 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

処理速度は以下のとおりでした

ベクトル化の方が圧倒的に速い結果となりました

高速化の理由・・・

ベクトル化は行ごとの処理ではなく

列を一括で処理するため処理速度が大幅に向上します

コツ2: データ型を最適化してメモリ使用量を削減

データ型の最適化は

メモリ使用量の削減

処理速度の改善に直結します

今回の例では以下の2つの例を紹介します

  • float64型 → float32型
  • object型 → category型

float64からfloat32への変換

Before: float64型の使用

例として10万行のデータフレームを作成し

A列とB列の値を足し合わせた,C列を作成します

import pandas as pd
import numpy as np
import time

# データの準備(A, B列にそれぞれランダムな値がある10万行のデータフレームを作成)
df = pd.DataFrame({'A': np.random.rand(100000), 'B': np.random.rand(100000)})

# データの確認
print(df)
              A         B
0      0.104770  0.460523
1      0.451645  0.743672
2      0.415677  0.348527
3      0.089152  0.404354
4      0.279895  0.849563
...         ...       ...
99995  0.400840  0.408237
99996  0.299817  0.691443
99997  0.427719  0.217992
99998  0.910969  0.620087
99999  0.200447  0.234848
[100000 rows x 2 columns]

# データフレームの情報を表示(float64型であることを確認)
print(df.info())
Data columns (total 2 columns):
 #   Column  Non-Null Count   Dtype  
 0   A       100000 non-null  float64  # データ型がfloat64であることを確認
 1   B       100000 non-null  float64  # データ型がfloat64であることを確認
dtypes: float64(2)
memory usage: 1.5 MB  # メモリ使用量が1.5MB

# 処理速度の計測
%%timeit
df['C'] = df['A'] + df['B']

# データの確認(処理後)
print(df)
              A         B         C
0      0.104770  0.460523  0.565293
1      0.451645  0.743672  1.195317
2      0.415677  0.348527  0.764205
3      0.089152  0.404354  0.493506
4      0.279895  0.849563  1.129458
...         ...       ...       ...
99995  0.400840  0.408237  0.809077
99996  0.299817  0.691443  0.991260
99997  0.427719  0.217992  0.645711
99998  0.910969  0.620087  1.531056
99999  0.200447  0.234848  0.435295
[100000 rows x 3 columns]

# 処理時間
1.2 ms ± 225 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

After: float32型に変換

float64型と同様の処理を

float32型に変換して行います

# float32型への変換
df['A'] = df['A'].astype('float32')
df['B'] = df['B'].astype('float32')

# データフレームの情報を表示(float32型であることを確認)
print(df.info())
Data columns (total 2 columns):
 #   Column  Non-Null Count   Dtype  
 0   A       100000 non-null  float32  # データ型がfloat32に変換されていることを確認
 1   B       100000 non-null  float32  # データ型がfloat32に変換されていることを確認
dtypes: float32(2)
memory usage: 781.4 KB  # メモリ使用量がfloat64の半分になっている

# 処理速度の計測
%%timeit
df['C'] = df['A'] + df['B']

# 処理時間
633 µs ± 168 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

処理速度は以下のとおりでした

float32の方が2倍程度速い結果となりました

高速化の理由・・・

float32の方が表現できる桁数が少ない

メモリ使用量も少なく処理も速くなります

df.info()の結果をみてもメモリ使用量が

1.5MB(float64)から781.4KB(float32)に

削減されていることがわかります

float64とfloat32の違いを以下に

簡単に説明しておくので

気になる方はクリックして確認してください

float64とfloat32の違いはこちらをクリック

float64

  • 64ビット(8バイト)で表現される浮動小数点数
  • 精度が高い(約15〜16桁の精度)
  • メモリ使用量が多い
  • 数値範囲が広い

float32

  • 32ビット(4バイト)で表現される浮動小数点数
  • 精度が低い(約7桁の精度)
  • メモリ使用量が少ない
  • 数値範囲が狭い

float32/float64の使い分け

  • float32:精度があまり必要がなく処理速度を重視する場合
  • float64:より高い精度が求められる科学計算をする場合など


objectからcategoryへの変換

Before: object型を使用したコード

例として20万行の都市名が入ったcity列を作成し

city列を大文字に変換したcity_upper列を作成します

# データの準備(都市名を格納した10万行のデータフレームを作成)
df = pd.DataFrame({'city': ['Tokyo', 'Osaka', 'Nagoya', 'Tokyo', 'Osaka'] * 200000})

# データの確認
print(df)
  city
0        Tokyo
1        Osaka
2       Nagoya
3        Tokyo
4        Osaka
...        ...
999995   Tokyo
999996   Osaka
999997  Nagoya
999998   Tokyo
999999   Osaka
[1000000 rows x 1 columns]

# データフレームの情報を表示(object型であることを確認)
print(df.info())
Data columns (total 1 columns):
 #   Column  Non-Null Count    Dtype 
 0   city    1000000 non-null  object  # データ型がobjectであることを確認
dtypes: object(1)
memory usage: 7.6+ MB  # メモリ使用量が7.6MB

# 処理速度の計測
%%timeit
df['city_upper'] = df['city'].str.upper()

# データの確認(処理後)
print(df)
          city      city_upper
0        Tokyo      TOKYO
1        Osaka      OSAKA
2       Nagoya     NAGOYA
3        Tokyo      TOKYO
4        Osaka      OSAKA
...        ...        ...
999995   Tokyo      TOKYO
999996   Osaka      OSAKA
999997  Nagoya     NAGOYA
999998   Tokyo      TOKYO
999999   Osaka      OSAKA
[1000000 rows x 2 columns]

# 処理時間
872 ms ± 172 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

After: category型に変換

object型と同様の処理を

category型に変換して行います

# category型への変換
df['city'] = df['city'].astype('category')

# データフレームの情報を表示(category型であることを確認)
print(df.info())
Data columns (total 1 columns):
 #   Column  Non-Null Count    Dtype   
 0   city    1000000 non-null  category  # category型に変換
dtypes: category(1)
memory usage: 976.8 KB  # メモリ使用量が7.6MBから976.8KBに削減

# 処理速度の計測
%%timeit
df['city_upper'] = df['city'].str.upper()

# 処理時間
30.5 ms ± 4.59 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

処理速度は以下のとおりでした

category型の方が圧倒的に速い結果となりました

高速化の理由・・・

カテゴリ型は内部的に整数でデータを扱うため

文字列よりも比較や検索

ソートなどの操作が高速に行えます

df.info()の結果をみてもメモリ使用量が

7.6MB(object)から976.8KB(category)に

削減されていることがわかります

object型とcategory型の違いを以下に

簡単に説明しておくので

気になる方はクリックして確認してください

object型とcategory型の違いはこちらをクリック

オブジェクト型 (object)

  • 説明: 文字列(テキスト)や混合データ型を扱うためのデータ型
  • 用途例: 名前や住所、説明などのテキストデータを保存する
  • 特徴:メモリ消費→多,処理速度→遅

カテゴリ型 (category)

  • 説明: 限られた数のユニークな値(カテゴリ)を持つデータ型
  • 用途例: 性別(男性/女性)や、状態(OK/NG)などのカテゴリデータを効率的に保存する
  • 特徴:メモリ消費→少, 処理速度→速

比較

  • メモリ効率: カテゴリ型 > オブジェクト型
  • パフォーマンス: カテゴリ型 > オブジェクト型


コツ3: NumPyのみを使用した計算を使用する

Pandasの内部で使用されるNumPyを

直接使用して処理速度を向上できます

Before: Pandasを使った計算

100行のデータフレームを作成し

A列の値を2乗したB列を作成します

import pandas as pd
import numpy as np
import time

# データの準備(A列にランダムな値がある100万行のデータフレームを作成)
df = pd.DataFrame({'A': np.random.rand(1000000)})

# データの確認
print(df)
    A
0	0.394342
1	0.195238
2	0.966231
3	0.831709
4	0.634390
...	...
999995	0.261430
999996	0.123959
999997	0.505840
999998	0.112474
999999	0.727452
1000000 rows × 1 columns

# 処理速度の計測(2乗計算)
%%timeit
df['B'] = df['A'] ** 2

# データの確認(処理後)
print(df)

    A	        B
0	0.394342	0.155505
1	0.195238	0.038118
2	0.966231	0.933601
3	0.831709	0.691740
4	0.634390	0.402451
...	...	...
999995	0.261430	0.068345
999996	0.123959	0.015366
999997	0.505840	0.255874
999998	0.112474	0.012650
999999	0.727452	0.529186
1000000 rows × 2 columns

# 処理時間
6.1 ms ± 837 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

After: NumPyを使用した計算`

NumPyを使用して

Pandasと同様の処理を行います

# 処理速度の計測(2乗計算)
%%timeit
df['B'] = np.square(df['A'].values)

# 処理時間 4.33 ms ± 363 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

処理速度は以下のとおりでした

NumPyの方が少しだけ速い結果となりました

高速化の理由・・・

numpyは内部的にC言語で実装されているため

高速な計算が可能です

おへんじ
Numpyだけで計算できる場合はNumpyを使うと速くなるよー


NumbaによるJITコンパイルの活用

この方法については別の記事で

詳しく解説しているので

参考にしてみてください

処理速度をupさせると

精度の低下などのデメリットもあるため

メリット・デメリットを把握した上で

使用することが大切です!

最新情報をチェックしよう!