論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
散布図
Scatter Plot
2変数を x軸・y軸にプロットして関係を視覚化。相関や非線形パターン、外れ値の確認に必須。
可視化scatter散布図

🔖 キーワード索引(補強・追加分)

散布図 関連の補強キーワード。 クリックで該当箇所へ:

マーカー ジッタリング 色分け 回帰線 散布図行列 透明度α 六角ビン LOWESS マージナル 対数軸

scatter plot」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「scatter plot」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。

scatter plot2 変量分布x-y 平面相関の視覚化外れ値検出回帰直線jitterバブルチャート対角ペアプロットhexbin / 2D 密度層別 (hue)過剰描画 overplot

これらのキーワードは「scatter plot の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。

💡 30秒で分かる結論

🍰 まずはやさしく

2つのデータの関係を点で見せる図です。

データにどんな傾向があるか知るために使います。

勉強時間とテストの点数の関係などを調べます。

図の見方や使い方の基本を学びましょう。

📖 もっと詳しく

散布図(scatter plot)は、 2変数の関係を視覚化する最基本ツール。 横軸 x、 縦軸 y にデータ点をプロットします。 統計分析を始めるときに 必ず最初に描くべき図 です。

判別ポイント

必須習慣:相関係数を計算する前に、 必ず散布図を描く。 同じ r = 0.5 でも、 きれいな直線関係と、 外れ値による偽の相関は全く違います。 Anscombe の4組(r が同じでも散布図が全然違う有名な例)を見ると痛感します。

応用:(i) 単回帰の直線を重ねる、 (ii) hex bin で密度を可視化(点が多すぎる場合)、 (iii) seaborn の jointplot で周辺分布も同時に表示。

👁️ 直感 — 散布図は「2変数の関係が見える」

散布図(scatter plot)は、 2つの量的変数の関係を点で可視化する基本グラフ。 各点が1つの観測単位(人、 県、 商品など)を表します。

散布図の基本

47都道府県の食料費(横軸)と教育費(縦軸)の関係。 点が右上がりに広がっていて、 食料費が高い県は教育費も高い傾向(r = 0.728)。

💡 散布図は相関分析・回帰分析の前段階として必ず描くべきグラフ。 「相関係数 r が大きい」と言う前に、 必ず散布図で関係の形を確認しましょう。

📊 散布図で見える6つの代表パターン

散布図の形から、 2変数の関係を診断できます:

散布図パターン
パターン 特徴 適切な分析
強い正の相関右上がりにまとまるPearson 相関、 線形回帰
弱い正の相関右上がりだが散らばるPearson 相関 + 信頼区間
負の相関右下がりにまとまる同上
無相関円形に分布独立性検定
非線形(U字、 周期)曲線パターン非線形回帰、 Spearman、 相関比
外れ値少数の極端な点ロバスト回帰、 除去 or 別分析

🎈 バブルチャート — 3変数を1枚で表現

点のサイズや色で3つ目の変数を表現するのがバブルチャート。 SSDSE データで食料費×教育費の関係に、 「総人口」を点のサイズと色で重ねます。

バブルチャート

人口が多い大都市府県(東京、 神奈川など)は大きな円で表示され、 食料費・教育費も比較的高い位置に。 3次元の情報を2D上に圧縮できる強力な可視化。

Python での実装

🎯 解説: SSDSE-B-2026 の 47 都道府県データを 2 軸の点で表現し、 2 変数の関係(正・負・無相関、 線形・非線形、 外れ値)を視覚的に把握する。 散布図は探索的データ解析(EDA)の出発点。
📥 入力例: data/raw/SSDSE-B-2026.csv x = df['A1101'](総人口) y = df['A1303'](65歳以上人口) n = 47 県
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# ── この抜粋で使うデータを用意します ──
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()   # 2023 年の 47 都道府県
# 本文で使う短い呼び名は、実在する列の別名として作る
df['総人口'] = df['A1101']
df['食料費'] = df['L322101']
df['教育費'] = df['L322108']
_BLOCKS = {'北海道': ['北海道'],
    '東北': ['青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県'],
    '関東': ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'],
    '中部': ['新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県',
             '岐阜県', '静岡県', '愛知県'],
    '近畿': ['三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県', '和歌山県'],
    '中国': ['鳥取県', '島根県', '岡山県', '広島県', '山口県'],
    '四国': ['徳島県', '香川県', '愛媛県', '高知県'],
    '九州・沖縄': ['福岡県', '佐賀県', '長崎県', '熊本県', '大分県', '宮崎県',
                   '鹿児島県', '沖縄県']}
_R = {p: g for g, ps in _BLOCKS.items() for p in ps}
df['地域'] = df['Prefecture'].map(_R)

x = df['食料費'].values
y = df['教育費'].values
sizes = df['総人口'].values / 1e4      # 点のサイズ用(万人)
color_var = df['A1303'].values / 1e4   # 色の濃さ用(65歳以上人口・万人)

plt.scatter(x, y,
    s=sizes * 0.1,           # 点のサイズ
    c=color_var,             # 点の色(連続値)
    cmap='viridis',
    alpha=0.6,
    edgecolors='white')
plt.colorbar(label='65歳以上人口(万人)')

# seaborn でより簡潔に
import seaborn as sns
sns.scatterplot(x='食料費', y='教育費', size='総人口', hue='地域', data=df)
📤 実行例: 散布図(人口 vs 高齢者人口) 右肩上がりの強い正の関係 東京・神奈川・大阪が右上に集中 鳥取・島根が左下
💬 読み方: 人口が多い県ほど高齢者数も多い(当然)。 ただし「率」(高齢化率)と区別すること。 点が直線状に並ぶ → 線形相関。 U 字状 → 非線形。 はずれ値は個別ラベルで確認。

🔷 大量データには hexbin — 重なり問題を解決

データ点が数千・数万になると、 通常の散布図では点が重なって何も見えません。 そんなときはhexbin(六角形ビン)

hexbin

2D空間を六角形のビンで区切り、 各ビン内のデータ数を色で表現。 大量データの密度パターンが見えます。

その他の対処法

📍 あなたが今見ているもの

🍰 まずはやさしく

データの関係を視覚化(図にすること)する道具です。

分析を始めるときにまず描くべき図です。

都道府県の人口と出生数の関係などを調べます。

定義から注意点までを順番に解説します。

論文中に 「散布図」として登場する用語。

散布図 とは:2変数を x軸・y軸にプロットして関係を視覚化。相関や非線形パターン、外れ値の確認に必須。

本ページでは「scatter plot」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。

「scatter plot」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。

🎨 散布図の拡張

🍰 まずはやさしく

散布図をさらに便利にした応用技です。

より多くの情報を一度に読み取るために使います。

地域ごとに点の色を変えてグループを分けます。

色の使い分けや曲線の描き方を学びましょう。

🎨 散布図の拡張

① 散布図行列(pairs plot)

変数が3つ以上ある時、 すべてのペアの散布図を一気に表示:

🎯 解説: matplotlib.pyplot.scatter で SSDSE-B-2026 の 2 変数散布図を最もシンプルに描画。 alpha でハイライト・色で第 3 変数を表現できる。
📥 入力例: data/raw/SSDSE-B-2026.csv x = df['A1101'](総人口) y = df['A4101'](出生数)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# ── この抜粋で使うデータを用意します ──
import pandas as pd
import seaborn as sns

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()   # 2023 年の 47 都道府県
# 本文で使う短い呼び名は、実在する列の別名として作る
df['食料費'] = df['L322101']
df['教育費'] = df['L322108']
df['住居費'] = df['L322102']
df['光熱費'] = df['L322103']   # 光熱・水道費(二人以上の世帯)

sns.pairplot(df[['食料費', '教育費', '住居費', '光熱費']], kind='reg')
📤 実行例: 47 点の散布図 右肩上がりの分布 東京が極端な右上外れ値
💬 読み方: 点が密集する領域は alpha=0.5 で重なりを可視化。 ラベル付け(県名)が必要なら annotate で個別に追加。 外れ値が回帰直線を引っ張らないか必ず確認。

② カテゴリ変数で色分け

地域・性別・カテゴリで色分けすると、 群間差が見える:

sns.scatterplot(x='食料費', y='教育費', hue='地域', style='都市規模', data=df)

③ LOWESS(局所重み付き回帰)

非線形な関係を平滑化した曲線で表示:

🎯 解説: seaborn.scatterplot で hue(色)・size(大きさ)・style(マーカー)を一括設定。 多次元情報を 1 つの散布図に詰め込める拡張機能。
📥 入力例: data/raw/SSDSE-B-2026.csv x='A1101', y='A4101' hue=地域区分(北海道~九州)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# ── この抜粋で使うデータを用意します ──
# seaborn の lowess=True はブラウザ版では statsmodels を見つけられないので、
# 平滑化は statsmodels の lowess で直接計算して重ねる。
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.nonparametric.smoothers_lowess import lowess

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()   # 2023 年の 47 都道府県
df['x'] = df['L322101'].astype(float)        # 食料費
df['y'] = df['L322108'].astype(float)        # 教育費
x, y = df['x'].values, df['y'].values

sns.scatterplot(x='x', y='y', data=df)
smoothed = lowess(y, x, frac=0.3)            # (x, 平滑後 y) の 2 列
plt.plot(smoothed[:, 0], smoothed[:, 1], color='crimson', label='LOWESS')
plt.legend()
print('平滑化後の先頭 3 点:', smoothed[:3].round(1).tolist())
📤 実行例: 散布図 + 地域別の色凡例 関東圏(赤)が右上に集中 地方(青)が左下
💬 読み方: 色分けで地域差が直感的に見える。 hue が連続変数だとカラーマップ、 カテゴリだと離散色。 凡例を必ず表示。 色覚多様性に配慮して colormap を選ぶ。

④ 信頼区間付き回帰直線

sns.regplot(x='食料費', y='教育費', data=df, ci=95) # 95%信頼区間が陰影で表示される

⑤ 結合プロット(jointplot)

散布図 + ヒストグラム + 統計量を1枚に:

sns.jointplot(x='食料費', y='教育費', data=df, kind='reg') # kind: 'scatter', 'hex', 'kde', 'reg', 'resid', 'hist'

🤖 機械学習での散布図

① EDA(探索的データ分析)

機械学習の前段階で、 各特徴量ペアの関係を散布図行列で確認。 「強い相関がある」「外れ値がある」「非線形だ」などを発見。

② クラスタリング結果の可視化

k-means や DBSCAN の結果を、 色分け散布図で確認。 2次元または PCA で2次元に圧縮してから描画:

🎯 解説: 散布図行列(pairplot)で SSDSE-B-2026 の数値変数全組み合わせを一括描画。 多変数間の関係性を一目で把握する EDA の鉄板手法。
📥 入力例: data/raw/SSDSE-B-2026.csv columns=['A1101','A1301','A1303','A4101'] 4 変数 × 4 変数 = 16 パネル
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ── この抜粋で使うデータを用意します ──
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]          # 2023 年の 47 都道府県
# 家計の 4 費目を標準化して 4 次元の X にする
X = StandardScaler().fit_transform(
    df[['L322101', 'L322102', 'L322103', 'L322108']].astype(float))

# 高次元データを2次元に
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)

# クラスタリング
km = KMeans(n_clusters=3, n_init=10, random_state=0)
labels = km.fit_predict(X)

plt.scatter(X_2d[:, 0], X_2d[:, 1], c=labels, cmap='viridis')
print('各クラスタの県数:', pd.Series(labels).value_counts().sort_index().tolist())
📤 実行例: 4×4 マトリクス 対角: ヒストグラム 非対角: 散布図 人口系変数間は強い線形関係
💬 読み方: 対角はその変数の分布、 非対角はペア相関。 強い線形関係が見えるペアは多重共線性に注意。 変数が 10 を超えると見づらいので相関行列ヒートマップを併用。

③ 残差プロット

回帰モデルの残差 vs 予測値を散布図にする。 ランダムなら適合良、 パターンがあればモデル改善の余地:

predictions = model.predict(X) residuals = y - predictions plt.scatter(predictions, residuals, alpha=0.5) plt.axhline(0, color='red') plt.xlabel('予測値') plt.ylabel('残差')

④ QQプロット

残差が正規分布に従うか確認する散布図:

🎯 解説: 回帰直線と 95% 信頼区間付きの散布図を seaborn.regplot で描画。 関係性の方向・強さ・不確実性を一画面で表現。
📥 入力例: data/raw/SSDSE-B-2026.csv x='A1101', y='A1303' ci=95
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ── この抜粋で使う残差を用意します ──
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]          # 2023 年の 47 都道府県
x = df['L322101'].astype(float).values       # 食料費
y = df['L322108'].astype(float).values       # 教育費
slope, intercept, *_ = stats.linregress(x, y)
residuals = y - (slope * x + intercept)      # 単回帰の残差

stats.probplot(residuals, dist='norm', plot=plt)
# 点が直線上 → 正規分布
print('残差の平均:', round(residuals.mean(), 6))
📤 実行例(実測) 残差の平均: -0.0 Q-Q プロット (probplot) が描かれる。 食料費 → 教育費 の単回帰は 傾き 0.5247、 切片 -32712.7、 R² 0.5296 (p = 6.8e-09)。 残差の歪度は -0.132 でほぼ対称、 点は概ね直線上に乗る。
💬 読み方: 信頼帯が狭い領域は推定が安定、 帯がデータ範囲外で広がる → 外挿不可。 R² が高くても残差プロットも見ること。 外れ値(東京)が直線を引っ張る場合は除外検討。

⑤ ROC曲線・精度再現率曲線

機械学習モデルの評価でも散布図的な可視化が頻出。

🔍 散布図の読み方 — 8つのチェックポイント

  1. 関係の方向:正・負・無関係
  2. 関係の強さ:点が直線にまとまる度合い
  3. 関係の形:直線か曲線か
  4. 外れ値:少数の極端な点が見えるか
  5. クラスタ:点の集まりが複数あるか(混在する集団)
  6. 異質性:ばらつきが変数の値で変わるか(不等分散)
  7. 境界・打ち切り:上限・下限に張り付く点があるか
  8. 欠損パターン:データが取れていない領域があるか

💡 散布図1枚から、 統計分析の方針(線形か非線形か、 ロバスト推定が必要か、 外れ値除去が必要か)が決まります。 数値だけ見るより必ず可視化を見る

散布図は「2 変数の関係を点でプロットする」可視化の王道。 1 点 = 1 観測(県・人・時刻)。 SSDSE-B-2026 で A1101 vs A1303(総人口 vs 高齢人口)を描くと、 ほぼ y=0.25x の直線上に並ぶが、 北海道のように高齢化率が突出して高い県は線の上側にずれて配置される。

💡 学習のコツ:直感で全体像を掴んだら、 次の「📐 定義・数式」で正確な意味を押さえ、 最後に「🧮 実値で計算してみる」で SSDSE-B-2026 の都道府県データを使った計算をなぞるのが効率的です。 比喩は厳密ではないので、 必ず数式と並べて確認してください。

散布図 は「可視化」カテゴリの中核概念。 初めて触れる読者は、 まずこの「🎨 直感」セクションだけ通読し、 必要になった時点で「📐 数式」「🐍 Python」「⚠️ 落とし穴」へ戻る読み方が定着しやすいです。

📐 定義・数式 — 散布図

🍰 まずはやさしく

散布図の仕組みを数式で表したものです。

正確なルールを共通の言葉で伝えるために使います。

点と点の離れ具合を計算して関係性を出します。

数式の記号が何を意味するかを確認しましょう。

やさしい説明で掴んだ感覚を、ここで 散布図 の定義式に対応づけます。下の式は左辺 r が何で決まるかを右辺で書き下したもので、x̄(平均)、Σ(合計)、分数(割り算) が現れます。それぞれの記号が何の量を指すのかは、次の「🔬 数式を言葉で読み解く」で 1 つずつ確かめてください。

【散布図 の中心定義式】
$$ r = \frac{\sum_{i}(x_i-\bar{x})(y_i-\bar{y})}{\sqrt{\sum (x_i-\bar{x})^2 \cdot \sum(y_i-\bar{y})^2}} $$
この式が「散布図」の骨格。 派生形・拡張形はここから生まれる。
📌 読み方のコツ:数式を見たら「左辺は何を定義しているか」「右辺の各項は何の合計・積・比か」を声に出して読み下してみる。 これだけで理解が大きく進みます。

🔬 数式を言葉で読み解く — 数式を「言葉」に翻訳

上の数式を眺めるだけでは身につかないので、 各記号がどんな役割を担っているかを言葉で押さえます。 「数式を音読する習慣」がつくと、 論文や教科書を読むスピードが体感で 2 倍ほど上がります。

左辺(結果側)
散布図 で定義したい量。 解釈の対象。 単位・スケールを必ず確認する。
右辺(構成要素)
観測できる入力変数(SSDSE-B-2026 でいえば A1101・L3221 など)と推定対象パラメータ(β, σ 等)の組合せ。
添字 i, j, t
i=サンプル(県)、 j=変数、 t=時点。 SSDSE-B-2026 は i ∈ {1..47} 県、 t ∈ {2008..2023}。
和記号 Σ
「足し合わせ」を表す。 添字 i が 1 から n まで動く範囲を明示するのが習慣。
期待値 E[·]、 分散 Var[·]
「ランダム変数の平均」と「ばらつき」。 SSDSE-B-2026 のような集計値でも、 標本誤差・年次変動の文脈で使える。
📚 補足:同じ記号でも分野・教科書によって意味が違うことがあります(例: $\hat{y}$ は予測値だが、 統計の文脈では推定量を意味することも)。 不明確なときは、 必ずその文書の記号定義表を確認しましょう。

🧮 SSDSE-B 実値計算例(47都道府県データ)

47 都道府県データで散布図を体系的に作成し、 ジッタリング・色分け・回帰線・マージナル分布まで含む実例。

① 計算コード

🎯 解説: 対数軸散布図(loglog)で SSDSE-B-2026 のスケール差が大きい変数を可視化。 人口や出生数は東京と鳥取で 30 倍近い差があるため対数軸が有効。
📥 入力例: data/raw/SSDSE-B-2026.csv plt.xscale('log'), yscale('log') 人口・出生数 は log 正規分布的
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
# 基本散布図
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
ax = axes[0, 0]
ax.scatter(df['C5401'], df['L3221'], alpha=0.7, s=60)
ax.set_xlabel('標準地価(円/㎡)'); ax.set_ylabel('消費支出(円)')
ax.set_title('① 基本散布図')

# 第3変数で色分け
ax = axes[0, 1]
sc = ax.scatter(df['C5401'], df['L3221'],
                c=df['A1101'], cmap='viridis', s=60, alpha=0.8)
plt.colorbar(sc, ax=ax, label='総人口')
ax.set_title('② 総人口で色分け')

# 回帰線つき
ax = axes[1, 0]
sns.regplot(x='C5401', y='L3221', data=df,
            ax=ax, scatter_kws={'alpha':0.6}, line_kws={'color':'red'})
ax.set_title('③ 回帰線(95% CI 帯)')

# 六角ビン(大きいデータ向け)
ax = axes[1, 1]
hb = ax.hexbin(df['C5401'], df['L3221'], gridsize=10, cmap='Blues')
plt.colorbar(hb, ax=ax, label='密度')
ax.set_title('④ 六角ビン')

plt.tight_layout(); plt.savefig('scatter_4panel.png', dpi=110)
📤 実行例: log-log 散布図 ほぼ直線状(べき乗則) 東京も平均的位置に 小さい県も識別可能
💬 読み方: log-log で直線 → べき乗則 y=a·x^b。 傾きが指数 b に相当。 軸目盛は 10^0, 10^1 などの底 10 表示が標準。 0 や負値があると log を取れない(+1 シフト等が必要)。

② 期待出力

項目 参考 解釈
基本観測47点1点=1都道府県
傾向相関係数r ≈ 0.31地価高い県ほど消費支出も高い(弱い正相関)
外れ東京高地価・高消費右上に位置
外れ埼玉・三重低〜中地価・高消費上部

👉 値は SSDSE-B-2026 の典型値。 同じ手順で他都道府県・他変数にも適用可能。

🧮 実値で計算してみる — SSDSE-B-2026

数式だけでは「実感」が湧きにくいので、 実データ data/raw/SSDSE-B-2026.csv(47 都道府県 × 16 年)で 1 度手計算してみると理解が定着します。

SSDSE-B-2026 (2023) で A1101 vs A1303 の Pearson 相関は r ≈ 0.991(非常に強い正相関、 人口の多い県ほど高齢人口も多いのは自明)。 ところが「高齢化率=A1303/A1101」を縦軸にすると相関は逆転し、 北海道 33.0%、 秋田県 39.1% などが上方に飛ぶ。 散布図は「軸の取り方で物語が変わる」典型例。

都道府県A1101 総人口A1303 65 歳以上L3221 消費支出
東京都14,086,0003,205,000341,320
神奈川県9,229,0002,390,000306,565
大阪府8,763,0002,424,000271,246
愛知県7,477,0001,923,000300,221
埼玉県7,331,0002,012,000344,092
千葉県6,257,0001,756,000306,943

上記は SSDSE-B-2026 (2023) からの抜粋。 手計算で確認した値が、 後述の Python 実装で得る値と一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。

🧮 数式に値を入れて手で計算する: 散布図と相関係数

合成 2 変数で r を計算し、 散布の特徴を確認する。

Step 1: データ

x = [2, 4, 7, 5, 3] y = [3, 7, 12, 9, 4]

Step 2: 相関

r = 0.990 (強い正の相関) 散布図が右上がりの直線に近い

🐍 Python で再現

1
2
3
4
import numpy as np
x = np.array([2,4,7,5,3])
y = np.array([3,7,12,9,4])
print(f"r: {np.corrcoef(x, y)[0,1]:.3f}")

📤 実行結果

r: 0.990

💬 手計算 (Step 2) r=0.990 と Python 出力が完全一致。

🐍 Python での散布図描画

matplotlib(基本)

🎯 解説: plotly.express.scatter でインタラクティブ散布図を作成。 ホバーで県名・値を表示、 ズーム・選択・色分けが可能。 ダッシュボード用途に最適。
📥 入力例: data/raw/SSDSE-B-2026.csv x='A1101', y='A4101' hover_name='都道府県'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import matplotlib.pyplot as plt

plt.figure(figsize=(8, 5))
plt.scatter(x, y, s=60, alpha=0.6, color='steelblue', edgecolors='white')
plt.xlabel('X変数')
plt.ylabel('Y変数')
plt.title('散布図のタイトル')
plt.grid(alpha=0.3)
plt.show()

# 回帰直線を追加
import numpy as np
z = np.polyfit(x, y, 1)
plt.plot(x, z[0]*x + z[1], 'r-', label=f'y={z[0]:.2f}x+{z[1]:.2f}')
plt.legend()
📤 実行例: ブラウザでインタラクティブ図 マウスホバー → 「東京都: 人口 14M, 高齢人口 3.2M」
💬 読み方: 静的画像と違い、 ズーム・パン・ホバーで詳細確認可能。 HTML 出力で配布可能。 大量データ(10万点超)では描画が重くなる → datashader 推奨。

seaborn(高機能)

🎯 解説: バブルチャート(散布図 + 点サイズ)で第 3 変数を表現。 SSDSE-B-2026 で x=人口、 y=高齢人口、 size=高齢化率 の 3 次元情報を 2D で可視化。
📥 入力例: data/raw/SSDSE-B-2026.csv x='A1101', y='A1303' size='高齢化率'(バブル半径)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# ── この抜粋で使うデータを用意します(SSDSE-B の 47 都道府県・最新年度)──
# 英字の項目コード(C5401 など)も後のブロックで使うので skiprows=[1] で読み、
# 本文で使う短い呼び名は、実在する列の別名として足す。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()].copy()   # 最新年度の 47 行
df['総人口'] = df['A1101']
df['食料費'] = df['L322101']      # 食料費(二人以上の世帯)
df['教育費'] = df['L322108']      # 教育費(二人以上の世帯)
df['住居費'] = df['L322102']      # 住居費(二人以上の世帯)
# 「地方」は SSDSE に無い列なので、都道府県名から 8 地方区分を作る
_BLOCKS = {
    '北海道': ['北海道'],
    '東北': ['青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県'],
    '関東': ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'],
    '中部': ['新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県',
             '岐阜県', '静岡県', '愛知県'],
    '近畿': ['三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県', '和歌山県'],
    '中国': ['鳥取県', '島根県', '岡山県', '広島県', '山口県'],
    '四国': ['徳島県', '香川県', '愛媛県', '高知県'],
    '九州・沖縄': ['福岡県', '佐賀県', '長崎県', '熊本県', '大分県', '宮崎県',
                   '鹿児島県', '沖縄県'],
}
_R = {p: g for g, ps in _BLOCKS.items() for p in ps}
df['地域'] = df['Prefecture'].map(_R)

import seaborn as sns

# 基本
sns.scatterplot(x='食料費', y='教育費', data=df)

# 色・サイズ分け
sns.scatterplot(x='食料費', y='教育費', hue='地域', size='総人口', data=df)

# 回帰直線 + 信頼区間
sns.regplot(x='食料費', y='教育費', data=df)

# 散布図行列
sns.pairplot(df[['食料費', '教育費', '住居費']], diag_kind='kde')

# joint plot
sns.jointplot(x='食料費', y='教育費', data=df, kind='reg')
📤 実行例: 47 個のバブル 右上(人口多・高齢人口多)は小バブル(高齢化率低) 左下は大バブル(地方の高齢化率高)
💬 読み方: バブルサイズは半径ではなく面積比例にすべき(s=value 引数)。 サイズの差が大きいと小バブルが見えなくなる → 正規化推奨。 凡例にサイズスケールを明記。

plotly(インタラクティブ)

🎯 解説: 外れ値検出付き散布図。 z-score > 3 や IQR 外の点を赤色で強調表示。 SSDSE-B-2026 で東京・大阪などの極端な県を視覚的に特定。
📥 入力例: data/raw/SSDSE-B-2026.csv z = (x - mean) / std 外れ値: |z| > 2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd
import plotly.express as px

# このページは英字の項目コード(C5401 など)で通しているので、同じ流儀で読む
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()].copy()
for _c in ('A1101', 'L3221', 'L322108', 'C5401'):
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

fig = px.scatter(df, x='L3221', y='L322108',      # 食料費 と 教育費
                 size='A1101',                     # 総人口
                 hover_data=['Prefecture'], trendline='ols')
fig.show()
# マウスを載せると都道府県名が出る
📤 実行例: 通常点(青)+ 外れ値(赤) 東京・神奈川・大阪・愛知が赤 人口が大きく外れる
💬 読み方: 外れ値は誤データかも本当に極端な値かを区別。 SSDSE では「東京が外れ値」は事実。 除外せず log 変換で対応するか、 ロバスト回帰を使う。

🚧 散布図の落とし穴

1️⃣ 軸のスケールで見え方が変わる

軸の範囲を狭めると関係が誇張、 広げると弱く見える。 必ずデータ全体を含む範囲で描画。

2️⃣ 点が重なって見えない(overplotting)

大量データでは alpha、 jitter、 hexbin で対処。

3️⃣ 色覚多様性への配慮

赤×緑の組合せは色覚多様性で識別できない人も。 viridis、 plasma などのカラーマップを推奨。

4️⃣ 第3変数の交絡

2変数の関係に見えるものが、 第3変数の影響かもしれません。 シンプソンのパラドックスに注意。

5️⃣ 因果と相関の混同

散布図で関係が見えても、 因果ではない。 「アイスクリーム売上 vs 水難事故」の典型例(共通因:気温)。

6️⃣ 単位の異なる軸を強引に比較しない

身長 vs 体重のような異なる単位の場合、 標準化後に比較するなどの工夫が必要なことも。

📜 散布図の歴史

散布図は170年以上の歴史を持ち、 今もデータサイエンスの中核可視化として使われ続けています。

🐍 Python 実装バリエーション(scikit-learn / scipy / Optuna)

A. scikit-learn による実装

🎯 解説: ジョイントプロット(散布図 + 周辺ヒストグラム)で 2 変数の同時分布と各々の周辺分布を 1 図に表示。 seaborn.jointplot で簡潔に実装。
📥 入力例: data/raw/SSDSE-B-2026.csv x='A1101', y='A1303' kind='scatter' or 'reg' or 'hex'
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
import numpy as np

# 散布図に非線形回帰曲線を重ねる(多項式)
# このブロックは英字の項目コードを使うので、ここで読み直す
import pandas as pd
import matplotlib.pyplot as plt
d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = d[d['SSDSE-B-2026'] == d['SSDSE-B-2026'].max()]
X = d[['C5401']].astype(float).values   # 住宅地の標準価格
y = d['L3221'].astype(float).values     # 消費支出

fig, ax = plt.subplots(figsize=(8, 5))
ax.scatter(X, y, alpha=0.6, s=50)

x_grid = np.linspace(X.min(), X.max(), 200).reshape(-1, 1)
for degree, color in [(1, 'red'), (2, 'green'), (3, 'blue')]:
    model = Pipeline([('poly', PolynomialFeatures(degree)), ('lr', LinearRegression())])
    model.fit(X, y)
    ax.plot(x_grid, model.predict(x_grid), color=color, label=f'degree={degree}')

ax.legend(); ax.set_xlabel('標準地価'); ax.set_ylabel('消費支出')
plt.savefig('scatter_poly.png', dpi=110)
📤 実行例: 中央: 散布図 上端: x のヒストグラム 右端: y のヒストグラム
💬 読み方: 周辺分布で各変数の歪み・多峰性を確認できる。 kind='hex' は点が多いとき密度を六角形ビンで表示。 kind='kde' でカーネル密度推定。

B. scipy / statsmodels による実装

🎯 解説: クラスタリング付き散布図。 SSDSE-B-2026 を K-means で 3 クラスタに分け、 都市型・地方型・中間型を色分け。 探索的グルーピングの可視化。
📥 入力例: data/raw/SSDSE-B-2026.csv KMeans(n_clusters=3) features: 人口・高齢人口・高齢化率
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from scipy import stats
from scipy.stats import gaussian_kde
import numpy as np

# 2D カーネル密度推定で散布図を補強
x = df['C5401'].values
y = df['L3221'].values
kde = gaussian_kde(np.vstack([x, y]))

xx, yy = np.mgrid[x.min():x.max():100j, y.min():y.max():100j]
positions = np.vstack([xx.ravel(), yy.ravel()])
zz = kde(positions).reshape(xx.shape)

fig, ax = plt.subplots(figsize=(8, 6))
ax.contourf(xx, yy, zz, levels=10, cmap='Blues', alpha=0.7)
ax.scatter(x, y, c='red', s=20, alpha=0.7, edgecolor='white')

# 相関と回帰
r, p = stats.pearsonr(x, y)
slope, intercept, _, _, _ = stats.linregress(x, y)
xs = np.array([x.min(), x.max()])
ax.plot(xs, intercept + slope * xs, 'k--', label=f'r={r:.2f}, p={p:.3f}')
ax.legend(); plt.savefig('scatter_kde.png', dpi=110)
📤 実行例: 3 色の散布図 クラスタ A(赤): 東京・神奈川・大阪(都市型) クラスタ B(青): 地方 クラスタ C(緑): 中間
💬 読み方: クラスタ数は事前に決める必要あり(エルボー法・シルエット係数で選定)。 K-means は球状クラスタを仮定するため非凸形状には DBSCAN を試す。 標準化必須。

C. Optuna でハイパラ・選択最適化

🎯 解説: 時系列散布図(過去 vs 現在)。 SSDSE-B-2026 で 2020 年と 2024 年の人口を散布図化し、 対角線からのズレで変化方向を確認。
📥 入力例: 過去データ + 現在データ x = 人口(2020) y = 人口(2024)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# LOWESS の bandwidth 最適化(散布図に滑らかな曲線を重ねる)
import statsmodels.api as sm
import optuna

def objective(trial):
    frac = trial.suggest_float('frac', 0.1, 0.9)
    smoothed = sm.nonparametric.lowess(y, x, frac=frac, return_sorted=False)
    return ((y - smoothed)**2).mean()  # MSE 最小化

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=30, show_progress_bar=False)
print('Best frac:', study.best_params)

# 最適 frac で散布図にプロット
best_frac = study.best_params['frac']
smoothed = sm.nonparametric.lowess(y, x, frac=best_frac)
fig, ax = plt.subplots(figsize=(8, 5))
ax.scatter(x, y, alpha=0.6)
ax.plot(smoothed[:, 0], smoothed[:, 1], 'r-', label=f'LOWESS frac={best_frac:.2f}')
ax.legend(); plt.savefig('scatter_lowess.png', dpi=110)
📤 実行例: 散布図 + 対角線(y=x) ほぼ対角上 → 変化少 対角下 → 減少県(多数) 対角上 → 増加県(少数)
💬 読み方: 対角線(y=x)からの距離が変化量。 上下どちら側かで増減の向き。 全県を一度に俯瞰でき、 地方の人口減少が顕著に見える。

D. ライブラリ早見表

ライブラリ / 関数 用途
matplotlib.pyplot.scatter最も柔軟・低レベル
seaborn.scatterplot回帰線・色分けが簡単
seaborn.regplot回帰線+CI 帯
seaborn.jointplotマージナル分布つき散布図
plotly.express.scatterインタラクティブな散布図
matplotlib.hexbin大量データの密度可視化

🐍 Python 実装 — 散布図

公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで 散布図 を動作させます。 まずはこのまま実行してみてください。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 296,888 東京都 14,086,000 3,205,000 341,320 沖縄県 1,468,000 350,000 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 散布図 を SSDSE-B-2026 で実行する最小コード
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]  # 2023 年のみ抽出
print(df.shape)  # (47, 112)
print(df[['Prefecture','A1101','A1303','L3221']].head())

import matplotlib.pyplot as plt
fig, axes = plt.subplots(1,2, figsize=(12,5))
axes[0].scatter(df['A1101'], df['A1303'], alpha=0.7)
axes[0].set_xlabel('A1101 総人口'); axes[0].set_ylabel('A1303 65歳以上')
axes[0].set_title('総人口 vs 高齢人口')
axes[1].scatter(df['A1101'], df['A1303']/df['A1101'], alpha=0.7, color='red')
axes[1].set_xlabel('A1101 総人口'); axes[1].set_ylabel('高齢化率')
axes[1].set_title('総人口 vs 高齢化率')
plt.tight_layout(); plt.savefig('scatter_demo.png', dpi=100)
print('r1:', df[['A1101','A1303']].corr().iloc[0,1])

▶ 実行 を押せばこのページの中でそのまま動きます(ライブラリもデータも同梱済みで、 準備は要りません)。 手元の Python に移して動かすときは pip install matplotlib numpy pandas scikit-learn scipy seaborn statsmodels が必要です。 読んでいるデータは data/raw/SSDSE-B-2026.csv。 日本語を含むので encoding='cp932' の指定を落とさないでください。

🎨 散布図を読みやすくする 6 つの描画テクニック

散布図は「点を打つだけ」に見えて、 実際には 点が重なる・軸の桁が違う・3 つ目の変数を載せたいといった問題に すぐぶつかる。 ここでは SSDSE-B-2026 を題材に、 現場で使う 6 つの手当てを 実際に走るコードと実測値で確認する。 いずれも図を「きれいにする」ためではなく、 データが持っている情報を消さずに見せるための技術である。

図の軸ラベルは日本語のままで問題ない。 このサイトの実行環境には IPAex ゴシックが同梱されており、 matplotlib を import した時点で 自動的に登録される。 手元の環境で日本語が □ に化ける場合は、 日本語フォントが 入っていないので matplotlib.rcParams['font.family'] = 'IPAexGothic' 等を指定してほしい。

① マーカー — 形と大きさで 3 つ目・4 つ目の情報を載せる

x と y だけでは 2 つの情報しか運べない。 マーカーのをカテゴリに、 面積を連続量に割り当てると、 1 枚の散布図で 4 変数を同時に見せられる。 注意点は 2 つ。 面積は半径ではなく面積で比例させること (matplotlib の s は面積なのでそのまま渡してよい)、 そして 形は 3〜4 種類までにすること(それ以上は読者が凡例を覚えられない)。

📥 入力: SSDSE-B-2026 の 2023 年 47 都道府県。 x=総人口、 y=出生数、 形=東日本/西日本、 面積=高齢化率。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023].copy()
d['aging'] = d['A1303'] / d['A1101'] * 100        # 高齢化率 (%)

east = ['北海道','青森県','岩手県','宮城県','秋田県','山形県','福島県','茨城県','栃木県',
        '群馬県','埼玉県','千葉県','東京都','神奈川県','新潟県','富山県','石川県','福井県',
        '山梨県','長野県','岐阜県','静岡県','愛知県']
d['area'] = d['Prefecture'].apply(lambda p: '東日本' if p in east else '西日本')

fig, ax = plt.subplots(figsize=(7, 5))
for area, mk in [('東日本', 'o'), ('西日本', '^')]:
    g = d[d['area'] == area]
    ax.scatter(g['A1101'] / 1e4, g['A4101'] / 1e3,
               marker=mk, s=(g['aging'] - 20) * 12,   # 面積で高齢化率を表す
               alpha=0.7, edgecolor='white', label=f'{area} (n={len(g)})')
ax.set_xlabel('総人口(万人)'); ax.set_ylabel('出生数(千人)')
ax.set_title('マーカーの形=地域、 面積=高齢化率')
ax.legend(); plt.tight_layout(); plt.savefig('scatter_marker.png', dpi=120)

print(f'東日本 {len(d[d["area"]=="東日本"])} 県 / 西日本 {len(d[d["area"]=="西日本"])} 県')
print(f'高齢化率の範囲: {d["aging"].min():.1f}% 〜 {d["aging"].max():.1f}%')
print(f'マーカー面積の比 = {(d["aging"].max()-20) / (d["aging"].min()-20):.1f} 倍')
print(f'東日本の平均高齢化率 {d[d["area"]=="東日本"]["aging"].mean():.2f}% / '
      f'西日本 {d[d["area"]=="西日本"]["aging"].mean():.2f}%')

📤 実行するとこの出力が得られる:

東日本 23 県 / 西日本 24 県 高齢化率の範囲: 22.8% 〜 39.1% マーカー面積の比 = 6.9 倍 東日本の平均高齢化率 31.18% / 西日本 31.97%

💬 結果の読み方: 形が地域、 面積が高齢化率を表す。 高齢化率は 22.8%〜39.1% の範囲だが、 20% を基準に引き算してから面積に割り当てているため、 面積比は 6.9 倍に拡大されて 違いが見えるようになった。 生の値をそのまま面積にすると 39.1/22.8 = 1.7 倍にしかならず、 目では区別できない。 一方これは誇張でもある ── 面積の比が実際の値の比と 一致しなくなるので、 凡例か本文で「基準を引いてある」と必ず断ること。 なお東日本 31.18% / 西日本 31.97% と平均はほぼ同じで、 形の違いには意味が乏しい。 差が無い変数を形に割り当てても情報は増えないという例でもある。

② ジッタリング — 離散値が重なって「1 点」に見えるのを防ぐ

合計特殊出生率のように小数第 1 位までしか動かない変数を横軸に取ると、 多くの県がまったく同じ x 座標に落ちて重なってしまう。 わざと微小な乱数を足して 位置をずらす手法がジッタリングである。 ずらし幅は 刻み幅の 2 割程度に留めるのが目安で、 これを超えると隣の目盛りと 混ざって値を読み違える。

📥 入力: SSDSE-B-2026 の 2023 年 47 都道府県。 合計特殊出生率 (A4103) を小数第 1 位に丸めたもの。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023].copy()

# 合計特殊出生率を小数第 1 位に丸めると、 値が 8 通りしか無い離散変数になる
d['tfr_round'] = d['A4103'].round(1)
n_unique = d['tfr_round'].nunique()
dup = len(d) - n_unique
print(f'丸めた出生率の異なる値: {n_unique} 通り(47 県中 {dup} 県が他県と同じ値)')
print('最も混み合う値:', d['tfr_round'].value_counts().idxmax(),
      '→', int(d['tfr_round'].value_counts().max()), '県が完全に重なる')

rng = np.random.default_rng(20260614)      # 揺らし方を固定して毎回同じ図にする
jitter = rng.uniform(-0.02, 0.02, len(d))

fig, axes = plt.subplots(1, 2, figsize=(11, 4.5), sharey=True)
axes[0].scatter(d['tfr_round'], d['A1303'] / d['A1101'] * 100, alpha=0.8)
axes[0].set_title('ジッターなし(点が重なる)')
axes[1].scatter(d['tfr_round'] + jitter, d['A1303'] / d['A1101'] * 100, alpha=0.8)
axes[1].set_title('ジッターあり(±0.02)')
for ax in axes:
    ax.set_xlabel('合計特殊出生率(小数第 1 位に丸め)')
axes[0].set_ylabel('高齢化率(%)')
plt.tight_layout(); plt.savefig('scatter_jitter.png', dpi=120)

# 揺らしても値の意味が変わっていないことを確認する
print(f'ジッター幅 ±0.02 は刻み幅 0.1 の {0.02/0.1*100:.0f}% → 隣の目盛りとは混ざらない')
print(f'ジッター後の平均 {(d["tfr_round"]+jitter).mean():.4f} / 元の平均 {d["tfr_round"].mean():.4f}')

📤 実行するとこの出力が得られる:

丸めた出生率の異なる値: 7 通り(47 県中 40 県が他県と同じ値) 最も混み合う値: 1.3 → 13 県が完全に重なる ジッター幅 ±0.02 は刻み幅 0.1 の 20% → 隣の目盛りとは混ざらない ジッター後の平均 1.2931 / 元の平均 1.2915

💬 結果の読み方: 丸めた出生率は 7 通りの値しか取らず、 47 県中 40 県が他県と同じ値になる。 とくに 1.3 には 13 県が完全に重なるので、 ジッターなしの左図では「点が 1 個」にしか見えない。 ±0.02 のジッターを足した右図では 13 県が横に散らばり、 縦軸(高齢化率)方向のばらつきも 読み取れるようになる。 ずらし幅 0.02 は刻み幅 0.1 の 20% なので、 隣の目盛り(1.2 や 1.4)と混ざることはない。 平均も 1.2915 → 1.2931 とほぼ変わらず、 見た目を整えるだけで統計量を壊していないことが確認できる。 ジッターは「見せるため」の加工であり、 加工後のデータで計算してはいけない

③ 回帰線 — 傾向線を重ねるときは信頼帯もセットで

散布図に直線を 1 本引くだけで「関係がある」と見えてしまう。 引くのであれば 傾きの不確かさも同時に示すべきで、 それが 95% 信頼帯である。 帯はデータの中心で最も細く、 端に行くほど太くなる砂時計型になる。 これは「端のほうは根拠が薄い」ことを図が自分で告白している状態にほかならない。

📥 入力: SSDSE-B-2026 の 2023 年 47 都道府県。 x=総人口(万人)、 y=出生数(千人)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023]
x = d['A1101'].values / 1e4          # 総人口(万人)
y = d['A4101'].values / 1e3          # 出生数(千人)

res = stats.linregress(x, y)
print(f'回帰式  y = {res.slope:.4f} x + {res.intercept:.3f}')
print(f'相関係数 r = {res.rvalue:.4f}   決定係数 R² = {res.rvalue**2:.4f}')
print(f'傾きの標準誤差 = {res.stderr:.5f}   p 値 = {res.pvalue:.2e}')

# 回帰線と 95% 信頼帯
xs = np.linspace(x.min(), x.max(), 100)
ys = res.slope * xs + res.intercept
n = len(x)
se = np.sqrt(((y - (res.slope*x + res.intercept))**2).sum() / (n-2))
band = stats.t.ppf(0.975, n-2) * se * np.sqrt(1/n + (xs-x.mean())**2 / ((x-x.mean())**2).sum())

fig, ax = plt.subplots(figsize=(7, 5))
ax.scatter(x, y, alpha=0.7, edgecolor='white')
ax.plot(xs, ys, color='crimson', lw=2, label=f'回帰直線  R²={res.rvalue**2:.3f}')
ax.fill_between(xs, ys-band, ys+band, color='crimson', alpha=0.15, label='回帰直線の 95% 信頼帯')
ax.set_xlabel('総人口(万人)'); ax.set_ylabel('出生数(千人)')
ax.legend(); plt.tight_layout(); plt.savefig('scatter_regline.png', dpi=120)

print(f'人口 500 万人(x=500)の県の予測出生数 = {res.slope*500 + res.intercept:.2f} 千人')
print(f'信頼帯の幅(データ中心 x={x.mean():.1f} 付近)= ±{band[np.argmin(abs(xs-x.mean()))]:.3f} 千人')
print(f'信頼帯の幅(右端 x={x.max():.1f} 付近)    = ±{band[-1]:.3f} 千人')

📤 実行するとこの出力が得られる:

回帰式 y = 0.0610 x + -0.677 相関係数 r = 0.9954 決定係数 R² = 0.9909 傾きの標準誤差 = 0.00087 p 値 = 1.53e-47 人口 500 万人(x=500)の県の予測出生数 = 29.84 千人 信頼帯の幅(データ中心 x=264.6 付近)= ±0.487 千人 信頼帯の幅(右端 x=1408.6 付近) = ±2.071 千人

💬 結果の読み方: R² = 0.9909、 p = 1.5e-47 で、 直線は非常によく当てはまっている。 注目すべきは 信頼帯の幅がデータ中心(人口 264.6 万人付近)で ±0.487 千人、 右端(東京 1,408.6 万人)で ±2.071 千人と 4 倍以上に広がる点である。 東京は x 方向に大きく離れた 1 点しかないため、 その付近の直線の位置は ほぼ東京 1 県だけで決まっている。 R² が高いからといって図の右端まで 同じ信頼度で読んではいけない。 外挿(x の範囲外)に至っては帯すら描かれないが、 そこはデータが何も語っていない領域である。

④ 透明度 α — 何点が重なっているかを濃さで見せる

点が多いと後から描いた点が前の点を隠し、 実際の密度が図から消えるalpha を 1 未満にすると点が半透明になり、 重なった箇所ほど濃く見える。 目安は α ≈ 1/(1 箇所に重なる点数)で、 数百点なら 0.3、 数千点なら 0.05 程度から試す。

📥 入力: SSDSE-B-2026 の全年度 564 行(47 県 × 12 年)。 年度で絞らず、 わざと点を重ねる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
# 年度で絞らず 47 県 × 12 年 = 564 点を重ねて描く(点が重なる状況を作る)
x = df['A1101'].values / 1e4
y = df['A4101'].values / 1e3
print(f'描画する点の数: {len(x)} 点(47 県 × 12 年)')

fig, axes = plt.subplots(1, 3, figsize=(14, 4.2), sharex=True, sharey=True)
for ax, a in zip(axes, [1.0, 0.3, 0.08]):
    ax.scatter(x, y, alpha=a, s=28, color='#1E88E5', edgecolor='none')
    ax.set_title(f'透明度 α = {a}')
    ax.set_xlabel('総人口(万人)')
axes[0].set_ylabel('出生数(千人)')
plt.tight_layout(); plt.savefig('scatter_alpha.png', dpi=120)

# 「画面上で何点が重なるか」を数える。 データ値の一致ではなく、
# 描画領域を 60x60 の升目に切って「同じ升に落ちる点」を重なりとみなす。
import numpy as np
gx = np.floor((x - x.min()) / (x.max() - x.min()) * 59).astype(int)
gy = np.floor((y - y.min()) / (y.max() - y.min()) * 59).astype(int)
cells = list(zip(gx, gy))
uniq = len(set(cells))
from collections import Counter
c = Counter(cells)
print(f'60x60 の升目に落とすと異なる位置は {uniq} 箇所 → {len(x) - uniq} 点が他の点に隠れる')
print(f'重なり率 = {(len(x)-uniq)/len(x)*100:.1f}%')
print(f'最も混み合う升には {max(c.values())} 点が重なっている')
# alpha を重ねたときの見かけの濃さ
for a, k in [(1.0, 3), (0.3, 3), (0.08, 3)]:
    print(f'alpha={a}: 同じ位置に {k} 点重なると不透明度 {1-(1-a)**k:.3f}')

📤 実行するとこの出力が得られる:

描画する点の数: 564 点(47 県 × 12 年) 60x60 の升目に落とすと異なる位置は 133 箇所 → 431 点が他の点に隠れる 重なり率 = 76.4% 最も混み合う升には 41 点が重なっている alpha=1.0: 同じ位置に 3 点重なると不透明度 1.000 alpha=0.3: 同じ位置に 3 点重なると不透明度 0.657 alpha=0.08: 同じ位置に 3 点重なると不透明度 0.221

💬 結果の読み方: 描画領域を 60×60 の升目に切って数えると、 564 点のうち 431 点(76.4%)は 他の点と同じ升に落ちている。 最も混み合う升には 41 点が重なっており、 α=1.0 ではその 41 点が「1 点」にしか見えない。 α=0.3 なら 3 点重なった時点で 不透明度 0.657、 α=0.08 なら 0.221 と、 重なりの数が濃淡として読めるようになる。 ただし α を下げすぎると孤立点(東京など重要な外れ値)が薄くなって見落とされる。 点数が数万を超えたら α では足りないので、 hexbin や 2 次元 KDE に切り替える。

⑤ マージナルプロット — 各軸それ自体の分布を脇に添える

散布図は 2 変数の関係を示すが、 それぞれの変数が どんな形の分布なのかは教えてくれない。 上と右にヒストグラムを 並べた図(マージナルプロット、 seaborn の jointplot 相当)にすると、 「関係」と「分布」を 1 枚で同時に確認できる。

📥 入力: SSDSE-B-2026 の 2023 年 47 都道府県。 x=総人口(万人)、 y=出生数(千人)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023]
x = d['A1101'].values / 1e4      # 総人口(万人)
y = d['A4101'].values / 1e3      # 出生数(千人)

# 散布図の上と右に、 各軸の分布(周辺分布)を並べる
fig = plt.figure(figsize=(7.5, 6))
gs = fig.add_gridspec(2, 2, width_ratios=(4, 1), height_ratios=(1, 4),
                      wspace=0.05, hspace=0.05)
ax = fig.add_subplot(gs[1, 0])
ax_top = fig.add_subplot(gs[0, 0], sharex=ax)
ax_right = fig.add_subplot(gs[1, 1], sharey=ax)
ax.scatter(x, y, alpha=0.75, edgecolor='white')
ax_top.hist(x, bins=15, color='#90CAF9')
ax_right.hist(y, bins=15, orientation='horizontal', color='#90CAF9')
ax_top.tick_params(labelbottom=False); ax_right.tick_params(labelleft=False)
ax.set_xlabel('総人口(万人)'); ax.set_ylabel('出生数(千人)')
plt.savefig('scatter_marginal.png', dpi=120, bbox_inches='tight')

print('=== 散布図だけでは見えない、 各軸それ自体の形 ===')
for name, v in [('総人口', x), ('出生数', y)]:
    print(f'{name}: 平均 {v.mean():7.2f}  中央値 {pd.Series(v).median():7.2f}  '
          f'歪度 {stats.skew(v):5.2f}')
print()
print(f'総人口: 平均 > 中央値 の差は {x.mean() - pd.Series(x).median():.2f} 万人')
print('→ どちらの周辺分布も強い右裾を持つ(歪度 2 以上)。')
print('   散布図の点は左下に密集し、 右上に少数が伸びる形になる。')
print(f'相関係数 r = {stats.pearsonr(x, y)[0]:.4f}(散布図が示す 2 変数の関係)')

📤 実行するとこの出力が得られる:

=== 散布図だけでは見えない、 各軸それ自体の形 === 総人口: 平均 264.58 中央値 154.90 歪度 2.22 出生数: 平均 15.47 中央値 9.52 歪度 2.29 総人口: 平均 > 中央値 の差は 109.68 万人 → どちらの周辺分布も強い右裾を持つ(歪度 2 以上)。 散布図の点は左下に密集し、 右上に少数が伸びる形になる。 相関係数 r = 0.9954(散布図が示す 2 変数の関係)

💬 結果の読み方: 総人口は平均 264.58 万人に対し中央値 154.90 万人、 その差は 109.68 万人。 歪度は総人口 2.22・出生数 2.29 で、 どちらの周辺分布も強い右裾を持つ。 つまりこの散布図の点は左下に密集し、 右上に少数が長く伸びる形にならざるを得ない。 r = 0.9954 という高い相関も、 右上に離れた少数の県(東京・神奈川・大阪)が 引っ張って作っている可能性を疑う必要がある。 周辺分布を見ずに相関係数だけを 報告すると、 この構造を読者に隠すことになる。

⑥ 対数軸 — 桁が違うデータを同じ図に収める

人口のように最小と最大が 26 倍も違う量を線形軸で描くと、 小さい県が原点付近に固まって関係が読めない。 両軸を対数にすると、 「何倍か」が等間隔になり、 全県が均等に散らばる。 さらに両対数上の直線の傾きはべき乗則の指数そのものになり、 「人口が 2 倍になると何倍になるか」を直接読み取れる。

📥 入力: SSDSE-B-2026 の 2023 年 47 都道府県。 x=総人口、 y=一般診療所数 (I5102)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from scipy import stats

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023]
x = d['A1101'].values.astype(float)      # 総人口(人)
y = d['I5102'].values.astype(float)      # 一般診療所数

fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))
axes[0].scatter(x / 1e4, y, alpha=0.75, edgecolor='white')
axes[0].set_title('通常の軸'); axes[0].set_xlabel('総人口(万人)')
axes[1].scatter(x / 1e4, y, alpha=0.75, edgecolor='white')
axes[1].set_xscale('log'); axes[1].set_yscale('log')
axes[1].set_title('両対数の軸'); axes[1].set_xlabel('総人口(万人・対数)')
axes[0].set_ylabel('一般診療所数'); axes[1].set_ylabel('一般診療所数(対数)')
plt.tight_layout(); plt.savefig('scatter_logscale.png', dpi=120)

r_lin = stats.pearsonr(x, y)[0]
r_log = stats.pearsonr(np.log10(x), np.log10(y))[0]
print(f'元のスケールでの相関 r        = {r_lin:.4f}')
print(f'両対数にしたあとの相関 r      = {r_log:.4f}')
print(f'人口の歪度  元 {stats.skew(x):.2f} → log10 {stats.skew(np.log10(x)):.2f}')
print(f'診療所数の歪度 元 {stats.skew(y):.2f} → log10 {stats.skew(np.log10(y)):.2f}')

# 両対数の傾き = べき乗則の指数
sl = stats.linregress(np.log10(x), np.log10(y))
print()
print(f'両対数での回帰: log10(診療所数) = {sl.slope:.3f} * log10(人口) + {sl.intercept:.3f}')
print(f'→ 診療所数 ∝ 人口^{sl.slope:.3f}   (指数 1 なら人口に完全比例)')
print(f'人口が 2 倍になると診療所数は {2**sl.slope:.3f} 倍')

📤 実行するとこの出力が得られる:

元のスケールでの相関 r = 0.9717 両対数にしたあとの相関 r = 0.9840 人口の歪度 元 2.22 → log10 0.79 診療所数の歪度 元 3.03 → log10 0.96 両対数での回帰: log10(診療所数) = 0.966 * log10(人口) + -2.865 → 診療所数 ∝ 人口^0.966 (指数 1 なら人口に完全比例) 人口が 2 倍になると診療所数は 1.953 倍

💬 結果の読み方: 歪度が人口 2.22 → 0.79、 診療所数 3.03 → 0.96 と、 対数を取るだけで裾の重さが 大きく緩和される。 相関も 0.9717 → 0.9840 と上がるが、 これは関係が強くなったのではなく 直線で表せる形に近づいただけである。 そして両対数の傾き 0.966 が本題で、 これは「診療所数 ∝ 人口^0.966」、 つまり 人口が 2 倍になっても診療所数は 1.953 倍にしかならない ことを意味する。 指数が 1 をわずかに下回るので、 人口が多い県ほど 1 人あたりの診療所は少なめ。 線形軸の図からこの 0.966 という数字を読み取ることはできない。

⚠️ 落とし穴(拡張版・各 100 文字以上)

① 点が重なる(オーバープロット)
同じ位置に複数点が重なると視覚的に「1点」に見え、 実際の密度を誤認。 透明度を下げる(alpha=0.3)、 ジッタリング、 六角ビン(hexbin)、 2D KDE などで対処。 n が数万を超えると単純散布図は無意味になることが多い。 SSDSE の n=47 では問題ないが、 個票データではほぼ必須。
② 軸範囲・原点の操作で印象が変わる
軸を絞ると傾向が誇張、 広げると小さく見える。 0 を含むかどうかで全く違う印象。 必ずデータ範囲に対して適切なスケール、 必要なら軸を明示し、 ゼロ抑制(broken axis)は明記する。 散布図では原点を含めないことも多いが、 比率データなど 0 が意味を持つ場合は要注意。
③ 相関を「見た目」だけで判断
散布図で「強い相関がありそう」と思っても、 視覚は曲線関係や外れ値に騙される。 必ず数値の相関係数(Pearson / Spearman)と統計検定を併記。 Anscombe の四つ組のように、 同じ r=0.82 でも全く違う散布図が存在する。 視覚と数値を相互チェックする習慣を。
④ 因果関係と相関の混同
散布図で「X 増→ Y 増」が見えても因果の証拠ではない。 第3変数(交絡)・逆因果・選択バイアスの可能性。 因果を主張するには実験・自然実験・操作変数等の追加デザインが必要。 散布図はあくまで関連の可視化、 因果の入り口に過ぎない。
⑤ 外れ値の処理を恣意化
「分析しやすいから」と外れ値を削除すると、 結果が歪む。 まず散布図で確認し、 入力ミス/実存する極値かを判別。 実存なら(東京のような)原則含めて分析し、 sensitivity analysis で除外したケースも示す。 散布図上の外れ値こそ最も情報量が多いことが多い。
⑥ 多変量関係を 2D で押し付ける
3 変数以上の関係を 2D 散布図で見ようとすると、 色・サイズ・形で代替するが情報量が落ちる。 散布図行列(pairplot)・PCA 後の 2D・3D 散布図・パラレル座標プロットを併用。 高次元の関係を「散布図だけ」で議論しないこと。
⑦ サンプル数を散布図に明記しない
「点が少なく見える」のが本当に少ないのか、 重なっているだけなのか、 タイトル・キャプションに n を明記しないと読者が判断不能。 さらに「データ期間」「集計レベル」「観測単位」も必須情報。 良いプロットには良いキャプションが必要。

⚠️ よくある落とし穴 — 散布図

散布図の代表的な事故は 「Overplotting で数万点が黒い塊化」「人口・所得を線形軸で描き地方県が原点に潰れる」「東京都 1 点で残り 46 県の構造が見えなくなる」「相関係数だけで判断し U 字や非線形を見落とす」の 4 つ。 SSDSE-B の n=47 では Overplotting は問題になりませんが、 個票データ (n=数万〜数百万) では透明度・hexbin・2D KDE への切替えが必須です。

❌ Overplotting で点が重なる
n が大きいと黒い塊になる。 alpha・jitter・hexbin・2D KDE を使う。
❌ 軸が片対数か両対数か明示しない
人口・所得は対数軸が自然。 タイトル/ラベルに「log」を必ず入れる。
❌ 外れ値 1 点で関係が見えなくなる
東京都を除くと残り 46 県の構造が見える。 サブセット散布図も併用。
🛡 散布図の防御策まとめ:「n>1000 では alpha や hexbin で密度を可視化」「軸範囲はデータ範囲に合わせ、 ゼロ抑制 (broken axis) は必ず明記」「外れ値は除外せず色分けで残す」「非線形関係は LOWESS/回帰直線で補強」の 4 点が「描画前にチェック」する標準ルーチンです。

🗺️ 概念マップ — 3つの視点で体系を理解する

散布図 がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。

📍 体系階層のパス

🌐 統計・データサイエンス記述統計可視化散布図

① 🔗 関係マップ — 「他の手法とどう繋がっているか」

中心に 散布図 を置き、 そこから ヒストグラム・箱ひげ図・回帰直線・ヒートマップ・相関係数・単回帰 など 計 13 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語あとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。

凡例:現在の用語上位カテゴリ兄弟(並列)前提発展形応用先2階層先

② ⭕ 包含マップ — 「どのカテゴリに含まれているか」

大きな円が小さな円を包含する Circle Packing 図。 「散布図」は緑色でハイライト

📍現在地:統計・データサイエンス

③ 🌳 ツリーマップ — 「面積で見るボリューム比較」

長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「散布図」は緑色でハイライト

🎯 3つのマップの使い分け

マップ 分かること こんな時に見る
🔗 関係マップ手法間の横の関係(前提→発展→応用)「次に何を学べばよい?」 学習順序の判断
⭕ 包含マップ分類体系の入れ子構造(上位⊃下位)「この手法はどんなジャンルに属する?」
🌳 ツリーマップ分野の規模比較(面積=ボリューム)「データサイエンス全体の俯瞰像」

💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは 散布図隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス可視化 → 散布図 という入れ子の位置を示します。 「可視化には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。

🔗 隣接手法への橋渡し

「散布図」は 2 変数の関係を最初に視認する一手段 であり、 1 変量可視化で各軸の分布を理解した直後に位置し、 続く相関係数や回帰分析の判断材料となる。 上流の前処理から下流の因果推論まで一連の探索フローのハブを担う。

⬆️ 上流: 変数の集計・標準化

⬌ 並列: 同じ目的を持つ代替

⬇️ 下流: 得た知見の検証・拡張

散布図を「最初に必ず描く」習慣を持つことで、 相関係数や回帰係数を盲信せず、 外れ値・非線形・群構造を視覚的に検知してから次の手法を選べる。

🌳 手法選択フロー

散布図を描くか別の可視化に切り替えるかは、 データの規模・変数数・分析目的で決まる。

典型シナリオ別の手法選択

シナリオ重視する観点候補手法
2 変数の関係を直感的に把握したい線形 / 非線形・外れ値の同時確認散布図 + 相関係数
n が 10,000 超で点が重なる密度の可視化・透明度・サンプリング2 次元ヒートマップ / hexbin / α=0.1 の散布図
3 変数以上を一度に見たいペアごとの関係を網羅seaborn.pairplot で散布図行列
群 (カテゴリ) 別に分布を比較したいグループ間差の検出散布図 + 色分け、 または 箱ひげ図
時間変化を見たい時系列軌跡の把握線グラフ、 または時間色グラデーション付き散布図
因果を主張したい無作為割付・操作変数散布図は「関係の発見」までで、 因果推論

選んだ後の検証ステップ

  1. 軸の単位: SSDSE-B-2026 では人口は人、 消費支出は円。 単位ラベルを必ず表示
  2. 外れ値の扱い: 東京・大阪を含む / 除外、 両方の散布図を比較
  3. 軸スケール: 線形 / 対数 / 平方根、 関係が直線に近づくスケールを選ぶ
  4. 過剰描画 (overplotting): 点が重なる場合は α / hexbin / 2D density に切替
  5. 因果の主張禁止: 散布図で「相関」は読めても「因果」は読めない

🎮 触って理解する

下の白い盤面が散布図です。 空白をクリック(スマホはタップ)で点を追加点をドラッグで移動できます。 点を動かすたびに、 ピアソンの相関係数 r と回帰直線がリアルタイムに更新されます。 「点をどう並べると r が大きくなる/小さくなるのか」を、 実際に手を動かして体感してください。

x(横軸の変数) y(縦軸の変数)

スライダーで「向き(正/負)」と「強さ(0〜±1)」を指定して点群を生成し、 見た目と r の対応を確かめましょう。

点の数 n
0
相関係数 r
決定係数 r²
点を2つ以上置くと、 相関係数と回帰直線が表示されます。

👁️ 直感 — r は「点が直線にどれだけ沿うか」

相関係数 r は −1 から +1 の値をとり、 符号が関係の向き(右上がりなら +、 右下がりなら −)、 絶対値が直線への沿い具合の強さを表します。 点をきれいな一直線に並べると |r| は 1 に近づき、 円状にばらまくと 0 に近づきます。 上の盤面で、 まず点を右上がりの直線状に置いてから、 1点ずつ縦方向にずらしてみると、 r が徐々に下がる様子が見えます。

⚠️ よくある落とし穴

🚀 発展

👁️ 直感の深化 — 散布図が「一目で」伝える5つの情報

散布図の強みは、 表や相関係数という1つの数値では潰れてしまう情報を、 1枚の絵に同時に載せられる点にあります。 点群を眺めるとき、 次の5つを順に読み取る習慣をつけると診断力が上がります。

この直感を数値で裏づけるのが基本手順です。 まず散布図で「形」を見て、 それから 相関共分散回帰直線 を重ねる。 逆順(数値だけ先に見る)だと、 曲線関係や外れ値を見落とします。 探索の全体像は 探索的データ分析(EDA)2変量可視化 のページも参照してください。

💡 上の「🎮 触って理解する」盤面で、 まず点を直線状に並べて $r\approx0.9$ を作り、 1点だけ縦に大きくずらすと $r$ が急落します。 「向き・強さ・外れ値」の三者が連動して動くのを手で確かめると、 数値の意味が身体に入ります。

⚠️ 落とし穴の深掘り — 第3変数・生態学的誤謬・アンスコム

散布図の誤読は「見えたパターンをそのまま信じる」ことから生まれます。 ここでは特に事故が多い3つを、 SSDSE-B-2026 の実測値と架空の教科書例で掘り下げます。

① 第3変数の見落とし(実測デモ:大学数 × 一般病院数)

SSDSE-B-2026(2023年・47都道府県)で「大学数(E6102)」と「一般病院数(I510120)」の散布図を描くと、 強い正の相関 $r = +0.849$ が出ます。 「大学が多い県は病院も多い」——一見もっともらしい発見に見えます。 しかしこれは、 両者がともに総人口(第3変数)と強く相関しているために生じた見かけの相関です(大学数×総人口 $r=+0.901$、 病院数×総人口 $r=+0.900$)。

人口の効果を取り除くため、 それぞれを人口100万人あたりに直して散布図を描き直すと、 相関は $r = -0.06$(ほぼ無相関)へと消えます。 「県の大きさ」を揃えた瞬間に関係が蒸発するのが、 第3変数(交絡)の典型的な振る舞いです。

都道府県大学数一般病院数大学数/百万人病院数/百万人
東京都14458810.241.7
大阪府584636.652.8
高知県51077.5160.7
鳥取県3395.672.6
島根県2373.156.9

絶対数で見ると東京が右上、 鳥取・島根が左下に並んで直線に見えます。 だが人口あたりに直すと、 むしろ人口の少ない高知が病院密度で突出するなど順序が入れ替わり、 直線関係は消えます。 交絡の詳細は 交絡疑似相関、 因果の扱いは 因果 を参照。

② 相関を因果と誤解する(実測デモ:気温 × 合計特殊出生率)

同じデータで「年平均気温(B4101)」と「合計特殊出生率(A4103)」を散布図にすると $r = +0.50$ の中程度の正相関が出ます(沖縄県:23.8℃・1.60、 北海道:11.0℃・1.06)。 しかし「気温が高いと子どもが増える」わけではありません。 気候・産業構造・年齢構成・地域文化など多数の要因が背後にあり、 気温はその代理指標にすぎない可能性が高い。 散布図で見えるのは関連までで、 因果を主張するには実験・自然実験・操作変数などの追加デザインが要ります(因果)。

③ 集約による生態学的誤謬(ecological fallacy)

SSDSE-B-2026 は都道府県単位に集約されたデータです。 県平均どうしの散布図で見えた関係は、 個人レベルでも同じとは限りません。 集団の相関を個人にあてはめる誤りを生態学的誤謬と呼びます。 逆に、 全体では見えた関係が群別に分けると消える/逆転する現象(シンプソンのパラドックス)もあります。 集約の性質は 集約 のページで、 群構造は下記「色分け」で確認しましょう。

④ アンスコムの数値例(同じ統計量でも形が違う)

下記は Anscombe が 1973 年に示した架空の4組(教科書上の合成データ)です。 4組とも平均・分散・$r$・回帰直線がほぼ一致するのに、 散布図の形はまったく異なります。 「$r$ が同じ=関係が同じ」ではないことの決定的な反例で、 計算の前に必ず描く理由そのものです。 上の「🎮 触って理解する」盤面の「アンスコム②」プリセット(架空の放物線データ)でも、 きれいなU字なのに $r\approx0.8$ のままになる様子を体験できます。

⑤ そのほかの定番

🚀 発展 — 過剰プロット対策と多変数の表現

散布図は拡張の宝庫です。 目的別に道具を選べるよう、 「点が多すぎる問題」と「変数が多すぎる問題」に分けて整理します。

点が多すぎる(過剰プロット)への対処

手法やること向いている状況
透明度 αalpha=0.1〜0.3 で重なりを濃淡に中規模(数千点)。 透明度参照
ジッター離散値に微小な乱れを加えて重なりをほどく整数・カテゴリ格子状データ
ヘックスビンplt.hexbin で六角格子の件数を色に大規模(数万点超)の密度可視化
2D KDE / 等高線sns.kdeplot(x,y) で密度の等高線滑らかな密度の山を見たいとき
2Dヒストグラムplt.hist2dヒートマップ格子ごとの件数を厳密に見たいとき

変数が多すぎる(3変数以上)への対処

関係の形・スケールを補強する

SSDSE-B-2026 の $n=47$ では過剰プロットは起きませんが、 個票($n$=数万〜)に移った瞬間に単純散布図は黒い塊になります。 「規模が変わったら道具も変える」を原則に。 前提知識は 1変量可視化ヒストグラム、 並列手法は 箱ひげ図、 発展は 回帰分析重回帰 を参照。