STEP 44:データ可視化のパフォーマンス最適化

⚡ STEP 44: データ可視化のパフォーマンス最適化

大量データを高速に可視化する技術をマスターしよう!

📋 このステップで学ぶこと

  • パフォーマンス問題が発生する原因と診断方法
  • データサンプリングで処理を軽くする技術
  • 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箇所だけ ScatterScattergl に変えるだけ
インタラクティブ操作が高速 ズーム、パン、ホバーがスムーズ
静的画像には不向き 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
・初回読み込み後はキャッシュされる場合:初回のみ許容

重要なのは「ユーザーが待てるか」を基準に判断することです。
📝

学習メモ

データ可視化マスター - Step 44

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE