2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Plotly] 3D散布図に注釈(ラベル)を追加する - 特定の点を"指さして説明できる"3D可視化へ

Posted at

はじめに

image.png

3D散布図で「この点だけ強調したい」「ここが重要ポイント」という場面は多いはず。Plotlyでは注釈(annotations)を使うことで、特定の点にテキストラベルを付けて説明できる3D図が作れます。今回は、最小構成から応用テクニックまで整理します。

この記事でできること

  • 3D散布図でポイントに注釈ラベルを追加できる
  • 外れ値や重要点を"指さし"できる可視化を作る
  • 3D空間用の注釈設定(x,y,z座標指定)を理解
  • Colabでそのまま動くコード例あり

基本の3D散布図を作る

まずは通常の3D点群を描画します。

import plotly.graph_objects as go
import numpy as np

np.random.seed(0)
x, y, z = np.random.randn(3, 200)

fig = go.Figure(data=[go.Scatter3d(
    x=x, y=y, z=z,
    mode='markers',
    marker=dict(size=4, color=z, colorscale='Viridis', opacity=0.7)
)])
fig.update_layout(
    title="3D Scatter (Base)",
    scene=dict(aspectmode='data')  # データの実際の比率を保つ
)
fig.show()

補足: aspectmode='data' はデータの実際の比率を保ちます。球形の分布を表現したい場合は 'cube' も検討してください。

image.png

特定の点に注釈(ラベル)を追加する

Plotlyの注釈は layout.scene.annotations に追加します。2Dとは違い、3Dでは x, y, z 座標を指定する必要があります。

重要: 3D空間では点群にラベルが埋もれやすいため、背景付き+ラベル位置の調整が必須です。

例:最大Z値の点にラベルをつける(視認性重視版)

# 注釈を付けたい点(例:Zが最大の点)
idx = np.argmax(z)
label_x, label_y, label_z = x[idx], y[idx], z[idx]

fig.update_layout(
    scene=dict(
        annotations=[
            dict(
                x=label_x,  # 矢印で指す点のX座標
                y=label_y,  # 矢印で指す点のY座標
                z=label_z,  # 矢印で指す点のZ座標
                text="Max Z",
                showarrow=True,
                ax=50,   # ラベル位置のオフセット(ピクセル単位)
                ay=-50,  # 負の値で上方向にずらす
                font=dict(
                    color='#1f77b4',  # 濃い青(見やすい)
                    size=15,
                    family='Arial Black'  # 太字で潰れない
                ),
                bgcolor='rgba(255,255,255,0.9)',  # 白背景で見やすい
                bordercolor='#1f77b4',
                borderwidth=2,
                borderpad=4,
                arrowcolor='#1f77b4',
                arrowsize=2,
                arrowwidth=2,
                arrowhead=2
            )
        ],
        xaxis=dict(backgroundcolor='rgb(245,245,250)'),
        yaxis=dict(backgroundcolor='rgb(245,245,250)'),
        zaxis=dict(backgroundcolor='rgb(250,250,255)'),
        aspectmode='data'
    )
)
fig.show()

image.png

注釈のポイント

パラメータ 内容
x, y, z 矢印で指す点の3D座標(実際のデータ点)
text 表示する文字列
ax, ay ラベル位置のオフセット(ピクセル単位)。省略時は自動配置
showarrow=True 矢印の有無
bgcolor 背景色(必須:白背景が最も読みやすい)
bordercolor / borderwidth 枠線のスタイル
font.color 文字色(#1f77b4など濃い目の色推奨)
font.family Arial Black推奨
arrowcolor / arrowwidth 矢印のスタイル

重要な違い:

  • x, y, z → 矢印の先端(指す点)の座標
  • ax, ay → ラベルテキストの位置調整(ピクセル単位)
  • ax=50, ay=-50 なら、点から右上50ピクセルの位置にラベルが表示される

ax, ay の省略について:

# ax, ay を省略した場合(基本形)
dict(
    x=label_x, y=label_y, z=label_z,
    text="Max Z",
    showarrow=True,
    # ax, ay を指定しない場合、Plotlyが自動で配置
    ...
)

# ax, ay でラベル位置を調整(推奨)
dict(
    x=label_x, y=label_y, z=label_z,
    text="Max Z",
    showarrow=True,
    ax=50, ay=-50,  # 明示的に位置を指定
    ...
)

明示的に ax, ay を指定することで、ラベルの位置を正確にコントロールでき、重なりを避けられます。

ラベルが見えにくい原因と改善策

3D散布図では、ラベルが点群に埋もれて読めない問題がよく発生します。

ラベルが見えない主な原因

  • ラベルが読みにくくなる主な原因として、背景色と文字色のコントラストが足りないことや、点・線・矢印が重なってしまうことが挙げられます。

  • フォントサイズが小さい場合も視認性が落ちやすく、3D空間の奥側に配置されることで他の点に隠れてしまうこともあります。

  • また、注釈の背景が透明だと点群に埋もれてしまい、ラベル自体が認識しづらくなる点にも注意が必要です。

必須の改善テクニック(優先度順)

1. 背景付きラベルでコントラストを確保

bgcolor='rgba(255,255,255,0.9)',  # 白背景が最も読みやすい
bordercolor='#1f77b4',  # 濃い青(洗練された印象)
borderwidth=2,
font=dict(color='#1f77b4', size=15)

2. ラベル位置を調整する(重要)

# x,y,z は矢印で指す点の座標
x=label_x,
y=label_y,
z=label_z,
# ax, ay でラベルテキストの位置をずらす(ピクセル単位)
ax=50,   # 正の値で右方向
ay=-50,  # 負の値で上方向

注意: x, y, z にオフセットを加えると矢印の先端(指す位置)がずれてしまいます。点を正確に指したまま、ラベルだけをずらすには ax, ay を使います。

3. フォントサイズ・太さを調整

font=dict(
    color='#1f77b4',      # 濃い青(見やすい)
    size=15,              # 大きめに
    family='Arial Black'  # 太字フォントで潰れない
)

4. 点群側の透明度を強める

# ラベルと競合しないように点を薄くする
marker=dict(size=4, color=z, colorscale='Viridis', opacity=0.4)

5. 軸面の背景色を明るくする

scene=dict(
    xaxis=dict(backgroundcolor='rgb(245,245,250)'),
    yaxis=dict(backgroundcolor='rgb(245,245,250)'),
    zaxis=dict(backgroundcolor='rgb(250,250,255)')
)

視認性が一気に改善するポイント

  • ラベルは白い背景に濃い青の文字を組み合わせると、もっとも読みやすく洗練された印象になります。

  • ラベル位置は ax, ay で調整すれば、点を正確に指したままテキストだけを見やすい位置へずらせます。

  • フォントは Arial Black のような太字系を使うと潰れにくく、3Dでも視認性が保ちやすくなります。

  • 軸面を淡い色にしておくと文字が背景に埋もれず、ラベルが自然に浮かび上がります。

  • 点側は透明度を 0.3〜0.5 にするとラベルとぶつかりにくく、注釈がすっきり読み取れるようになります。

複数の重要点にラベルを付ける

例として、上位3点にラベルを追加します(視認性重視版)。

top_idx = np.argsort(z)[-3:]   # Z値の大きい上位3点

# ラベル位置のオフセット(ピクセル単位)を変えて重ならないようにする
offsets = [
    (60, -60),   # Top 1: 右上
    (-60, -60),  # Top 2: 左上
    (0, -80),    # Top 3: 真上
]

annotations = []
for i, idx in enumerate(top_idx):
    ax, ay = offsets[i]
    annotations.append(
        dict(
            x=x[idx],  # 矢印で指す点の座標
            y=y[idx],
            z=z[idx],
            ax=ax,     # ラベル位置のオフセット
            ay=ay,
            text=f"Top {i+1}",
            showarrow=True,
            font=dict(
                color='#2ca02c',  # 濃い緑(視認性良好)
                size=13,
                family='Arial Black'
            ),
            bgcolor='rgba(255,255,255,0.9)',
            bordercolor='#2ca02c',
            borderwidth=2,
            borderpad=3,
            arrowcolor='#2ca02c',
            arrowsize=2,
            arrowwidth=2,
            arrowhead=2
        )
    )

fig.update_layout(
    scene=dict(
        annotations=annotations,
        xaxis=dict(backgroundcolor='rgb(245,245,250)'),
        yaxis=dict(backgroundcolor='rgb(245,245,250)'),
        zaxis=dict(backgroundcolor='rgb(250,250,255)')
    )
)
fig.show()

image.png

複数ラベルを重ならせないコツ

近接した点にラベルを付ける場合、ax, ay の値を変えてラベル位置を分散させます。

# パターン1: 放射状に配置
offsets = [
    (80, 0),    # 右
    (-80, 0),   # 左
    (0, -80),   # 上
    (0, 80),    # 下
]

# パターン2: 上下左右に散らす
offsets = [
    (60, -60),   # 右上
    (-60, -60),  # 左上
    (60, 60),    # 右下
    (-60, 60),   # 左下
]

# パターン3: 縦に並べる
offsets = [
    (50, -100),  # 高い位置
    (50, -50),   # 中間
    (50, 0),     # 低い位置
]

ax, ay はピクセル単位なので、データの座標系に依存せず、画面上で一定の距離を保てます。

複数ラベルも簡単に追加できます。グループ分析・ピーク検出などに便利です。

グループ別に色分け+ラベルを付ける(分類可視化向け)

グループごとに色分けして、代表点(重心)にラベルを付けると分かりやすくなります。

# グループ分け
group = np.random.choice(['A', 'B'], size=200)
maskA = (group == 'A')
maskB = (group == 'B')

# 各グループの重心
cx_a, cy_a, cz_a = x[maskA].mean(), y[maskA].mean(), z[maskA].mean()
cx_b, cy_b, cz_b = x[maskB].mean(), y[maskB].mean(), z[maskB].mean()

# グループごとに色分けしてプロット
fig = go.Figure()

fig.add_trace(go.Scatter3d(
    x=x[maskA], y=y[maskA], z=z[maskA],
    mode='markers',
    name='Group A',
    marker=dict(size=4, color='#9467bd', opacity=0.6),  # 紫
    showlegend=True
))

fig.add_trace(go.Scatter3d(
    x=x[maskB], y=y[maskB], z=z[maskB],
    mode='markers',
    name='Group B',
    marker=dict(size=4, color='#ff7f0e', opacity=0.6),  # オレンジ
    showlegend=True
))

# 各グループの重心にラベル(重ならないようにax, ayを調整)
annotations = [
    dict(
        x=cx_a, y=cy_a, z=cz_a,  # 矢印で指す点(重心)
        ax=70, ay=-70,  # ラベル位置(右上)
        text="Group A Center",
        showarrow=True,
        font=dict(color='#9467bd', size=14, family='Arial Black'),
        bgcolor='rgba(255,255,255,0.9)',
        bordercolor='#9467bd',
        borderwidth=2,
        borderpad=4,
        arrowcolor='#9467bd',
        arrowsize=2,
        arrowwidth=2,
        arrowhead=2
    ),
    dict(
        x=cx_b, y=cy_b, z=cz_b,  # 矢印で指す点(重心)
        ax=-70, ay=70,  # ラベル位置(左下)
        text="Group B Center",
        showarrow=True,
        font=dict(color='#ff7f0e', size=14, family='Arial Black'),
        bgcolor='rgba(255,255,255,0.9)',
        bordercolor='#ff7f0e',
        borderwidth=2,
        borderpad=4,
        arrowcolor='#ff7f0e',
        arrowsize=2,
        arrowwidth=2,
        arrowhead=2
    )
]

fig.update_layout(
    title="3D Scatter with Group Labels",
    scene=dict(
        annotations=annotations,
        xaxis=dict(backgroundcolor='rgb(245,245,250)'),
        yaxis=dict(backgroundcolor='rgb(245,245,250)'),
        zaxis=dict(backgroundcolor='rgb(250,250,255)'),
        aspectmode='data'
    ),
    legend=dict(x=0.7, y=0.9)  # 凡例の位置
)
fig.show()

image.png

ラベルが重なる場合の対処法

グループの重心が近い場合、ラベルが重なってしまいます。以下の方法で回避できます。

1. ax, ay の符号を変える(推奨)

# Group Aは右上、Group Bは左下に配置
annotations = [
    dict(x=cx_a, y=cy_a, z=cz_a, ax=70, ay=-70, ...),  # 右上
    dict(x=cx_b, y=cy_b, z=cz_b, ax=-70, ay=70, ...)   # 左下
]

2. ax, ay の値を大きくする

# より大きく離す
annotations = [
    dict(x=cx_a, y=cy_a, z=cz_a, ax=100, ay=-100, ...),
    dict(x=cx_b, y=cy_b, z=cz_b, ax=-100, ay=100, ...)
]

3. 矢印を目立たせる

# 矢印を太くして視認性を上げる
arrowsize=3,
arrowwidth=3

ポイント

  • グループごとに色分けすると、どの点がどの分類に属するか一目で分かる。
  • 凡例(legend)を表示すれば、グループの識別がさらに簡単になる。
  • 重心にラベルを付けることで、各グループの中心位置が直感的に理解できる。
  • ラベルと点の色をそろえると視覚的な一貫性が生まれ、見やすさが向上する。
  • ax, ay を使ってラベル位置を調整すれば、ラベル同士の重なりを避けられる。

3D空間で"クラスの分布と中心"を示せるため、クラスタリングやPCAの可視化とも相性がよい。

色分けでラベルを使い分ける

用途別に色を変えると、さらに分かりやすくなります。

# 最大値は赤、最小値は青でラベル
max_idx = np.argmax(z)
min_idx = np.argmin(z)

annotations = [
    dict(
        x=x[max_idx], y=y[max_idx], z=z[max_idx],  # 矢印で指す点
        ax=60, ay=-60,  # ラベル位置(右上)
        text="Max",
        font=dict(color='#d62728', size=14, family='Arial Black'),  # 濃い赤
        bgcolor='rgba(255,255,255,0.9)',
        bordercolor='#d62728',
        borderwidth=2,
        borderpad=4,
        arrowcolor='#d62728',
        showarrow=True,
        arrowsize=2,
        arrowwidth=2,
        arrowhead=2
    ),
    dict(
        x=x[min_idx], y=y[min_idx], z=z[min_idx],  # 矢印で指す点
        ax=-60, ay=60,  # ラベル位置(左下)
        text="Min",
        font=dict(color='#1f77b4', size=14, family='Arial Black'),  # 濃い青
        bgcolor='rgba(255,255,255,0.9)',
        bordercolor='#1f77b4',
        borderwidth=2,
        borderpad=4,
        arrowcolor='#1f77b4',
        showarrow=True,
        arrowsize=2,
        arrowwidth=2,
        arrowhead=2
    )
]

fig.update_layout(
    scene=dict(
        annotations=annotations,
        xaxis=dict(backgroundcolor='rgb(245,245,250)'),
        yaxis=dict(backgroundcolor='rgb(245,245,250)'),
        zaxis=dict(backgroundcolor='rgb(250,250,255)')
    )
)

用途に応じた色分けで、直感的に理解しやすいグラフになります。

image.png

おすすめ配色パターン

用途 色コード 説明
標準・汎用 #1f77b4 濃い青(落ち着いた印象)
重要・最大値 #d62728 濃い赤(注目を集める)
成功・ポジティブ #2ca02c 濃い緑(安心感)
グループA #9467bd 濃い紫(区別しやすい)
グループB #ff7f0e オレンジ(明るい印象)

配色のポイント:
これらの濃い色は、記事で推奨している淡い軸面背景(rgb(245,245,250)など)との組み合わせで最も見やすくなります。軸面を濃い色にする場合は、ラベルの背景を完全不透明(rgba(255,255,255,1.0))にするとコントラストが保てます。

トラブルシュート

症状 原因・対処
ラベルが見えない scene.annotations の階層ミスが多い。要注意。
矢印が正確に点を指さない x,y,zにオフセットを加えている。ax, ayを使う。
文字が点群に埋もれて読めない bgcolor で背景色を付ける / ax, ayで位置調整 / フォントサイズを大きく
ラベル同士が重なる ax, ayの符号を変える(片方+、片方-)/ 値を大きくする
矢印が不自然な向き arrowhead を変更 or 座標の位置を微調整
ラベルが密集して見にくい ax, ayの絶対値を大きくする(例: 100)

まとめ

image.png

3D散布図に注釈を付けるときは、scene.annotations に x, y, z で「矢印が指す点」を指定し、ax, ay でラベルの位置をピクセル単位で調整します。

ラベルは白背景+濃い色+太字フォントにすると一気に読みやすくなり、色は #1f77b4(青)や #2ca02c(緑)、#d62728(赤)などを使うとメリハリが出ます。

ラベル同士が重なるときは、ax, ay の符号や大きさを変えて分散させ、軸面を淡い色、点の透明度を0.3〜0.5程度にすることで、文字がきれいに浮かび上がります。

x,y,z にオフセットを足してしまうと矢印が正しい点を指さなくなるので、必ず ax, ay 側で調整するのがポイントです。3D注釈をきちんと設計するだけで、外れ値や代表点、ピークの説明力が大きく向上します。

参考情報

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?