はじめに
3Dデータは、場合によっては2Dよりも外れ値(異常点)が見つけやすい可視化方法のひとつです。
Plotlyの3D散布図では、色・サイズ・透明度・形状を組み合わせることで
異常点だけを自然に際立たせることができます。
今回は、外れ値を"目で見て判断できる"3D可視化を作っていきます。
この記事でできること
- 3次元データの外れ値をPlotlyで強調表示する
- 色(color)、サイズ(size)、透明度(opacity)で外れ値を分離
- 簡単な外れ値検出(Zスコア方式)の実装
- Colabでそのまま動くコード例
実装例: ランダム点群を生成する
まずは基本の3D散布図を作ります。
import plotly.graph_objects as go
import numpy as np
np.random.seed(0)
# 正規分布の点群
x, y, z = np.random.randn(3, 300)
fig = go.Figure(data=[go.Scatter3d(
x=x, y=y, z=z,
mode='markers',
marker=dict(size=4, color='royalblue', opacity=0.7)
)])
fig.update_layout(title="Base 3D Scatter", scene=dict(aspectmode='data'))
fig.show()
まずは"普通の散布図"で外れ値を含めたデータを表示します。
外れ値を追加する
np.random.randn()だけでは外れ値が発生しにくいため、意図的に明確な外れ値を追加します。
# 明確な外れ値を3点追加
outlier_points = np.array([
[5.0, 5.0, 5.0],
[-4.5, -4.0, 4.5],
[4.0, -5.0, -4.0]
])
x = np.append(x, outlier_points[:, 0])
y = np.append(y, outlier_points[:, 1])
z = np.append(z, outlier_points[:, 2])
これで確実に検出できる外れ値が含まれるようになります。
Zスコアで外れ値を検出する
以下は最もシンプルで動作確認しやすい方法です。
# Zスコアで外れ値判定
from scipy.stats import zscore
data = np.vstack([x, y, z]).T
zs = np.abs(zscore(data, axis=0))
outlier_mask = (zs > 3).any(axis=1) # Zスコア3を超える点
# 検出数を確認
print(f"検出された外れ値の数: {outlier_mask.sum()}件")
print(f"通常点の数: {(~outlier_mask).sum()}件")
- Zスコア > 3 を「外れ値」とするシンプルな基準
- 任意にしきい値を変えてもOK
- "どの軸方向でも外れ値なら外れ" というルール
Zスコアは各軸を独立に評価する簡易法であり、軸間の相関は考慮しません。より厳密な方法としてはマハラノビス距離やLOFなどがあります。
(zs > 3).any(axis=1) は「どの軸でもZスコアが3を超えたら外れ値」と判定します。
外れ値を色とサイズで強調表示する
外れ値(赤・大きい)と通常点(青・小さい)という2系統で可視化します。
fig = go.Figure()
# 通常点
fig.add_trace(go.Scatter3d(
x=x[~outlier_mask], y=y[~outlier_mask], z=z[~outlier_mask],
mode='markers',
marker=dict(size=4, color='lightblue', opacity=0.5),
name='Normal'
))
# 外れ値
fig.add_trace(go.Scatter3d(
x=x[outlier_mask], y=y[outlier_mask], z=z[outlier_mask],
mode='markers',
marker=dict(size=8, color='crimson', opacity=1.0, symbol='diamond'),
name='Outlier'
))
fig.update_layout(
title="3D Scatter with Outlier Highlight",
scene=dict(aspectmode='data')
)
fig.show()
外れ値を際立たせる属性設定
| 属性 | 外れ値への効果 |
|---|---|
| 色 = crimson | 一番目立つ |
| size = 8 | 通常点の2倍 |
| opacity = 1.0 | 完全不透明で"浮き上がる" |
| symbol = diamond | 形状も差別化 |
3D空間でも外れ値が確実に見つかる表現になります。
距離ベースで外れ値を強調する
中心からの距離で判定する方法も実用的です。
center = np.array([x.mean(), y.mean(), z.mean()])
dist = np.sqrt((x-center[0])**2 + (y-center[1])**2 + (z-center[2])**2)
threshold = dist.mean() + 2 * dist.std()
outlier_mask2 = dist > threshold
print(f"距離ベースで検出された外れ値: {outlier_mask2.sum()}件")
# 可視化
fig = go.Figure()
# 通常点
fig.add_trace(go.Scatter3d(
x=x[~outlier_mask2], y=y[~outlier_mask2], z=z[~outlier_mask2],
mode='markers',
marker=dict(size=4, color='lightblue', opacity=0.5),
name='Normal'
))
# 外れ値(距離ベース)
fig.add_trace(go.Scatter3d(
x=x[outlier_mask2], y=y[outlier_mask2], z=z[outlier_mask2],
mode='markers',
marker=dict(size=8, color='orange', opacity=1.0, symbol='diamond'),
name='Outlier (Distance)'
))
# 中心点を表示(オプション)
fig.add_trace(go.Scatter3d(
x=[center[0]], y=[center[1]], z=[center[2]],
mode='markers',
marker=dict(size=10, color='green', symbol='cross'),
name='Center'
))
fig.update_layout(
title="Distance-based Outlier Detection",
scene=dict(aspectmode='data')
)
fig.show()
球状のデータの"外側"にある点が外れ値として強調されます。
トラブルシュート
| 症状 | 原因・対処 |
|---|---|
| 外れ値が見えづらい | 色をcrimson・sizeを10〜12に調整 |
| 外れ値が多すぎる | Zスコア閾値 3 → 3.5 などに変更 |
| 外れ値が1個も検出されない | 意図的に外れ値を追加するか、閾値を 3 → 2.5 に下げる |
| 正常点が濃く重なる | opacity を 0.4〜0.6 に下げて透明に |
| 点が重すぎてColabが重い | 5000点以上は subsample 推奨 |
PlotlyはWebGL対応で10万点でも動く場合がありますが、ブラウザや環境によっては重くなるため、必要に応じてサンプリングを推奨します。
まとめ
外れ値は Zスコアや距離ベースで手軽に検出でき、色・サイズ・透明度などで強調できます。
3D表示は距離感をそのまま視覚化できるため、平面では見逃しがちな“飛び出し”が直感的に見つかります。
異常検知やセンサーデータ、PCA可視化など幅広い用途で役立つ表現です。
参考情報





