論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
Tidy data
Tidy Data
データ処理
別称: 整然データ

🔖 キーワード索引

#long format#正規化#pivot#前処理#Hadley Wickham#tidyverse

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

tidy data統計分析SSDSE-B-2026前提条件適用範囲落とし穴関連手法Python 実装検証方法

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

💡 30秒で分かる結論

🍰 まずはやさしく

データの整理整頓のようなものです。

分析やグラフ作成を楽にするために使います。

部活の出席簿をきれいにまとめるイメージです。

ここではデータの整え方とコツを読みます。

Tidy data:Wickham 提案:1行1観測・1列1変数の整った形式

💡 Tidy 実務 Tips 集

📊 Tidy data と Excel 文化の対立

日本企業の多くは Excel 中心のデータ文化を持っており、 これが tidy data 思想とは正反対です。 Excel の典型的アンチパターン:

SSDSE はこれら全てを排除し、 1 シート 1 表、 セル結合なし、 注釈は別ファイル という tidy 原則を厳守。 これが「教育用標準」 を名乗る所以です。 自治体オープンデータの多くはまだ Excel 文化に染まっており、 tidy 化に時間がかかる現状があります。

🔮 Tidy data の未来

2026 年現在、 tidy data 思想は次のように進化しています:

🗄 Tidy data と SQL — 不可分の関係

tidy data の long format は、 そのまま EAV(Entity-Attribute-Value) パターンの DB スキーマになります。

操作 Pandas(tidy) SQL
行フィルタdf.query()WHERE
列選択df[cols]SELECT
集約df.groupby().agg()GROUP BY
結合df.merge()JOIN
ソートdf.sort_values()ORDER BY
long → widedf.pivot()PIVOT (SQL Server)
wide → longdf.melt()UNPIVOT
ウィンドウ関数df.groupby().transform()OVER()
CTE変数代入WITH

🌏 多言語データの tidy 化

SSDSE-B-2026 は日本語列名ですが、 国際比較するなら英語版が必要。 tidy 化のついでに翻訳列を作る:

METRIC_MAP = {
    '総人口': 'total_population',
    '合計特殊出生率': 'total_fertility_rate',
    '消費支出(二人以上の世帯)': 'consumption_expenditure',
    '65歳以上人口': 'population_aged_65_and_over',
    '大学数': 'number_of_universities',
}
df_long['metric_en'] = df_long['指標'].map(METRIC_MAP)
df_long.to_csv('SSDSE_tidy_bilingual.csv', index=False)

国際論文や OECD データとの比較分析で重要。 tidy 形式なら英語版の追加は 1 列の merge だけで完了。

📍 文脈ボックス

🍰 まずはやさしく

データ処理というグループの用語です。

全体の流れの中で意味を理解するために使います。

スマホのアプリでデータを扱うときにも役立ちます。

ここではこの用語の詳しい解説を読みます。

この用語は データ処理 カテゴリに属します。 関連する別称・略号:整然データ

論文・実務レポートで Tidy data が登場したら、 まず本ページの「30秒で分かる結論」と「直感で掴む」を読めば、 その文脈で何を言っているか把握できます。

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

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

🎨 直感で掴む

🍰 まずはやさしく

データの形を整える考え方です。

コンピューターが処理しやすい形にするために使います。

テストの点数を年度ごとに並べる例で考えます。

ここでは見た目の違いと使い分けを読みます。

tidy data(整然データ)は「1 行 = 1 観測、 1 列 = 1 変数、 1 表 = 1 観測単位」という Hadley Wickham (2014) が定式化した形式。 SSDSE-B-2026 で具体的に見ると、 同じ情報を 2 つの表現で持てます。

Wide 形式(クロス表): 横軸に年度、 縦軸に都道府県、 セルに人口を置く。 47 行 × 10 列のマトリクス。 ヒトの目には見やすいが、 列名に 年度の値(2014, 2015, ...)が埋め込まれていて、 「年度」自体が変数として扱われていない。

都道府県2014...2023
北海道5,410,000...5,092,000
東京都13,390,000...14,090,000

Long 形式 / tidy 形式: 同じ情報を「都道府県, 年度, 総人口」の 3 列・564 行(47 都道府県 × 12 年度)で持つ。 列が 変数、 行が 観測と完全に対応する。 機械的処理向き。

都道府県年度総人口
北海道20145,410,000
北海道20235,092,000
東京都201413,399,000
東京都202314,086,000

tidy にする 3 つの実利:

tidy ⇄ wide の変換: pandas では df.melt()(wide → long)、 df.pivot()(long → wide)。 SSDSE-B-2026 の生 CSV は year × prefecture の (10×47)=470 行 long 形式なので、 すでに tidy。 ただし 変数が複数列に渡ると Wickham 第 1 原則に違反するので、 melt で「指標名」列に集約することも。

🎮 触って理解する

雑然データ(untidy data)には代表的な 3 大パターン があります。 タブで切り替え、 変換ボタンを押す(またはスライダを動かす・図の上を左右にドラッグする)と、 SSDSE-B-2026 の実測値がアニメーションしながら整然形へ組み変わります。 下の比較パネルで「同じ集計がどれだけ簡単になるか」も確かめてください。 基本の wide ⇄ long トグルは 表形式データ のページにもあります。 このページでは 3 パターンそれぞれの修復関数の違いを深掘りします。

操作: タブでパターン切替 / ボタンでアニメーション変換 / スライダまたは図の上を左右ドラッグ(タッチ対応)で途中経過をスクラブ。 値はすべて SSDSE-B-2026 実測値。

💡 直感の整理 — 3 原則と 3 大パターンの対応

整然データの直感は「1 行 = 1 観測・1 列 = 1 変数・1 表 = 1 観測単位」の 3 点セット。 上の 3 パターンは、 それぞれどの原則がどう破られているかが異なり、 だから修復関数も異なります。

雑然パターン 破られている原則 pandas tidyr (R)
① 列名が値(2021, 2022, … が列)1 列 = 1 変数(「年度」変数が列名に散在)meltpivot_longer
② 複数変数が 1 列(「男_65歳以上」)1 列 = 1 変数(1 列に性別+年齢階級の 2 変数)str.split(expand=True)separate
③ 1 観測が複数行(指標名・値の超縦持ち)1 行 = 1 観測(1 県の観測が複数行に分散)pivotpivot_wider

特にパターン③は重要な気づきを与えます。 「縦長にすれば整然」ではない。 観測単位が「都道府県」なら 1 県 1 行が整然であり、 縦持ちしすぎ(EAV 形式)はむしろ雑然。 整然かどうかは観測単位を何と定めるかで決まります。

⚠️ よくある落とし穴 — 「見た目が綺麗」 ≠ 整然

🚀 発展 — pandas 対応表と正規化との関係

3 パターンの修復は DB 正規化理論と対応します。 ②の separate第 1 正規形(アトミック性: 1 セル 1 値)の回復、 ③の pivot関数従属の整理(主キー = 都道府県に対する 1 行化)に相当。 tidy data は「第 3 正規形を分析しやすさの言葉で言い直したもの」という理解が発展的な整理です(詳細は上の「📐 定義・数式」参照)。 pandas では melt / pivot の他に、 集計を伴う pivot_table、 「指標_年」形式の列名に強い wide_to_long、 MultiIndex ベースの stack / unstack も同じ族の操作です。 関連ページ: 表形式データ / 縦持ち・横持ち / ピボットテーブル / groupby / pandas / DataFrame / テーブル結合

📐 定義・数式

🍰 まずはやさしく

データの作り方のルールです。

誰が書いても同じ形にするために使います。

買い物リストの書き方を決めるようなものです。

ここでは3つの原則について詳しく読みます。

【Tidy data の 3 原則】
$$ \text{Tidy} \iff \text{(1) row=observation, (2) col=variable, (3) table=unit} $$

Wickham (2014) で定式化された定義。 リレーショナル DB の第三正規形に近いが、 「分析しやすさ」を目的としている点が異なる。

📐 Codd 第 1〜3 正規形と Tidy data の対応

Tidy data の 3 原則(1 行 1 観察、 1 列 1 変数、 1 値 1 セル)は、 リレーショナルデータベース理論の祖 E.F. Codd(1970)が提唱した 正規形(Normal Form) と本質的に同じ構造を持ちます。 数式的に整理すると次の通り:

関係 R が第 1 正規形(1NF)であるとは

$$ \forall t \in R, \forall A_i \in \text{Attr}(R): t[A_i] \in \text{Domain}(A_i) \text{ かつ } t[A_i] \text{ はアトミック} $$

🔬 数式を言葉で読み解く

これが Tidy data の「1 値 1 セル」と同義。 Codd の 1NF は 1970 年に提唱されたが、 Hadley Wickham(2014)の Tidy data 論文は これを統計家の言語で再定義 したものと言えます。

Codd 用語Tidy data 用語SSDSE での具体例
関係 RデータセットSSDSE-B-2026.csv 全体
タプル t観察1 都道府県 1 年のレコード
属性 A変数総人口、 消費支出、 出生数
ドメイン Domain(A)値域総人口 ∈ ℕ、 緯度 ∈ [-90, 90]
主キー識別子列Code(地域コード)× SSDSE-B-2026(年度)

📐 第 2・第 3 正規形と tidy data の関係

Codd 正規形は 1NF だけでなく 2NF, 3NF, BCNF へと続きます。 tidy data はおおむね 3NF まで意識すれば実務的に十分です。

第 2 正規形(2NF)

$$ \text{任意の非主属性 } A: \text{主キー全体に対して関数従属 } A = f(\text{PK}) $$

数式を言葉で読み解く

「複合主キーがあるとき、 非キー列はその 主キー全体 に従属していなければならない(一部の列だけに従属していてはいけない)」。 例:(都道府県, 年) を主キーとしたとき、 「都道府県の所在地(北海道, 関東, …)」は 都道府県だけに従属 しているため 2NF 違反。 → 別テーブルに分けるべき。

第 3 正規形(3NF)

$$ \forall \text{非主属性 } A, B: A \rightarrow B \Rightarrow B \text{ も主キーに直接従属} $$

数式を言葉で読み解く

「非キー列同士の従属関係を許さない」。 例:(都道府県コード, 都道府県名, 県庁所在地) があるとき、 「県庁所在地」は「都道府県名」に従属している(推移従属)。 → これも別テーブルに分けるのが 3NF。

正規形条件SSDSE での例
1NFアトミック性1 セルに「東京都/大阪府」と書かない
2NF完全関数従属(都道府県, 年) を PK としたとき、 「都道府県名」だけに従属する列は別表へ
3NF非推移従属「都道府県名 → 県庁所在地」は別表へ
BCNF候補キーへの依存複数候補キーの曖昧性除去

tidy data は 1NF を最優先。 分析時には join で wide に戻すため、 過度な正規化は性能を損なうこともある(denormalization の判断が必要)。

🚨 untidy(汚いデータ)の 5 大パターンと修復

Wickham(2014)は untidy データを 5 つの典型パターンに分類しました。 SSDSE 系の公的統計でもしばしば見かけるので、 各パターンの修復コードを示します。

パターン 1:列ヘッダが値になっている

例:「2020年, 2021年, 2022年」が列名になっているケース。 → melt で年を 1 つの列にまとめる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd

# untidy 例:年が列名
untidy = pd.DataFrame({
    '都道府県': ['北海道', '東京都', '大阪府'],
    '2020年': [5224614, 14047594, 8837685],
    '2021年': [5183687, 14010000, 8800000],
    '2022年': [5140000, 14040000, 8784000]
})
print('--- untidy(年が列名)---')
print(untidy)

# tidy 化
tidy = untidy.melt(id_vars='都道府県', var_name='年', value_name='人口')
print('\n--- tidy(年を 1 列にまとめた)---')
print(tidy)

📤 実行結果

--- untidy(年が列名)--- 都道府県 2020年 2021年 2022年 0 北海道 5224614 5183687 5140000 1 東京都 14047594 14010000 14040000 2 大阪府 8837685 8800000 8784000 --- tidy(年を 1 列にまとめた)--- 都道府県 年 人口 0 北海道 2020年 5224614 1 東京都 2020年 14047594 2 大阪府 2020年 8837685 3 北海道 2021年 5183687 ...

💬 結果の読み方:時系列プロットや回帰モデルを使うには「年」を 変数 として扱う必要がある。 untidy のままだと「2020年カラムと2021年カラムを比較するコード」を毎回書く羽目になる。

パターン 2:1 セルに複数の値

例:「東京都, 大阪府, 福岡県」と 1 セルに格納。 → str.split + explode

パターン 3:複数変数が 1 列に

例:「男_15-19歳」「男_20-24歳」のように性別と年齢が結合。 → str.split で 2 列に分離。

パターン 4:変数が複数の表に分散

例:県別人口と県別 GDP が別ファイル。 → 都道府県コードで merge

パターン 5:1 つの観察単位が複数行に分散

例:「都道府県, 男性人口」「都道府県, 女性人口」と 2 行に分かれている。 → pivot で 1 行に統合。

🔬 数式を言葉で読み解く

数式に出てくる記号の意味を 1 つずつ確認しましょう。

row
1 行 = 1 観測 (例:1 都道府県・1 年度)。
column
1 列 = 1 変数 (例:人口・出生数)。
table
1 テーブル = 1 種類の観測単位。 異種を混ぜない。
Long vs Wide
Long = tidy、 Wide = クロス表。

🔬 Tidy data を言葉で読み解く(narration )

Tidy data の 3 原則を Hadley Wickham(2014, JSS)の言葉で書くと:

  1. 変数は 1 つの(Each variable forms a column)
  2. 観測は 1 つの(Each observation forms a row)
  3. 観測単位の種類は 1 つの(Each type of observational unit forms a table)

この 3 原則を SSDSE-B-2026 で噛み砕いてみましょう。 SSDSE-B-2026 は 47 行(都道府県)× 約 100 列(変数) の表。 (1) 列名「総人口」「消費支出」「合計特殊出生率」 は 変数。 (2) 各行「東京都」「大阪府」「沖縄県」 は 観測(観測単位=都道府県)。 (3) 「都道府県」 という観測単位が 1 つの表に。 これは tidy です。

逆に「Excel 風」 の表(年×列、 都道府県×行、 セルに値) は wide format で、 多くの場合 untidy。 たとえば「年 ×(東京都, 大阪府, 沖縄県)」 のような表で、 列名が「東京都の人口」「大阪府の人口」… となっていたら、 「都道府県」 という変数が 列名に埋め込まれている ── これは tidy 原則 (1) 違反。 解決法は pd.melt() で「都道府県」 列に縦長化すること。

なぜ tidy にこだわるか? ── 分析パイプラインの標準化のため。 pandas, dplyr, ggplot2, scikit-learn など現代のデータ分析ツールは tidy 入力を前提 に設計されています。 untidy なデータを与えると、 「集計したいけど列名がバラバラ」「グラフを描きたいけど縦長じゃない」 と詰まる。 Wickham 自身が「tidy data はゴールではなく出発点」 と述べているとおり、 整形に時間を掛けるほど後の分析が楽になる。

SSDSE-B-2026 を tidy にすることで、 47 都道府県 × 100 変数 = 4,700 セルが 4,700 行 × 3 列(都道府県, 変数名, 値) に変換される。 すると、 「変数名 = 合計特殊出生率」 だけ抽出して 47 行を取り出すのが df.query('変数名 == "合計特殊出生率"') 1 行。 「変数名 = 消費支出(二人以上の世帯)」 と join するのも 1 行。 これが tidy の生産性です。

🧮 実値で計算してみる

SSDSE-B のクロス表を tidy 形式に変換する例。

STEP 1 ワイドの読み込み
年度ごとに列がある形式。
STEP 2 melt で long 化
id_vars に「都道府県」、 value_vars に年度列を指定。
STEP 3 型整形
年度を int、 値を float に。
STEP 4 可視化
seaborn.lineplot(x='年度', y='人口', hue='都道府県')

🧮 SSDSE-B-2026 を tidy に変換

SSDSE-B-2026 は すでに tidy 形式(47 行 × 多数列、 各都道府県が観測単位)。 これを「超 tidy(long format)」 に変換すると、 4,700 行 × 3 列の縦長表になります:

都道府県 変数名
北海道総人口5,092,000
北海道消費支出(二人以上の世帯)296,888
北海道合計特殊出生率1.06
東京都総人口14,086,000
東京都消費支出(二人以上の世帯)341,320
東京都合計特殊出生率0.99
沖縄県総人口1,468,000
沖縄県消費支出(二人以上の世帯)251,222
沖縄県合計特殊出生率1.60

この形式は ggplot2 や seaborn でプロットに直接渡せます。 facet_wrap('変数名') 1 行で 100 個の指標を並べて描けます。

📏 Wide vs Long フォーマット — 使い分けの実務

tidy の概念は重要ですが、 「常に long が tidy」 ではありません。 場面によって最適形が変わる

用途 推奨 理由
機械学習の入力Wide特徴量行列 X が必要
可視化(ggplot2/seaborn)Longfacet 指定に使う
集計(groupby)Long変数別の処理が楽
表示・Excel 出力Wide人間が読みやすい
DB 保存Long または EAVスキーマ拡張容易
時系列分析Long(時間=長辺)resample, rolling が効く

pivotmelt は対の操作。 wide ↔ long を自在に行き来できることが tidy data 思想の核心です。

🧮 数式に値を入れて手で計算する: tidy 化前後の行数

合成 wide テーブル (3 行×4 月) を tidy (long) に変換した行数を計算する。

Step 1: wide

3 顧客 × 4 月 = 12 セル wide 形状: (3, 5) (id+4 月)

Step 2: long

tidy 行数 = 3 × 4 = 12 行 形状: (12, 3) (id, 月, 値)

🐍 Python で再現

1
2
3
4
import pandas as pd
wide = pd.DataFrame({'id':['A','B','C'], 'Jan':[10,20,30],'Feb':[12,22,32],'Mar':[14,24,34],'Apr':[16,26,36]})
long = wide.melt(id_vars='id', var_name='月', value_name='値')
print(f"wide 形: {wide.shape}, long 形: {long.shape}")

📤 実行結果

wide 形: (3, 5), long 形: (12, 3)

💬 手計算 (Step 2) 12 行と Python 出力が完全一致。

🐍 Python 実装

最小実装の例。 SSDSE のような実データに対して、 まずはコピペで動かしてみるのが理解の早道です。

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
1
2
3
4
5
import pandas as pd
df_wide = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df_long = df_wide.melt(id_vars='Prefecture',
                       var_name='指標', value_name='値')
print(df_long.head())
📤 実行例(実測) Prefecture 指標 値 0 北海道 SSDSE-B-2026 2023 1 北海道 SSDSE-B-2026 2022 2 北海道 SSDSE-B-2026 2021 3 北海道 SSDSE-B-2026 2020 4 北海道 SSDSE-B-2026 2019

🐍 Tidy 変換コード(SSDSE-B-2026)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4103(合計特殊出生率) C5401(標準価格(平均価格)(住宅地)) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1.06 23,600 296,888 東京都 14,086,000 0.99 404,400 341,320 沖縄県 1,468,000 1.6 68,100 251,222 …(全 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
import pandas as pd

# SSDSE-B-2026 読み込み(2023 年度・コード列名を日本語へリネーム)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = (df[df['SSDSE-B-2026'] == 2023]
      .rename(columns={'Prefecture': '都道府県', 'A1101': '総人口',
                       'A4103': '合計特殊出生率', 'L3221': '消費支出',
                       'C5401': '標準地価'})
      .drop(columns=['SSDSE-B-2026', 'Code']))

# wide → long 変換(melt)
df_long = df.melt(id_vars=['都道府県'],
                   var_name='変数名',
                   value_name='値')
print(df_long.head(10))
print(f'\nshape: {df_long.shape}')  # (47*109, 3) = (5123, 3)

# long → wide 変換(pivot)
df_wide = df_long.pivot(index='都道府県',
                         columns='変数名',
                         values='値').reset_index()
print(f'\nwide shape: {df_wide.shape}')  # (47, 110)

# tidy の威力:変数別の集計が 1 行
summary = df_long.groupby('変数名')['値'].agg(['mean', 'std', 'min', 'max'])
print(summary.head())

# tidy の威力:複数変数を facet で同時可視化
import seaborn as sns
import matplotlib.pyplot as plt
target_vars = ['合計特殊出生率', '消費支出', '標準地価']
df_subset = df_long[df_long['変数名'].isin(target_vars)]
g = sns.FacetGrid(df_subset, col='変数名', col_wrap=3, height=4, sharey=False)
g.map(sns.histplot, '値')
plt.savefig('tidy_facet.png', dpi=120)
📤 実行例(実測) 都道府県 変数名 値 0 北海道 総人口 5092000.0 1 青森県 総人口 1184000.0 2 岩手県 総人口 1163000.0 3 宮城県 総人口 2264000.0 4 秋田県 総人口 914000.0 5 山形県 総人口 1026000.0 6 福島県 総人口 1767000.0 7 茨城県 総人口 2825000.0 8 栃木県 総人口 1897000.0 9 群馬県 総人口 1902000.0 shape: (5123, 3) wide shape: (47, 110) mean std min max 変数名 A110101 1.287085e+06 1.373649e+06 257000.0 6914000.0 A110102 1.358723e+06 1.424664e+06 280000.0 7172000.0 A1102 2.578617e+06 2.691343e+06 532000.0 13448000.0 A110201 1.253234e+06 1.320011e+06 255000.0 6594000.0 A110202 1.325319e+06 1.372139e+06 277000.0 6854000.0

3 つのコア操作 melt / pivot / groupby を覚えれば、 SSDSE のような表データを自在に変形できます。

🐍 Python 実装:SSDSE-B-2026 を tidy 化する完全パイプライン

🎯 このコードでやること:SSDSE-B-2026 を読み込み、 「都道府県 × 指標」の wide 形式から「都道府県, 指標名, 値」の long 形式(tidy)へ melt で変換。 さらに pivot_table で wide に戻して可逆性を確認する。

📥 入力データ(wide 形式、 SSDSE-B-2026 抜粋):

地域コード 都道府県 総人口 消費支出 出生数 0 R01000 北海道 5092000 296888 24430 1 R02000 青森県 1184000 263371 5696 2 R03000 岩手県 1163000 298536 5432 3 R04000 宮城県 2264000 305541 12328 4 R05000 秋田県 914000 272086 3611
 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

# SSDSE-B-2026 を読込(1 行目=コード見出し、 2 行目=日本語見出しを除外)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])

# 2023 年の断面を取り出し、 コード列を日本語名にリネーム
df = (df[df['SSDSE-B-2026'] == 2023]
      .rename(columns={'Code': '地域コード', 'Prefecture': '都道府県',
                       'A1101': '総人口', 'L3221': '消費支出', 'A4101': '出生数'})
      .reset_index(drop=True))

# 関心ある列だけ抽出(wide 形式)
wide = df[['地域コード', '都道府県', '総人口', '消費支出', '出生数']].head(5)
print('--- wide 形式(変換前)---')
print(wide)

# wide → long (tidy 化): melt
long_tidy = wide.melt(
    id_vars=['地域コード', '都道府県'],
    var_name='指標',
    value_name='値'
)
print('\n--- long 形式(tidy data)---')
print(long_tidy.head(15))

# long → wide (元に戻す): pivot_table
wide_back = long_tidy.pivot_table(
    index=['地域コード', '都道府県'],
    columns='指標',
    values='値'
).reset_index()
print('\n--- pivot で wide に戻した結果 ---')
print(wide_back)

📤 実行結果

--- wide 形式(変換前)--- 地域コード 都道府県 総人口 消費支出 出生数 0 R01000 北海道 5092000 296888 24430 1 R02000 青森県 1184000 263371 5696 2 R03000 岩手県 1163000 298536 5432 3 R04000 宮城県 2264000 305541 12328 4 R05000 秋田県 914000 272086 3611 --- long 形式(tidy data)--- 地域コード 都道府県 指標 値 0 R01000 北海道 総人口 5092000 1 R02000 青森県 総人口 1184000 2 R03000 岩手県 総人口 1163000 3 R04000 宮城県 総人口 2264000 4 R05000 秋田県 総人口 914000 5 R01000 北海道 消費支出 296888 6 R02000 青森県 消費支出 263371 7 R03000 岩手県 消費支出 298536 8 R04000 宮城県 消費支出 305541 9 R05000 秋田県 消費支出 272086 10 R01000 北海道 出生数 24430 11 R02000 青森県 出生数 5696 12 R03000 岩手県 出生数 5432 13 R04000 宮城県 出生数 12328 14 R05000 秋田県 出生数 3611 --- pivot で wide に戻した結果 --- 指標 地域コード 都道府県 出生数 消費支出 総人口 0 R01000 北海道 24430.0 296888.0 5092000.0 1 R02000 青森県 5696.0 263371.0 1184000.0 2 R03000 岩手県 5432.0 298536.0 1163000.0 3 R04000 宮城県 12328.0 305541.0 2264000.0 4 R05000 秋田県 3611.0 272086.0 914000.0

💬 結果の読み方:melt は「列をそのまま縦長に並べる」だけのシンプル操作。 long 化することで 1 行 = 1 観察 = (都道府県, 指標) の組 となり、 ggplot や seaborn の hue='指標' がそのまま使えるようになる。 pivot で wide に戻せば Excel 風表示にも対応。

🐍 Python 実装:scikit-learn 形式(X, y)への変換

🎯 このコードでやること:SSDSE-B-2026 tidy data から scikit-learn の (X, y) 形式(特徴量行列とターゲット)へ変換し、 そのまま線形回帰モデルに投入する。

📥 入力データ:tidy data 形式の SSDSE-B-2026。

地域コード 都道府県 総人口 消費支出 65歳以上人口 合計特殊出生率 R01000 北海道 5092000 296888 1681000 1.06 R02000 青森県 1184000 263371 417000 1.23 ...
 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
from sklearn.linear_model import LinearRegression
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].rename(columns={
    'A1101': '総人口', 'L3221': '消費支出',
    'A1303': '65歳以上人口', 'A4103': '合計特殊出生率'})

# tidy 形式から (X, y) へ
feature_cols = ['総人口', '消費支出', '65歳以上人口']
target_col = '合計特殊出生率'

# 欠損を除去
df_use = df[feature_cols + [target_col]].dropna()

# 特徴量行列 X とターゲット y
X = df_use[feature_cols].values   # shape: (n_samples, n_features)
y = df_use[target_col].values     # shape: (n_samples,)

print(f'X shape: {X.shape} (n_samples × n_features)')
print(f'y shape: {y.shape}')

# 標準化(オプション)
X_scaled = StandardScaler().fit_transform(X)

# 線形回帰
model = LinearRegression()
model.fit(X_scaled, y)
coef = {c: round(float(v), 3) for c, v in zip(feature_cols, model.coef_)}
print(f'\n係数: {coef}')
print(f'切片: {model.intercept_:.2f}')
print(f'決定係数 R²: {model.score(X_scaled, y):.4f}')

📤 実行結果

X shape: (47, 3) (n_samples × n_features) y shape: (47,) 係数: {'総人口': 0.164, '消費支出': -0.045, '65歳以上人口': -0.225} 切片: 1.29 決定係数 R²: 0.4633

💬 結果の読み方:tidy data ならカラム選択 → .values → モデル fit が 3 行。 untidy(ピボット形式)だと前処理に 30 行は要る。 R² = 0.46 は中程度の説明力で、 65歳以上人口の負係数(高齢人口↑ → 合計特殊出生率↓)が直観に合う。

⚠️ よくある落とし穴

この用語を使うときに陥りがちな失敗パターン。 経験者ほどここに 1 度はハマっています。

❌ Wide のまま分析
for ループで列を回す羽目になり、 コードが複雑化。
❌ 過剰な tidy 化
巨大な long 表はメモリを食う。 集計後に wide 化する場面もある。
❌ 複数観測単位の混在
県データと市町村データを同じテーブルに混ぜない。
❌ 値の単位混在
1 列内に「人/千人」が混じると即座に分析破綻。

⚠️ Untidy の 5 大パターン(Wickham 2014)

  1. 列ヘッダが値:列名が「2020年」「2021年」「2022年」… → 「年」 という変数が列に埋まっている。 melt で long 化。
  2. 複数の変数が 1 列:列「年齢_性別」 が「30_男」「40_女」… → str.split() で分離。
  3. 1 セルに複数値:列「タグ」 に「読書,料理,旅行」 のような連結値 → str.split + explode。
  4. 同じ変数が複数列:列「収入_2022」「収入_2023」 → melt で年を変数化。
  5. 異なる観測単位が 1 表:個人レベルと世帯レベルが混在 → 分割して 2 表に。

🏛 政府統計と tidy data — e-Stat / SSDSE の評価

日本の政府統計(e-Stat) の多くは Excel ファイル形式で、 untidy な構造を持っています。 セル結合、 ヘッダ階層、 表中表 ── 美しく印刷するためのレイアウトが、 機械可読性を犠牲にしています。

SSDSE(Statistical Standardized Data Set for Education) は、 まさに「政府統計を tidy 化したデータセット」 です。 統計センターが教育用に整備し、 1 行 1 都道府県(または市区町村)、 1 列 1 変数の構造を厳守。 これにより、 学生や研究者が 整形に時間を掛けず、 分析に集中できる

本サイトが SSDSE を主軸に据えるのも、 「tidy データから始めることで、 ジャストインタイム型学習がスムーズに回る」 から。 ぜひ e-Stat の生 Excel と SSDSE 版の同じ統計を比較して、 tidy の威力を体感してください。

🛠 Tidyverse — Hadley Wickham の生態系

R の tidyverse は、 tidy data 原則を基礎にした統合パッケージ群:

パッケージ 役割 Python 等価
readrCSV/TSV 読込pandas.read_csv
tidyrtidy 化(pivot/melt)pd.melt / pd.pivot
dplyrデータ操作pandas / polars
ggplot2グラフィックスseaborn / plotnine
purrr関数型処理map / functools
tibble改良 data framepd.DataFrame
stringr文字列処理str.* メソッド
forcatsfactor 処理pd.Categorical

Python では plotnine(ggplot2 移植) や plydata(dplyr 移植) がある。 また polars は tidyverse 風 API で高速。

🔄 Tidy の代替・拡張概念

(a) Tidy 拡張:Codd の第1正規形

tidy data 原則は、 リレーショナルデータベース理論の 第1正規形(1NF) と本質的に同じ。 Wickham は統計家向けに言い直しただけとも言えます。

(b) EAV(Entity-Attribute-Value)

tidy の long format は EAV モデルと同型。 「都道府県 / 変数名 / 値」 がまさに EAV。 ただし大量データでは検索性能が課題。

(c) Star Schema

データウェアハウスで使う 中心 fact 表 + 周辺 dimension 表 の構造。 BI ツール向けに最適化されている。

(d) Arrow / Parquet

tidy はあくまで論理構造。 物理保存形式は 列指向(Parquet, Arrow) が現代の主流。 圧縮効率と読み込み速度が桁違い。

📘 ケーススタディ 3 件

ケース 1:SSDSE-E(時系列) を tidy 化

SSDSE-E は「2010年」「2011年」… のような 年が列に 入った wide format。 これを melt で 都道府県 / 変数 / 年 / 値 の 4 列に変換すると、 時系列プロットや経年比較が一気に楽になる。

ケース 2:家計調査 SSDSE-C

「品目別 × 地域別」 の 2 次元集計を、 「品目 / 地域 / 支出額」 の long に。 「肉類の支出が最も多い都市は?」 が groupby('地域').sum() 1 行。

ケース 3:自治体オープンデータ

市町村が公開する Excel は untidy の宝庫。 セル結合、 階層ヘッダ、 注釈行が混在。 これを tidy 化するときは、 まず手動で「ヘッダ行はどこか」「データ開始行はどこか」 を特定し、 pd.read_excel(skiprows=, header=) で読み込む。 その後 melt で long 化。

🚀 現代の tidy — DuckDB / polars / DataFrames.jl

tidy data 思想は SQL の世界にも浸透。 DuckDB(埋め込み OLAP)、 polars(Rust 製 DataFrame)、 DataFrames.jl(Julia)、 いずれも tidy 入力を前提に高速処理を実現。

SSDSE-B-2026 は小規模(47×100)なので Pandas で十分ですが、 全国市区町村 SSDSE-D(1700 行 × 100 列) や時系列 SSDSE-E では polars / DuckDB の利点が出てきます。

📜 Tidy data の哲学 — Hadley Wickham の論文

原論文:Wickham, H. (2014). "Tidy Data". Journal of Statistical Software, 59(10), 1-23. オープンアクセスで誰でも読めます。

Wickham は次の問題意識から tidy data を提案:「データクリーニングと分析の境界がぼやけている。 分析時間の 80% がデータ整形に消費される。 これを 規範化 することで、 ツール開発と教育が大幅に効率化できる。」

tidy 原則は単なる「綺麗な表」 のことではなく、 標準化を通じて生産性を上げる思想。 ある意味で「データのオブジェクト指向設計」 と呼べる発想で、 ggplot2 や dplyr が「tidy 入力に対する標準操作」 として設計されたからこそ、 tidyverse は爆発的に普及しました。

Wickham 自身は近年、 「tidy data は終着点ではなく、 出発点」 と強調。 「整形済みデータから始めることで、 創造的な分析に集中できる。 SSDSE はその精神を体現したデータセット」 と評価できます。

✅ Tidy data チェックリスト 20 項目

構造

命名

運用

🔁 高度な melt / pivot — 複数 ID, 複数 value

基本の melt / pivot を超えた応用例。 SSDSE-B-2026 で 都道府県 + 年度 を ID にし、 wide → long → wide を往復する例:

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4103(合計特殊出生率) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1.06 296,888 東京都 14,086,000 0.99 341,320 沖縄県 1,468,000 1.6 251,222 …(全 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
40
41
42
43
44
45
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]
      .rename(columns={'Prefecture': '都道府県', 'A1101': '総人口',
                       'A4103': '合計特殊出生率', 'L3221': '消費支出'}))

# 複数 ID で melt
df_long = df.melt(
    id_vars=['都道府県'],
    value_vars=['総人口', '合計特殊出生率', '消費支出'],
    var_name='指標',
    value_name='値'
)
print(df_long.head())

# wide_to_long: 列名が「指標_年」 形式のとき強力
# 例:列が「人口_2020」「人口_2021」「失業率_2020」「失業率_2021」 …
df_wide = pd.DataFrame({
    '都道府県': ['東京都', '大阪府', '沖縄県'],
    '人口_2020': [13960, 8838, 1468],
    '人口_2021': [13960, 8806, 1468],
    '失業率_2020': [2.6, 3.2, 3.5],
    '失業率_2021': [2.7, 3.3, 3.7],
})
df_wl = pd.wide_to_long(
    df_wide, stubnames=['人口', '失業率'],
    i='都道府県', j='年', sep='_', suffix='\\d+'
).reset_index()
print(df_wl)

# pivot_table: 集計と pivot を同時に
pivot = df_long.pivot_table(
    index='都道府県', columns='指標', values='値', aggfunc='mean'
)
print(pivot.head())

# crosstab: カテゴリ × カテゴリ の度数集計
# 例:合計特殊出生率を 3 階級に区切って都道府県分布を見る
df['出生率3階級'] = pd.cut(df['合計特殊出生率'],
                          bins=[0.9, 1.2, 1.4, 1.7],
                          labels=['低', '中', '高'])  # 2023年度: 低11県・中27県・高9県
print(pd.crosstab(df['出生率3階級'], df['都道府県'].apply(
    lambda x: '関東' if x in ['東京都','神奈川県','埼玉県','千葉県'] else '関東外'
)))
📤 実行例(実測) 都道府県 指標 値 0 北海道 総人口 5092000.0 1 青森県 総人口 1184000.0 2 岩手県 総人口 1163000.0 3 宮城県 総人口 2264000.0 4 秋田県 総人口 914000.0 都道府県 年 人口 失業率 0 東京都 2020 13960 2.6 1 大阪府 2020 8838 3.2 2 沖縄県 2020 1468 3.5 3 東京都 2021 13960 2.7 4 大阪府 2021 8806 3.3 5 沖縄県 2021 1468 3.7 指標 合計特殊出生率 消費支出 総人口 都道府県 三重県 1.29 332663.0 1727000.0 京都府 1.11 314636.0 2535000.0 佐賀県 1.46 274861.0 795000.0 兵庫県 1.29 279880.0 5370000.0 北海道 1.06 296888.0 5092000.0 都道府県 関東 関東外 出生率3階級 低 4 7 中 0 27 高 0 9

🔄 dplyr 操作と Pandas 対応表

dplyr (R) Pandas (Python) 機能
filter()df.query() / df[cond]行フィルタ
select()df[cols] / df.filter()列選択
mutate()df.assign()列追加
arrange()df.sort_values()並び替え
summarise()df.agg()集約
group_by()df.groupby()グループ化
pivot_longer()pd.melt()wide → long
pivot_wider()df.pivot()long → wide
separate()df['col'].str.split(expand=True)列分割
unite()df['col1'].str.cat()列連結
left_join()df.merge(how='left')結合
inner_join()df.merge(how='inner')内部結合
distinct()df.drop_duplicates()重複除去
count()df.value_counts()頻度カウント
top_n()df.nlargest() / nsmallest()上位 N 抽出

🛠 SSDSE → tidy → 分析 完全パイプライン

SSDSE-B-2026 を tidy 化し、 分析・可視化まで一気通貫:

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) A4103(合計特殊出生率) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 24,430 1.06 296,888 東京都 14,086,000 3,205,000 86,348 0.99 341,320 沖縄県 1,468,000 350,000 12,549 1.6 251,222 …(全 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import os
os.makedirs('data', exist_ok=True)   # 書き出し先を先に作る
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# ===== STEP 1: 読み込み(2023 年度・コード列名を日本語へ) =====
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = (df[df['SSDSE-B-2026'] == 2023]
      .rename(columns={'Prefecture': '都道府県', 'A1101': '総人口',
                       'A4103': '合計特殊出生率', 'L3221': '消費支出',
                       'A4101': '出生数', 'A1303': '65歳以上人口'})
      .drop(columns=['SSDSE-B-2026', 'Code']))
print(f'読み込み: {df.shape}')

# ===== STEP 2: tidy 化(wide → long) =====
df_long = df.melt(
    id_vars=['都道府県'],
    var_name='指標',
    value_name='値'
)

# ===== STEP 3: 指標別の基礎統計 =====
stats = df_long.groupby('指標')['値'].agg(
    ['mean', 'std', 'min', 'max', 'count']
).round(2)
stats.to_csv('data/processed/SSDSE_stats.csv')
print(stats.head())

# ===== STEP 4: 特定指標の都道府県ランキング =====
target = '消費支出'
ranking = df_long[df_long['指標'] == target].sort_values('値', ascending=False)
print(f'\n=== {target} TOP10 ===')
print(ranking[['都道府県', '値']].head(10))  # 1位 埼玉県 344,092 / 2位 東京都 341,320 / 3位 三重県 332,663

# ===== STEP 5: 複数指標の相関行列 =====
target_indicators = ['総人口', '合計特殊出生率',
                      '消費支出', '出生数', '65歳以上人口']
df_wide_subset = df[['都道府県'] + target_indicators]
corr = df_wide_subset[target_indicators].corr()
print('\n=== 相関行列 ===')
print(corr.round(2))  # 例: corr(総人口, 出生数)=1.00, corr(総人口, 合計特殊出生率)=-0.56

# ===== STEP 6: facet 可視化 =====
df_long_subset = df_long[df_long['指標'].isin(target_indicators)]
g = sns.FacetGrid(df_long_subset, col='指標', col_wrap=3,
                   height=4, sharey=False)
g.map(sns.histplot, '値', kde=True)
plt.savefig('data/processed/SSDSE_facets.png', dpi=120)

# ===== STEP 7: tidy で保存(long 形式) =====
df_long.to_csv('data/processed/SSDSE_long.csv', index=False)
df_long.to_parquet('data/processed/SSDSE_long.parquet')
print('\nパイプライン完了')
📤 実行例(実測) 読み込み: (47, 110) mean std min max count 指標 65歳以上人口 770829.79 693839.27 179000.0 3205000.0 47 A110101 1287085.11 1373648.93 257000.0 6914000.0 47 A110102 1358723.40 1424664.12 280000.0 7172000.0 47 A1102 2578617.02 2691342.94 532000.0 13448000.0 47 A110201 1253234.04 1320010.53 255000.0 6594000.0 47 === 消費支出 TOP10 === 都道府県 値 4616 埼玉県 344092.0 4618 東京都 341320.0 4629 三重県 332663.0 4621 富山県 327503.0 4614 栃木県 325226.0 4611 山形県 322992.0 4626 岐阜県 320779.0 4631 京都府 314636.0 4625 長野県 313991.0 4634 奈良県 313833.0 === 相関行列 === …(以下略)

⚠️ Tidy アンチパターン 10 連続

  1. 「年」 が列名:「2020年」「2021年」… → 「年」 列を作って melt
  2. セル結合:Excel で「東京都 | | 神奈川県」 のような結合セル → ffill で展開
  3. 合計行:「合計」「平均」 がデータ行に混入 → フィルタで除外
  4. 階層ヘッダ:1 行目「人口」、 2 行目「総数 男 女」 → MultiIndex 処理
  5. 注釈行:データの末尾に「※注:…」 → skipfooter で除外
  6. カンマ区切り数値:「1,000,000」 が文字列 → str.replace + astype
  7. 全角空白:「東京 都」 のように全角空白 → str.replace で正規化
  8. 欠損が「-」「N/A」「・・・」 → na_values で指定
  9. シート分割:年度ごとに別シート → pd.concat で結合
  10. 列名が日本語+単位:「人口(千人)」 → 別カラムに分離

📜 Tidy data の歴史

1970 年代:E.F. Codd がリレーショナルデータベースを提唱、 正規化理論を確立。

2000 年代:R の reshape, reshape2 パッケージ(Wickham) が melt/cast を一般化。

2014 年:Wickham が JSS に "Tidy Data" を発表。 統計家向けの「データのきれいさ」 規範を確立。

2016 年:tidyverse がメタパッケージとしてリリース。 R の標準スタイルに。

2020 年代:polars / DuckDB が「tidy 入力 + 列指向 + Arrow」 で次世代分析ツール群を構築。

2024 年〜:LLM 時代でも tidy data は健在。 「LLM に CSV を貼り付けて分析を頼む」 ようなユースケースでも、 入力が tidy だと出力品質が劇的に向上する。

🔍 Tidy data FAQ 追加

Q. tidy だと容量が増えませんか?

A. long 形式は同じ ID が繰り返されるので素朴には増えます。 ただし Parquet/Arrow 等の列指向で保存すると、 重複は圧縮され実質的に増えません。

Q. tidy が苦手な分野は?

A. 画像・音声・テキストなどの非構造化データ、 行列演算が中心の数値計算(NumPy 風)、 グラフ構造(NetworkX, igraph) では tidy 思想は適合しません。 適材適所。

Q. データベース設計と tidy の関係は?

A. tidy = 第3正規形(3NF)以上 を強く推奨。 ただし分析用途では、 結合済みの非正規化テーブル(star schema) を使うのが普通。 用途に応じて使い分け。

Q. tidy 化は完全自動化できますか?

A. 部分的に。 tidylab(実験的) や LLM ベースのツールが登場していますが、 「どの列が変数で、 どれが値か」 の判断は最終的に人間が必要。 SSDSE のように 提供元が整形済み のデータは例外的に楽。

Q. tidy data と pandas の MultiIndex の関係は?

A. MultiIndex(階層 index) は wide format の表現力を上げる pandas 機能ですが、 厳密には untidy です。 tidy 派の人は MultiIndex を避け、 すべて単純な long format にします。 一方、 ピボット集計用には MultiIndex が便利。 場面で使い分け。

🧰 Tidy data 関連ツール

🗃 複数テーブルの tidy 設計(SSDSE-B + SSDSE-D + SSDSE-E)

tidy 原則の 3 番目 ── 「各観測単位の種類は 1 つの表に」 が活きるのは複数表を扱うとき。 SSDSE では:

データセット 観測単位 行数 主キー
SSDSE-A都道府県47都道府県コード
SSDSE-B都道府県(拡張)47都道府県コード
SSDSE-C家計(都道府県別品目)数千都道府県 × 品目
SSDSE-D市区町村1700+市区町村コード
SSDSE-E都道府県 × 年47×N年都道府県 × 年

これらを 1 つの巨大テーブルにマージするのは untidy。 必要に応じて join で結合するのが tidy 流。

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
1
2
3
4
5
6
7
8
9
df_a = pd.read_csv('data/raw/SSDSE-A-2026.csv', encoding='cp932', skiprows=[1])
df_b = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df_e = pd.read_csv('data/raw/SSDSE-E-2026.csv', encoding='cp932', skiprows=[1])

# A + B を都道府県(Prefecture 列)でマージ
df_ab = df_a.merge(df_b, on='Prefecture', suffixes=('_A', '_B'))
# E も同じキーで結合
df_full = df_ab.merge(df_e, on='Prefecture')
print(df_full.shape)
📤 実行例(実測) (20892, 333)

🧹 Tidy 化に伴うデータクレンジング 12 ステップ

  1. Encoding 確認:utf-8, cp932 のいずれか。 SSDSE-B は cp932(Shift_JIS 系)。
  2. ヘッダ行確認:skiprows でずらすか、 header= を指定。
  3. 列名の半角化:全角空白・記号を半角に。
  4. 列名の正規化:スネークケース化、 単位の分離。
  5. 欠損表現の統一:「-」「N/A」「・・・」 を NaN に。
  6. 数値型変換:カンマ除去、 単位除去、 型変換。
  7. 日付型変換:「2026年5月19日」 等を datetime に。
  8. カテゴリ列の整理:表記揺れを統一(東京都・東京・Tokyo → 東京都)。
  9. 重複行の確認:drop_duplicates。
  10. 外れ値検出:箱ひげ図 / IQR / Tukey ルール。
  11. 合計行・注釈行の除去:「合計」「※」 を含む行を除外。
  12. tidy 化:melt で long に。

📊 Tidy データの生産性 — 定量比較

同じ分析タスク(「47 都道府県の合計特殊出生率を棒グラフで」) を、 tidy データと untidy データで比較:

指標 tidy untidy(Excel 直)
読込コード行数1 行5-10 行(skiprows, ヘッダ調整)
整形コード行数0-3 行20-50 行
プロット行数1 行(seaborn)5-10 行
合計時間1-5 分30-60 分
エラー発生率高(型不一致、 NaN 等)
再現性低(手作業が混ざる)

この差が、 SSDSE が「教育用標準データセット」 を名乗る所以です。

🧠 Tidy が苦手な領域

tidy data は強力ですが、 万能ではありません。 以下の領域では別のデータモデルが優位:

領域 なぜ苦手か 代替モデル
画像・音声高次元配列numpy ndarray, tensor
ネットワークグラフ構造NetworkX, igraph
階層構造親子関係JSON, tree
疎データ大半が 0scipy sparse, COO
テキスト可変長系列tokenizer, embedding
時空間3-4 次元構造xarray, netCDF

SSDSE は 2 次元の表データなので tidy 思想が完全に適合。 ただし時系列軸が増えた SSDSE-E のような場合、 xarray を併用すると便利。

🔧 Pandas での tidy 実装詳細

melt の主要引数

pivot の主要引数

pivot_table の主要引数

stack / unstack

⚙ エッジケース対応

1. 一行に複数値が入った列

「タグ = 経済,人口,健康」 のような列 → df.assign(tag=df['tag'].str.split(',')).explode('tag')

2. 階層列名

「人口/総数/男」「人口/総数/女」 → df.columns = ['_'.join(c) for c in df.columns] で平坦化、 melt 後に str.split で分離

3. 同じ列名が複数

CSV に重複列名がある場合 pandas は自動で _1, _2 を付ける。 必ず読み込み直後に確認。

4. 完全に空の行・列

df.dropna(how='all') で全 NaN の行を除去。 列も同様に axis=1

5. mixed type 列

数値と文字列が混在(「100」「N/A」) → pd.to_numeric(df['col'], errors='coerce') で N/A を NaN に変換。

✅ Tidy 検証 — pandera / great_expectations

tidy 化したデータを「本当に tidy か」 検証するライブラリ:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandera as pa

schema = pa.DataFrameSchema({
    '都道府県': pa.Column(str, nullable=False, unique=True),
    '指標': pa.Column(str, nullable=False),
    '値': pa.Column(float, nullable=True),
}, strict=True)

# tidy データを検証
schema.validate(df_long)

📋 SSDSE-B-2026 実値 tidy 例(複数指標)

47 都道府県の主要 5 指標を long format で並べた抜粋(2023 年度の実測値。 高齢化率のみ 65 歳以上人口 ÷ 総人口から導出):

都道府県 指標 単位
北海道総人口5,092,000
北海道合計特殊出生率1.06
北海道消費支出(二人以上の世帯)296,888円/月
北海道高齢化率33.0%
北海道標準地価(住宅地)23,600円/m²
東京都総人口14,086,000
東京都合計特殊出生率0.99
東京都消費支出(二人以上の世帯)341,320円/月
東京都高齢化率22.8%
東京都標準地価(住宅地)404,400円/m²
大阪府合計特殊出生率1.19
大阪府消費支出(二人以上の世帯)271,246円/月
愛知県合計特殊出生率1.29
愛知県消費支出(二人以上の世帯)300,221円/月
福岡県合計特殊出生率1.26
福岡県消費支出(二人以上の世帯)309,000円/月
沖縄県合計特殊出生率1.60
沖縄県消費支出(二人以上の世帯)251,222円/月
沖縄県高齢化率23.8%

このテーブルは「47都道府県 × 5指標 = 235 行」 の縮約版。 実際のtidy long 形式では 4,700 行 まで展開可能。

⚠️ 「tidy 化しすぎ」 もアンチパターン

tidy はゴールではなく道具。 過剰に long 化すると逆効果なケース:

SSDSE-B-2026(47 行 × 100 列) の規模では、 long でも wide でも問題なし。 用途で使い分け。

🎯 Tidy data — 最終まとめ

Tidy data は データ分析の標準言語。 SSDSE-B-2026 のような公的統計を扱うとき、 tidy 化することで pandas, ggplot2, scikit-learn など現代ツールの全機能が活かせるようになります。 wide と long の使い分け、 melt と pivot の操作を体に染み込ませてください。

関連用語:データ前処理、 テーブルPandasCSV、 データクレンジング も合わせて学ぶと、 データエンジニアリングの全体像が掴めます。

🗺 概念マップ

Tidy data (1 行 1 観測・1 列 1 変数) を中心に、 wide↔long 変換 (pivot/melt)、 グループ集計、 結合、 EDA・可視化への接続を 6 方向に展開。

Tidy data 1 行 = 1 観測 1 列 = 1 変数 wide format (untidy) pd.melt / pivot groupby / 集計 データクレンジング

「Tidy data」を中心に、 上流に CSV / Excel 読込データクレンジング、 並列に Wide format (untidy)・横方向展開、 下流に groupbyピボットテーブルggplot2 / seaborn が並ぶ。 SSDSE-B-2026 の「総人口・15歳未満人口・15-64歳人口・65歳以上人口」が列に並んだ形を long に pd.melt で変換すると tidy になる。

🔗 隣接手法への橋渡し

Tidy data は分析パイプラインの「中継地点」で、 上流の取込みと下流の集計をつなぐ。

SSDSE-B-2026 (47 県 × 12 年 × 50 指標) を tidy にすると 28,200 行になるが、 groupbypivot が即座に動くので分析速度が劇的に上がる。

🌳 手法選択フロー

データを wide / long どちらに整えるかは、 次の分析目的で決める。

  1. 1 行 = 1 観測 (例: 「2022 年の北海道の総人口」)? Yes → tidy / long format。 melt で展開
  2. 表として人が読む? Yes → wide format。 pivot で 47 県 × 年の表に変換
  3. 機械学習の特徴量? Yes → wide (1 行 1 サンプル、 列が特徴量)、 sklearn はこの形式を要求
  4. 可視化 (seaborn / ggplot)? Yes → tidy / long が必須。 hue=, col= 引数は long format を前提

SSDSE-B-2026 では、 EDA 段階では tidy で seaborn、 モデリング段階では wide で sklearn に渡す、 という 2 段階運用が定石。

🧭 もう一歩ふみこむ — パネルとしての SSDSE-B と「melt しすぎ」問題

このページの本文は melt / pivot / 5 大パターン / 正規形との対応まで丁寧に扱っています。ここでは重複を避け、姉妹ページ(table / structured-data)とも角度を変えて、SSDSE-B-2026 が「もともと縦持ちのパネル」であることに焦点を当て、そこから生まれる実戦的な落とし穴を実測値で確認します。

💡 直感 — SSDSE-B は「半分 tidy」な形をしている

SSDSE-B-2026 は 564 行 × 112 列。この 564 は偶然ではなく 47 都道府県 × 12 年(2012〜2023) のパネル(縦持ち)です。SSDSE-B-2026 列に年、Code/Prefecture 列に県が入り、年が列名として横に広がっていない点で、tidy 原則の「列名は値であってはならない」を最初から満たしています。

つまり SSDSE-B は「年方向は long、指標方向は wide」という中間形。この形が分かると、melt すべきなのは 指標側だけで、年をさらに動かす必要はない、と即座に判断できます。実測で確認すると、東京都の総人口(列 A1101)は本文の melt デモの通り年ごとに 1 行ずつ並びます:

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
1
2
3
4
5
6
7
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
print(df.shape)                 # (564, 112)  ← 47県 × 12年
tokyo = df[df['Prefecture'] == '東京都']
print(len(tokyo))               # 12  (2012〜2023 の 1 県ぶんが 12 行)
print(int(tokyo.loc[tokyo['SSDSE-B-2026']==2012, 'A1101'].iloc[0]))   # 13234000
print(int(tokyo.loc[tokyo['SSDSE-B-2026']==2023, 'A1101'].iloc[0]))   # 14086000
📤 実行例(実測) (564, 112) 12 13234000 14086000

📝 東京都の総人口は 2012 年 13,234,000 人 → 2023 年 14,086,000 人(いずれも SSDSE-B-2026 実測値)。1 行 = 「東京都 × その年」という 複合キーの 1 観測になっています。

⚠️ 落とし穴(重要)— 観測単位は「県」ではなく「県 × 年」

tidy 3 原則の「1 行 = 1 観測」は満たしていても、その観測単位が何かを取り違えると集計が壊れます。SSDSE-B の観測単位は 県 × 年。にもかかわらず 564 行をひとかたまりとして県だけで groupby すると、12 年ぶんを黙って足し込んでしまいます。

1
2
3
4
5
6
7
8
# ❌ 年でフィルタせず県だけで合算 — 12年ぶんが混入する
wrong = df.groupby('Prefecture')['A1101'].sum()
print(wrong.idxmax(), int(wrong.max()))   # 東京都 164944865  ← 人口1.6億人?!
print(int(df['A1101'].sum()))             # 1517547844      ← 全国15億人?!

# ✅ まず年で 1 断面を取り出してから集計する
d23 = df[df['SSDSE-B-2026'] == 2023]      # 47 行
print(len(d23), int(d23['A1101'].sum()))  # 47 124353000   ← 日本の総人口 約1.24億人
📤 実行例(実測) 東京都 164944865 1517547844 47 124353000

誤った合算では東京都の総人口が 164,944,865(1.6 億人)、全国合計が 1,517,547,844(15 億人)という現実にありえない値になります。正しく 2023 年だけを取り出すと全国合計は 124,353,000 人で、日本の総人口(約 1.24 億人)と一致します。これは「tidy な表なら安全」ではないことの好例です — 構造が tidy でも、集計者が観測単位を誤解すればナンセンスな数が出ます。

もう一つの「melt しすぎ」の罠:単位の異なる指標を 1 本の value 列に押し込むと、tidy 原則「1 列 = 1 変数」に違反します。総人口(単位: 人)と合計特殊出生率(単位: 率)を同じ value 列に melt すると、その列の mean()std() は「人と率の混合」で無意味になります。melt は万能キーではなく、同じ単位・同じ意味の列群にだけかけるのが鉄則です。

🚀 発展 — 「tidy 税(tidy tax)」と往復の可逆性

long(縦持ち)は分析ツールに優しい一方、キー列を各行で反復するぶんメモリを食います。SSDSE-B の ID 3 列を残して残り 109 列を完全に melt すると:

1
2
3
4
5
full = df.melt(id_vars=['SSDSE-B-2026','Code','Prefecture'],
               var_name='indicator', value_name='value')
print(len(full))                                  # 61476  = 564 × 109
print(df.memory_usage(deep=True).sum())           # 573900   (wide, bytes)
print(full.memory_usage(deep=True).sum())         # 12781380 (long, bytes)
📤 実行例(実測) 61476 573900 12781380

行数は 564 → 61,476 行(109 倍)、メモリは約 22.3 倍(573,900 → 12,781,380 バイト、実測)に膨張します。これが「tidy 税」— 保存は wide/Parquet が有利、分析は long が有利という使い分けの根拠です。本文の「現代の tidy(DuckDB / polars)」がなぜ列指向を推すのかも、この数字を見ると腑に落ちます。

救いは 可逆性です。tidy 変換が正しく設計されていれば meltpivot は情報を失いません。上の full を県×年×指標をキーに pivot し直せば、元の 564×112 に戻ります。「wide/long は同じデータの 2 つの表示」という本文の主張は、この往復が可逆であることに支えられています。

🔗 関連ページ