論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
データストーリーテリング
Data Storytelling
リテラシー

🔖 キーワード索引

この用語と一緒に検索・参照されやすいタグ。 関連ページに飛ぶときの手がかりにも使えます。

#リテラシー#ストーリーテリング#可視化#コミュニケーション#レポート

このページの節への目次です。

30 秒の結論SCR(状況→課題→解決)効果量 d を手計算3 つの物語・期間・人数と率注釈と強調の体験見出しと指標のずれ参考文献・理解度チェック手法選択フロー落とし穴の深掘りナラティブ構造・倫理

💡 30秒で分かる結論

🍰 まずはやさしく

データの物語を伝える技法です。

相手に納得して動いてもらうために使います。

部活の改善案をグラフで伝えるときなどに役立ちます。

この章では結論と注意点を学びます。

データストーリーテリングは、 数値・グラフ・物語を組み合わせて聞き手の意思決定を促す表現技法。

ここまでが要点です。 ただし実際に使う前に、 このページの「⚠️ よくある落とし穴」で挙げた ストーリーが事実を曲げる/ジャンクチャート/数字の羅列 には必ず目を通してください。 つまずくのは知識が無いときより、 知ってはいたが確認を飛ばしたときです。

📍 文脈:「データストーリーテリング」はどんな場面で出てくる?

🍰 まずはやさしく

分析結果を伝えるための道具です。

正しい分析を相手に届けるために使います。

スマホの利用時間をまとめて発表する場面で使えます。

この章では使う場面や考え方を学びます。

「分析結果が良い」と「伝わる」は別物。 本サイトの再現論文も、 分析(コード)の後段で解釈とレポートを必ず作ります。 そこで活きるのがストーリーテリング。

🎨 直感で掴む

🍰 まずはやさしく

映画のような構成で伝える方法です。

相手に直感的に理解してもらうために使います。

買い物の傾向をグラフで見出し付きで伝える例です。

この章ではイメージの掴み方を学びます。

📐 定義・数式

🍰 まずはやさしく

伝えるための決まった型のことです。

誰にでも伝わる構成を作るために使います。

文化祭の計画を状況と課題に分けて話す例です。

この章では具体的な構成のルールを学びます。

【ストーリーテリングの黄金フォーマット】
$$ \text{Situation} \to \text{Complication} \to \text{Resolution} $$
McKinsey の SCR や Pyramid Principle で語られる古典構成。 多くのプレゼンが暗黙にこの型に従う。

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

Situation
現状の事実共有
Complication
問題・葛藤・発見
Resolution
解決策・推奨アクション
KPI
聞き手にとっての関心指標
Call to Action
「次に何をすべきか」

📐 の $\text{Situation} \to \text{Complication} \to \text{Resolution}$ の矢印は、「聞き手がすでに知っている事実から始め、予想とのずれを示し、次の行動で閉じる」という順序を表しています。数式というより、文の並べ方の規則です。3 つの項を言葉に直すと、S は「誰も反論しない事実」(出生数は 11 年で 29.9% 減った)、C は「S のままでは済まない理由」(2019 年度以降は減り方が年率 −2.6% から −4.3% に速まった)、R は「だから何をするか」(速まった原因が産む人の数か産み方かを確かめる)です。C が無いと S は単なる報告で終わり、R が無いと聞き手は行動できません。逆に S を飛ばして C から始めると、聞き手は前提を共有していないので、ずれの大きさを評価できません。この章の 🐍 の「見出しの数字を選ぶ」節で、SSDSE-B-2026 の数字を使って 3 つの項を実際に埋めています。

🧮 実値で計算してみる — 数式に値を入れて手で計算する

データストーリーテリングに数式はないが、 「ストーリーの骨格となる集計指標」を手計算で確認することが理解の核心。 ここでは SCR フォーマット(Situation→Complication→Resolution)に対応する 3 つの指標を SSDSE-B-2026 の 5 県データで手計算する。

使用する数式(ストーリーの強さを定量化する「効果量 Cohen's d」):

【ストーリーの「驚き」を定量化する効果量】
$$ d = \frac{\bar{x}_A - \bar{x}_B}{s_{\text{pooled}}} $$
$\bar{x}_A$: 大都市圏グループの平均、$\bar{x}_B$: 過疎県グループの平均、$s_{\text{pooled}}$: プール標準偏差。 $|d| > 0.8$ で「大きな差」(Cohen 1988)。

📥 使用データ(SSDSE-B-2026 2023年、5県抜粋)

都道府県グループ高齢化率 (%)
東京都大都市圏22.8
神奈川県大都市圏25.9
鳥取県過疎県33.3
島根県過疎県34.9
秋田県過疎県39.1

Step 1: 各グループの平均を計算

大都市圏グループ(n=2): $\bar{x}_A = \frac{22.8 + 25.9}{2} = \frac{48.7}{2} = 24.35$

過疎県グループ(n=3): $\bar{x}_B = \frac{33.3 + 34.9 + 39.1}{3} = \frac{107.3}{3} = 35.77$

Step 2: 各グループの分散・標準偏差

大都市圏 $s_A$: 偏差 = $(22.8-24.35)^2 + (25.9-24.35)^2 = 2.4025 + 2.4025 = 4.805$。 $s_A = \sqrt{4.805/1} = 2.192$

過疎県 $s_B$: 偏差 = $(33.3-35.77)^2 + (34.9-35.77)^2 + (39.1-35.77)^2 = 6.084 + 0.751 + 11.111 = 17.947$。 $s_B = \sqrt{17.947/2} = 2.996$

Step 3: プール標準偏差と効果量 d

$$s_{\text{pooled}} = \sqrt{\frac{(n_A-1)s_A^2 + (n_B-1)s_B^2}{n_A+n_B-2}} = \sqrt{\frac{1 \times 4.805 + 2 \times 8.973}{3}} = \sqrt{\frac{22.752}{3}} = \sqrt{7.584} \approx 2.754$$

$$d = \frac{24.35 - 35.77}{2.754} = \frac{-11.42}{2.754} \approx -4.15$$

手計算結果: $d \approx -4.15$(大都市圏の高齢化率が低い方向に 4 標準偏差以上の差)。 $|d| > 0.8$ の閾値を大幅に超え、 「東京 vs 秋田」のストーリーに 統計的根拠 を付与できる。

手計算で得た値と、 下記の Python 実装で算出した値が一致することを確認する。

🎯 このコードでやること:上記 Step 1〜3 の手計算(Cohen's d = −4.15)を numpy で再現し、 一致を確認する。

📥 入力: 大都市圏 [22.8, 25.9]、 過疎県 [33.3, 34.9, 39.1](SSDSE-B-2026 2023年 高齢化率、 65 歳以上人口 ÷ 総人口 × 100 を小数 1 桁に丸めた値)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
# SSDSE-B-2026 2023年 高齢化率 (5 県、65 歳以上人口 ÷ 総人口 × 100 を小数 1 桁に丸めた値)
metro = np.array([22.8, 25.9])        # 東京都, 神奈川県
rural = np.array([33.3, 34.9, 39.1])  # 鳥取県, 島根県, 秋田県

mean_a, mean_b = metro.mean(), rural.mean()
var_a, var_b = metro.var(ddof=1), rural.var(ddof=1)
na, nb = len(metro), len(rural)
pooled_std = np.sqrt(((na-1)*var_a + (nb-1)*var_b) / (na+nb-2))
d = (mean_a - mean_b) / pooled_std

print(f'大都市圏平均: {mean_a:.2f} %')
print(f'過疎県平均  : {mean_b:.2f} %')
print(f'プール SD  : {pooled_std:.3f}')
print(f"Cohen's d  : {d:.2f}")
📤 出力 大都市圏平均: 24.35 % 過疎県平均 : 35.77 % プール SD : 2.754 Cohen's d : -4.15

💬 Python 出力の平均 24.35 % と 35.77 %、プール SD 2.754、d = −4.15 が手計算の Step 1〜3 と一致。 差 11.42 ポイントがグループ内のばらつき約 2.75 ポイントの 4 倍以上あり、 「大都市圏と過疎県の高齢化率は標準偏差 4 個分以上離れている」というストーリーの数値根拠になる。 ただし 2 県対 3 県を選んで比べた値なので、 両端の県を選べば d はいくらでも大きくなることも添えておく。

実際に、47 都道府県をすべて使い、2023 年度の総人口の上位 10 県と残り 37 県に分けて同じ式で計算すると、平均は 27.99% と 32.56%、プール標準偏差は 2.78 で、d = −1.64 になります。−4.15 の 4 割ほどの大きさです。5 県を選んだ d も 47 県で分けた d も計算としては正しいので、物語に効果量を添えるときは「どの県をどう分けたか」を必ず書き、選び方で値が変わることを聞き手に隠さないようにします。

同じ事実から 3 つの物語 — 出生数の 12 年

データストーリーテリングの核心は「どの切り口を選ぶか」です。 SSDSE-B-2026 の出生数(2012 → 2023)を例に、 すべて正しいのに印象がまったく違う 3 つの語り方を作ってみます。

語り口見せる数字読み手が受け取る印象
A. 全国で語る1,037,165 → 727,269 件、−29.9%「日本全体が一様に 3 割減った」
B. 最も減った県で語る秋田県 −44.8%、岩手県 −41.4%「地方は半減寸前、危機的だ」
C. 最も減らなかった県で語る東京都 −19.6%、大阪府 −24.3%「都市部は持ちこたえている」

3 つとも数字は正確で、SSDSE-B-2026 から直接計算できます。 にもかかわらず、A だけを見せられた読み手は地域差の存在に気づけません。 実際には 47 都道府県の変化率は −44.8%(秋田)から −19.6%(東京)まで 25 ポイントの幅があり、 中央値は −33.3% で、全国値 −29.9% より悪いのです。 全国値が中央値より高く出るのは、東京・大阪など出生数の多い都市部の減り方が小さいからで、 人口の重みが平均を押し上げています。

💬 誠実な語り方は、A・B・C のどれか 1 つを選ぶことではなく、 「全国で −29.9%。ただし県別には −44.8% 〜 −19.6% と 25 ポイントの幅があり、中央値は −33.3%」 と代表値と散らばりを同時に出すことです。 ストーリーを作る技術は「印象的な 1 つの数字を選ぶ技術」だと誤解されがちですが、 実際には「1 つの数字が隠しているものを、もう 1 行で補う技術」だと考えてください。

もう一点。B と C はどちらも極端値を代表として語る形になっています。 47 個のうち最大・最小を選ぶ行為は、それだけで 選択バイアスそのものです。 「秋田県では」と書くときは、必ず「47 県中最も減少幅が大きい県」と添えてください。 それを書くだけで、読み手は自分で割り引いて受け取れるようになります。

図で見る — 1 つの数字が隠している 47 個の値

上の表の A・B・C を 1 枚にまとめると、全国値・中央値・両端の県の位置関係が一目で分かります。

ドットプロット: 47 都道府県の出生数の変化率(2012→2023 年度)。全国値 −29.9% と中央値 −33.3% の線
図1: 出生数の 2012→2023 年度の変化率(SSDSE-B-2026、47 都道府県、減り方の大きい順)。実線は全国の合計で計算した −29.9%、破線は 47 県の中央値 −33.3%。最も減ったのは秋田県 −44.8%、最も減らなかったのは東京都 −19.6%。全国値より減り方が大きい県は 47 のうち 36。

全国値の実線より左(減り方が大きい側)に 47 のうち 36 の県があります。「全国で −29.9%」という見出しは、多くの県にとっては実態より楽観的な数字です。誠実なストーリーは、この図のように代表値と散らばりを同時に見せるところから始まります。図を載せられないスライドや見出しでは、「全国 −29.9%(県別の中央値 −33.3%、範囲 −44.8〜−19.6%)」のように括弧で 1 行を補うだけでも、散らばりの存在は伝わります。

期間を変えると物語の主役が変わる

語り口 C は「東京都が最も減らなかった」でした。これは 2012 年度を起点に選んだ場合の話です。起点を 2019 年度に変えると、同じ SSDSE-B-2026 から別の主役が現れます。本文に書く数字を手で写すと起点を変えたときに書き直し漏れが起きるので、文章そのものをコードで作ります。

🎯 このコードでやること:出生数の変化率を「起点の年度・終点の年度」を引数にして計算し、全国値・中央値・東京都の順位・最も減らなかった県と最も減った県を 1 つの文章にして出力する。起点を 2012 年度と 2019 年度にした 2 通りを並べる。

📥 入力例 SSDSE-B-2026.csv(564 行 = 47 都道府県 × 2012〜2023 年度) SSDSE-B-2026 Prefecture A4101(出生数) 2023 北海道 24,430 2023 東京都 86,348 2012 東京都 107,401 2012 秋田県 6,543 …(全 564 行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
b = df.pivot(index='Prefecture', columns='SSDSE-B-2026', values='A4101')   # 行 = 県、列 = 年度の出生数

def story(start, end, pref='東京都'):
    chg = (b[end] / b[start] - 1) * 100
    rank = int(chg.rank(ascending=False)[pref])                           # 減り方が小さい順の順位
    nat = (b[end].sum() / b[start].sum() - 1) * 100
    return (f'{start}→{end} 年度: 全国 {nat:+.1f}%、47 県の中央値 {chg.median():+.1f}%。'
            f'{pref}は {chg[pref]:+.1f}% で、減り方の小さい順に 47 県中 {rank} 位。'
            f'最も小さいのは{chg.idxmax()}({chg.max():+.1f}%)、最も大きいのは{chg.idxmin()}({chg.min():+.1f}%)。')

print(story(2012, 2023))
print(story(2019, 2023))
📤 実行例(実測) 2012→2023 年度: 全国 -29.9%、47 県の中央値 -33.3%。東京都は -19.6% で、減り方の小さい順に 47 県中 1 位。最も小さいのは東京都(-19.6%)、最も大きいのは秋田県(-44.8%)。 2019→2023 年度: 全国 -15.9%、47 県の中央値 -17.5%。東京都は -15.2% で、減り方の小さい順に 47 県中 11 位。最も小さいのは大阪府(-11.6%)、最も大きいのは秋田県(-23.1%)。

💬 2012→2023 年度では東京都(−19.6%)が 47 県中 1 位で「都市部は持ちこたえている」物語の主役ですが、2019→2023 年度では東京都は −15.2% で 11 位に下がり、最も減らなかったのは大阪府(−11.6%)になります。一方、最も減ったのはどちらの期間でも秋田県(−44.8%、−23.1%)で、こちらは期間を選んでも変わらない、頑健な事実です。「どの期間でも成り立つこと」と「特定の起点でだけ成り立つこと」を区別し、後者を語るときは起点を必ず書きます。

このように文章をコードで生成しておけば、データが更新されたときも起点を変えたときも、本文の数字が自動で正しい値に置き換わります。図と本文で数字が食い違う事故は、ストーリーテリングで最も信頼を失うミスの 1 つです。

人数で語るか、率で語るか — 同じ東京都が「最も減らなかった県」にも「2 番目に多く減った県」にもなる

起点の年度と並んで物語を左右するのが、増減を人数で見るか率で見るかの選択です。出生数の 2012→2023 年度の変化を、両方の物差しで並べます。

🎯 このコードでやること:47 県の出生数の 2012→2023 年度の増減を、人数(増減数)と率(増減率)の 2 通りで計算し、それぞれの順位をつける。人数・率で最も減った 3 県と、2 つの順位の順位相関を出す。

📥 入力例 SSDSE-B-2026.csv(564 行 = 47 都道府県 × 2012〜2023 年度) SSDSE-B-2026 Prefecture A4101(出生数) 2023 北海道 24,430 2023 東京都 86,348 2012 東京都 107,401 2012 秋田県 6,543 …(全 564 行)
1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
b = df.pivot(index='Prefecture', columns='SSDSE-B-2026', values='A4101')
t = pd.DataFrame({'2012': b[2012], '2023': b[2023]})
t['増減数'] = t['2023'] - t['2012']
t['増減率(%)'] = (t['増減数'] / t['2012'] * 100).round(1)
t['人数で多く減った順'] = t['増減数'].rank().astype(int)            # 1 = 最も多くの人数が減った
t['率で大きく減った順'] = t['増減率(%)'].rank().astype(int)          # 1 = 最も大きな率で減った
print(t.loc[['東京都', '大阪府', '神奈川県', '秋田県', '鳥取県']].to_string())
print('人数で最も減った 3 県:', '、'.join(f'{p}({v:,})' for p, v in t['増減数'].nsmallest(3).items()))
print('率で最も減った 3 県:', '、'.join(f'{p}({v}%)' for p, v in t['増減率(%)'].nsmallest(3).items()))
print('2 つの順位の順位相関 ρ =', round(t['人数で多く減った順'].corr(t['率で大きく減った順'], method='spearman'), 3))
📤 実行例(実測) 2012 2023 増減数 増減率(%) 人数で多く減った順 率で大きく減った順 Prefecture 東京都 107401 86348 -21053 -19.6 2 47 大阪府 73012 55292 -17720 -24.3 4 46 神奈川県 75477 53991 -21486 -28.5 1 41 秋田県 6543 3611 -2932 -44.8 36 1 鳥取県 4771 3263 -1508 -31.6 47 30 人数で最も減った 3 県: 神奈川県(-21,486)、東京都(-21,053)、愛知県(-19,511) 率で最も減った 3 県: 秋田県(-44.8%)、岩手県(-41.4%)、静岡県(-38.4%) 2 つの順位の順位相関 ρ = -0.231

💬 東京都は率では −19.6% で「最も減らなかった県」(率で大きく減った順に 47 位)ですが、人数では −21,053 人で「2 番目に多く減った県」です。人数で最も減ったのは神奈川県(−21,486 人)、率で最も減ったのは秋田県(−44.8%)で、2 つの順位の順位相関は ρ = −0.231 とむしろ逆向きです。秋田県の −2,932 人は人数では 47 県中 36 番目の減り方にすぎず、鳥取県(−1,508 人、−31.6%)は人数では最も少ない減少です。

人数は「全国の出生数の減少にどの県がどれだけ寄与したか」、率は「その県の中でどれだけ深刻か」に答える物差しです。「東京都でも出生数は大きく減っている」も「東京都は相対的に持ちこたえている」も、物差しを書けばどちらも正しい文になります。物差しを書かずに片方だけを見出しにするのが、ストーリーテリングでの典型的な印象操作です。

「秋田県 −44.8%」を分解する — 物語に「なぜ」を足す

語り口 B(最も減った県)を使うなら、読み手が次に知りたいのは「なぜそんなに減ったのか」です。出生数は「15〜64 歳人口 × 15〜64 歳人口千人あたりの出生数」に分けられるので、減少のどれだけが人口の縮小から来ているかを計算できます(15〜64 歳人口は出産年代の人口そのものではなく、その目安です)。

🎯 このコードでやること:出生数の 2012→2023 年度の変化を、15〜64 歳人口の変化と、15〜64 歳人口千人あたり出生数の変化に分解する(対数で足し算になる)。秋田県・東京都・沖縄県と全国について、減少のうち人口要因が占める割合を出す。

📥 入力例 SSDSE-B-2026.csv(564 行 = 47 都道府県 × 2012〜2023 年度) SSDSE-B-2026 Prefecture A4101(出生数) A1302(15~64歳人口) 2023 秋田県 3,611 474,000 2012 秋田県 6,543 619,000 2023 東京都 86,348 9,368,000 2012 東京都 107,401 8,924,000 …(全 564 行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
p = lambda c: df.pivot(index='Prefecture', columns='SSDSE-B-2026', values=c)
births, work = p('A4101'), p('A1302')                   # 出生数、15〜64 歳人口
rate = births / work * 1000                             # 15〜64 歳人口千人あたりの出生数

# 出生数の変化(対数)= 15〜64 歳人口の変化(対数)+ 千人あたり出生数の変化(対数)
t = pd.DataFrame({
    '出生数(%)': (births[2023] / births[2012] - 1) * 100,
    '15〜64歳人口(%)': (work[2023] / work[2012] - 1) * 100,
    '千人あたり出生数(%)': (rate[2023] / rate[2012] - 1) * 100,
})
t['人口要因の割合'] = np.log(work[2023] / work[2012]) / np.log(births[2023] / births[2012])
print(t.loc[['秋田県', '東京都', '沖縄県']].round(2).to_string())
tot = births.sum(), work.sum()
nat_b = tot[0][2023] / tot[0][2012] - 1
nat_w = tot[1][2023] / tot[1][2012] - 1
print(f'全国: 出生数 {nat_b:+.1%}  15〜64 歳人口 {nat_w:+.1%}  人口要因の割合 {np.log(1 + nat_w) / np.log(1 + nat_b):.2f}')
print('47 県で 千人あたり出生数 が減った県:', int((t['千人あたり出生数(%)'] < 0).sum()))
📤 実行例(実測) 出生数(%) 15〜64歳人口(%) 千人あたり出生数(%) 人口要因の割合 Prefecture 秋田県 -44.81 -23.42 -27.93 0.45 東京都 -19.60 4.98 -23.41 -0.22 沖縄県 -26.50 -3.18 -24.09 0.11 全国: 出生数 -29.9% 15〜64 歳人口 -7.8% 人口要因の割合 0.23 47 県で 千人あたり出生数 が減った県: 47

💬 秋田県の出生数 −44.81% は、15〜64 歳人口の減少(−23.42%)と、千人あたり出生数の減少(−27.93%)の両方から来ており、対数で見た減少の 45% が人口要因です。一方、千人あたり出生数の減り方だけを比べると、秋田県 −27.93%、東京都 −23.41%、沖縄県 −24.09% と大きな差はなく、47 県すべてで減っています。東京都は 15〜64 歳人口が +4.98% 増えたので、人口要因はむしろ出生数を支えました(割合 −0.22)。全国では減少の 23% が人口要因です。

ここから組み立てられる物語は「秋田県は特別に子どもを産まなくなった県」ではなく、「どの県でも人口あたりの出生は 2〜3 割減っており、秋田県ではそれに働き手世代の人口の 23% 減が重なった」です。極端な値を語るときは、それを部品に分けて、どの部品が極端なのかまで示すと、読み手は数字を正しい大きさで受け取れます。

🐍 Python 実装

公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで動作させます。 ファイルパス(data/raw/SSDSE-B-2026.csv)は自分の環境に合わせて変更してください。 まずはこのまま動かすことが理解の最短ルートです。

🎯 目的:SSDSE-B-2026 の 2023 年度 47 都道府県データから「高齢化率と死亡率の相関」を散布図化し、 タイトル自体に結論(r ≈ +0.97)を埋め込む「結論を見出しに」型グラフを生成する。
📥 入力:data/raw/SSDSE-B-2026.csv (ヘッダ 2 行・47 都道府県、 2023 年度)。 X 軸=高齢化率(65 歳以上人口割合)、 Y 軸=死亡率(人口千人あたり死亡数)を散布図に。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 「結論を見出しに」型グラフ:タイトルに結論を書く
import os
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['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
df['年度'] = pd.to_numeric(df['年度'], errors='coerce')
df = df[df['年度'] == df['年度'].max()].copy()
for _c in ['総人口', '65歳以上人口', '死亡数']:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 高齢化率・死亡率は SSDSE にそのままの列が無いので、実在する列から計算する
df['高齢化率'] = df['65歳以上人口'] / df['総人口'] * 100
df['死亡率'] = df['死亡数'] / df['総人口'] * 1000

r = df['高齢化率'].corr(df['死亡率'])
df.plot.scatter(x='高齢化率', y='死亡率', figsize=(7, 4))
plt.title(f'高齢化が進む県ほど死亡率も高い(r={r:+.2f})', fontsize=14)
os.makedirs('figures', exist_ok=True)
plt.savefig('figures/story.png', dpi=120)
print(f'高齢化率と死亡率の相関係数 r = {r:.3f}')
📤 出力 高齢化率と死亡率の相関係数 r = 0.972 figures/story.png (7×4 インチ・120dpi) タイトル: 「高齢化が進む県ほど死亡率も高い(r=+0.97)」 散布図 47 点、 右肩上がりの強い線形傾向
💬 解釈:r = 0.972 がそのままタイトルの「r=+0.97」になり、 図を一目見ただけで主張が伝わる。 ただしこの強い相関の大半は、 高齢者が多い県ほど亡くなる人が多いという年齢構成の違いそのもの(死亡率は東京都の千人あたり 9.7 から秋田県の 19.2 まで)で、 県の健康状態の差を示しているわけではない。 タイトルで「健康」を語りたいなら、 年齢調整死亡率に置き換えてから結論を書く。

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

上のコードが描く図 — そして「当たり前」を取り除いた後の物語

散布図: 2023 年度の 47 都道府県の高齢化率と死亡率。タイトルに結論 r = +0.97 を書いたもの
図2: 「結論を見出しに」型の散布図(SSDSE-B-2026、2023 年度、47 都道府県)。高齢化率と死亡率(人口千人あたり)の r = +0.972、回帰直線の傾きは高齢化率 1 ポイントあたり 0.610。回帰直線から上に最も離れた青森県(残差 +1.28)と、下に最も離れた奈良県(残差 −1.65)に注釈。

図 2 のタイトルは一目で伝わりますが、上の 💬 にあるとおり、この関係は「高齢者の多い県では亡くなる人も多い」という年齢構成の当たり前を言っているにすぎません。聞き手にとって新しい情報は、むしろその当たり前から外れている県にあります。回帰直線からのずれ(残差)を計算して、物語の焦点を移します。

🎯 このコードでやること:2023 年度の 47 県で、死亡率(人口千人あたり死亡数)を高齢化率で直線回帰し、高齢化率では説明できない部分(残差)が大きい県と小さい県を 3 つずつ挙げる。

📥 入力例 SSDSE-B-2026.csv の 2023 年度・47 行 Prefecture A1101(総人口) A1303(65歳以上人口) A4200(死亡数) 北海道 5,092,000 1,681,000 75,120 東京都 14,086,000 3,205,000 137,241 …(全 47 行。死亡率 = A4200 ÷ A1101 × 1000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023].copy()
d['高齢化率'] = d['A1303'] / d['A1101'] * 100
d['死亡率'] = d['A4200'] / d['A1101'] * 1000          # 人口千人あたり死亡数

b, a = np.polyfit(d['高齢化率'], d['死亡率'], 1)       # 死亡率 ≈ a + b × 高齢化率
d['予測'] = a + b * d['高齢化率']
d['残差'] = d['死亡率'] - d['予測']                     # 高齢化率では説明できない部分
r = d['高齢化率'].corr(d['死亡率'])
print(f'r = {r:.3f}  決定係数 R² = {r ** 2:.3f}  傾き = {b:.3f}(高齢化率 1 ポイントあたり千人あたり死亡数)')
cols = ['Prefecture', '高齢化率', '死亡率', '予測', '残差']
print('高齢化率から予測されるより死亡率が高い 3 県:')
print(d.nlargest(3, '残差')[cols].round(2).to_string(index=False))
print('予測より低い 3 県:')
print(d.nsmallest(3, '残差')[cols].round(2).to_string(index=False))
print(f'残差の標準偏差 = {d["残差"].std():.2f}、死亡率の標準偏差 = {d["死亡率"].std():.2f}')
📤 実行例(実測) r = 0.972 決定係数 R² = 0.945 傾き = 0.610(高齢化率 1 ポイントあたり千人あたり死亡数) 高齢化率から予測されるより死亡率が高い 3 県: Prefecture 高齢化率 死亡率 予測 残差 青森県 35.22 17.60 16.32 1.28 東京都 22.75 9.74 8.71 1.03 沖縄県 23.84 10.29 9.38 0.92 予測より低い 3 県: Prefecture 高齢化率 死亡率 予測 残差 奈良県 32.64 13.10 14.74 -1.65 京都府 29.70 12.12 12.95 -0.83 兵庫県 29.96 12.32 13.11 -0.79 残差の標準偏差 = 0.49、死亡率の標準偏差 = 2.09

💬 高齢化率だけで死亡率のばらつきの 94.5%(R² = 0.945)が説明でき、死亡率の標準偏差 2.09 に対して残差の標準偏差は 0.49 まで小さくなります。残ったずれで見ると、青森県は高齢化率 35.22% から予測される 16.32 より 1.28 高い 17.60、東京都も予測 8.71 に対して 9.74 と 1.03 高くなっています。逆に奈良県は予測 14.74 に対して 13.10 と 1.65 低い県です。「秋田県の死亡率が最も高い」(19.17)は高齢化率でほぼ説明がつく一方、「青森県は同じくらい高齢化した県より死亡率が高い」は、図 2 の見出しからは出てこない、次の分析に値する物語です。

ただし、この残差も年齢構成の影響を完全には取り除けていません(65 歳以上をひとまとめにしているため、75 歳以上の割合の違いが残差に混ざります)。「青森県は健康状態が悪い」と見出しにするには年齢調整死亡率での確認が必要で、ここで言えるのは「調べる価値のある県が見つかった」までです。ストーリーの見出しは、データが支える範囲より一歩手前で止めるのが誠実な書き方です。

「代表値だけの物語」を自分で壊してみる

このコードでやること:全国 1 つの数字で語りたくなったときに、 その裏にある散らばりを 5 行で出すためのコードです。 ストーリーを書く前に必ず実行して、自分の主張が中央値・四分位と矛盾しないか確かめます。

📥 入力データ(SSDSE-B-2026 の出生数、2012 年と 2023 年):

2012 年 全国合計 1,037,165 件(東京 107,401 / 秋田 6,543 など 47 県) 2023 年 全国合計 727,269 件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 「全国 -29.9%」が隠しているもの — 県別の散らばりを出す
import pandas as pd

df = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932", header=1)
df = df[df["地域コード"].astype(str).str.match(r"^R\d{5}$", na=False)].copy()
df["出生数"] = pd.to_numeric(df["出生数"], errors="coerce")
df["年度"] = pd.to_numeric(df["年度"], errors="coerce")

a = df[df["年度"] == 2012].set_index("都道府県")["出生数"]
b = df[df["年度"] == 2023].set_index("都道府県")["出生数"]
chg = ((b - a) / a * 100).sort_values()

print(f"全国合計         : {(b.sum() - a.sum()) / a.sum() * 100:+.1f}%")
print(f"県別の中央値     : {chg.median():+.1f}%")
print(f"県別の範囲       : {chg.min():+.1f}% 〜 {chg.max():+.1f}%")
print(f"25%点 / 75%点    : {chg.quantile(.25):+.1f}% / {chg.quantile(.75):+.1f}%")
print()
print("最も減った 3 県:", ", ".join(f"{k} {v:+.1f}%" for k, v in chg.head(3).items()))
print("最も減らない 3 県:", ", ".join(f"{k} {v:+.1f}%" for k, v in chg.tail(3).items()))

📤 実行結果:

全国合計 : -29.9% 県別の中央値 : -33.3% 県別の範囲 : -44.8% 〜 -19.6% 25%点 / 75%点 : -35.2% / -30.1% 最も減った 3 県: 秋田県 -44.8%, 岩手県 -41.4%, 静岡県 -38.4% 最も減らない 3 県: 福岡県 -25.9%, 大阪府 -24.3%, 東京都 -19.6%

💬 読み方:全国合計 −29.9% と県別中央値 −33.3% の 3.4 ポイントのずれが、 「全国で語ると実態より穏やかに見える」ことを数値で示しています。 さらに 25%点 −35.2%、75%点 −30.1% を見れば、真ん中の半数の県は −35.2% 〜 −30.1% という狭い帯に収まっており、全国値 −29.9% はその帯の外(より穏やかな側)にあることが分かります。 ストーリーに書くべきは「−29.9%」ではなく、 「全国では約 3 割減。ただし県別には 2 割減から 4 割半減まで開きがある」という 1 文です。

このコードは出生数以外でもそのまま使えます。 "出生数" を "婚姻件数" や "着工新設住宅戸数" に差し替えるだけで、 別の指標について同じ確認ができます。 「全国 1 つの数字を出す前に、必ず散らばりを見る」—— これを習慣にするだけで、誤解を招くストーリーの大半は防げます。

見出しの数字を選ぶ — 同じ「少子化」が 3 割減にも 1 割減にも見える

ストーリーの冒頭(Situation)に置く数字は 1 つに絞るのが定石ですが、どの数字を選ぶかで聞き手の受け取る深刻さが変わります。SSDSE-B-2026 の 2012→2023 年度について、「子どもが減っている」ことを表す候補の数字を並べます。

🎯 このコードでやること:47 都道府県の合計で、出生数の減少数と変化率、人口千人あたりの出生数、合計特殊出生率(47 県の単純平均)、15 歳未満人口の変化率を 2012 年度と 2023 年度で比べる。出生数の前年比を並べ、2019 年度の前後で減り方の速さ(年率)を比べる。

📥 入力例 SSDSE-B-2026.csv(564 行 = 47 都道府県 × 2012〜2023 年度) SSDSE-B-2026 Prefecture A1101(総人口) A1301(15歳未満) A4101(出生数) A4103(合計特殊出生率) 2023 北海道 5,092,000 … 24,430 1.06 …(全 564 行)
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

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df.rename(columns={'SSDSE-B-2026': '年度'})
g = df.groupby('年度')[['A4101', 'A1101', 'A1301']].sum()
tfr = df.groupby('年度')['A4103'].mean()                # 47 県の単純平均(全国値ではない)

a, b = 2012, 2023
cands = {
    '出生数の減少数(人)': g.loc[a, 'A4101'] - g.loc[b, 'A4101'],
    '出生数の変化率(%)': (g.loc[b, 'A4101'] / g.loc[a, 'A4101'] - 1) * 100,
    '人口千人あたり出生数 2012→2023': f"{g.loc[a, 'A4101'] / g.loc[a, 'A1101'] * 1000:.2f} → {g.loc[b, 'A4101'] / g.loc[b, 'A1101'] * 1000:.2f}",
    '合計特殊出生率(47県平均)2012→2023': f'{tfr[a]:.3f} → {tfr[b]:.3f}',
    '15歳未満人口の変化率(%)': (g.loc[b, 'A1301'] / g.loc[a, 'A1301'] - 1) * 100,
}
for k, v in cands.items():
    print(f'{k:<28} {v:,.1f}' if not isinstance(v, str) else f'{k:<28} {v}')

# 物語の「転」を探す: 出生数の前年比
yoy = (g['A4101'].pct_change() * 100).round(1)
print('出生数の前年比(%)', yoy.dropna().to_dict())
print(f'2012→2019 年率 {((g.loc[2019, "A4101"] / g.loc[2012, "A4101"]) ** (1 / 7) - 1) * 100:.2f}%  '
      f'2019→2023 年率 {((g.loc[2023, "A4101"] / g.loc[2019, "A4101"]) ** (1 / 4) - 1) * 100:.2f}%')
📤 実行例(実測) 出生数の減少数(人) 309,896.0 出生数の変化率(%) -29.9 人口千人あたり出生数 2012→2023 8.13 → 5.85 合計特殊出生率(47県平均)2012→2023 1.460 → 1.293 15歳未満人口の変化率(%) -14.3 出生数の前年比(%) {2013: -0.7, 2014: -2.5, 2015: 0.2, 2016: -2.8, 2017: -3.2, 2018: -2.9, 2019: -5.8, 2020: -2.8, 2021: -3.5, 2022: -5.0, 2023: -5.6} 2012→2019 年率 -2.56% 2019→2023 年率 -4.25%

💬 同じ 11 年間の少子化が、出生数なら「31 万人減・−29.9%」、合計特殊出生率なら「1.460 → 1.293(−11.4%)」、15 歳未満人口なら「−14.3%」と表せます。出生数が合計特殊出生率より大きく減っているのは、合計特殊出生率が女性 1 人あたりの値で、子どもを産む年齢の女性の人数の減少を含まないからです。「女性 1 人が産む子どもの数は 1 割しか減っていないのに、生まれる子どもは 3 割減った」という対比そのものが物語の核になります。どれか 1 つを見出しにするなら、聞き手の問い(出生の「件数」が知りたいのか、「産み方」の変化が知りたいのか)に合わせて選び、他の数字との違いを脚注に書きます。合計特殊出生率はここでは 47 県の単純平均で、国全体の値とは一致しない点も明記が必要です。

前年比の並びは、SCR の Complication(転)を探す材料になります。出生数は 2012→2019 年度には年率 −2.56% で減っていたのが、2019→2023 年度は年率 −4.25% と減り方が速まっています。この数字を使うと、SCR は次のように書けます。

SCRSSDSE-B-2026 の数字を使った文
Situation(状況)47 都道府県の出生数の合計は、2012 年度から 2023 年度までに 29.9%(31 万人)減った。
Complication(課題)減り方は一定ではなく、2019 年度までの年率 −2.6% から、2019 年度以降は年率 −4.3% に速まった(前年比は 2019 年度 −5.8%、2022 年度 −5.0%、2023 年度 −5.6%)。
Resolution(次の一手)速まった原因が「産む人の数」か「産み方」かを分けるため、合計特殊出生率(47 県平均 1.460 → 1.293)と、出産年齢の女性人口の推移を別の統計で確かめる。

Resolution を「対策」ではなく「次に確かめること」にしているのは、SSDSE-B-2026 だけでは速まった原因を特定できないからです。データが言えることの範囲で物語を閉じることも、ストーリーテリングの技術の一部です。

🎮 体験:注釈・強調・順序で「伝わるメッセージ」は変わる

同じ実データ(SSDSE-B-2026 の 2023 年・高齢化率=65歳以上人口÷総人口×100、 10 県抜粋)を使っても、 注釈(基準線・ハイライト・矢印)と結論見出しを足すだけで「ただのグラフ」が「主張が伝わる図」に変わります。 下のトグルを切り替えて体感してください。 さらに「全体→注目→示唆」のステップ送りで語りの順序を、 最後の「y軸切詰め」で強調とごまかしの境界を確認できます。

注釈トグル:
語りの順序: 自由モード (全体 → 注目 → 示唆)
まず全体像から。 トグルはすべてオフ、 ただ 10 県の高齢化率を並べただけの「ただのグラフ」です。 ばらつきは見えますが、 何を伝えたいのかは読み手に委ねられています。
⚖️ 強調 vs ごまかしの境界:
✅ y軸は 0% 起点。 バーの高さは値をそのまま反映し、 東京と秋田の差もありのまま。 これは誠実な強調です。

🧭 直感:データに「文脈」と「焦点」を与える

上の実験でオフ状態のグラフは、 数値としては完全に正しいのに「だから何?」に答えられません。 基準線は「多い/少ないの物差し」という文脈を、 ハイライトと矢印は「どこを見るべきか」という焦点を、 見出しは「何が言いたいか」という結論を与えます。 データストーリーテリングの本質は新しい数値を足すことではなく、 既にある数値に文脈と焦点を重ねる注釈レイヤにあります。

📊 実データで確かめる — 強調する 2 本を決めると図が語り出す

上のウィジェットの「強調」を、SSDSE-B-2026 の実データで試します。47 県の出生数を 2012 年度 = 100 の指数にして、同じ 47 本の線を 2 通りに描きました。

2 枚の折れ線グラフ: 47 都道府県の出生数の指数。左は 47 色、右は灰色の 47 本に秋田県・東京都・全国を強調
図3: 出生数の指数(2012 年度 = 100、SSDSE-B-2026)。左は 47 本を 47 色で描いたもの、右は灰色の 47 本の上に秋田県(2023 年度 55.2)、東京都(80.4)、全国(70.1、破線)だけを色と直接ラベルで重ねたもの。

🎯 このコードでやること:47 県の出生数を 2012 年度 = 100 の指数にし、図 3 の右で強調する秋田県・東京都・全国の 2023 年度の値と、47 県の最小・中央値・最大、全国の指数を下回る県の数を出す。

📥 入力例 SSDSE-B-2026.csv(564 行 = 47 都道府県 × 2012〜2023 年度) SSDSE-B-2026 Prefecture A4101(出生数) 2023 北海道 24,430 2023 東京都 86,348 2012 東京都 107,401 2012 秋田県 6,543 …(全 564 行)
1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
b = df.pivot(index='Prefecture', columns='SSDSE-B-2026', values='A4101')
idx = b.div(b[2012], axis=0) * 100                   # 各県の 2012 年度 = 100
nat = b.sum() / b[2012].sum() * 100                  # 全国の指数
focus = ['秋田県', '東京都']
for p in focus:
    print(f'{p}: 2023 年度の指数 {idx.loc[p, 2023]:.1f}')
print(f'全国: 2023 年度の指数 {nat[2023]:.1f}')
print(f'47 県の 2023 年度の指数: 最小 {idx[2023].min():.1f}  中央値 {idx[2023].median():.1f}  最大 {idx[2023].max():.1f}')
below = (idx[2023] < nat[2023]).sum()
print(f'全国の指数 {nat[2023]:.1f} を下回る県: {below} / 47')
📤 実行例(実測) 秋田県: 2023 年度の指数 55.2 東京都: 2023 年度の指数 80.4 全国: 2023 年度の指数 70.1 47 県の 2023 年度の指数: 最小 55.2 中央値 66.7 最大 80.4 全国の指数 70.1 を下回る県: 36 / 47

💬 強調した 2 県は、2023 年度の指数で最小(秋田県 55.2)と最大(東京都 80.4)の両端です。47 県の中央値は 66.7 で、全国の 70.1 を下回る県が 47 のうち 36 あります。右の図の灰色の束の大半が全国の破線より下にあるのは、この 36 県のためです。左の図では 47 色のどれが何県かを凡例で探すことすらできず、この情報は何も伝わりません。

強調する線を選ぶことは、同時に「何を語らないか」を選ぶことでもあります。右の図で両端の 2 県だけを強調すると、中央付近の県は背景に退きます。両端を選んだことを図の注記に書き(「2023 年度の指数が最小と最大の県」)、全国の線を基準として一緒に描くことで、読み手が自分の県を灰色の束の中に位置づけられるようにします。これが、上の「⚠️ 落とし穴」にある誤解を招く強調と、正当な焦点の違いです。

⚠️ 落とし穴:チェリーピッキング・過剰演出・誤解を招く強調

🚀 発展:ナラティブ可視化・注釈レイヤ・オーサリング

このウィジェットは Segel & Heer のナラティブ可視化(martini-glass 構造=著者が順序を導いた後に読み手の自由探索へ渡す)を最小実装したものです。 「注釈トグル」は注釈レイヤ(データ層と分離した強調要素)を、 「ステップ送り」は著者が意図を設計するオーサリングの工程を体験させます。 実務では 注釈・インタラクティブ可視化・視覚属性・複合的なグラフ と組み合わせ、 データ可視化 の素材に物語の骨格を与えます。

⚠️ よくある落とし穴

データストーリーテリングで頻出する失敗は、 (1) ストーリーに合わせて事実を曲げる (チェリーピッキング)、 (2) ビジュアルに凝りすぎて根拠数値が薄い、 (3) 主役のメッセージが拡散して結論が伝わらない、 の 3 つです。 Cole Nussbaumer Knaflic が勧める「誰に向けて」「何を知って・してほしいか」「データでどう示すか」の 3 つを毎回先に書き出せば、 大半は事前に回避できます。

❌ ストーリーが事実を曲げる
「分かりやすさ」を優先しすぎて、 不確実性・反例を消してはいけない。
❌ ジャンクチャート
3D 円グラフ、 不必要な装飾。 メッセージを薄める。
❌ 数字の羅列
聞き手は数字を覚えられない。 1 スライド 1 数字に絞る。
❌ ターゲット不在
「誰のためのストーリーか」が無いと刺さらない。

⚠️ 実データで確かめる — 見出しの言葉と指標がずれていないか

「高齢化が進んだ県ほど子どもが生まれにくい」という見出しは、多くの人が直感的に正しいと感じます。これを SSDSE-B-2026 で裏づけようとすると、どの指標を使うかで結論が逆になります。

🎯 このコードでやること:12 年度それぞれの 47 県の横断面で、高齢化率と「粗出生率(人口千人あたり出生数)」の相関、高齢化率と「合計特殊出生率(女性 1 人あたりの子どもの数)」の相関を計算する。代表的な 4 県の値も並べる。

📥 入力例 SSDSE-B-2026.csv(564 行 = 47 都道府県 × 2012〜2023 年度) SSDSE-B-2026 Prefecture A1101(総人口) A1303(65歳以上人口) A4101(出生数) A4103(合計特殊出生率) 2023 秋田県 914,000 357,000 3,611 1.10 2023 沖縄県 1,468,000 350,000 12,549 1.60 2023 東京都 14,086,000 3,205,000 86,348 0.99 …(全 564 行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df['出生率'] = df['A4101'] / df['A1101'] * 1000      # 人口千人あたり出生数(粗出生率)
df['TFR'] = df['A4103']                             # 合計特殊出生率(女性 1 人あたり)

rows = []
for y, g in df.groupby('SSDSE-B-2026'):
    rows.append((y, g['高齢化率'].corr(g['出生率']), g['高齢化率'].corr(g['TFR'])))
t = pd.DataFrame(rows, columns=['年度', 'r(高齢化率, 粗出生率)', 'r(高齢化率, 合計特殊出生率)']).round(3)
print(t.iloc[[0, 3, 6, 9, 11]].to_string(index=False))
print('12 年の範囲:', t.iloc[:, 1].min(), '〜', t.iloc[:, 1].max(), '/', t.iloc[:, 2].min(), '〜', t.iloc[:, 2].max())
d = df[df['SSDSE-B-2026'] == 2023].set_index('Prefecture')
print(d.loc[['秋田県', '沖縄県', '東京都', '島根県'], ['高齢化率', '出生率', 'TFR']].round(2).to_string())
📤 実行例(実測) 年度 r(高齢化率, 粗出生率) r(高齢化率, 合計特殊出生率) 2012 -0.657 0.152 2015 -0.692 0.040 2018 -0.659 0.123 2021 -0.554 0.200 2023 -0.615 0.201 12 年の範囲: -0.692 〜 -0.554 / 0.04 〜 0.227 高齢化率 出生率 TFR Prefecture 秋田県 39.06 3.95 1.10 沖縄県 23.84 8.55 1.60 東京都 22.75 6.13 0.99 島根県 34.92 5.78 1.46

💬 粗出生率との相関は 12 年すべてで −0.692〜−0.554 と、見出しどおり「高齢化した県ほど出生が少ない」になります。ところが合計特殊出生率との相関は 0.04〜0.227 と弱い正で、「高齢化した県ほど女性 1 人あたりの子どもが少ない」とは言えません。島根県は高齢化率 34.92% と高いのに合計特殊出生率は 1.46 と上位で、東京都は高齢化率 22.75% と最も低いのに 0.99 で最下位です。粗出生率の負の相関は、高齢化した県では出産する年代の人口の割合が小さいという年齢構成の効果です。

見出しの「子どもが生まれにくい」が「出生数が少ない」の意味なら粗出生率、「1 人の女性が産む子どもが少ない」の意味なら合計特殊出生率が対応する指標で、後者の意味ではこの見出しはデータに支えられません。ストーリーを書いたら、見出しの言葉を 1 語ずつ指標の定義に置き換え、その指標で確かめたかを点検します。都合のよい指標だけを示すことは、数値が正確でもチェリーピッキングの一種です。

⚠️ 実データで確かめる — 「何と比べて」を書かない物語は、差を大きくも小さくも見せられる

「秋田県の出生数の減り方は深刻だ」という物語の強さは、比べる相手(ベンチマーク)で変わります。全国と比べるか、同じ地方の他県と比べるか、47 県の中央値と比べるかを、実データで並べます。

🎯 このコードでやること:出生数の 2012→2023 年度の変化率を、秋田県と東京都について、全国の合計・同じ地方の他県の合計(東北 6 県・関東 7 都県から自県を除く)・47 県の中央値と並べ、47 県の中での順位を出す。

📥 入力例 SSDSE-B-2026.csv の出生数 A4101(47 都道府県 × 2012〜2023 年度)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
w = df.pivot(index='Prefecture', columns='SSDSE-B-2026', values='A4101')   # 出生数 47 × 12
ch = (w[2023] / w[2012] - 1) * 100                                          # 2012→2023 の変化率

tohoku = ['青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県']
kanto = ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県']
for pref, region, name in [('秋田県', tohoku, '東北 6 県'), ('東京都', kanto, '関東 7 都県')]:
    others = [p for p in region if p != pref]
    reg = (w.loc[others, 2023].sum() / w.loc[others, 2012].sum() - 1) * 100
    nat = (w[2023].sum() / w[2012].sum() - 1) * 100
    rank = int(ch.rank(ascending=True)[pref])        # 1 = 最も減った
    print(f'{pref}: {ch[pref]:+.1f}%  全国 {nat:+.1f}%  {name}の他県 {reg:+.1f}%  中央値 {ch.median():+.1f}%  減少の大きさ {rank} 位/47')
📤 実行例(実測) 秋田県: -44.8% 全国 -29.9% 東北 6 県の他県 -36.4% 中央値 -33.3% 減少の大きさ 1 位/47 東京都: -19.6% 全国 -29.9% 関東 7 都県の他県 -29.1% 中央値 -33.3% 減少の大きさ 47 位/47

💬 秋田県の −44.8% は全国(−29.9%)より 14.9 ポイント大きく減っていますが、同じ東北の他の 5 県(−36.4%)と比べると差は 8.4 ポイントに縮みます。東京都の −19.6% は全国より 10.3 ポイント小さい減少ですが、関東の他の 6 県(−29.1%)とは 9.5 ポイント差です。どの比較も事実ですが、「全国より 15 ポイントも多く減った」と「東北の中では 8 ポイント多い」では、聞き手の受け取る深刻さが違います。秋田県が 47 県で最も減った県であること(1 位)は比較の相手によらず変わらないので、順位と、比べた相手の名前をセットで書くのが誠実な書き方です。東北全体も中央値(−33.3%)より大きく減っているという事実は、「秋田県だけの問題」という物語を弱める反例として、Complication に入れておく価値があります。

図にするときは、比べる相手を図の中に描き込みます。秋田県の折れ線だけを描いて「45% 減」と見出しを付けるのではなく、全国(−29.9%)と東北の他の 5 県(−36.4%)の線を細い灰色で重ね、見出しを「秋田県の出生数は 11 年で 44.8% 減、東北の他県(36.4% 減)よりさらに大きく減った」とすれば、比較の相手が見出しと図の両方に現れ、聞き手は自分で差の大きさを確かめられます。

🗺 概念マップ

「データストーリーテリング」は、 データ可視化・ナラティブ・聴衆設計の三位一体。 以下の概念マップで中核概念と隣接領域の包含・対比関係を整理する。

Data Storytelling データ可視化 ナラティブ設計 聴衆分析 BI ツール 統計分析 意思決定支援
データストーリーテリング
📊 データ基盤
SSDSE-B-2026
公的統計
前処理・集計
📖 ナラティブ
SCR 構造
三幕構成
Pyramid Principle
🎨 視覚化
Grammar of Graphics
散布図・棒・箱ひげ
Plotly / matplotlib
👥 聴衆設計
政策担当者
研究者・市民
Call to Action
↕ 包含・依存関係
上流: データリテラシー → データ収集 → EDA
並列: インフォグラフィック / BI ダッシュボード
下流: 政策提言 / 意思決定 / 報告書

SVG 関係図: データストーリーテリングを中心とした概念の包含・上下関係

データストーリーテリング データリテラシー (前提・上流) 探索的分析 (EDA) (発見フェーズ) データ可視化 (並列・手段) BI ダッシュボード (並列・常時) 政策提言 (下流・成果) 意思決定支援 (下流・目的) 報告書・発表 (下流・成果物) 上流 並列 下流
概念データSTとの関係違い・注意
データ可視化手段・部品可視化単独ではストーリーにならない
インフォグラフィック並列 (静的情報伝達)物語構造より情報圧縮に重点
プレゼンテーション媒体・チャネルデータ ST が中身、プレゼンは形式
EDA (探索的分析)上流フェーズEDA で発見 → ST で伝達
データジャーナリズム応用分野報道目的に特化した ST

🔗 隣接手法への橋渡し

「データストーリーテリング」は単独で完結する手法ではなく、 上流・並列・下流の隣接手法と連携することで真価を発揮する。

位置隣接手法接続ポイント実務での順序
上流データリテラシー / EDA「何が面白いか」を発見EDA → DS
並列データ可視化 / BI ツール図表生成・ダッシュボードDS と同時並行
統計的根拠相関分析 / ANOVA「差は偶然か?」の検証DS の「Conflict」補強
下流政策提言 / レポート執筆「Resolution」の実装DS → 報告書

SSDSE コンペでは「EDA (散布図) → DS (三幕) → 統計検定 (ANOVA p=0.0003) → 報告書(Result セクション)」の順が高評価を得やすい。 DS は「つなぎ役」として機能し、 データと聴衆の橋渡しをする。

🌳 手法選択フロー

「データストーリーテリング」は適切な場面で使って初めて効果を発揮する。 以下のフローで「この場面に DS が適切か」を 4 分岐で判断する。

判断基準Yes の場合No の場合
① 伝達相手が人間か?次へ自動処理なら DS 不要
② 行動変容を求めるか?DS が最適。 3 幕構成を採用記録・参照用なら BI ダッシュボード
③ データに驚きの落差はあるか?Discovery Arc or Problem-Solution Arc落差を探す EDA を先行
④ 聴衆の専門知識は?専門家 → 統計指標主体の DS一般 → 比喩・具体例主体の DS

SSDSE-B-2026 コンペでの典型ケース: ① 審査員(人間)→ ② 政策示唆あり(行動変容あり)→ ③ 東京と秋田の高齢化率 16 ポイント差(22.8% 対 39.1%)が落差 → ④ 専門家向けに p 値・効果量を添えた DS を選択。

🧠 直感を深める:3 要素の「かけ算」で洞察を運ぶ

冒頭の「🎨 直感で掴む」を一歩進めます。 データストーリーテリングの正体は、 データ(data)・ビジュアル(visual)・ナラティブ(narrative)の 3 要素を足し算ではなくかけ算で重ねる営みです。 かけ算なので、 どれか一つが 0 に近いと全体が伝わらなくなります。

要素担う役割これが欠けると起きること
データ主張の根拠・信頼性の源泉「印象論」になり、 反論で崩れる
ビジュアル一瞬で構造を掴ませる知覚の近道数字の羅列になり、 記憶に残らない
ナラティブ「だから何?」に答え、 行動へ導く図の墓場になり、 意思決定が動かない

鍵は文脈化(contextualization)です。 単独の数値「東京都の高齢化率 22.8%(SSDSE-B-2026・2023年・実測)」は、 それだけでは高いか低いか判断できません。 基準(全国の人口加重平均 29.1%)と対比(秋田県 39.1%)を隣に置いて初めて「東京は全国平均より 6 ポイント以上低く、 最高の秋田とは 16.3 ポイントの開きがある」という意味が立ち上がります。 このページ上部の「🎮 体験」ウィジェットで、 同じ実データが注釈レイヤの追加だけで「ただのグラフ」から「主張が伝わる図」へ変わるのを確認できます。

そして 3 要素の先には必ず聴衆に合わせた翻訳と行動喚起(call to action)を置きます。 「格差 16.3 ポイント」という事実を、 知事には「地方拠点への介入シナリオ」、 現場には「市区町村別の年齢層変動」へと翻訳して初めて、 グラフの羅列ではない物語になります。

💡 一文でいうと:データストーリーテリングとは「新しい数字を足す」ことではなく、 既にある数字に文脈・焦点・意味・次の一手を重ねる編集作業である。

⚠️ 落とし穴を深掘り(重要)

「物語を強くしたい」という動機は、 容易に事実を曲げる圧力に転化します。 上部の「⚠️ よくある落とし穴」を、 可視化の欺き・認知バイアス・文脈の欠落の 3 系統に整理して深掘りします。 いずれも「悪意」ではなく「善意の熱意」から起きるのが厄介な点です。

落とし穴何が起きるか対処
軸操作(y 軸切り詰め・対数の乱用)見た目の差を数倍に誇張。 量の比較で 0 起点を捨てると印象がねじれる量の比較は 0 起点が原則。 変化率を語るなら軸の非 0 を明示
チェリーピッキング主張に都合の良い県・期間だけ抽出し、 全体分布を歪める代表抜粋(最小・最大・平均を含む)か全数。 逆の結論を支持する数値も並べる
data-ink 比の無視・過剰装飾3D・影・無意味な色でメッセージが薄まる(チャートジャンク)Tufte の data-ink 比を意識し、 意味のないインクを削る
聴衆を無視した専門用語「効果量 d」「p 値」を説明なしに出し、 相手が置き去りになる相手のリテラシーに合わせ翻訳。 専門家には指標、 一般には比喩
相関を因果と語る「高齢化が進む県ほど死亡率も高い」を因果と断言してしまう交絡(年齢構成など)を疑い、 「観察された関連」と明記
確証バイアス・結論先行先に結論を決め、 それを支持するデータだけ集める反証データを能動的に探す。 「間違っていたら何が見えるはず?」を自問
文脈欠如の数値基準・単位・母数のない「裸の数字」で誤解を招く基準線・n・期間・単位を必ず添える

とりわけ軸操作は「強調」と「ごまかし」の境界に立ちます。 上部ウィジェットの「✂ y軸を切り詰める」を押すと、 東京と秋田の実際の差(16.3 ポイント)が見た目で約 2 倍に膨らむ様子を体感できます。 詳細は 誤解を招くグラフ を参照。 過剰装飾と知覚の話は 視覚属性 と ゲシュタルト原則、 色の乱用は 配色 に整理があります。

相関と因果の混同は、 ストーリーが最も「気持ちよく嘘になる」瞬間です。 相関分析 で得た関連を、 因果 と 交絡 の観点で吟味し、 断定を避けます。 聴衆の専門用語問題は、 読み手のリテラシーを測る データリテラシー とセットで考えると事故が減ります。

🛡 一言防御:ストーリーを作ったら「この図は、 私が伝えたい結論の反対を主張する人でも納得する形になっているか?」を一度問う。 これだけで上記の大半を自己検知できる。

🚀 発展:ナラティブ構造・注釈設計・倫理的可視化

最後に、 実装レベルで効く 6 つの発展トピックを整理します。 上部の「🎮 体験」ウィジェットは、 このうちナラティブ構造・注釈レイヤ・プログレッシブディスクロージャーを最小実装したものです。

なお、 これら注釈は 棒グラフ や散布図といった素材(=データ可視化)の上に重ねて初めて機能します。 「効果量で驚きを裏づける」手法は 効果量 を、 全体の素材づくりは データ可視化 を参照してください。(本サイトに独立ページのない「データビジュアライゼーション基礎」等は、 これらの実在ページで代替できます。)

🧪 架空の対比例:以下は仕組みを示すための架空のシナリオです(数値は SSDSE ではありません)。 「売上が前年比 3% 増」という同じ事実を、 経営層には「3% 増=目標未達 2pt、 追加施策 A/B の意思決定を要請」、 現場には「店舗別で +12%〜−5% の分散、 下位 3 店の要因分析へ」と翻訳する——同じ数字でも聴衆で物語が変わる。