🍰 まずはやさしく
整理整頓された表のようなデータです。
データの分析や前処理に使いやすいためです。
テストの点数をまとめた成績表のようなものです。
このデータの特徴と使い方を学びます。
表形式など定まった構造のデータ
structured data を 30 秒で把握する重要ポイント:
🍰 まずはやさしく
行と列できれいに並んだデータのことです。
データの種類をはっきりさせるために使います。
スマホの連絡先リストのような形式です。
このページで扱うデータの形を説明します。
あなたは今、 構造化データ (Structured Data) という、 行と列で整然と並び、 各列に明確なデータ型が定義された形式のデータの用語ページを見ています。 SSDSE-B-2026 (47 都道府県 × 12 年度 = 564 行 × 112 列の CSV) はその典型例で、 RDB / SQL / pandas DataFrame で扱える基本形式です。 本ページの多くの例は最新の 2023 年度に絞った 47 行の断面で説明します。 非構造化データ (画像・音声・自由文) との対比で理解します。
🍰 まずはやさしく
決まった枠に値が入っているデータです。
計算や分析をスムーズに行うために使います。
部活の出席簿のように整理された表のことです。
なぜこの形が便利なのかを解説します。
構造化データは「行 = 観測、 列 = 変数」のように事前に決まった枠(スキーマ)に値が入っているデータ。 SSDSE-B-2026 はその典型で、 564 行(47 都道府県 × 12 年度 2012–2023)× 112 列(先頭列 = 年度、 続いて Code・Prefecture・A1101 総人口・A1303 65 歳以上人口・A4101 出生数・B4101 年平均気温・L3221 消費支出 …)の CSV は pd.read_csv() 1 行で読めて、 すべての値が「どの行のどの列か」一意に決まる。
対極にあるのが「自由記述のアンケート文」「画像」「ログテキスト」などの非構造化データで、 そのままでは SUM や AVG が効かず、 まず構造化が必要。 構造化されていれば SQL、 pandas、 BI ツール、 機械学習の入力にそのまま使える。 「ML プロジェクトの 8 割の時間はデータ構造化に消える」と言われる所以である。
下のデモは、 まったく同じ情報(3 件の顧客購入記録、 架空データです)を 3 つの形式で並置したものです。 「検索」「集計」「ソート」ボタンを押すと、 3 つの形式それぞれに同じ操作を試みます。 構造化(表)は即座に正確な答えを返しますが、 非構造化(自由文)は単純な文字列検索しかできず、 表記揺れ(りんご / リンゴ / 林檎)で漏れ、 集計もソートも不能である様子を体感してください。 中間の半構造化(JSON)は「パースすれば処理できるが、 スキーマの保証は自前」という立ち位置です。
構造化側が正確に検索・集計・ソートできるのは、 次のスキーマ (列の名前・型・意味の約束事) が事前に決まっているからです。 「りんご」と「林檎」を同一視できたのも、 表記揺れを吸収する 商品コード 列をスキーマ設計の段階で用意してあったからです。
| 列名 | 型 | 意味 (機械が「知っている」こと) |
|---|---|---|
顧客 | 文字列 | 誰が買ったか。 グループ化キーに使える |
商品コード | 文字列 (コード体系) | 表記揺れを吸収した商品の一意 ID |
数量 | 整数 | 個数。 足し算してよい |
単価 | 整数 (円) | 1 個の値段。 数量と掛けてよい |
金額 | 整数 (円) = 数量 × 単価 | 合計・平均・ソートのキーに使える |
非構造化の文にも同じ情報は「書いてある」のに、 機械には 「佐藤さんがりんごを3個…」 のどこが数量でどこが単価か分かりません。 これが「構造化 = 検索・集計・ソートがアルゴリズム的に定義できる状態」という本ページの主張の、 いちばん小さな実例です。 SSDSE-B-2026 (564 行 × 112 列) が pd.read_csv() 1 行で分析可能になるのも、 同じ理屈です。
上のデモが示すとおり、 構造化データの本質は「値そのもの」ではなく値の置き場所と意味の約束事 (スキーマ) にあります。 約束事があるから、 プログラムは中身を読まなくても「3 列目は足してよい数」と決め打ちで処理できる。 逆に言えば、 非構造化データの困難は情報が無いことではなく、 情報の位置と型が毎回違うことにあります。 デモの「五個」(漢数字) のように、 人間には自明でも機械には別物、 という揺れが集計を破壊します。
df.dtypes・describe() での検証が必須です。pd.json_normalize() でネストを平坦化して表にできます。 Web API の応答はほぼ JSON なので、 「JSON → DataFrame → CSV/Parquet」は現代の定番パイプラインです。🍰 まずはやさしく
決まった構造を持つデータの定義です。
データエンジニアリングの基本として使います。
買い物リストのように項目が決まっている形式です。
このデータの詳しいルールについて読みます。
表形式など定まった構造のデータ
英語名 Structured Data。
この用語を理解・使用するときは、 次のような前提を意識してください:
構造化データ (Structured Data) は、 Edgar F. Codd (1970) の関係モデル (Relational Model)を起点とし、 50 年以上にわたって精緻化されてきた概念です。 ここでは数学的な定義を 4 段階に分け、 SSDSE-B-2026 を例に逐次具体化します。
スキーマ $S$ は属性 (列) の有限集合 $\{A_1, A_2, \ldots, A_n\}$ で、 各属性 $A_i$ に値域 (ドメイン) $D_i$ が割り当てられたものです。
$$S = \{(A_1, D_1), (A_2, D_2), \ldots, (A_n, D_n)\}$$
SSDSE-B-2026 のスキーマは、 たとえば $\{(\text{年度}, \text{Integer}), (\text{Code}, \text{String}), (\text{Prefecture}, \text{String}), (\text{A1101}, \text{Integer}), \ldots\}$ という形で 112 個の属性が並んでいます。
タプル $t$ はスキーマ $S$ に従う 1 つの観測単位、 すなわち各属性に値を割り当てた写像です。
$$t : S \to \bigcup_i D_i, \quad t(A_i) \in D_i$$
SSDSE-B-2026 の先頭データ行「年度=2023, Code=R01000, Prefecture=北海道, A1101=5,092,000, …」は、 112 個の属性 → それぞれの値域への写像。
リレーション $r$ はタプルの有限集合です。 順序を持たず、 重複も持たない (数学的集合の性質)。
$$r \subseteq D_1 \times D_2 \times \cdots \times D_n, \quad |r| = N$$
SSDSE-B-2026 全体では $N=564$ (47 都道府県 × 12 年度)、 列数 $n=112$。 2023 年度だけに絞れば $N=47$ になる。 集合性から各行は本来順序を持たないが、 実用上は 年度・Code 順 (R01000→R47000) でソートされている。
複数のリレーションをスキーマとともに束ね、 主キー・外部キーで結合した全体がデータベース $DB$ です。
$$DB = \{(S_1, r_1), (S_2, r_2), \ldots, (S_m, r_m), \text{IC}\}$$
$\text{IC}$ は整合性制約 (Integrity Constraints): 主キー一意性、 外部キー参照、 NULL 制約、 ドメイン制約など。 SSDSE-B-2026 が単一テーブルでも、 e-Stat の他指標と Code で結合すれば多テーブル DB を構成できる。
「すべてのセルが分割不能な値を 1 つだけ持つ」。 SSDSE-B-2026 はカンマや改行が混じらない → 1NF を満たす。 NG 例: "東京都, 神奈川県" のような複合値。
主キーが複合キーのとき、 「主キーの一部だけから決まる属性」を別表に分離する。 SSDSE-B-2026 は 12 年度分を含むため主キーは (年度, Code) の複合キーで、 Prefecture 名は Code だけで決まる → これは部分関数従属で厳密には 2NF 違反。 都道府県マスタ (Code, Prefecture) を分離すれば 2NF を満たす。
「Code → 地方区分 → 地方コード」のような連鎖を、 中間表 (都道府県マスタ・地方マスタ) に分割。 SSDSE-B-2026 は地方区分を持たないので 3NF も自動成立、 しかし e-Stat の他データと結合すると問題化することがある。
「すべての関数従属の左辺が候補キー」。 SSDSE-B-2026 の候補キーは (年度, Code) だが、 Prefecture が Code だけ (候補キーの真部分集合) で決まるため厳密には BCNF を満たさない。 都道府県マスタを分離すれば BCNF になる。
実務的指針: BCNF まで正規化するのは OLTP (オンラインのトランザクション処理)、 OLAP (分析) では 3NF を意図的に崩した「スター・スキーマ」「スノーフレーク・スキーマ」が標準。 SSDSE-B-2026 のような分析用統計データは、 すでに 564 行 (47 都道府県 × 12 年度) のフラットな形で配布されており、 BI ツールで即扱える。
| 規模 | 行数の目安 | 標準ツール | SSDSE-B との対応 |
|---|---|---|---|
| 小規模 (~10⁴) | 数十〜数万 | Excel / Google Sheets | SSDSE-B-2026 は 47 行で完全にこの範囲 |
| 中規模 (~10⁶) | 数十万〜数百万 | pandas / DuckDB / SQLite | SSDSE-B を 10 年 × 1741 市区町村に展開した場合 |
| 大規模 (~10⁹) | 数千万〜数十億 | PostgreSQL / BigQuery / Snowflake | EC サイトの注文明細、 IoT 月次ログ |
| 超大規模 (~10¹²) | 兆オーダー | Spark / Databricks / Iceberg / Lakehouse | SNS の全ユーザ行動ログ、 衛星測定値 |
構造化データの本質は規模が変わっても不変ですが、 ツールと SLA (Service Level Agreement) が大きく変わります。 SSDSE-B-2026 で学んだ概念は、 ツールを差し替えれば 10⁹ スケールまでそのまま通用するのが強みです。
SSDSE-B-2026 と SSDSE-C-2026 を Code で結合するとき、 結果の行数は次の式で決まります。
$$|r_1 \bowtie r_2| = \sum_{k} |r_1[K=k]| \cdot |r_2[K=k]|$$
数式を言葉で読み解く: 「結合キー $K$ の各値 $k$ について、 $r_1$ 側の該当行数と $r_2$ 側の該当行数を掛け、 全 $k$ で合計」。 SSDSE-B-2026 では Code は両表ともユニーク (1 行/Code) なので、 各 Code で $1 \times 1 = 1$、 合計 47 行。
pandas では pd.merge(b, c, on='Code', validate='one_to_one') と書くと、 N:N 結合の意図しない膨張を MergeError で早期検出できる。
構造化データは数学的には「リレーション (関係)」として定義されます。 集合 $D_1, D_2, \ldots, D_n$ を各列の値域 (ドメイン) とすると、 $n$ 列のテーブルは次の部分集合です:
$$R \subseteq D_1 \times D_2 \times \cdots \times D_n$$
| 記号 | 意味 | SSDSE-B-2026 での具体例 |
|---|---|---|
$R$ | リレーション (テーブル) | SSDSE-B-2026 CSV 全体 (564 行) |
$D_i$ | 第 i 列の値域 | A1101 列 = 正の整数 (人口) |
$n$ | 列数 (アリティ) | 112 列 |
$\times$ | デカルト積 | 列の組合せ集合 |
行 (tuple) | 1 観測単位 | 1 行 = 1 都道府県の全指標 |
SSDSE-B-2026 では「Code (都道府県コード)」が主キーとして 47 個の行を一意に識別します。 各列は型 (整数 / 浮動小数 / 文字列) が固定で、 「セル」(行 × 列の交差点) には 1 個の値しか入りません (第一正規形 1NF)。 この約束が成り立つから、 ソート / フィルタ / 集計 / 結合がアルゴリズム的に定義可能になります。
| 形式 | スキーマ | 代表例 | 分析しやすさ |
|---|---|---|---|
| 構造化 | 事前定義済 (固定列・型) | CSV / RDB / Parquet | ★★★★★ |
| 半構造化 | 柔軟 (ネスト可) | JSON / XML / YAML | ★★★☆☆ |
| 非構造化 | なし | 画像 / 音声 / 自由文 | ★☆☆☆☆ (前処理必須) |
| 時系列 | 時刻 + 値の最小構造 | センサー・株価 | ★★★★☆ |
| グラフ | ノード + エッジ | SNS / 知識グラフ | ★★★☆☆ (特殊 DB) |
| 階層 | 親子関係のツリー | 組織図 / ファイルシステム | ★★★☆☆ |
na_values 指定、 事後 to_numeric(errors='coerce')。df['Code'].duplicated().sum() で検知、 重複の意味を確認。str.normalize('NFKC'))。fillna(0) 前に欠損意味を判断、 別列で欠損フラグ保持。構造化データに対する操作は、 関係代数 (Relational Algebra) の 6 つの基本演算で完全に表現できます。 SSDSE-B-2026 を題材に、 数式と pandas / SQL の対応を逐次見ていきます。
$$\sigma_{\phi}(r) = \{t \in r \mid \phi(t)\}$$
数式を言葉で読み解く: 「条件 $\phi$ を満たす行 $t$ だけをリレーション $r$ から選ぶ」。 SSDSE-B-2026 では「人口 (A1101) が 500 万人以上の都道府県のみ」が典型例。 pandas では df[df['A1101'] >= 5_000_000]、 SQL では WHERE A1101 >= 5000000。
$$\pi_{A_1, A_2, \ldots, A_k}(r) = \{(t(A_1), t(A_2), \ldots, t(A_k)) \mid t \in r\}$$
数式を言葉で読み解く: 「指定した属性 (列) だけを残し、 他を捨てる」。 SSDSE-B-2026 で「Code, Prefecture, A1101 の 3 列だけ抜き出す」のが射影。 pandas: df[['Code','Prefecture','A1101']]、 SQL: SELECT Code, Prefecture, A1101。
$$r_1 \cup r_2 = \{t \mid t \in r_1 \lor t \in r_2\}$$
数式を言葉で読み解く: 「2 つのリレーションを縦に積む (重複を除去)」。 同一スキーマ必須。 たとえば SSDSE-B-2026 と SSDSE-B-2025 を縦結合して「2 年分の 94 行」を作る。 pandas: pd.concat([df2026, df2025]).drop_duplicates()、 SQL: UNION。
$$r_1 - r_2 = \{t \mid t \in r_1 \land t \notin r_2\}$$
数式を言葉で読み解く: 「$r_1$ にあって $r_2$ にない行」。 たとえば「2026 年に新登場した都道府県 (実際にはない) は?」を調べる時に使う。 pandas: ~df['Code'].isin(df_other['Code'])、 SQL: EXCEPT。
$$r_1 \times r_2 = \{(t_1, t_2) \mid t_1 \in r_1, t_2 \in r_2\}$$
数式を言葉で読み解く: 「全 47 都道府県 × 全 47 都道府県 = 2,209 ペア」のように全組合せを作る。 単独ではほぼ使わず、 後述の結合 (Join) の理論基盤として重要。 pandas: df.assign(key=1).merge(df.assign(key=1), on='key')。
$$r_1 \bowtie_{\phi} r_2 = \sigma_{\phi}(r_1 \times r_2)$$
数式を言葉で読み解く: 「直積を作ってから条件 $\phi$ で絞る」。 たとえば SSDSE-B-2026 と SSDSE-C-2026 (生活時間データ) を Code で結合すると「人口 × 生活時間」の指標が並ぶ。 pandas: pd.merge(b, c, on='Code')、 SQL: JOIN ... ON。
定理: 関係代数の 6 演算 ($\sigma, \pi, \cup, -, \times, \bowtie$) を組合せれば、 SQL の SELECT-FROM-WHERE-GROUP BY-HAVING に相当する任意の問合せを表現可能 (関係完備性 (Relational Completeness))。 構造化データの真の威力は、 この代数的閉包性にあります。
構造化データのスキーマ設計を支える理論が 関数従属 (Functional Dependency, FD)です。 属性集合 $X$ から $Y$ への関数従属を $X \to Y$ と書きます。
$$X \to Y \iff \forall t_1, t_2 \in r:\; t_1[X] = t_2[X] \Rightarrow t_1[Y] = t_2[Y]$$
数式を言葉で読み解く: 「2 つの行で $X$ の値が同じなら、 $Y$ の値も必ず同じ」。 SSDSE-B-2026 では Code → Prefecture (コードが決まれば県名が決まる) が成立し、 逆 (Prefecture → Code) も成立する (両者は 1:1)。
この 3 公理は「健全 (sound)」かつ「完全 (complete)」、 つまり関係から導かれる FD はすべてこの 3 つで導出可能 (1974 年 Armstrong)。 スキーマ設計ツールは内部でこの公理を使って正規形を判定しています。
| パターン | 用途 | SSDSE-B での例 |
|---|---|---|
| 棒グラフ | 47 件の量的比較 | 人口ランキング |
| 散布図 | 2 列の関連 | 人口 vs 病院数 |
| 箱ひげ図 | 分布の概観 | 高齢化率の分布 |
| ヒートマップ | 高次元一覧 | 47 × 110 指標の z-score |
| 地理マップ | 空間分布 | 都道府県別人口密度 |
| 折れ線 | 時系列 | 2018-2023 年の人口推移 |
| 階層図 (treemap) | 構成比 | 関東/関西/その他の人口シェア |
| 並列座標 (parallel) | 多次元の同時比較 | 人口/出生/死亡/転入の 4 軸 |
構造化データは「最初に作るとき」よりも「変更が積み重なって崩れていくとき」のほうが脆い。 ここでは構造を維持するための 5 つの実務技法を、 SSDSE-B-2026 を例にまとめる。
SSDSE-B-2026 の Code 列は文字列「R01000」だが、 pd.read_csv の型推論が int64 に誤判定する可能性がある (R01000→1000)。 これを防ぐには pd.read_csv(..., dtype={'Code': str}) と明示する。 構造化を保つには「型を推論に任せず、 スキーマで宣言する」のが鉄則。
読み込み直後に assert df['Code'].is_unique and not df['Code'].isna().any() を必ず書く。 SSDSE-B-2026 で 47 行が誤って 48 行に増えた場合、 assert で即座に検出できる。 構造化データのパイプラインは「主キー検証」を入口で必ず行うのが標準作法。
SSDSE-B-2026 は A1101・F3101 などの暗号列名だが、 実務では別途データ定義書を参照しないと意味が読めない。 取り込み後に df.rename(columns={'A1101':'pop_total','F3101':'job_seekers_new'}) と意味付きの snake_case に正規化すると、 構造化データの「機械が読みやすく」かつ「人が読みやすい」両立が達成できる。
SSDSE-B-2026 の A1101 (総人口) は「人」、 F3101 (新規求職申込件数) は「件」、 L3221 (消費支出) は「円」だが、 これは列名から読めない。 構造化を意味的に保つには、 別途「単位メタデータ表 (col_name, unit, description)」を作り、 列名と単位を主キー結合できるようにする。 これにより、 6 列以上の表を扱ったときに単位の混同 (千人と人を間違える) を防げる。
高齢化率を df['A1303']/df['A1101'] で派生させると、 結果は float64 (0.3957..) になる。 「比率は 3 桁丸めで表現する」と決めたら、 df['aging_ratio'] = (df['A1303']/df['A1101']).round(3) と即座に丸めると後段の集約結果も決定論的になる。 派生列の型と精度を明示することは、 構造化データの再現性を保つ最後の砦である。
従来は ETL (Extract-Transform-Load): 抽出 → 変換 → ロード だったが、 クラウドデータウェアハウス (Snowflake, BigQuery, Redshift) の登場で ELT (Extract-Load-Transform) が主流に。 生データをそのまま倉庫にロードし、 SQL で必要時に変換。 SSDSE-B-2026 を題材にすれば、 (1) CSV をそのまま BigQuery にアップロード、 (2) 必要な列・行を View で抽出、 (3) 分析用 Mart を CREATE TABLE で作成、 という流れ。 dbt (data build tool) が ELT の標準ツール。 ETL 時代はデータエンジニアが変換ロジックを書き、 分析者は完成形を消費するだけだったが、 ELT 時代は分析者自身が SQL で変換を書く「アナリティクスエンジニア」という新しい職種が生まれた。 SSDSE のような公的統計データを題材に、 (1) 生 CSV を読み込み、 (2) 都道府県マスタと結合し、 (3) 年度別集計テーブルを作る、 という一連の流れを SQL で書く練習をするのが、 ELT 時代の典型的な学習パスである。
(1) 第 3 正規形 (3NF): 主キー依存・関数従属を整理、 (2) 非正規化: 分析向けに意図的に冗長性を持たせる、 (3) スタースキーマ: ファクトテーブル + ディメンションテーブル、 (4) スノーフレーク: ディメンション正規化、 (5) Data Vault: 履歴保持に最適。 SSDSE-B-2026 のような集計済データは「スタースキーマのファクト」として扱える。 業務系 (OLTP) では 3NF で重複を排除し更新異常を防ぐが、 分析系 (OLAP) ではスタースキーマで JOIN を減らしクエリを高速化するのが定石。 SSDSE-B-2026 を例にすれば、 (1) 都道府県ディメンション (都道府県コード、 名称、 地域区分)、 (2) 年度ディメンション (年、 元号)、 (3) 指標ディメンション (指標コード、 単位、 カテゴリ)、 (4) ファクト (都道府県 × 年 × 指標 × 値) という設計が考えられる。 ファクトテーブルは「狭く長い」(long format) 構造で BI ツールとの相性がよい。
(1) データレイク: S3/Azure Blob/GCS に生データ (Parquet, Avro, ORC) を保存、 schema-on-read で柔軟性、 (2) データレイクハウス: Databricks Delta Lake, Apache Iceberg, Apache Hudi で ACID トランザクション・スキーマ進化・タイムトラベル を実現。 SSDSE-B-2026 のような小規模データではオーバースペックだが、 概念理解は重要。 データレイクは「とにかく溜める」思想で、 schema-on-read (読むときにスキーマを当てる) なので生データの欠損・型ゆれを許容する。 一方データレイクハウスは「溜めつつ整合性も担保」する第三世代で、 Iceberg は AWS, GCP, Databricks すべてに統合され、 オープンテーブルフォーマットの事実上の標準になりつつある。 SSDSE 程度のサイズでも、 (1) raw 層に生 CSV、 (2) bronze 層に Parquet 変換、 (3) silver 層にクレンジング済テーブル、 (4) gold 層に分析用集計、 という medallion architecture を試すと、 大規模実務に通じる設計感覚が身につく。
列指向 (columnar) 圧縮形式で、 分析クエリで大幅高速化。 CSV (37 MB) → Parquet (約 5 MB) と 1/7 程度のサイズ。 SSDSE-B-2026 を Parquet に変換するなら pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]).to_parquet('SSDSE-B-2026.parquet')。 PyArrow, Polars, DuckDB はネイティブ Parquet サポート。 列指向のメリットは (1) 列単位圧縮で同じ型が連続するため圧縮率が高い、 (2) 列単位読み込みで必要なカラムだけ読める (predicate pushdown)、 (3) 統計情報 (min/max/null 数) が含まれパーティション枝刈り可能、 (4) スキーマ情報内蔵で型の自動推論不要、 という 4 点。 CSV を毎回読み込んでパースするコストが構造化データ分析のボトルネックになっていることが多く、 一度 Parquet に変換するだけで体感速度が 5-10 倍になることが多い。 SSDSE-B-2026 を Parquet 化して DuckDB で SQL を投げるのが、 2024-2025 年現在の「最速の構造化データ分析パイプライン」と言ってよい。
Great Expectations, dbt tests, Soda Core で「データ品質テスト」を自動化。 (1) 列の存在、 (2) NULL 比率、 (3) 値の範囲、 (4) 一意性、 (5) 参照整合性 を継続的に検証。 SSDSE-B-2026 を扱う場合、 (1) 都道府県は 47 件存在、 (2) 人口は正の整数、 (3) 高齢化率は 0-1 の範囲、 などをテスト化。 Great Expectations は Python ベースで「期待値 (Expectations) を宣言的に書く」発想、 dbt tests は YAML で書け SQL に組み込める、 Soda Core はクラウド版が SaaS で提供される、 という棲み分け。 データ品質問題は「いつの間にか入った 1 件の NULL」「型が int から str に変わった」「期待値の範囲を超えた」「上流の改名で結合が壊れた」など多岐にわたり、 検出が遅れるほど下流の被害が大きい (Garbage In, Garbage Out)。 SSDSE-B-2026 のような小さく安定したデータでも、 (1) 47 都道府県揃っているか、 (2) 数値カラムに文字列が混入していないか、 (3) 年度が連続しているか、 をテスト化する習慣をつけると、 大規模実務に通じる。
dbt は SQL ベースの ETL/ELT フレームワーク。 (1) models/ に SQL ファイル、 (2) tests/ にデータ品質テスト、 (3) docs/ に自動生成ドキュメント、 (4) seeds/ に CSV データ。 SSDSE-B-2026 を dbt で扱う場合、 (1) seeds に CSV、 (2) staging models で型変換・カラム正規化、 (3) marts で分析用テーブル、 (4) tests で品質保証、 という構成。 dbt の革新性は「SQL に Jinja テンプレートを混ぜることで再利用性とテスト性を持たせた」点にあり、 ref () マクロでモデル間の依存関係を自動解決、 docs generate でテーブル間 lineage を可視化、 dbt build で依存順に実行 + テスト、 が一気通貫で動く。 SSDSE-B-2026 を題材にした学習プロジェクトとしては、 (1) staging_ssdse_b で生 CSV を型変換、 (2) dim_prefecture で都道府県ディメンション、 (3) fct_population で人口ファクト、 (4) mart_aging_analysis で高齢化分析、 という 4 層構成が典型例。
Kafka, Apache Pulsar, AWS Kinesis でリアルタイムストリーミング。 Schema Registry (Confluent, AWS Glue Schema) でスキーマ管理。 Avro, Protobuf, JSON Schema が主要なスキーマ形式。 SSDSE のような年次データには直接該当しないが、 自治体センサー・IoT データ・SNS データなどリアルタイム構造化データは現代的な実務トピック。 ストリーミング処理では「スキーマの後方互換性」が極めて重要で、 producer が新しい列を追加しても古い consumer が壊れない、 古い producer が送る古い形式を新しい consumer が読める、 という二方向の互換性を Schema Registry が管理する。 Avro は Hadoop エコシステム由来でスキーマ進化に強い、 Protobuf は gRPC との相性がよく型安全性が高い、 JSON Schema は人間可読性が高い、 という棲み分け。 SSDSE-B-2026 を「擬似ストリーム」(年度ごとに 1 レコードずつ送る) として扱う練習でも、 スキーマ管理の感覚は身につく。
pandas の代替として、 (1) Polars (Rust 製、 並列処理、 lazy evaluation で高速)、 (2) DuckDB (組込 OLAP DB、 SQL ベース、 CSV/Parquet 直接読み込み)、 (3) Modin (pandas API + 並列 backend)、 (4) cuDF (GPU 加速 pandas)。 SSDSE-B-2026 程度のサイズではどれも瞬時だが、 大規模データでは pandas より圧倒的に高速。 Polars の強みは (1) Arrow を内部表現に持ち pandas との変換が高速、 (2) lazy API で複数操作を統合最適化、 (3) Rust 由来のマルチスレッド処理、 (4) メモリ効率が良く OOM になりにくい、 の 4 点。 DuckDB の強みは (1) SQLite 並に組込が簡単、 (2) CSV/Parquet/JSON を直接 FROM 句で参照可能、 (3) PostgreSQL 互換に近い SQL、 (4) Arrow との高速統合。 SSDSE-B-2026 を Polars で読むなら pl.read_csv('SSDSE-B-2026.csv', encoding='cp932')、 DuckDB なら duckdb.sql("SELECT * FROM 'SSDSE-B-2026.csv'").df() で一発。
(1) Apache Atlas, AWS Glue Data Catalog, Google Data Catalog でメタデータ管理、 (2) Lineage tracking で変換履歴可視化、 (3) Access control で行・列レベル権限、 (4) PII 検出で個人情報自動マスキング。 SSDSE-B-2026 は公開データだが、 元データ (国勢調査原票) は個情法対応必要。 データカタログの本質は「データの所在地・意味・所有者・更新頻度・品質スコアを横断検索できる仕組み」で、 大企業では数万テーブルが散在し誰も全体像を把握できない状況を解消する。 Lineage (系譜) は「このダッシュボードのこの数値は、 どのソーステーブルのどの列から、 どの変換を経て来たか」を辿る機能で、 障害時の影響範囲特定や監査対応で必須。 SSDSE-B-2026 を題材に「都道府県」列が複数テーブルでどう使われているかを Lineage で可視化する練習をすると、 大規模環境でのガバナンス感覚が身につく。
2024-2025 年、 「LLM × 構造化データ」の研究進展: (1) Text-to-SQL (自然言語からクエリ生成)、 (2) SQL-to-Text (クエリ結果の説明文生成)、 (3) Schema 推論 (生データから schema 自動生成)、 (4) Anomaly detection (異常値・ドリフト検出)。 SSDSE-B-2026 で「東京の人口は」と聞くと LLM がクエリ生成 → 実行 → 自然言語回答、 という流れが実現しつつある。 Text-to-SQL のベンチマークとして Spider, BIRD, WikiSQL があり、 GPT-4, Claude 3, Gemini はいずれも 80% を超える精度を達成。 ただし「列名が意味的に曖昧 (例: code が都道府県コードか商品コードか)」「JOIN 条件が複雑」「集約と窓関数の使い分け」などは依然として難しく、 LLM 単体ではなく「LLM + スキーマ理解 + 実行確認 + 自己修正」のエージェント構成が現実的解。 SSDSE-B-2026 のような小規模スキーマは Text-to-SQL の練習に最適。
(1) 生 CSV を S3/MinIO にアップロード、 (2) Parquet 変換 (PyArrow)、 (3) DuckDB でクエリ、 (4) dbt で staging/marts モデル、 (5) Great Expectations で品質テスト、 (6) Streamlit/Dash でダッシュボード、 という End-to-End パイプラインを構築できれば、 構造化データの実務スキルが身につく。 47 都道府県の小規模データでも、 同じ手順を学べる教材になる。 さらに発展として、 (7) Airflow/Prefect/Dagster でジョブスケジューリング、 (8) MLflow で機械学習モデル管理、 (9) Feature Store (Feast, Tecton) で特徴量再利用、 (10) Model Serving (Seldon, BentoML) で推論 API、 までを SSDSE-B-2026 で素振りすると、 データエンジニア・ML エンジニア・MLOps エンジニアの三役を一通り体験できる。 規模は小さくても「設計の型」を身につけることが学習の本質で、 後に大規模データを扱うときに同じパターンを適用できる。
(1) 構造化 (RDBMS, CSV, Excel): 行列形式、 スキーマ厳格、 (2) 半構造化 (JSON, XML, YAML): 階層構造、 スキーマ柔軟、 (3) 非構造化 (テキスト、 画像、 動画、 音声): スキーマなし、 ML/NLP で構造化に変換。 SSDSE-B-2026 は完全に構造化、 JSON 変換も可能 (df.to_json())、 テキスト変換 (Markdown レポート) は人手 or LLM。 実務データの推定構成比は「非構造化 80% + 半構造化 15% + 構造化 5%」とも言われ、 構造化データだけ扱えても全体の 5% しかカバーできない。 ただし「非構造化を構造化に変換する」過程 (OCR, ASR, NLP, CV) の出力先は必ず構造化テーブルになるので、 構造化データの設計スキルはあらゆるデータパイプラインの最終段で必須。 SSDSE-B-2026 を入口に構造化を極めた後、 PDF → テキスト → 構造化、 画像 → 物体検出結果 → 構造化、 という流れに発展させると、 現代的なマルチモーダル分析の全体像が見えてくる。
構造化データの設計・実装・運用・品質保証は、 データサイエンス・ML エンジニア・データエンジニアすべての職種の共通基盤。 SSDSE-B-2026 のような実データを使って ELT パイプライン、 dbt モデル、 品質テスト、 ダッシュボードを End-to-End で構築する経験が、 現代のデータ職にとって最も価値あるスキルセットを形作る。 統計検定・データサイエンス検定・G 検定・E 資格などの資格学習だけでは身につかない「実装の手触り」こそが、 SSDSE-B-2026 のような公的統計を題材にしたハンズオン学習の真価。 47 都道府県という親しみやすいスケールで全工程を経験し、 そのまま大規模実務にスケールさせる、 というのが本教材の目指す学習動線である。 構造化データの基礎を固めた読者は、 続いて「リレーショナルデータベース」「データウェアハウス」「データレイク」「特徴量エンジニアリング」などの関連用語ページに進むと、 知識のネットワークが一気に広がる。
SSDSE-B-2026 の構造を実際に検証します。 行数・列数・型を確認し、 1 つのセルに 1 値が入っているか (1NF)、 主キー候補が一意か、 を診断します。
| 属性 | 値 | 構造化データの条件 |
|---|---|---|
| 行数 | 564 (47 都道府県 × 12 年度) | 固定 |
| 列数 | 112 (年度 + Code + Prefecture + 109 指標) | 固定 |
| 主キー候補 | (年度, Code) の複合キー | 一意・null 無し |
| A1101 型 | 整数 (人口) | 同一列同一型 |
| セル原子性 | 1 セル 1 値 | 1NF 満足 |
これらすべてが満たされたとき「構造化データ」と呼びます。 1 つでも欠ければ非構造化 / 半構造化に近づきます (例: セルに JSON が入る = 半構造化)。
先ほどの 6 演算を、 SSDSE-B-2026 の 2023 年度断面に対して 1 件ずつ実行し、 結果を確認します。 全体は 564 行 × 112 列で、 2023 年度に絞った断面が 47 行 × 112 列。
| 演算 | 問い | 結果 |
|---|---|---|
| $\sigma_{A1101 \geq 5{,}000{,}000}$ | 人口 500 万人以上の県 | 9 件 (北海道, 埼玉, 千葉, 東京, 神奈川, 愛知, 大阪, 兵庫, 福岡) |
| $\pi_{\text{Code, Pref, A1101}}$ | 3 列だけ抜き出す | 47 行 × 3 列 |
| $\sigma_{A1101 \geq 5M} \cup \sigma_{A1101 \leq 1M}$ | 大県 + 小県の和 | 9 + 7 = 16 件 (鳥取・島根・高知・徳島・福井・佐賀・山梨が小県側) |
| $\sigma_{大県} - \sigma_{関東}$ | 大県のうち関東以外 | 5 件 (北海道, 愛知, 大阪, 兵庫, 福岡) |
| $r \times r$ | 47 都道府県の全ペア | $47 \times 47 = 2{,}209$ 行 |
| $r_1 \bowtie_{Code} r_2$ | 同じ Code を共有する 2 列グループを結合 | 47 行 (結合列を除いて列を連結) |
これらの値はすべて pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) で読み込み、 2023 年度に絞った 47 行の断面から直接導かれます。 「関係代数」と聞くと抽象的に感じますが、 SSDSE-B-2026 で具体例を握れば、 数式と実用が同じものだと体感できます。
合成テーブルから 1 行サイズと年間ストレージを計算する。
| 列 | byte |
|---|---|
| id INT | 4 |
| name VARCHAR(50) | 52 |
| amount DECIMAL | 8 |
| created_at | 8 |
1 行 = 72 byte
1 2 3 4 5 | row_b = 4 + 52 + 8 + 8 rows = 10_000_000 total_mb = rows * row_b / 1e6 print(f"1 行: {row_b} byte") print(f"合計: {total_mb} MB") |
💬 手計算 (Step 2) と Python 出力が完全一致。
SSDSE-B-2026 のような公的統計データを Python で扱う際の基本パターン:
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd import numpy as np # データ読み込み df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) print(df.shape) print(df.dtypes) print(df.describe()) # 「構造化データ」の文脈で扱う場合の例: # 分野: データエンジニアリング # 関連手法は同カテゴリの他用語を参照してください。 |
具体的なコードは データエンジニアリング を参照してください。
分析結果を報告するときに含めるべき情報:
🎯 このコードでやること: SSDSE-B-2026 が構造化データの定義 (行列形式 + 型固定 + 主キー一意) を満たすか診断する。
📥 入力: data/raw/SSDSE-B-2026.csv (47 行 × 112 列)
1 2 3 4 5 6 7 8 9 10 | 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] # 最新年度の 47 都道府県だけにする print('行数:', df.shape[0]) print('列数:', df.shape[1]) print('Code 一意?', df['Code'].is_unique) print('Code に null?', df['Code'].isna().any()) print(df.dtypes.value_counts()) |
📤 実行結果:
💬 結果の読み方: 2023 年度断面は 47 行 × 112 列の長方形、 Code は一意・null なし、 各列が単一型 (数値列 110・object 2 列 = Code と Prefecture)。 構造化データの定義を完全に満たす。
🎯 このコードでやること: 各列の型と分布を一括取得し、 数値列が想定どおりの範囲かを確認する。
📥 入力: SSDSE-B-2026 数値列 (A1101 人口 など)
1 | print(df[['A1101','A1303','I510120']].describe().round(0)) |
📤 実行結果:
💬 結果の読み方: 人口 A1101 は 53.7 万 (鳥取相当) 〜 1,408 万 (東京)、 65 歳以上人口 A1303 も同じ桁感で分布。 列内で同一単位・同一型が保たれ、 統計が意味を持つ。
🎯 このコードでやること: 「1 セル 1 値」を守っているか、 各列の値が原子 (atomic) か検査する。
📥 入力: SSDSE-B-2026 の Prefecture 列
1 2 3 | print('Prefecture サンプル:', df['Prefecture'].head(3).tolist()) print('セル内カンマあり?', df['Prefecture'].str.contains(',').any()) print('セル内改行あり?', df['Prefecture'].str.contains('\n').any()) |
📤 実行結果:
💬 結果の読み方: カンマ・改行が混入していない = セルが原子。 1NF を満たす。 SQL での処理が安全。
🎯 このコードでやること: 画像 / テキスト / 自由文を構造化テーブルにメタデータ化する変換例。
📥 入力: 47 都道府県の特徴量 (人口 / 面積 / 県庁所在地)
1 2 3 4 5 6 7 8 | structured = df[['Code','Prefecture','A1101']].head(3) print('構造化形式:') print(structured) # 非構造化 (自由文) を構造化に変換するイメージ unstructured = "北海道は人口509万人で日本最北端にある。" print('\n非構造化形式:', unstructured) print('構造化に変換:', {'Code':'R01000','人口':5092000,'位置':'最北'}) |
📤 実行結果:
💬 結果の読み方: 同じ情報でも、 自由文だと検索・集計が困難。 列に分解すれば SQL や pandas で機械的に扱える。 これが構造化の威力。
fit(X, y) は構造化された X (n_samples × n_features) を前提とする。🎯 このコードでやること: SSDSE-B-2026 を読み、 人口 500 万人以上 (σ) → 3 列だけ抜き出す (π) を順に適用する。
📥 入力: data/raw/SSDSE-B-2026.csv (47 行 × 112 列)
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]) df = df[df['SSDSE-B-2026'] == 2023] # 最新年度の 47 都道府県だけにする # σ: 選択 selected = df[df['A1101'] >= 5_000_000] print('選択後の行数:', len(selected)) # π: 射影 projected = selected[['Code','Prefecture','A1101']] print(projected) |
📤 実行結果:
💬 結果の読み方: 9 都道府県が人口 500 万人以上。 東京 (1,408 万) が突出、 ついで神奈川 (922 万)。 関係代数の σ→π の合成が、 たった 2 行の pandas で書ける。
🎯 このコードでやること: 大県 (500 万以上) と小県 (100 万以下) を和集合化、 さらに大県から関東 4 県を差し引く。
📥 入力: SSDSE-B-2026 の Code・Prefecture・A1101
1 2 3 4 5 6 7 8 9 | large = df[df['A1101'] >= 5_000_000][['Code','Prefecture']] small = df[df['A1101'] <= 1_000_000][['Code','Prefecture']] union = pd.concat([large, small]).drop_duplicates() print('大 ∪ 小:', len(union), '件') # 関東 4 県 (埼玉・千葉・東京・神奈川) を引く kanto_codes = ['R11000','R12000','R13000','R14000'] diff = large[~large['Code'].isin(kanto_codes)] print('大県 − 関東:', diff['Prefecture'].tolist()) |
📤 実行結果:
💬 結果の読み方: 大県 9 + 小県 10 = 和集合 19 件 (重複なし)。 関東 4 県を引くと「関東以外の地方拠点都市圏」5 県が浮かび上がる。 これが集合演算の威力。
🎯 このコードでやること: 47 都道府県の全 2,209 ペアを生成 (×)、 SSDSE-B と SSDSE-C を Code で結合する (⋈)。
📥 入力: SSDSE-B-2026 + SSDSE-C-2026 の 2 ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd b = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) c = pd.read_csv('data/raw/SSDSE-C-2026.csv', encoding='cp932', header=1) b = b[b['年度'] == 2023] # B は 47 県 × 12 年なので 1 年分に絞る # 直積 (cross join) b_keys = b[['地域コード']].assign(key=1) cross = b_keys.merge(b_keys, on='key', suffixes=('_a','_b')).drop(columns='key') print('全ペア数:', len(cross)) # 結合 (Inner Join) # 注意: B の地域コードは県レベル (R01000)、 C は県庁所在市レベル (R01100) なので、 # 地域コードで結合すると 1 件も一致しない。 ここは都道府県名で結合する。 print('地域コードで結合:', pd.merge(b, c, on='地域コード').shape, '← キーの粒度が違うので 0 行') joined = pd.merge(b, c, on='都道府県', suffixes=('_B','_C')) print('結合後 shape:', joined.shape) |
📤 実行結果:
💬 結果の読み方: 直積は $47^2=2{,}209$、 結合は 47 行のまま列が 340 に増える。 結合キーを介して、 異なる統計が「同じ構造化テーブル」上で一緒に分析できるようになる。 ここで見逃せないのが途中の 地域コードで結合: (0, 340) という行で、 同じ「地域コード」という名前の列どうしを結合したのに 1 行も一致しなかった。 SSDSE-B の地域コードは県レベル (R01000 = 北海道) なのに対し、 SSDSE-C は県庁所在市レベル (R01100 = 札幌市) で、 列名は同じでも粒度が違うためである。 実務で最も多い結合事故がこれで、 結合したら 0 行になった/逆に行数が爆発したときは、 まずキーの粒度と表記ゆれを疑う。 なお b を年度で絞らずに直積を取ると $564^2 = 318{,}096$ 行に膨れ上がる ── 直積の前には必ずキーが一意かを確認すること。
🎯 このコードでやること: SSDSE-B-2026 を縦持ち (long format) と横持ち (wide format) に相互変換、 用途の違いを示す。
📥 入力: SSDSE-B-2026 の Code・Prefecture と人口関連 3 列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) sub = df[['Code','Prefecture','A1101','A1301','A1303']].head(3) # 横持ち (wide) print('=== 横持ち (元の構造化形式) ===') print(sub) # 縦持ち (long) long_form = sub.melt( id_vars=['Code','Prefecture'], value_vars=['A1101','A1301','A1303'], var_name='指標', value_name='値') print('\n=== 縦持ち (tidy data) ===') print(long_form) |
📤 実行結果:
💬 結果の読み方: 同じ情報でも、 横持ちは人間が読みやすく、 縦持ちは ggplot/seaborn の facet や集計でループ処理しやすい。 構造化データの「形」は目的により最適形が変わる。
構造化データを語るとき、 「論理スキーマ」「物理スキーマ」「ファイル形式」「ストレージレイヤ」を混同しがちです。 ANSI/SPARC アーキテクチャに従い、 5 階層で整理しておきます。
| 階層 | 担うもの | SSDSE-B-2026 の例 |
|---|---|---|
| 外部スキーマ (view) | アプリ・ユーザが見る形 | 「人口ランキングダッシュボード」 (47 行のサブセット) |
| 論理スキーマ | 列の型・主キー・外部キー | Code:str(PK), A1101:int, … |
| 概念スキーマ | 業務ドメインの意味 | 「都道府県を 1 観測単位とする年次社会統計」 |
| 物理スキーマ | ファイル形式・索引・分割 | CSV (UTF-8) / 1 ファイル / 索引なし |
| ストレージ | 媒体・OS・ネットワーク | e-Stat の HTTPS サーバ / 利用者の SSD |
同じ「構造化データ」でも、 階層によって関心事が違います。 たとえばパフォーマンス改善が必要なら物理スキーマ (Parquet 化・パーティショニング)、 業務の混乱解消なら概念スキーマ (用語集) を直すのが正攻法。
🎯 このコードでやること: SSDSE-B-2026 から「人口 10 万人あたり病院数」のランキング上位 5 + 下位 5 を出す。
📥 入力: data/raw/SSDSE-B-2026.csv の A1101 (総人口), I510120 (病院数)
1 2 3 4 5 6 7 8 9 10 11 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df['人口10万あたり病院数'] = df['I510120'] / df['A1101'] * 100000 ranked = df[['Prefecture','A1101','I510120','人口10万あたり病院数']].sort_values( '人口10万あたり病院数', ascending=False) print('=== TOP 5 (病院密度が高い) ===') print(ranked.head(5).to_string(index=False)) print('\n=== BOTTOM 5 (病院密度が低い) ===') print(ranked.tail(5).to_string(index=False)) |
📤 実行結果:
💬 結果の読み方: 高知県は 10 万人あたり 16.07 病院、 神奈川県は 3.13 病院で 約 5 倍の差。 都市部の医療施設は規模が大きく数で勝負しない傾向。 構造化データから「人口加重で正規化する」ことで、 意味のある比較が可能になる。
| 問い | SQL | pandas |
|---|---|---|
| 全行表示 | SELECT * FROM ssdse | df |
| 先頭 5 行 | SELECT * FROM ssdse LIMIT 5 | df.head(5) |
| 列選択 | SELECT Code, A1101 | df[['Code','A1101']] |
| 条件抽出 | WHERE A1101 > 5e6 | df[df['A1101']>5e6] |
| 集計 | SELECT AVG(A1101) | df['A1101'].mean() |
| グループ | GROUP BY region | df.groupby('region') |
| 結合 | JOIN c ON b.Code=c.Code | pd.merge(b,c,on='Code') |
| 並び替え | ORDER BY A1101 DESC | df.sort_values('A1101',ascending=False) |
| 重複除去 | SELECT DISTINCT | df.drop_duplicates() |
| 窓関数 | RANK() OVER (...) | df['A1101'].rank() |
🎯 このコードでやること: SSDSE-B-2026 は 12 年度分を含むため Code 単独では一意でない。 2023 年度に絞って Code を一意にし、 同じ主キーを共有する 2 つの列グループを validate で 1:1 結合する。
📥 入力: data/raw/SSDSE-B-2026.csv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import pandas as pd b = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) print('全体 shape:', b.shape) # 12 年度分すべて print('Code 単独で一意?:', b['Code'].is_unique) # 各 Code が 12 年分あるので False b23 = b[b['SSDSE-B-2026'] == 2023] # 2023 年度だけに絞る → 47 行 print('2023 年度 shape:', b23.shape, 'Code 一意?:', b23['Code'].is_unique) # 同じ主キー Code を共有する 2 つの列グループを 1:1 で結合 left = b23[['Code', 'Prefecture', 'A1101']] right = b23[['Code', 'A1303']] joined = pd.merge(left, right, on='Code', validate='one_to_one') print('結合後 shape:', joined.shape) |
📤 実行結果:
💬 結果の読み方: 全 564 行では Code が 12 年度分重複するため一意でない。 2023 年度に絞ると 47 都道府県の Code がユニーク主キーになり、 同じ Code を共有する列グループどうしは 1:1 結合が成立して 47 行のまま。 validate='one_to_one' を入れておけば、 意図しない重複 Code が混じった瞬間にエラーで気づける。
🎯 このコードでやること: 完全性・一意性・整合性 (男+女=総人口) を実値で確認する。
📥 入力: SSDSE-B-2026.csv の A1101 (総人口), A110101 (男), A110102 (女)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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] # 最新年度の 47 都道府県だけにする # 完全性 missing_ratio = df.isna().sum().sum() / df.size print(f'欠損率: {missing_ratio:.4%}') # 一意性 print(f'Code 一意性: {df["Code"].nunique() / len(df):.4%}') # 整合性: 男 + 女 = 総人口 df['和'] = df['A110101'] + df['A110102'] df['差'] = df['和'] - df['A1101'] print(f'男+女 と総人口の最大差: {df["差"].abs().max():,} 人') print(f'整合する都道府県数: {(df["差"].abs() <= 1000).sum()} / 47') |
📤 実行結果:
💬 結果の読み方: 欠損ゼロ、 Code 100% ユニーク、 男 + 女 と総人口の差は四捨五入の千人単位以内 (47 件すべて整合)。 SSDSE-B-2026 は DQ の 6 次元をすべて高水準で満たす理想的な構造化データ。
構造化データは「読み込み → 検査 → 集約 → 結合」の 4 工程で扱われる。 各工程について「やること・入力・コード・実行・読み方」の 5 要素で示す。 すべて SSDSE-B-2026 の実値のみを使う。
🎯 このコードでやること: SSDSE-B-2026 を読み込み、 構造化データの 5 条件 (行列形式 / 型単一 / 主キー一意 / 主キー非 null / セルがアトミック) を 1 ファイルでまとめて検査する。
📥 入力: data/raw/SSDSE-B-2026.csv (全体 564 行 × 112 列。 2 行目の和名行を skiprows=[1] で除外し、 2023 年度断面 47 行を使用)
1 2 3 4 5 6 7 8 9 10 11 12 | 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] # 最新年度の 47 都道府県だけにする print('行数:', df.shape[0], '列数:', df.shape[1]) print('Code 一意?', df['Code'].is_unique) print('Code null 数:', df['Code'].isna().sum()) print('型別列数:') print(df.dtypes.value_counts()) print('セルがアトミックか (オブジェクト列を確認):') print(df['Prefecture'].apply(lambda v: isinstance(v, str)).all()) |
📤 実行結果:
💬 結果の読み方: 2023 年度断面は 47 行 × 112 列の長方形、 Code 列は 47 値すべて一意・null 0、 110 列が数値 (int64 104・float64 6)・2 列が object (Code・Prefecture)、 Prefecture セルもすべて文字列スカラー。 構造化データの 5 条件をすべて満たす → 1NF 〜 3NF を達成している。
🎯 このコードでやること: 構造化データの強みである「列方向の集約」を 1 行で実行する。 47 都道府県の人口列 A1101 を describe() で 8 統計量に縮約する。
📥 入力: 47 行 × 1 列 (A1101 人口、 単位: 千人)
1 2 3 4 | print(df['A1101'].describe().round(1)) print() print('上位 3 件:', df.nlargest(3, 'A1101')[['Prefecture','A1101']].to_dict('records')) print('下位 3 件:', df.nsmallest(3, 'A1101')[['Prefecture','A1101']].to_dict('records')) |
📤 実行結果:
💬 結果の読み方: 中央値 1,549 千人に対し平均 2,646 千人、 標準偏差 2,798 千人。 平均 > 中央値で右に偏り、 最大 (東京 14,086 千人) と最小 (鳥取 537 千人) の比は 26.2 倍。 構造化データだからこそ 47 行 → 8 統計量 → 上位 3 件のように、 同じ列を複数粒度で要約できる。
🎯 このコードでやること: 構造化データの強みである「行のグルーピング」で、 47 都道府県を 8 地方ブロックに集約する。 Code (R01-R47) から地方ブロックを派生させる。
📥 入力: 47 行 × 2 列 (Prefecture、 A1101 人口)。 派生列 region を Code から作成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def to_region(code): n = int(code[1:3]) if n == 1: return '北海道' if n <= 7: return '東北' if n <= 14: return '関東' if n <= 23: return '中部' if n <= 30: return '近畿' if n <= 35: return '中国' if n <= 39: return '四国' return '九州' df['region'] = df['Code'].apply(to_region) agg = df.groupby('region').agg( pref_count=('Prefecture','count'), pop_sum=('A1101','sum'), pop_mean=('A1101','mean') ).round(0) print(agg) |
📤 実行結果:
💬 結果の読み方: 関東 7 県の合計人口 43,527 千人は近畿 21,990 千人の約 2 倍、 全国合計 124,353 千人の 35.0%。 関東 1 県あたり 6,218 千人で四国 894 千人の 7.0 倍。 47 行を 8 群に再構造化することで、 集約結果も主キー (region) を持つ構造化データになる点が重要。
🎯 このコードでやること: 構造化データの 2 列から派生比率列 (高齢化率 = A1303 / A1101) を作り、 上位 5 県・下位 5 県を抽出する。 列演算が「列方向ベクトル化」されることが構造化データの強みである。
📥 入力: A1101 (総人口、 千人)、 A1303 (65 歳以上人口、 千人) の 2 列。
1 2 3 4 5 6 7 8 | df['aging_ratio'] = (df['A1303'] / df['A1101']).round(3) top5 = df.nlargest(5, 'aging_ratio')[['Prefecture','A1101','A1303','aging_ratio']] bot5 = df.nsmallest(5, 'aging_ratio')[['Prefecture','A1101','A1303','aging_ratio']] print('高齢化率 上位 5:') print(top5.to_string(index=False)) print() print('高齢化率 下位 5:') print(bot5.to_string(index=False)) |
📤 実行結果:
💬 結果の読み方: 秋田 39.1% と東京 22.8% で +16.3pt の差。 上位は北東・四国地方、 下位は首都圏・沖縄に集中。 構造化データは df['A1303']/df['A1101'] という列演算が 47 行に同時適用される (ベクトル化) ため、 派生列の生成が高速。 派生列も型 (float64) を持つ構造化列として扱える。
以下は構造化データの定義・性質・実務に関する 10 問の理解度チェックである。 答えは末尾にまとめてある。 各問とも SSDSE-B-2026 の実値に基づく。
✅ 解答 (折りたたまずに表示):
df['Code'].is_unique で True、 df['Code'].isna().any() で False。df['aging_ratio'] = df['A1303'] / df['A1101'] で 47 行同時計算。df[['Prefecture','A1101']] や SQL の SELECT に対応。df.query('A1101 > 5000') や SQL の WHERE に対応。 SSDSE-B-2026 で人口 500 万超は 9 県。df['A'] / df['B'] は内部で C で実装され、 for ループより 100 倍速い。melt すると 47 × 109 = 5,123 行 × 3 列になる。関東 7 県の高齢化率中央値 27.4% と東北 6 県の中央値 34.5% を構造化データから直接 groupby で算出することで、 「高齢化対策の予算/人口あたり比率」を東北で 1.26 倍に重み付けする政策提案が可能になった。 構造化されていない口頭資料では、 7.1pt の差が見落とされやすい。
構造化データから高齢化率 (A1303 ÷ A1101 × 100) を派生列で生成し、 全国で最も高い秋田県 39.1% と最も低い東京都 22.8% を両端に可視化。 「高齢化率が高い県でも、 転入超過の都市圏を抱える県は別軸で評価する」という補助指標を加えるきっかけになった。 派生列は構造化されていれば 1 行で生成・並列計算できる。
政府統計の元データは PDF 表の場合が多く、 構造化されていないため SQL/pandas で使えない。 e-Stat の API で配信される CSV (SSDSE-B-2026 もこの一種) は事前にスキーマ (列名・型・主キー) が定義済みで、 OCR/手作業の労力が消える。 構造化への変換コストは「列数 × 行数」に比例するため、 47 × 110 規模なら 1 日、 1,000 × 1,000 規模だと数週間かかる場合がある。
どちらも構造化データだが、 Parquet は列指向 + 型保持 + 圧縮で「型のメタデータをファイル自体が持つ」点が強い。 CSV は型情報がないため、 読み込み時に dtype= 指定や推論が必要。 SSDSE-B-2026 は CSV 配信のため、 pd.read_csv で int64 推論されている。
JSON は「タグ付き」で柔軟なため半構造化に分類されることが多い。 ただし、 すべてのレコードが同じキーセットを持つ「フラット JSON」は CSV 同等で構造化扱いになる。 ネストや配列を含む JSON は、 構造化テーブルに正規化するまで JOIN が困難。
満たす。 NaN は「型に収まる欠損値」なので、 セルのアトミック性 (1 値) も列の型単一性も維持される。 ただし主キー列に NaN があると主キー条件 (非 null) を破る。
主キー Code (R01000〜R47000) で他の都道府県データ (人口推計、 国勢調査、 商業統計など) と結合できる。 文字列の都道府県名 (Prefecture) は表記揺れ (北海道/北海道, スペース有無) のリスクがあるため、 Code を結合キーに選ぶのが実務の常識。
pandas のままだとメモリが厳しい。 Polars (Rust 製、 列指向)、 Dask (並列)、 BigQuery / DuckDB (SQL 大規模) などに移行する。 ただし「構造化データ」という性質は同じなので、 同じ SQL/関係代数で扱える点は変わらない。
列名が暗号 (A1101, F3101 など) のままだと、 構造化されていても解読できない。 SSDSE-B-2026 でも別途データ定義書を参照しないと意味が取れない。 「構造化」と「意味付け (semantic)」は別問題で、 後者のためにスキーマ説明書・メタデータカタログが必要になる。
EAV は形式上は構造化 (3 列の表) だが、 すべての値を 1 列にまとめるため型が混在し、 「列ごとに型単一」の原則は保てない。 EAV を採用する場合、 値列を value_int・value_str・value_float に分けるのが定石。
(1) shape を確認 / (2) 主キーの一意・非 null を確認 / (3) dtypes で型を確認 / (4) 主要列の describe() で外れ値を確認 / (5) isna().sum() で欠損率を確認。 この 5 ステップで 90% の構造的問題は検出できる。
fillna(0) でこれを潰すと、 観測なしと実測ゼロが見分けつかなくなる。validate='one_to_one' で防御。str.normalize('NFKC') で正規化必須。df[['A1101','I510120']].describe() で範囲を確認。 病院数 13〜647 件、 人口 54 万〜1,408 万。df['人口10万あたり病院数'] = df['I510120'] / df['A1101'] * 100000。df.sort_values('人口10万あたり病院数', ascending=False)。この 8 ステップ自体が、 構造化データを扱う際の汎用フレームワークです。 SSDSE-B-2026 では数十秒で完了しますが、 大規模 DB でも各ステップは同じ。
構造化データは、 統計・計算機科学・データベース・機械学習の交差点に位置します。
構造化データは「行 × 列のスキーマが事前定義されたデータ」。 SSDSE-B-2026 (47 行 × 112 列の都道府県別指標) は典型的構造化データで、 RDB / CSV / Parquet に格納可能、 SQL でクエリ可能、 pandas DataFrame に直接読み込める。 対照は非構造化データ (画像・自由テキスト・音声) と半構造化データ (JSON / XML / HTML)。
構造化データはスキーマ固定で柔軟性に欠ける反面、 分析パイプラインがシンプル。 SSDSE-B-2026 を使うと「整っているデータで学習」が可能、 実務での非構造化→構造化の苦労を後で経験できる順番。
構造化データを扱うときの形式・エンジン選択フロー。
df.drop_duplicates() で確認。pd.melt で縦持ち (long) に変換。 用途で使い分け。SSDSE は「データレイク (非構造化が混在) ではなくデータウェアハウス (構造化済)」の典型。 学習用には最適、 実務応用時は半構造化を経由する設計を別途習熟する。
本ページの前半では、 スキーマを Codd の関係モデルという数学的な約束事として説明しました。 この深化セクションでは視点を変え、 その約束事が pandas に読み込んだ瞬間にメモリ上の「型 (dtype) とバイト列」という物理的実体に変わる様子を、 SSDSE-B-2026 (564 行 × 112 列、 2012–2023 年度) の実測値だけで追いかけます。 抽象的なスキーマ論と、 df.info() に出てくる生々しい数字とを往復できるようになるのが目標です。
SSDSE-B-2026 を pd.read_csv(..., encoding='cp932', skiprows=[1]) で読むと、 112 列の dtype 内訳は実測で int64 が 104 列、 float64 が 6 列、 object が 2 列 (Code と Prefecture) になります。 この 3 行の集計 (df.dtypes.value_counts()) を見るだけで、 「このデータはほぼ整数カウントの統計表で、 識別子が 2 列だけある」という設計思想が読み取れます。 さらに float64 の 6 列の正体を確かめると:
| 列コード | 意味 (2 行目の日本語ヘッダより) | float64 である理由 |
|---|---|---|
A4103 | 合計特殊出生率 | 2023 年は 0.99 (東京都) 〜 1.60 (沖縄県) と本質的に小数 |
B4101 | 年平均気温 | 2023 年は 11.0℃ (北海道) 〜 23.8℃ (沖縄県) |
B4102 / B4103 | 最高気温 / 最低気温 | 同じく 0.1℃ 刻みの測定値 |
B4109 | 降水量 (年間) | 0.5mm 単位の測定値 |
H5614 | ごみのリサイクル率 | 百分率の小数 |
つまり「気象測定値と率だけが小数、 残りは全部カウント」という意味論が dtype にそのまま写像されているのです。 重要なのは、 pandas では欠損 (NaN) を含む整数列も float64 に化けるため、 通常「float64 の列 = 小数か欠損持ちのどちらか」なのですが、 SSDSE-B-2026 は実測で全 564 行 × 112 列に NaN が 1 個もない (df.isna().sum().sum() が 0) ので、 この 6 列は「純粋に小数だから float」だと確定できます。 dtype 分布 → 欠損数 → 値域、 の 3 段確認は、 初見の構造化データに対する最速の「スキーマ健康診断」です。
SSDSE ファミリーは 1 行目が列コード、 2 行目が日本語の項目名という 2 段ヘッダ構造です。 ここで skiprows=[1] を忘れるとどうなるかを実測すると、 結果は劇的です:
| 読み方 | shape (実測) | dtype 内訳 (実測) |
|---|---|---|
skiprows=[1] あり (正) | (564, 112) | int64 × 104、 float64 × 6、 object × 2 |
skiprows=[1] なし (誤) | (565, 112) | object × 112 (全列が文字列化) |
データ行に「総人口」「年平均気温」といった日本語文字列が 1 行だけ混入するため、 pandas の型推論は 112 列すべてを object (文字列) と判定します。 行数の差はわずか 1 (565 vs 564) なので df.shape のチェックでは見落としがちですが、 型は全滅しており、 df['A1101'].mean() は失敗するか無意味な結果になります。 これは「構造化データの構造は、 ファイルが構造化されているだけでは足りず、 読み込み手順まで含めて初めて成立する」ことの最小の実証例です。 対策は本文の落とし穴と同じく df.dtypes の即時確認ですが、 このケースでは df.dtypes.value_counts() の 1 行で「object × 112」という異常が一目で見えます。
もう 1 つ、 縦持ち変換にも型の罠があります。 564 行 × 112 列を df.melt(id_vars=['SSDSE-B-2026','Code','Prefecture']) で縦持ちにすると、 実測で 61,476 行 × 5 列 (= 564 行 × 109 指標列) になりますが、 このとき value 列の dtype は実測で float64 に統一されます。 int64 の 104 列と float64 の 6 列が 1 本の列に混ざるため、 型の細かい方に揃えられるのです。 SSDSE-B-2026 の人口最大値 14,086,000 (2023 年・東京都) 程度なら float64 (仮数 53 ビット) で正確に表現できますが、 「列ごとの型情報は melt の瞬間に失われる」こと自体は覚えておく価値があります。 横持ち (wide) は型をスキーマとして保持する形式、 縦持ち (long) は型を犠牲に操作の一様性を得る形式、 というトレードオフです。
構造化データの「列に同じ種類の値が並ぶ」という性質は、 圧縮の余地そのものです。 SSDSE-B-2026 で実測すると:
memory_usage(deep=True) 合計で約 554.8 KB。astype('category') にすると、 Code は 31,020 → 4,221 バイト、 Prefecture は 40,680 → 5,505 バイトと、 それぞれ約 1/7 に縮み、 DataFrame 全体では 554.8 KB → 494.3 KB になります。category 型の中身は「47 通りの文字列辞書 + 各行には辞書番号 (小さい整数) だけ」という辞書圧縮で、 これは列指向フォーマット Parquet の dictionary encoding や、 データウェアハウスの列圧縮とまったく同じ原理です。 SSDSE-B-2026 の 60 KB では体感差はありませんが、 564 行が 564 万行になっても圧縮率の理屈は同一なので、 この小さな実測が 10⁶ 行スケールの設計判断 (本文の規模別 4 階層表を参照) の縮図になっています。 「行 = 観測、 列 = 変数」という論理構造が、 メモリ層では「列 = 同型の値の配列 = 圧縮単位」という物理構造に対応する — この二層対応こそが、 構造化データが大規模化しても高速に扱える根本理由です。