📋 このステップで学ぶこと
パフォーマンス問題が発生する原因と診断方法
データサンプリングで処理を軽くする技術
datashaderによる大量データの高速描画
WebGLレンダリングでブラウザ表示を高速化
ファイルサイズを削減するテクニック
メモリ管理と効率的なデータ処理
🎯 1. パフォーマンス問題の原因
なぜパフォーマンス最適化が必要なのか
データ可視化において、パフォーマンス(処理速度) は非常に重要です。グラフの表示に何十秒もかかったり、ブラウザがフリーズしたりすると、ユーザー体験が大きく損なわれます。
特に実務では、数十万〜数百万件のデータを扱うことが珍しくありません。そのようなデータを効率的に可視化するための技術を学びましょう。
💡 身近な例で考えてみよう
パフォーマンス最適化は、料理に似ています:
100人分の料理を作る場合:
❌ 悪い方法:1人分ずつ100回作る → 時間がかかりすぎる
✓ 良い方法:大きな鍋でまとめて作る → 効率的
データ可視化も同じで、データの処理方法 や描画の仕方 を工夫することで、大幅に高速化できます。
パフォーマンスが遅くなる4つの原因
⚠️ パフォーマンス問題の主な原因
原因
具体例
症状
1. データ量が多すぎる
100万点の散布図、1000万行のCSV
描画に数十秒〜数分かかる
2. 複雑なグラフ構造
100個のサブプロット、複雑なアニメーション
メモリ不足、フリーズ
3. ファイルサイズが大きい
高解像度PNG(10MB以上)
読み込みが遅い、共有が困難
4. メモリ不足
大量データを一度にメモリに読み込み
クラッシュ、強制終了
最適化の目標値
どこまで最適化すべきかの目安を持っておくことが大切です。以下は一般的な目標値です。
⚡ 最適化の目標値(目安)
項目
目標値
理由
描画速度
3秒以内
3秒を超えるとユーザーが離脱しやすい
ファイルサイズ
1MB以下
Webページの表示速度、メール添付
メモリ使用量
利用可能メモリの50%以下
他の処理の余地を残す
インタラクション
100ms以内の応答
操作が「もたつく」と感じない限界
処理時間の計測方法
最適化の第一歩は、現状を測定する ことです。Pythonのtimeモジュールを使って、処理時間を計測しましょう。
# 処理時間を計測する方法
import time
import matplotlib.pyplot as plt
import numpy as np
# 計測開始
start_time = time.time()
# === ここに計測したい処理を書く ===
# 例: 10万点の散布図を描画
x = np.random.randn(100000)
y = np.random.randn(100000)
plt.scatter(x, y, alpha=0.1, s=1)
plt.title(‘100,000 Points Scatter Plot’)
plt.savefig(‘scatter.png’)
plt.close()
# === 計測したい処理ここまで ===
# 計測終了
end_time = time.time()
# 経過時間を表示
elapsed_time = end_time – start_time
print(f”処理時間: {elapsed_time:.2f} 秒”)
【実行結果の例】
処理時間: 4.23 秒
※ この場合、目標の3秒を超えているので最適化が必要です。
📝 処理時間計測のコード解説
コード
何をしているか
なぜ使うのか
time.time()
現在時刻を秒単位で取得
開始・終了時刻を記録するため
end_time - start_time
経過時間を計算
処理にかかった時間を知るため
f"{elapsed_time:.2f}"
小数点以下2桁で表示
読みやすい形式で出力するため
📊 2. データサンプリング
サンプリングとは
サンプリング とは、大量のデータから一部だけを抜き出して使う方法です。例えば、100万件のデータから1万件だけを抽出すれば、処理時間は約100分の1になります。
「一部だけで大丈夫?」と思うかもしれませんが、統計的に適切にサンプリングすれば、全体の傾向を十分に把握できます。これは選挙の出口調査と同じ原理です。
【サンプリングの効果】
データ量と処理時間の関係(目安):
データ量 処理時間(散布図)
─────────────────────────────
1万点 0.1秒
10万点 1秒
100万点 10秒
1000万点 100秒以上
→ データを1/10にすれば、処理時間も約1/10に!
【サンプリングのポイント】
・ランダムに抽出する(偏りを防ぐ)
・元のデータの傾向が維持されるサイズを選ぶ
・散布図なら1万〜5万点で十分なことが多い
基本的なサンプリング方法
Pandasのsample()メソッドを使えば、簡単にランダムサンプリングができます。
# 基本的なサンプリング
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
# 100万行のサンプルデータを作成
np.random.seed(42)
n = 1000000 # 100万行
df = pd.DataFrame({
‘x’: np.random.randn(n),
‘y’: np.random.randn(n),
‘category’: np.random.choice([‘A’, ‘B’, ‘C’], n)
})
print(f”元のデータ: {len(df):,} 行”)
# ========== 方法1: 全データで描画(遅い) ==========
start = time.time()
plt.figure(figsize=(8, 6))
plt.scatter(df[‘x’], df[‘y’], alpha=0.1, s=1)
plt.title(‘All Data (1,000,000 points)’)
plt.savefig(‘all_data.png’, dpi=100)
plt.close()
print(f”全データ描画: {time.time() – start:.2f} 秒”)
# ========== 方法2: サンプリングして描画(速い) ==========
start = time.time()
# ランダムに1万行を抽出
df_sample = df.sample(n=10000, random_state=42)
plt.figure(figsize=(8, 6))
plt.scatter(df_sample[‘x’], df_sample[‘y’], alpha=0.3, s=5)
plt.title(‘Sampled Data (10,000 points)’)
plt.savefig(‘sampled_data.png’, dpi=100)
plt.close()
print(f”サンプリング描画: {time.time() – start:.2f} 秒”)
【実行結果】
元のデータ: 1,000,000 行
全データ描画: 8.45 秒
サンプリング描画: 0.12 秒
→ サンプリングにより約70倍高速化!
サンプリングの種類
🎯 サンプリングの3つの方法
方法
説明
コード例
使いどころ
ランダムサンプリング
完全にランダムに抽出
df.sample(n=10000)
一般的な用途、散布図
層化サンプリング
カテゴリ別に同じ割合で抽出
df.groupby('cat').sample(frac=0.01)
カテゴリ別の分布を維持したい時
等間隔サンプリング
N行ごとに1行抽出
df.iloc[::100]
時系列データ、折れ線グラフ
# 3種類のサンプリング方法
import pandas as pd
import numpy as np
# サンプルデータ(100万行)
np.random.seed(42)
df = pd.DataFrame({
‘x’: np.random.randn(1000000),
‘y’: np.random.randn(1000000),
‘category’: np.random.choice([‘A’, ‘B’, ‘C’], 1000000, p=[0.5, 0.3, 0.2])
})
# ========== 方法1: ランダムサンプリング ==========
# n=件数を指定、または frac=割合を指定
sample1 = df.sample(n=10000, random_state=42) # 1万件を抽出
print(f”ランダム: {len(sample1):,} 行”)
print(f”カテゴリ分布:\n{sample1[‘category’].value_counts(normalize=True)}\n”)
# ========== 方法2: 層化サンプリング ==========
# カテゴリごとに同じ割合(1%)で抽出
sample2 = df.groupby(‘category’, group_keys=False).apply(
lambda x: x.sample(frac=0.01, random_state=42)
)
print(f”層化: {len(sample2):,} 行”)
print(f”カテゴリ分布:\n{sample2[‘category’].value_counts(normalize=True)}\n”)
# ========== 方法3: 等間隔サンプリング ==========
# 100行ごとに1行を抽出(時系列データに有効)
sample3 = df.iloc[::100] # 0, 100, 200, 300, … 行目を抽出
print(f”等間隔: {len(sample3):,} 行”)
【実行結果】
ランダム: 10,000 行
カテゴリ分布:
A 0.502
B 0.298
C 0.200
層化: 10,000 行
カテゴリ分布:
A 0.500
B 0.300
C 0.200
等間隔: 10,000 行
→ 層化サンプリングは元のカテゴリ比率(A:50%, B:30%, C:20%)を正確に維持
⚠️ サンプリングの注意点
サンプリングが適さないケース:
・外れ値や異常値の検出(レアケースを見逃す可能性)
・正確な集計値が必要な場合(合計、最大値など)
・少数派カテゴリの分析(サンプル数が不足する)
対策:
・可視化はサンプリング、集計は全データで行う
・少数派カテゴリは層化サンプリングで確保する
🔥 3. datashaderの活用
datashaderとは
datashader は、数百万〜数十億点のデータを高速に可視化するためのPythonライブラリです。従来の方法では不可能だった超大量データの散布図やヒートマップを、数秒で描画できます。
【datashaderの仕組み】
通常の散布図:
┌─────────────────────────────────────┐
│ 1点1点を個別に描画 │
│ → 100万点 = 100万回の描画処理 │
│ → 非常に遅い │
└─────────────────────────────────────┘
datashader:
┌─────────────────────────────────────┐
│ ピクセルごとに「何点あるか」を集計 │
│ → 800×600ピクセル = 48万回の集計 │
│ → 密度に応じて色を付ける │
│ → 非常に速い! │
└─────────────────────────────────────┘
【メリット】
✓ 100万点でも1秒以下で描画
✓ 密度の違いが色で分かる(オーバープロット解消)
✓ メモリ効率が良い
datashaderのインストールと基本的な使い方
# datashaderのインストール(Google Colabの場合)
!pip install datashader
# datashaderの基本的な使い方
import datashader as ds
import datashader.transfer_functions as tf
import pandas as pd
import numpy as np
import time
# 100万点のサンプルデータを作成
np.random.seed(42)
n = 1000000
df = pd.DataFrame({
‘x’: np.random.randn(n),
‘y’: np.random.randn(n)
})
print(f”データ数: {len(df):,} 点”)
# ========== datashaderで描画 ==========
start = time.time()
# Step 1: キャンバス(描画領域)を作成
cvs = ds.Canvas(
plot_width=800, # 横800ピクセル
plot_height=600 # 縦600ピクセル
)
# Step 2: 点を集計(各ピクセルに何点あるかをカウント)
agg = cvs.points(df, ‘x’, ‘y’)
# Step 3: 集計結果を画像に変換(密度に応じて色付け)
img = tf.shade(agg)
# Step 4: 画像を保存
img.to_pil().save(‘datashader_scatter.png’)
print(f”datashader描画時間: {time.time() – start:.2f} 秒”)
【実行結果】
データ数: 1,000,000 点
datashader描画時間: 0.45 秒
→ 100万点を0.5秒以下で描画!
→ 通常のmatplotlibでは8秒以上かかっていた処理が約18倍高速化
コードの解説
📝 datashaderのコード解説
コード
何をしているか
なぜ使うのか
ds.Canvas(plot_width, plot_height)
描画領域(キャンバス)を定義
出力画像のサイズを決めるため
cvs.points(df, 'x', 'y')
各ピクセルに何点あるか集計
密度を計算するため
tf.shade(agg)
集計結果を色に変換
密度を視覚化するため
img.to_pil().save()
画像として保存
結果をファイルに出力するため
datashaderのカラーマップとカテゴリ別表示
# datashaderの応用: カラーマップとカテゴリ別表示
import datashader as ds
import datashader.transfer_functions as tf
from datashader.colors import viridis, inferno
import pandas as pd
import numpy as np
# カテゴリ付きのデータを作成
np.random.seed(42)
n = 1000000
# 3つのクラスター(カテゴリ)を作成
df = pd.concat([
pd.DataFrame({‘x’: np.random.randn(n//3) – 2,
‘y’: np.random.randn(n//3) – 2,
‘cat’: ‘A’}),
pd.DataFrame({‘x’: np.random.randn(n//3) + 2,
‘y’: np.random.randn(n//3) – 2,
‘cat’: ‘B’}),
pd.DataFrame({‘x’: np.random.randn(n//3),
‘y’: np.random.randn(n//3) + 2,
‘cat’: ‘C’})
], ignore_index=True)
# カテゴリをcategory型に変換(datashaderで必要)
df[‘cat’] = df[‘cat’].astype(‘category’)
cvs = ds.Canvas(plot_width=800, plot_height=600)
# ========== 方法1: カラーマップで密度を表現 ==========
agg = cvs.points(df, ‘x’, ‘y’)
# viridis: 黄→緑→青のグラデーション
img1 = tf.shade(agg, cmap=viridis)
img1.to_pil().save(‘datashader_viridis.png’)
# inferno: 黄→赤→黒のグラデーション
img2 = tf.shade(agg, cmap=inferno)
img2.to_pil().save(‘datashader_inferno.png’)
# ========== 方法2: カテゴリ別に色分け ==========
agg_cat = cvs.points(df, ‘x’, ‘y’, ds.count_cat(‘cat’))
# カテゴリごとに異なる色
img3 = tf.shade(agg_cat, color_key={‘A’: ‘red’, ‘B’: ‘green’, ‘C’: ‘blue’})
img3.to_pil().save(‘datashader_category.png’)
print(“3つの画像を保存しました:”)
print(“- datashader_viridis.png: viridisカラーマップ”)
print(“- datashader_inferno.png: infernoカラーマップ”)
print(“- datashader_category.png: カテゴリ別色分け”)
【実行結果】
3つの画像を保存しました:
– datashader_viridis.png: viridisカラーマップ
– datashader_inferno.png: infernoカラーマップ
– datashader_category.png: カテゴリ別色分け
→ カテゴリ別に色分けすることで、3つのクラスターが明確に区別できます
🚀 4. WebGLレンダリング
WebGLとは
WebGL は、ブラウザ上でGPU(グラフィックカード)を使って高速に描画する技術です。Plotlyでは、通常のScatterの代わりにScatterglを使うことで、WebGLレンダリングが有効になります。
【WebGLの仕組み】
通常のレンダリング(SVG):
┌─────────────────────────────────────┐
│ CPU が1点1点を処理 │
│ → 並列処理できない │
│ → 大量データで遅くなる │
└─────────────────────────────────────┘
WebGLレンダリング:
┌─────────────────────────────────────┐
│ GPU が数千点を同時に処理 │
│ → 超並列処理が可能 │
│ → 大量データでも高速 │
└─────────────────────────────────────┘
【PlotlyのWebGL対応グラフ】
・Scattergl : 散布図(WebGL版)
・Scatter3d : 3D散布図(自動的にWebGL)
・Heatmapgl : ヒートマップ(WebGL版)
※ WebGLはブラウザ表示用。
静的画像の保存にはdatashaderが適しています。
Plotly + WebGLの使い方
# Plotly + WebGLで高速描画
import plotly.graph_objects as go
import numpy as np
import time
# 10万点のサンプルデータ
np.random.seed(42)
n = 100000
x = np.random.randn(n)
y = np.random.randn(n)
# ========== 通常のScatter(SVG、遅い) ==========
start = time.time()
fig1 = go.Figure(data=go.Scatter(
x=x,
y=y,
mode=’markers’,
marker=dict(size=3, opacity=0.3)
))
fig1.update_layout(title=’Standard Scatter (SVG) – 100,000 points’)
# ブラウザで表示(表示時間を計測)
# fig1.show()
print(f”SVG版の準備時間: {time.time() – start:.2f} 秒”)
# ========== WebGL版のScattergl(高速) ==========
start = time.time()
fig2 = go.Figure(data=go.Scattergl( # Scatter → Scattergl に変更
x=x,
y=y,
mode=’markers’,
marker=dict(size=3, opacity=0.3)
))
fig2.update_layout(title=’WebGL Scatter – 100,000 points’)
# ブラウザで表示
fig2.show()
print(f”WebGL版の準備時間: {time.time() – start:.2f} 秒”)
【実行結果】
SVG版の準備時間: 1.23 秒
WebGL版の準備時間: 0.45 秒
→ ブラウザでの表示はさらに差が出ます:
SVG版: 表示に5〜10秒、操作がもたつく
WebGL版: 即座に表示、スムーズに操作可能
📝 WebGLを使う際のポイント
ポイント
説明
コード変更は1箇所だけ
Scatter → Scattergl に変えるだけ
インタラクティブ操作が高速
ズーム、パン、ホバーがスムーズ
静的画像には不向き
PNG保存の場合はdatashaderを使う
ブラウザ依存
古いブラウザではWebGLが動作しない場合あり
📦 5. ファイルサイズ削減
なぜファイルサイズが重要か
グラフを画像として保存・共有する際、ファイルサイズが大きいと問題が起きます。メール添付できない、Webページが重くなる、ストレージを圧迫するなど。適切なサイズに最適化しましょう。
📊 画像形式の比較
形式
特徴
サイズ目安
おすすめ用途
PNG
可逆圧縮、透過対応
中〜大
Web、プレゼン(背景透過が必要な場合)
JPEG
非可逆圧縮、透過不可
小
写真、複雑なグラデーション
SVG
ベクター形式、拡大してもきれい
小(単純な図)〜大(複雑な図)
ロゴ、シンプルなグラフ、印刷物
PDF
ベクター、印刷向け
中
論文、レポート、印刷物
解像度(DPI)の調整
DPI(Dots Per Inch) は、1インチあたりのドット数を表します。DPIが高いほど高画質ですが、ファイルサイズも大きくなります。
# 解像度(DPI)の調整
import matplotlib.pyplot as plt
import numpy as np
import os
# サンプルグラフ
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.figure(figsize=(8, 6))
plt.plot(x, y, linewidth=2)
plt.title(‘Sample Chart’)
plt.xlabel(‘X’)
plt.ylabel(‘Y’)
# 異なるDPIで保存してサイズを比較
dpi_values = [72, 100, 150, 300]
for dpi in dpi_values:
filename = f’chart_dpi{dpi}.png’
plt.savefig(filename, dpi=dpi, bbox_inches=’tight’)
size_kb = os.path.getsize(filename) / 1024
print(f”DPI {dpi:3d}: {size_kb:6.1f} KB”)
plt.close()
【実行結果】
DPI 72: 23.4 KB
DPI 100: 35.8 KB
DPI 150: 68.2 KB
DPI 300: 198.5 KB
【DPIの使い分け目安】
・Web表示: 72〜100 DPI(十分きれい、サイズ小)
・プレゼン: 100〜150 DPI(バランス良い)
・印刷: 300 DPI(高画質が必要)
不要な要素の削除
グラフの余白や不要な装飾を削除することで、ファイルサイズを削減できます。
# 不要な要素を削除してファイルサイズを削減
import matplotlib.pyplot as plt
import numpy as np
import os
x = np.linspace(0, 10, 100)
y = np.sin(x)
# ========== 通常のグラフ ==========
fig1, ax1 = plt.subplots(figsize=(8, 6))
ax1.plot(x, y)
ax1.set_title(‘Normal Chart’)
ax1.set_xlabel(‘X’)
ax1.set_ylabel(‘Y’)
ax1.grid(True)
fig1.savefig(‘chart_normal.png’, dpi=100)
size1 = os.path.getsize(‘chart_normal.png’) / 1024
plt.close()
# ========== 最適化したグラフ ==========
fig2, ax2 = plt.subplots(figsize=(8, 6))
ax2.plot(x, y)
ax2.set_title(‘Optimized Chart’)
ax2.set_xlabel(‘X’)
ax2.set_ylabel(‘Y’)
# 不要な要素を削除
ax2.grid(False) # グリッドを削除
ax2.spines[‘top’].set_visible(False) # 上の枠線を削除
ax2.spines[‘right’].set_visible(False) # 右の枠線を削除
# 余白を最小化して保存
fig2.savefig(‘chart_optimized.png’,
dpi=100,
bbox_inches=’tight’, # 余白を自動調整
pad_inches=0.1) # 最小限のパディング
size2 = os.path.getsize(‘chart_optimized.png’) / 1024
plt.close()
print(f”通常版: {size1:.1f} KB”)
print(f”最適化版: {size2:.1f} KB”)
print(f”削減率: {(1 – size2/size1) * 100:.1f}%”)
【実行結果】
通常版: 42.3 KB
最適化版: 35.8 KB
削減率: 15.4%
→ シンプルな例でも15%削減。
複雑なグラフではさらに効果大!
📝 ファイルサイズ削減のコード解説
コード
何をしているか
削減効果
ax.grid(False)
グリッド線を削除
線の描画分が削減
ax.spines['top'].set_visible(False)
上の枠線を非表示
不要な線を削除
bbox_inches='tight'
余白を自動調整
無駄な余白を削除
pad_inches=0.1
最小限のパディング
余白を最小化
💾 6. メモリ管理
メモリ使用量の確認方法
大量データを扱う際、メモリ不足でプログラムがクラッシュすることがあります。まずは現在のメモリ使用量を確認する方法を学びましょう。
# メモリ使用量の確認
import psutil
import os
import pandas as pd
import numpy as np
def show_memory():
“””現在のメモリ使用量を表示”””
process = psutil.Process(os.getpid())
mem_mb = process.memory_info().rss / 1024 / 1024
print(f”プロセスメモリ: {mem_mb:.1f} MB”)
# システム全体のメモリ
system_mem = psutil.virtual_memory()
print(f”システムメモリ: {system_mem.percent}% 使用中”)
print(f”利用可能: {system_mem.available / 1024 / 1024 / 1024:.1f} GB”)
# 初期状態
print(“=== 初期状態 ===”)
show_memory()
# 大きなDataFrameを作成
print(“\n=== DataFrame作成後 ===”)
df = pd.DataFrame({
‘col1’: np.random.randn(1000000),
‘col2’: np.random.randn(1000000),
‘col3’: np.random.randn(1000000),
})
show_memory()
# DataFrameのメモリ使用量を詳細に確認
print(f”\nDataFrameのメモリ: {df.memory_usage(deep=True).sum() / 1024 / 1024:.1f} MB”)
print(df.memory_usage(deep=True))
【実行結果の例】
=== 初期状態 ===
プロセスメモリ: 125.3 MB
システムメモリ: 45% 使用中
利用可能: 8.2 GB
=== DataFrame作成後 ===
プロセスメモリ: 148.7 MB
システムメモリ: 46% 使用中
利用可能: 8.1 GB
DataFrameのメモリ: 22.9 MB
Index 128
col1 8000000
col2 8000000
col3 8000000
dtype: int64
→ 100万行×3列のfloat64で約23MBを使用
データ型の最適化
Pandasはデフォルトで大きなデータ型(float64, int64)を使います。データの範囲に応じて小さなデータ型に変換することで、メモリを大幅に削減できます。
📊 データ型とメモリ使用量
データ型
メモリ/要素
値の範囲
int64(デフォルト)
8バイト
-9,223京 〜 9,223京
int32
4バイト
-21億 〜 21億
int16
2バイト
-32,768 〜 32,767
int8
1バイト
-128 〜 127
float64(デフォルト)
8バイト
±1.8×10^308(高精度)
float32
4バイト
±3.4×10^38(標準精度)
category
可変(非常に小さい)
繰り返しの多い文字列に最適
# データ型の最適化でメモリ削減
import pandas as pd
import numpy as np
# サンプルデータ(100万行)
np.random.seed(42)
df = pd.DataFrame({
‘id’: np.arange(1000000), # 0〜999,999
‘age’: np.random.randint(0, 100, 1000000), # 0〜99
‘score’: np.random.randn(1000000), # 小数
‘category’: np.random.choice([‘A’, ‘B’, ‘C’], 1000000) # 3種類のみ
})
# 最適化前のメモリ使用量
print(“=== 最適化前 ===”)
print(df.dtypes)
mem_before = df.memory_usage(deep=True).sum() / 1024 / 1024
print(f”メモリ: {mem_before:.1f} MB\n”)
# ========== データ型を最適化 ==========
df_opt = df.copy()
# id: 0〜999,999 → int32で十分(-21億〜21億)
df_opt[‘id’] = df_opt[‘id’].astype(‘int32’)
# age: 0〜99 → int8で十分(-128〜127)
df_opt[‘age’] = df_opt[‘age’].astype(‘int8’)
# score: 通常の精度で十分 → float32
df_opt[‘score’] = df_opt[‘score’].astype(‘float32’)
# category: 3種類しかない → category型
df_opt[‘category’] = df_opt[‘category’].astype(‘category’)
# 最適化後のメモリ使用量
print(“=== 最適化後 ===”)
print(df_opt.dtypes)
mem_after = df_opt.memory_usage(deep=True).sum() / 1024 / 1024
print(f”メモリ: {mem_after:.1f} MB”)
print(f”\n削減量: {mem_before – mem_after:.1f} MB”)
print(f”削減率: {(1 – mem_after/mem_before) * 100:.1f}%”)
【実行結果】
=== 最適化前 ===
id int64
age int64
score float64
category object
dtype: object
メモリ: 88.2 MB
=== 最適化後 ===
id int32
age int8
score float32
category category
dtype: object
メモリ: 9.5 MB
削減量: 78.7 MB
削減率: 89.2%
→ データ型の最適化だけで約90%削減!
メモリ解放
不要になったデータは明示的に削除し、メモリを解放することが重要です。
# メモリ解放の方法
import pandas as pd
import numpy as np
import gc
def show_memory():
import psutil, os
mem_mb = psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024
print(f”メモリ: {mem_mb:.1f} MB”)
# 大きなDataFrameを作成
print(“=== データ作成 ===”)
large_df = pd.DataFrame(np.random.randn(1000000, 10))
show_memory()
# データを処理(例: 平均を計算)
result = large_df.mean()
print(f”平均値: {result[0]:.4f}”)
# ========== メモリ解放 ==========
print(“\n=== メモリ解放 ===”)
# 方法1: del でオブジェクトを削除
del large_df
# 方法2: gc.collect() でガベージコレクションを強制実行
gc.collect()
show_memory()
print(“不要なデータを削除してメモリを解放しました”)
【実行結果】
=== データ作成 ===
メモリ: 201.5 MB
平均値: -0.0002
=== メモリ解放 ===
メモリ: 125.3 MB
不要なデータを削除してメモリを解放しました
→ delとgc.collect()でメモリを解放
📝 メモリ管理のコード解説
コード
何をしているか
なぜ使うのか
df.astype('int32')
データ型を変換
小さなデータ型でメモリ削減
df.astype('category')
カテゴリ型に変換
繰り返し文字列のメモリ削減
del variable
変数を削除
不要なオブジェクトを解放
gc.collect()
ガベージコレクション実行
削除したメモリを即座に解放
📝 STEP 44 のまとめ
✅ このステップで学んだこと
トピック
重要ポイント
効果
パフォーマンス問題
データ量、複雑さ、ファイルサイズ、メモリが原因
問題の診断ができる
サンプリング
df.sample()でデータを削減
10〜100倍高速化
datashader
100万点以上でも1秒以下で描画
超大量データの可視化
WebGL
Scatter → Scattergl でGPU活用
ブラウザ表示の高速化
ファイルサイズ
DPI調整、不要要素削除
50〜80%のサイズ削減
メモリ管理
データ型最適化、del + gc.collect()
90%のメモリ削減
💡 最適化の順序(優先度順)
パフォーマンス問題に直面したら、以下の順序で対処しましょう:
Step 1: データを減らす → サンプリング(最も効果的)
Step 2: データ型を最適化 → int64 → int32、float64 → float32
Step 3: 描画を高速化 → datashader、WebGL
Step 4: 出力を最適化 → DPI調整、不要要素削除
Step 5: メモリを解放 → del + gc.collect()
まず計測、次に最適化 が鉄則です。問題の原因を特定してから対処しましょう!
📝 実践演習
演習 1
基礎
100万行のDataFrameをサンプリングし、散布図を作成してください。処理時間も計測しましょう。
解答を見る
【解答コード】
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
# 100万行のデータを作成
np.random.seed(42)
df = pd.DataFrame({
‘x’: np.random.randn(1000000),
‘y’: np.random.randn(1000000)
})
# サンプリング & 描画
start = time.time()
# 1万行にサンプリング
df_sample = df.sample(n=10000, random_state=42)
# 散布図を作成
plt.figure(figsize=(8, 6))
plt.scatter(df_sample[‘x’], df_sample[‘y’], alpha=0.3, s=5)
plt.title(‘Sampled Scatter Plot (10,000 points)’)
plt.xlabel(‘X’)
plt.ylabel(‘Y’)
plt.savefig(‘sampled_scatter.png’, dpi=100)
plt.show()
print(f”処理時間: {time.time() – start:.2f} 秒”)
演習 2
応用
PlotlyのWebGL機能(Scattergl)を使って、10万点の散布図を作成してください。
解答を見る
【解答コード】
import plotly.graph_objects as go
import numpy as np
# 10万点のデータ
np.random.seed(42)
n = 100000
x = np.random.randn(n)
y = np.random.randn(n)
# WebGL版で描画
fig = go.Figure(data=go.Scattergl(
x=x,
y=y,
mode=’markers’,
marker=dict(
size=3,
opacity=0.3,
color=y,
colorscale=’Viridis’
)
))
fig.update_layout(
title=’WebGL Scatter Plot (100,000 points)’,
xaxis_title=’X’,
yaxis_title=’Y’,
height=600
)
fig.show()
演習 3
発展
datashaderを使って、100万点のデータを可視化してください。カテゴリ別に色分けも行いましょう。
解答を見る
【解答コード】
import datashader as ds
import datashader.transfer_functions as tf
import pandas as pd
import numpy as np
# 100万点のカテゴリ付きデータ
np.random.seed(42)
n = 1000000
# 3つのクラスターを作成
df = pd.concat([
pd.DataFrame({‘x’: np.random.randn(n//3) – 3,
‘y’: np.random.randn(n//3) – 3,
‘cat’: ‘Cluster A’}),
pd.DataFrame({‘x’: np.random.randn(n//3) + 3,
‘y’: np.random.randn(n//3) – 3,
‘cat’: ‘Cluster B’}),
pd.DataFrame({‘x’: np.random.randn(n//3),
‘y’: np.random.randn(n//3) + 3,
‘cat’: ‘Cluster C’})
], ignore_index=True)
df[‘cat’] = df[‘cat’].astype(‘category’)
# datashaderで描画
cvs = ds.Canvas(plot_width=800, plot_height=600)
agg = cvs.points(df, ‘x’, ‘y’, ds.count_cat(‘cat’))
# カテゴリ別に色分け
img = tf.shade(agg, color_key={
‘Cluster A’: ‘#E74C3C’, # 赤
‘Cluster B’: ‘#2ECC71’, # 緑
‘Cluster C’: ‘#3498DB’ # 青
})
# 保存
img.to_pil().save(‘datashader_clusters.png’)
print(“datashader_clusters.png を保存しました”)
❓ よくある質問
Q1: サンプリングすると、データの傾向が変わりませんか?
ランダムサンプリングであれば、全体の傾向は維持されます。
統計学的に、適切なサンプルサイズ(一般に1万件以上)があれば、母集団の特徴を高い精度で推定できます。これは選挙の出口調査や品質管理の抜き取り検査と同じ原理です。
ただし、外れ値や少数派の検出には不向きです。そのような分析には全データを使いましょう。
Q2: datashaderとWebGLはどう使い分けますか?
用途によって使い分けます。
datashader: 静的な画像を生成したい場合。PNG/JPGで保存してレポートや論文に使う。サーバーサイドで処理。
WebGL(Plotly): ブラウザでインタラクティブに操作したい場合。ズームやホバーで詳細を確認。ダッシュボードに埋め込む。
両方を組み合わせることも可能です。概要はdatashaderで、詳細分析はPlotlyで、といった使い分けがおすすめです。
Q3: Google Colabでメモリ不足になったらどうすればいいですか?
いくつかの対策があります。
1. データ型を最適化: int64→int32、float64→float32に変換
2. 不要なデータを削除: del + gc.collect()でメモリ解放
3. チャンク処理: 大きなCSVはread_csv(chunksize=)で分割読み込み
4. ランタイムをリセット: 「ランタイム」→「ランタイムを再起動」
5. Colab Proにアップグレード: より多くのRAMが利用可能(有料)
Q4: 「3秒以内」の目標は絶対ですか?
状況によって柔軟に考えてください。
3秒はWebユーザビリティの研究に基づく目安です。ユーザーは3秒以上待つとストレスを感じ、10秒以上で離脱する傾向があります。
ただし、以下の場合は例外です:
・バッチ処理(夜間実行など):時間制約なし
・一度だけ生成する高品質レポート:多少遅くてもOK
・初回読み込み後はキャッシュされる場合:初回のみ許容
重要なのは「ユーザーが待てるか」を基準に判断することです。
×
artnasekai
#artnasekai #学習メモ