論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説(ジャストインタイム型データサイエンス教育)
データエンジニアリング
Data Engineering
分析の8割は前処理 — 信頼できる分析の土台を作る
前処理ETLクレンジングPipeline

🔖 キーワード索引

データエンジニアリング (data engineering)」は信頼性の高いデータパイプラインを設計・構築・運用する工学分野。 ETL/ELT・データレイク・データウェアハウス・データレイクハウス・データメッシュなどのアーキテクチャと、 dbt・Airflow・Spark などのツール群を活用する。 本ページでは Modern Data Stack・バッチ vs ストリーミング・スキーマ進化・データ品質テスト・観測可能性を整理する。

ETL / ELTデータレイク / DWH / レイクハウスデータメッシュバッチ vs ストリーミングdbt / Airflow / Dagsterスキーマ進化データ品質テスト (Great Expectations)観測可能性 (data observability)Modern Data Stack

これらのキーワードは「データを集める → 流す → 保つ → 信頼を担保する」というデータエンジニアリングの中核責務を構成する。

🔖 キーワード索引(チップから該当箇所へジャンプ)

各リンクをクリックすると、 該当セクションへジャンプします:

直感で掴む 定義・数式 記号の読み解き 実値で計算 Python 実装 落とし穴 ETL / ELT ほか関連手法 関連用語 概念マップ 隣接手法への橋渡し 手法選択フロー

💡 30秒で分かる結論

🍰 まずはやさしく

データの整理整頓のことです。

分析しやすくするために使います。

スマホの写真をフォルダに分ける似ています。

データの変換やまとめ方を読みます。

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

🍰 まずはやさしく

分析の準備をまとめて行うことです。

正しい結果を出すために使います。

部活の記録を表にまとめる作業に似ています。

データの掃除や加工の手順を読みます。

本ページでは、 データエンジニアリングを統合的に解説します。 ETL/ELTデータクレンジング結合・集約欠損補完エンコーディングスケーリングパイプラインを一気通貫で扱います。

「分析の 80% は前処理」と言われます。 SSDSE-B のような綺麗な統計データでも、 結合・型変換・欠損処理が必要です。 ここでは pandas を中心に実務で使う技を整理します。

🎨 直感で掴む — データエンジニアリングの正体

🍰 まずはやさしく

料理の下ごしらえのようなものです。

精度の良い予測を作るために使います。

買い物リストを整理する感覚に似ています。

データを整えて流す仕組みについて読みます。

データエンジニアリングは 「料理の下ごしらえ」 です。 シェフ(モデル)の腕がいくら良くても、 食材(データ)が泥だらけだったり、 単位がバラバラだったりすれば、 美味しい料理(精度の良い予測)は作れません。 SSDSE-B-2026 を例にとっても、 「都道府県別人口」と「市区町村別所得」を結合するときに、 主キーの粒度・年次・コード体系(JIS コード)の不整合を整える作業が、 すべての分析の土台になります。

具体的には ETL(Extract → Transform → Load) のサイクルでデータを流通させます。 SSDSE-B-2026 → 型変換 → 結合 → 欠損補完 → 標準化 → モデル投入、 という一連の道筋を Pipeline オブジェクト として固定化すれば、 同じ前処理を train と test に一貫して適用できるため データリーク防止 にも直結します。

🎨 補講:バッチ vs ストリーミング — SSDSE で考える選択軸

データエンジニアの設計上の最大の分岐が バッチ vs ストリーミング。 SSDSE-B-2026 のような「年次更新の公的統計」はバッチが最適。 一方、 EC サイトのクリックログや IoT センサー値はストリーミングが必要。 選択基準を整理します。

📊 比較表

観点バッチ処理ストリーミング処理
更新頻度日次 / 時次 / 週次秒未満 / リアルタイム
レイテンシ分〜時間ミリ秒〜秒
代表ツールAirflow + Spark/dbtKafka + Flink/Spark Streaming
失敗時のリカバリ容易(再実行で OK)難(チェックポイント設計が必須)
運用コスト高(24/7 稼働)
SSDSE が当てはまるか○ 年次更新× オーバースペック
EC クリックログは△ 翌日では遅い○ レコメンドに即反映

📐 数式:ストリーミングのウィンドウ集計

$$\text{TumblingWindow}(t) = \sum_{i: t - W \le t_i < t} x_i \quad (W: \text{window size})$$

🔬 数式を言葉で読み解く

🐍 Python 実装 12:SSDSE 年次データをミニ「バッチパイプライン」化

🎯 このコードでやること:SSDSE-B-2026 を year でグループ化し、 全国合計を年次集計するシンプルなバッチを書く。 これがストリーミングなら「リアルタイム集計」になる。

📥 入力データ:SSDSE-B-2026.csv 全行。

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.rename(columns={'SSDSE-B-2026': 'year'})

# 「年次ウィンドウ」での総人口集計(バッチ)
agg = df.groupby('year')['A1101'].sum().sort_index(ascending=False)
print("日本全国 総人口(年次推移、 直近 5 年)")
print(agg.head(5).apply(lambda v: f"{v:,.0f}"))

print(f"\n2023 → 2018 の変動: {agg.loc[2023] - agg.loc[2018]:,.0f} 人")
print(f"年率変化     : {(agg.loc[2023]/agg.loc[2018])**(1/5) - 1:+.4%}")

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

日本全国 総人口(年次推移、 直近 5 年) year 2023 124,353,000 2022 124,946,000 2021 125,500,000 2020 126,146,099 2019 126,555,000 2023 → 2018 の変動: -2,395,000 人 年率変化 : -0.3808%

💬 結果の読み方:日本の総人口は 2018→2023 で約 240 万人減(年率 -0.38%)。 これがリアルタイムストリーミングだったら「秒ごと」に更新できますが、 人口統計に秒粒度は不要 → バッチが正解。 「データの意思決定粒度に合わせて頻度を選ぶ」のが正しい設計です。

⚠️ バッチ vs ストリーミング選択の落とし穴

📚 まとめ:データエンジニアリングの 3 行サマリ

  1. 原則:取得 → 変換 → 配信 → 監視 を、 SLI/SLO で定量管理。
  2. 選択軸:行 vs 列、 バッチ vs ストリーム、 ETL vs ELT、 full vs incremental。
  3. 姿勢:コード半分 + ガバナンス半分。 「この数字どこから?」に即答できる pipeline を作る。

本ページの全 12 個の Python 実装は、 すべてあなたのローカル PC で data/raw/SSDSE-B-2026.csv 1 ファイルから動きます。 まずは 実装 1(completeness 測定)から始め、 最後の 実装 12(年次バッチ集計)まで自分で動かしてみてください。 SSDSE は小さいですが、 BigQuery で 1TB のデータを扱うときも、 設計原則は同じです。

🎨 まとめの一言:データエンジニアリングは「裏方」だが「主役」

BI ダッシュボードや ML モデルが脚光を浴びがちですが、 その裏で「データが正しく・速く・安く・継続的に流れている」状態を作るのがデータエンジニアの仕事です。 派手ではないが、 ここが崩れるとすべてが崩れる——まさに インフラの役目。

本ページでは SSDSE-B-2026 を題材に 14 個の Python 実装SLI/SLO 等の数式MDS 全レイヤガバナンス・リネージバッチ vs ストリーミング までを 1 ファイルで学べる構成にしました。 知識として読むだけでなく、 ぜひ自分の手で 1 本ずつ動かしてみてください。 動かすと、 数字が見え、 設計判断の根拠が体感できるはずです。

最終目標は「3 年後の自分でも 1 時間で原因究明できる pipeline」を作れること。 リネージ・カタログ・SLO・契約・テスト、 これらすべてが将来の自分(や仲間)への贈り物です。

🛠 実践編:ETL vs ELT / Airflow / dbt を SSDSE-B-2026 で動かす

1. ETL と ELT の違い — どちらを選ぶか

SSDSE-B-2026 のような 564 行 × 112 列 の小規模データなら ETL(Extract → Transform → Load)で十分。 一方、 e-Stat 全体(数千万行)を扱う場合は ELT(Extract → Load → Transform)が定石。 ロード先(BigQuery / Snowflake / DuckDB)の計算資源で変換するほうが速い。

2. Airflow — DAG で依存を可視化

🎯 このコードでやること:SSDSE-B-2026 を「取得 → 検証 → 集計 → ロード」する 4 段 DAG を Airflow で書き、 依存関係を Python 表記で示す。

📥 入力例(タスク名と依存):

extract → validate → aggregate → load
(SSDSE-B-2026.csv の取得 → 列・型検査 → 都道府県別集計 → DuckDB へ insert)
 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
import os
os.makedirs('data', exist_ok=True)   # 書き出し先を先に作る
import os
os.makedirs('data/stg', exist_ok=True)  # 保存先のフォルダを作っておく
os.makedirs('data/mart', exist_ok=True)  # 保存先のフォルダを作っておく

import pyarrow  # parquet の読み書きに必要(ブラウザには無い)
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime
import pandas as pd, duckdb

def extract():
    df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932')
    df.to_parquet('data/stg/ssdse_b_2026.parquet')
    return len(df)

def validate():
    df = pd.read_parquet('data/stg/ssdse_b_2026.parquet')
    assert df['Prefecture'].nunique() == 47, '都道府県数が 47 でない'
    assert df['A1101'].notna().all(), 'A1101 に NA'
    return 'ok'

def aggregate():
    df = pd.read_parquet('data/stg/ssdse_b_2026.parquet')
    g = df.groupby('Prefecture')['A1101'].sum().reset_index()
    g.to_parquet('data/mart/pop_by_pref.parquet')
    return len(g)

def load():
    con = duckdb.connect('data/mart.duckdb')
    con.execute("CREATE OR REPLACE TABLE pop AS SELECT * FROM 'data/mart/pop_by_pref.parquet'")
    return con.execute('SELECT COUNT(*) FROM pop').fetchone()[0]

with DAG('ssdse_b_etl', start_date=datetime(2026, 5, 1), schedule='@monthly', catchup=False) as dag:
    t1 = PythonOperator(task_id='extract', python_callable=extract)
    t2 = PythonOperator(task_id='validate', python_callable=validate)
    t3 = PythonOperator(task_id='aggregate', python_callable=aggregate)
    t4 = PythonOperator(task_id='load', python_callable=load)
    t1 >> t2 >> t3 >> t4

📤 実行例(airflow tasks test ssdse_b_etl extract 2026-05-01):

[2026-05-29 10:00:01] extract: Returned value: 564
[2026-05-29 10:00:02] validate: Returned value: ok
[2026-05-29 10:00:03] aggregate: Returned value: 47
[2026-05-29 10:00:04] load: Returned value: 47

💬 >> 演算子で依存を 1 行で書けるのが Airflow の強み。 失敗したタスクから再実行できる(idempotent な処理にしておくこと)。 SSDSE-B のように月次更新がない場合でも、 schedule=@monthly で源データの更新検知 + 通知だけ走らせるのが定石。

3. dbt — SQL 変換のテスト・ドキュメント・lineage を自動生成

dbt を使うと、 SQL ファイルに {{ ref('staging_ssdse_b') }} と書くだけで 依存 DAG が自動生成される。 さらに schema.yml に「not_null / unique / accepted_values」と書くだけでテストが走る。 SSDSE-B のような明確に正規化されたデータは dbt の練習素材として最適。

4. Prefect / dlt — モダンな代替

📌 SSDSE-B-2026 のような「年 1 回更新・1 年あたり 47 行(累計 564 行)」の小規模データに Airflow を立てるのは過剰。 まず Python script + cron + Slack 通知で MVP を作り、 「複数 DAG が絡む」段階で初めて Airflow / Prefect を導入するのが現実的。

🎨 概念図で押さえる

データエンジニアリングは「収集 → 蓄積 → 加工 → 配信」のパイプラインを安定運用する分野。 ここでは「ETL vs ELT」「Lambda / Kappa アーキテクチャ」「データレイクハウス階層」を概念図で押さえる。

ETLとELTの違い概念図ETL (上) と ELT (下) の処理順序ETL: Extract → Transform → LoadSource変換 (Spark等)DWH (整形済)ELT: Extract → Load → TransformSourceLake (生データ)変換 (dbt等)→ 整形済→ 生も保持'>
図 A. ETL は格納前に変換、 ELT は格納後に変換。 クラウド DWH(BigQuery, Snowflake)の出現で ELT が主流化。 生データをそのまま保持できるので再加工や監査に強い。
Lambda アーキテクチャ概念図Lambda アーキテクチャ (バッチ + ストリーム 二重化)SourceBatch Layer日次 Spark / HadoopSpeed Layerリアルタイム KafkaServing Layer統合ビューバッチは正確だが遅い、 ストリームは速いが概算。 両方を統合して堅牢性 + 即時性を両立'>
図 B. Lambda アーキテクチャ。 バッチで正確性を、 ストリームで即時性を確保し、 統合層で最終的なビューを提供する。 二重実装の手間が問題で、 Kappa(ストリーム一本化)に置き換わることもある。
メダリオン階層 (Bronze/Silver/Gold) 概念図メダリオン階層 (Bronze → Silver → Gold)🥉 Bronze生データSSDSE-B 原 CSVスキーマほぼ無し🥈 Silver清浄化済tidy + 型統一欠損補完・重複削除🥇 Gold業務集計済KPI / ML特徴量BI / ダッシュボード'>
図 C. データレイクハウスのメダリオン階層。 段階的に「データを浄化・集約」していき、 各段階で再現性と監査性を保つ。 Bronze に立ち戻れるので失敗時のロールバックが容易。

📐 定義・数式 — 主要な変換の数学

🍰 まずはやさしく

データを数式で変換することです。

データの単位を揃えるために使います。

テストの点数を偏差値にする感覚に似ています。

数式を使ったデータの変換方法を読みます。

代表的な前処理は以下のとおり数学的に厳密に書けます。

$$ \text{Standardize: } z_i = \frac{x_i - \mu_x}{\sigma_x}, \quad \text{Min-Max: } x'_i = \frac{x_i - x_{\min}}{x_{\max} - x_{\min}}, \quad \text{Robust: } r_i = \frac{x_i - \text{median}(x)}{\text{IQR}(x)} $$

SSDSE-B-2026 で「総人口(A1101)」を z スコア化することは、 K-means や SVM のような距離・勾配ベース手法で 必須 です。 数式に従って $\mu$ と $\sigma$ を train で計算、 同じ値を test に適用するのが鉄則です。

🔬 数式を言葉で読み解く — 記号 → 意味

記号意味SSDSE-B-2026 での例
$\mu_x$学習データでの平均A1101(人口)の 47 都道府県平均(2023 年・約 265 万人)
$\sigma_x$学習データでの標準偏差A1101 の SD(東京の極大により大きめ)
IQR第 1 ・第 3 四分位の差外れ値の影響を受けにくいスケール尺度

数式の意味は「平均から何 SD 離れているか(z)」「最小最大の間でどの位置か(Min-Max)」「中央値と IQR で測ったロバストな位置(Robust)」。 用途で使い分けます。

🔬 深掘り:データエンジニアリングの全体像を SSDSE-B-2026 で解剖する

データエンジニアリングは「データを正しく・速く・安く・継続的に流す」職能です。 ここでは公的データ SSDSE-B-2026(47 都道府県 × 12 年 × 100 列以上)を題材に、 取得 → 変換 → 配信 → 監視 の各工程を、 実コードと実数値で具体化します。 単に Airflow を紹介するのではなく、 「なぜその設計か」を式と数値で説明します。

🎨 直感:データエンジニアリング = 「配管 + 検針メーター + 警報装置」

水道に例えると、 取水(収集)→ 浄水(クレンジング)→ 配水(DWH/Lake)→ 蛇口(BI/ML)まで、 すべての配管を保守する仕事です。 水質が悪化したら警報(データ品質アラート)が鳴り、 漏水(欠損・重複)が見つかれば修理します。 単に「ETL を書く人」ではなく、 SLO(Service Level Objective)を守る運用者でもあります。

水道のメタファーデータエンジニアリング用語SSDSE 例での具体物
取水ポンプIngestion(取り込み)SSDSE-B-2026.csv を毎年自動 DL
浄水場Cleansing / Validation欠損 NULL → 0 補完、 cp932 → UTF-8 変換
貯水池Data Lake(生データ層)data/raw/SSDSE-B-*.csv をそのまま保管
配水ポンプETL / ELT(変換層)都道府県 × 年 × 指標の long 形式へ整形
給水塔DWH(分析用テーブル)SQLite/BigQuery の prefecture_population 表
水質検査Data Quality(DQ)「総人口 ≥ 男 + 女」整合性チェック
蛇口Serving(BI/API/ML)BI で人口グラフ、 ML で人口予測
水道台帳Metadata / Lineage「列 A1101 は総人口、 source=SSDSE-B-2026」

📐 数式:SLI と SLO で品質を定量化する

データパイプラインの品質は感覚ではなく、 SLI(Service Level Indicator、 サービス指標)SLO(目標値)で測ります。 代表的な指標は freshness(鮮度)completeness(完全性)accuracy(正確性)です。

例えば完全性 SLI は次の式で定義できます:

$$\text{Completeness} = 1 - \dfrac{N_{\text{missing}}}{N_{\text{total}}}, \quad \text{Freshness} = T_{\text{now}} - T_{\text{last\_update}}$$

🔬 数式を言葉で読み解く

🧮 実値で計算:SSDSE-B-2026 の品質指標を測る

SSDSE-B-2026 を読み込んで、 上記 SLI を実際に計算してみます。 概算:

実際に NaN を数えると、 SSDSE-B-2026 は欠損ゼロですが、 仮に民間ログのように欠損が 600 セルあったとすると:

Completeness = 1 - 600 / 61476 = 1 - 0.00976 ≈ 0.9902 (99.0%)

SLO「0.99 以上」をギリギリ満たす水準です。 もし 0.99 を下回ったら、 欠損補完ロジック追加 or 過去データの再取得を発動します。

🐍 Python 実装 1:SSDSE-B-2026 を取り込み、 完全性を測る

🎯 このコードでやること:SSDSE-B-2026 を読み込み、 行数・列数・欠損セル数・Completeness(SLI)を計算する。

📥 入力データ(SSDSE-B-2026.csv の先頭 3 行、 cp932):

SSDSE-B-2026,Code,Prefecture,A1101,A110101,A110102,A1102,A110201,... 年度,地域コード,都道府県,総人口,総人口(男),総人口(女),日本人人口,日本人人口(男),... 2023,R01000,北海道,5092000,2405000,2688000,5041000,2380000,...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd

# 1. Ingestion: SSDSE-B-2026 を読み込む(cp932, 2 行目はラベル行なので skip)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])

# 2. データ列のみ抽出(年度・地域コード・都道府県名は除く)
data_cols = df.columns[3:]

# 3. SLI を計算
n_rows, n_cols = df.shape[0], len(data_cols)
n_total = n_rows * n_cols
n_missing = df[data_cols].isna().sum().sum()
completeness = 1 - n_missing / n_total
print(f"rows={n_rows}, cols={n_cols}, total={n_total}, missing={n_missing}, completeness={completeness:.4f}")

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

rows=564, cols=109, total=61476, missing=0, completeness=1.0000

💬 結果の読み方:SSDSE-B-2026 は欠損ゼロの高品質データ(completeness = 1.000)。 SLO「0.99 以上」を余裕でクリア。 公的統計だけあって極めてクリーンです。 もし民間ログデータなら 0.85 程度に落ち込むことも多く、 SLI 監視の意義が出ます。

🐍 Python 実装 2:ETL — wide 形式を long 形式に変換

🎯 このコードでやること:SSDSE-B-2026 の「年度 × 都道府県 × 指標」wide 形式を、 BI/分析向きの long 形式(縦持ち)に melt で変換する。

📥 入力データ(wide 形式・df.head() 結果):

SSDSE-B-2026 Code Prefecture A1101 A1102 ... 0 2023 R01000 北海道 5092000 5041000 ... 1 2022 R01000 北海道 5140000 5098000 ...
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])
df = df.rename(columns={'SSDSE-B-2026': 'year', 'Code': 'code', 'Prefecture': 'pref'})

# wide → long: 1 行 = (year, pref, metric, value)
long_df = df.melt(
    id_vars=['year', 'code', 'pref'],
    var_name='metric',
    value_name='value'
)

print(long_df.shape)
print(long_df.head(3))

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

(61476, 5) year code pref metric value 0 2023 R01000 北海道 A1101 5092000.0 1 2022 R01000 北海道 A1101 5140000.0 2 2021 R01000 北海道 A1101 5183000.0

💬 結果の読み方:行数が 564 → 61,476 に増えました(109 倍)。 long 形式は「BI ツールが集計しやすい」「新しい指標が増えても列追加不要」というメリットがあります。 一方ファイルサイズは大きくなるため、 Parquet 等の列指向圧縮と組み合わせるのが定石です。

🐍 Python 実装 3:Data Quality チェック — 整合性アサーション

🎯 このコードでやること:SSDSE-B-2026 で「総人口 = 男性人口 + 女性人口」が成り立つかをチェックし、 違反行を表示する(整合性 DQ)。

📥 入力データ:A1101(総人口), A110101(男), A110102(女)の 3 列を使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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': 'year', 'Prefecture': 'pref'})

# 整合性チェック: A1101 (総人口) == A110101 (男) + A110102 (女) ?
df['sum_mf'] = df['A110101'] + df['A110102']
df['diff'] = df['A1101'] - df['sum_mf']

# 1000 人以上の乖離を異常としてフラグ
bad = df[df['diff'].abs() > 1000][['year', 'pref', 'A1101', 'sum_mf', 'diff']]
print(f"violations: {len(bad)} rows")
print(f"最大の乖離: {df['diff'].abs().max()} 人(許容 1000 人)")
if bad.empty:
    print("整合性チェック OK — 総人口 = 男 + 女 がすべての行で成り立っています")
else:
    print(bad.head(5))

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

violations: 0 rows 最大の乖離: 1000 人(許容 1000 人) 整合性チェック OK — 総人口 = 男 + 女 がすべての行で成り立っています

💬 結果の読み方:違反ゼロ。 SSDSE は四捨五入があるため小さな差はあるものの、 ±1000 人の閾値内で完全整合。 民間データ(行動ログ等)ではこうしたチェックで 1〜10% 程度の不整合が見つかることが多く、 上流バグの早期検知に役立ちます。

🐍 Python 実装 4:DWH 配信 — SQLite に書き出して BI 連携

🎯 このコードでやること:long 形式の SSDSE データを SQLite データベースに投入し、 BI/分析クエリから SQL でアクセス可能にする。

📥 入力データ:直前の long_df(行 61,476)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import sqlite3
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': 'year', 'Code': 'code', 'Prefecture': 'pref'})
long_df = df.melt(id_vars=['year', 'code', 'pref'], var_name='metric', value_name='value')

# DWH 役の SQLite に投入
conn = sqlite3.connect('warehouse.db')
long_df.to_sql('ssdse_b_long', conn, if_exists='replace', index=False)

# 分析クエリ: 2023 年の人口 TOP5
q = """SELECT pref, value AS population FROM ssdse_b_long
       WHERE year = 2023 AND metric = 'A1101'
       ORDER BY value DESC LIMIT 5"""
print(pd.read_sql(q, conn))
conn.close()

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

pref population 0 東京都 14086000 1 神奈川県 9229000 2 大阪府 8763000 3 愛知県 7477000 4 埼玉県 7331000

💬 結果の読み方:BI ツールから「2023 年人口 TOP5」がワンクエリで出ました。 SQLite は単一ファイルなので個人検証向き。 本番では BigQuery / Snowflake / Redshift など列指向 DWH を選びます。 ここまでが「収集 → 変換 → 配信」の最小エンドツーエンド。

⚠️ 落とし穴(深掘り版)

🌐 関連手法・派生

🗺 概念マップ:データエンジニアリングの全工程

工程代表ツール主な指標 (SLI)典型的な失敗モード
Ingestion(取り込み)Fivetran, Airbyte, 自作 APIingestion_lagAPI レート制限、認証切れ
Storage(保管)S3, GCS, HDFSavailability, durability権限ミス、誤削除
Processing(処理)Spark, dbt, Pandasjob_duration, success_rateOOM、 スキュー、 タイムアウト
Quality(品質)Great Expectations, dbt testscompleteness, accuracy整合性違反、 schema drift
Warehouse(DWH)BigQuery, Snowflake, Redshiftquery_latency, costクエリ暴走、 課金過大
Orchestration(DAG)Airflow, Dagster, PrefectSLA miss rate循環依存、 リトライ無限
Catalog(カタログ)Datahub, Amundsen, OpenMetadatacoverage古い記述、 オーナー不在
Serving(配信)BI(Looker/Tableau), API, Feature Storeavailability, p99 latencyキャッシュ汚染、 古いダッシュボード

🔗 関連用語(前提・並列・発展)

📥 前提

  • RDB — テーブル設計
  • SQL — 変換言語
  • JSON — 半構造化
  • API — 取り込み源

🔀 並列

🚀 発展

🔬 補講:ストレージ形式・パーティション・スキーマ管理

データエンジニアリングの 7 割は「ストレージ設計」で性能とコストが決まります。 ここでは SSDSE-B-2026 を題材に、 行指向 vs 列指向、 パーティション設計、 スキーマ進化の実例を、 具体的な数値と Python で示します。

📊 行指向 vs 列指向:何が違うのか

観点行指向(CSV / RDB)列指向(Parquet / ORC / DWH)
1 行の読み書き速い(OLTP 向き)遅い(行が散らばっている)
列集計(SUM/AVG)遅い(全列読み込み)速い(列だけ読む、 ベクトル化)
圧縮率低い(雑多な型が混在)高い(同型が並ぶので 5〜10x 圧縮)
typical usageOLTP, ログ書き込みOLAP, BI, ML 学習
代表ファイルCSV, Avro, RDB row storeParquet, ORC, Arrow, BigQuery

SSDSE-B-2026 のような「都道府県 × 年 × 指標」の集計データは、 ほぼ間違いなく列指向(Parquet)で持つべきです。 「2023 年の総人口列だけ欲しい」というクエリで、 ファイル全体を読まなくて済むからです。

📐 パーティション設計の数式:プルーニング率

$$\text{Pruning Ratio} = 1 - \dfrac{\text{Scanned Partitions}}{\text{Total Partitions}}$$

🔬 数式を言葉で読み解く

🧮 実値で計算:パーティション分割のコスト効果

BigQuery の課金は「スキャンしたバイト数」基準(オンデマンド 6.25 ドル/TB)。 SSDSE-B 想定で計算:

非パーティション : 全 360KB スキャン → ¥0.003 / クエリ year パーティション: 約 30KB スキャン → ¥0.00025 / クエリ (12 倍安い) 仮に 1 日 10,000 クエリ流すと: 非パーティション : ¥30 / 日 → ¥900 / 月 year パーティション: ¥2.5 / 日 → ¥75 / 月 (年で ¥9,900 節約)

小さなデータでは差は微々たるものですが、 1TB 規模では 月数十万円→数千円に変わります。 「partition 設計はコスト設計」と覚えてください。

🐍 Python 実装 9:Parquet を year でパーティション保存

🎯 このコードでやること:SSDSE-B-2026 を year ごとに分割した Parquet(Hive 形式パーティション)として保存する。

📥 入力データ:SSDSE-B-2026.csv の全 564 行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import os
os.makedirs('ssdse_part', exist_ok=True)   # 書き出し先を先に作る
import pyarrow  # parquet の読み書きに必要(ブラウザには無い)
import pandas as pd
import os

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df.rename(columns={'SSDSE-B-2026': 'year'})

# year をパーティションキーに → ssdse_part/year=2023/part-0.parquet
df.to_parquet('ssdse_part/', partition_cols=['year'], compression='snappy')

# どんなパーティションが作られた?
parts = sorted(os.listdir('ssdse_part'))
print("partitions created:", len(parts))
print("first 3:", parts[:3])
print("last 3:", parts[-3:])

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

partitions created: 12 first 3: ['year=2012', 'year=2013', 'year=2014'] last 3: ['year=2021', 'year=2022', 'year=2023']

💬 結果の読み方:12 個のパーティション(年)が自動で作られた。 「WHERE year=2023」クエリは 1/12 のファイルだけ読めば済む = 約 12 倍速・12 倍安い。 BigQuery / Athena / DuckDB / Spark すべてで同じ仕組み。

🐍 Python 実装 10:スキーマ進化(schema evolution)の検出

🎯 このコードでやること:SSDSE-B-2026 と仮想の「来年版(列追加)」を比較し、 schema drift を検出する。

📥 入力データ:今年の DataFrame と、 列を 1 つ追加した DataFrame。

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

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

# 仮想「来年版」: 既存 + 新列 NEW_METRIC、 一部列が消失
df_next = df_now.copy()
df_next['NEW_METRIC'] = 0
df_next = df_next.drop(columns=['A1101'])   # 仮想削除

added = set(df_next.columns) - set(df_now.columns)
removed = set(df_now.columns) - set(df_next.columns)

print(f"added cols  : {sorted(added)}")
print(f"removed cols: {sorted(removed)}")
print(f"breaking change?: {'YES' if removed else 'no'}")

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

added cols : ['NEW_METRIC'] removed cols: ['A1101'] breaking change?: YES

💬 結果の読み方:列追加(NEW_METRIC)は後方互換だが、 列削除(A1101 = 総人口)は 下流の BI/ML が壊れる致命的変更。 CI で検出して PR をブロックする運用を強く推奨。 dbt の contract や Protobuf でも同様の仕組みが使えます。

⚠️ ストレージ設計の落とし穴

ここまでで本ページに登場した Python 実装は 10 本、 すべて SSDSE-B-2026 を入力とし、 ローカルで動きます。 「データエンジニアリングとは何か」を理屈で理解するだけでなく、 ファイルサイズ・スループット・コスト・スキーマの数値を自分の手で測ってこそ、 実務感覚が身につきます。

🔗 補講:データリネージとガバナンス — 「この数字どこから来たの?」に答える

BI ダッシュボードに「東京の総人口 1,408 万人」と出たとき、 ステークホルダーから必ず来る質問が 「この数字、 どこから来たの?」。 これに即答できないと、 データの信頼性は崩壊します。 解は データリネージ(lineage、 系譜)の整備です。

🎨 リネージの直感:「祖先と子孫を辿れる家系図」

BI 上の数字を「子孫」、 元データを「祖先」とすると、 リネージは家系図のようなもの。 BI → SQL view → dbt model → DWH table → ELT → raw csv → 外部 API、 と遡れる必要があります。

レイヤSSDSE 例変換責任者
L0 (Source)e-Stat / SSDSE 公式サイト政府統計(外部)
L1 (Raw)data/raw/SSDSE-B-2026.csvIngestion DAG
L2 (Staging)stg_ssdse_b (列名英語化)dbt staging
L3 (Mart)fact_population_yearlydbt mart
L4 (Serving)BI ダッシュボードの「東京人口」BI 開発者

🐍 Python 実装 11:簡易リネージグラフの構築

🎯 このコードでやること:SSDSE-B-2026 を起点とする小さなリネージグラフを辞書で表現し、 「BI の指標 → raw csv まで遡る」探索を実装する。

📥 入力データ:パイプラインのノード間依存(辞書)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lineage = {
    'bi_tokyo_population': ['fact_population_yearly'],
    'fact_population_yearly': ['stg_ssdse_b'],
    'stg_ssdse_b': ['raw_ssdse_b_2026_csv'],
    'raw_ssdse_b_2026_csv': ['external_estat_api'],
    'external_estat_api': [],
}

def trace(node, depth=0):
    print('  ' * depth + '└─ ' + node)
    for parent in lineage.get(node, []):
        trace(parent, depth + 1)

trace('bi_tokyo_population')

# 影響範囲分析: external API が変わったら何に波及する?
downstream = {n for n, ps in lineage.items() if 'external_estat_api' in ps}
print('downstream of external_estat_api:', downstream)

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

└─ bi_tokyo_population └─ fact_population_yearly └─ stg_ssdse_b └─ raw_ssdse_b_2026_csv └─ external_estat_api downstream of external_estat_api: {'raw_ssdse_b_2026_csv'}

💬 結果の読み方:BI の数字を 4 ステップ遡って外部 API まで到達できる。 影響範囲分析(impact analysis)で、 上流変更時の波及先も即座にわかる。 dbt は dbt docs generate で同等のグラフを自動生成します。

🌐 ガバナンスの 3 本柱

⚠️ ガバナンスの落とし穴

リネージ・カタログ・契約は、 一見「事務作業」に見えますが、 障害対応の速度・新規メンバーのオンボーディング・経営層の信頼を直接決めます。 データエンジニアリングはコード半分、 ガバナンス半分の職能だと心得てください。

🔬 補講:よくある Q&A — データエンジニアリングの初学者がつまずく 10 問

現場で頻出する質問を Q&A 形式で整理します。 SSDSE-B-2026 の例を交えて答えます。

Q1. データエンジニアとデータサイエンティストの違いは?

A. 大雑把には「DE はパイプライン、 DS はモデル」。 DE は SSDSE を毎年取得・整形・配信する。 DS はその整形済みデータから人口予測モデルを作る。 ただし最近は Analytics Engineer という中間職もあり、 境界はグラデーション。

Q2. SQL と Python、 どちらを優先で学ぶ?

A. SQL を先に。 DWH 中心の現代では、 集計・結合・WINDOW 関数を SQL で書ける方が圧倒的に生産的。 Python は ETL や ML での周辺ロジックに使う。

Q3. データレイクとデータウェアハウスの違いは?

A. Lake は雑多な形式で生データを安く貯める場所(S3/GCS 上の CSV/JSON/Parquet)。 Warehouse は型付きテーブル + SQL 高速集計のための整理済み倉庫(BigQuery/Snowflake)。 最近の Lakehouse(Delta/Iceberg)は両者を融合。

Q4. dbt とは何? なぜみんな使うのか

A. dbt は「SQL に Jinja マクロ + テスト + ドキュメント + lineage」を追加するツール。 DWH 内の Transform 層を Git 管理し、 CI/CD を回せる。 SSDSE 例では stg_ssdse_bfact_population の SQL 変換を dbt model として書き、 テストとドキュメントを自動生成。

Q5. Airflow と dbt は競合?

A. 競合しません。 Airflow は DAG オーケストレータ(取得・変換・通知の流れ全体)。 dbt は DWH 内の SQL 変換専用。 通常は「Airflow が dbt run を呼ぶ」構成で組み合わせます。

Q6. なぜ Parquet なのか、 CSV じゃダメ?

A. (1) 列指向で集計が速い、 (2) 繰り返しの多い大規模データなら Snappy/Zstd で 5〜10x 圧縮、 (3) スキーマを内包する、 (4) BigQuery/Athena/Spark/DuckDB がネイティブ対応。 ただし SSDSE のような 564 行の小データでは圧縮効果は限定的で、 Snappy だと CSV と同程度(実装 6 参照)。 真価は大規模データで出る。

Q7. データ品質はどこまで担保すべき?

A. 「意思決定に使われる粒度まで」が原則。 全列全行を 100% 検証するのは過剰。 KPI に直結する列・キー列・PII 列に集中投資。 dbt tests / Great Expectations で宣言的に書く。

Q8. ETL ジョブが落ちました。 何から見る?

A. (1) ログで例外箇所特定、 (2) 入力データの schema/件数を前日比で確認、 (3) 上流 API のステータス、 (4) リソース(OOM/Disk Full)、 (5) idempotent ならリトライ、 そうでなければ手動修正後リラン。 リネージで影響範囲を即把握。

Q9. オンプレ DB から DWH への移行手順は?

A. (1) 移行対象テーブルの優先順位付け、 (2) Fivetran/Airbyte で初期 + CDC レプリ、 (3) dbt で staging → mart 再構築、 (4) BI を新 DWH に切替、 (5) 平行稼働で検証 → 旧停止。 SSDSE 規模なら 1 日、 業務 DB 1000 テーブルなら数ヶ月。

Q10. 今から学ぶならどの順番?

A. (1) SQL(PostgreSQL/DuckDB), (2) Python + pandas, (3) dbt-core + DuckDB(ローカル DWH), (4) Airflow(小さな DAG), (5) BigQuery/Snowflake(クラウド DWH), (6) Great Expectations(DQ), (7) Datahub / OpenLineage(ガバナンス)。 SSDSE は (1)〜(3) の練習材料として最適。

本ページは「データエンジニアリングとは何か」を、 概念定義だけでなく、 SSDSE-B-2026 を使った 14 個の Python 実装 + 10 個の Q&A + 各種数式・落とし穴で立体的に解説しました。 概念マップ → 実装 → 補講 の順で読み返すと、 自分の中に「使える知識」として定着します。

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

SSDSE-B-2026 の A1101(総人口)について、 3 つの正規化を 2023 年・47 都道府県の最小・中位付近・最大の代表 3 県(鳥取県・岐阜県・東京都)で電卓レベルで追ってみます。 平均 μ ≈ 2,645,809 人、 標準偏差 σ ≈ 2,797,551 人、 最小 = 537,000 人(鳥取)、 中央値 ≈ 1,549,000 人、 最大 = 14,086,000 人(東京)、 IQR ≈ 1,602,500 人とします。

原値z-scoreMin-MaxRobust (IQR)
鳥取県537,000−0.750.00−0.63
岐阜県1,931,000−0.260.10+0.24
東京都14,086,000+4.091.00+7.82

z-score では東京が +4.09σ という強い外れ値だが、 Min-Max は東京を 1.0、 鳥取を 0.0 に張り付ける(中位付近の岐阜は 0.10 で左寄り)。 Robust スケーリングでは東京が +7.8 を超え、 外れ値性がより強調されます。 線形回帰では z-score、 木モデルでは Min-Max、 外れ値検出では Robust と使い分けます。

🧮 さらに深掘り:パイプラインの観測性・冪等性・パフォーマンス

SLI 監視だけでは不十分です。 ここでは 観測性(Observability)冪等性(Idempotency)パフォーマンス(並列・列指向) を、 SSDSE-B-2026 を題材に Python で実演します。

📐 数式:Little の法則とパイプラインのスループット

パイプラインの「待ち行列の長さ $L$」「到着率 $\lambda$(rows/sec)」「滞在時間 $W$(sec)」には、 Little の法則:

$$L = \lambda \cdot W$$

🔬 数式を言葉で読み解く

🧮 実値で計算:SSDSE 取り込みのスループット見積もり

SSDSE-B-2026 は 564 行・約 360KB。 ローカルでの読み込み時間を仮に 0.05 秒とすると:

throughput = 564 / 0.05 = 11,280 rows/sec 1 行あたり滞在時間 W ≈ 0.05 sec Little の法則: L = 11280 × 0.05 = 564 rows(バッファ内に常時 564 行)

本番では並列度 8 で読み込めば、 同じバッファで $\lambda$ を 8 倍に伸ばせます(理論値)。 ただし I/O ボトルネックや GIL の影響で実測は 3〜5 倍に留まることが多いです。

🐍 Python 実装 5:冪等な MERGE(UPSERT)パターン

🎯 このコードでやること:SSDSE データを (year, code, metric) を主キーにして SQLite に冪等投入する。 二度実行しても重複しない。

📥 入力データ:long_df(year, code, pref, metric, value の 5 列)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sqlite3
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': 'year', 'Code': 'code', 'Prefecture': 'pref'})
long_df = df.melt(id_vars=['year', 'code', 'pref'], var_name='metric', value_name='value')

conn = sqlite3.connect('warehouse.db')
conn.execute("""CREATE TABLE IF NOT EXISTS ssdse_b_upsert (
    year INTEGER, code TEXT, pref TEXT, metric TEXT, value REAL,
    PRIMARY KEY (year, code, metric)
)""")

# 冪等な UPSERT: 同じキーは置換 → 何度流しても結果が同じ
rows = long_df.to_dict('records')
conn.executemany(
    """INSERT OR REPLACE INTO ssdse_b_upsert
       VALUES (:year, :code, :pref, :metric, :value)""", rows)
conn.commit()
print(conn.execute("SELECT COUNT(*) FROM ssdse_b_upsert").fetchone())

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

(61476,) # 2 回流しても (61476,) のまま → 冪等性 OK

💬 結果の読み方:2 回流しても件数が変わらない=冪等。 障害復旧時にリトライしても安全。 これがない pipeline は「2 倍に膨れる事故」を起こします。

🐍 Python 実装 6:列指向 Parquet で行指向 CSV を圧縮する

🎯 このコードでやること:SSDSE-B-2026 を Parquet(列指向 + Snappy 圧縮)に変換し、 CSV と比較してファイルサイズを測る。

📥 入力データ:SSDSE-B-2026.csv(約 360KB, 564 行 × 112 列)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import pyarrow  # parquet の読み書きに必要(ブラウザには無い)
import pandas as pd
import os

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

# Parquet (Snappy) で保存
df.to_parquet('ssdse_b_2026.parquet', compression='snappy')

size_csv = os.path.getsize('data/raw/SSDSE-B-2026.csv')
size_pq = os.path.getsize('ssdse_b_2026.parquet')
print(f"CSV     : {size_csv:>10,} bytes")
print(f"Parquet : {size_pq:>10,} bytes  (対 CSV 比 {size_pq/size_csv:.2%})")

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

CSV : 359,821 bytes Parquet : 361,420 bytes (対 CSV 比 100.44%)

💬 結果の読み方:この 564 行の小さな整数中心データでは、 Snappy Parquet はむしろ CSV と同程度(約 100%)で、 圧縮メリットは出ません(zstd/brotli にすると約 70% まで下がります)。 Parquet の本質的な強みは「サイズ」より 列指向で必要な列だけ読める点と、 数百万行・繰り返しの多い大規模データで初めて効く 5〜10x 圧縮です。 DWH/Lake 標準フォーマットとして BigQuery / Athena / Spark がネイティブ対応します。

🐍 Python 実装 7:Great Expectations 風のシンプル DQ

🎯 このコードでやること:SSDSE-B-2026 の総人口列に「常に正」「東京 ≥ 沖縄」「合計 = 男 + 女」など複数の期待値を書き、 違反を一括検出する。

📥 入力データ:SSDSE-B-2026(A1101 総人口、 A110101 男、 A110102 女)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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': 'year', 'Prefecture': 'pref'})

expectations = []

# 1. 総人口は常に正
expectations.append(('A1101 > 0', (df['A1101'] > 0).all()))

# 2. 各年で東京(13) ≥ 沖縄(47) の人口
yearly = df.pivot_table(index='year', columns='pref', values='A1101')
expectations.append(('Tokyo >= Okinawa', (yearly['東京都'] >= yearly['沖縄県']).all()))

# 3. 総人口 == 男 + 女 (±5000 人許容)
diff = (df['A1101'] - df['A110101'] - df['A110102']).abs()
expectations.append(('|total - (M+F)| <= 5000', (diff <= 5000).all()))

for name, ok in expectations: print(f"[{'PASS' if ok else 'FAIL'}] {name}")

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

[PASS] A1101 > 0 [PASS] Tokyo >= Okinawa [PASS] |total - (M+F)| <= 5000

💬 結果の読み方:3 つの期待値すべて PASS。 本番では Great Expectations / dbt tests でこれを宣言的に書き、 CI/CD に組み込みます。 期待値を「コード化」することで、 上流変更時に即検知できます。

⚠️ アンチパターン(実務でよく見る失敗)

📚 補足:データエンジニアのキャリアと組織

役割主な責務SSDSE 例での貢献
Data EngineerPipeline 設計・実装・運用SSDSE 自動取得・整形 DAG
Analytics Engineerdbt で DWH 内の変換層都道府県 long テーブル化
ML EngineerFeature Store / Model 配信人口予測モデルの定期再学習
Data Scientist分析・モデリングARIMA 予測モデルの開発
Data Steward品質・ガバナンス列辞書・ owner・SLO 管理
Platform Engineer基盤(Spark/K8s/IAM)処理基盤の費用最適化

SSDSE-B-2026 という小さなデータでも、 「取得 → 変換 → 配信 → 監視」のすべての工程を体験できます。 まずは本ページの 7 つの Python 実装をローカルで動かし、 自分の手で SLI を測り、 冪等性を確かめ、 Parquet 化することから始めてみてください。 大規模になった時、 同じ原則がそのまま効きます。

🧮 数式に値を入れて手で計算する: ETL パイプラインのスループット

合成データで ETL 段階別の処理時間とボトルネックを特定する。

Step 1: 段階別処理 [秒/100MB]

段階時間スループット [MB/s]
抽出 (E)1010.0
変換 (T)402.5
ロード (L)156.67

Step 2: ボトルネックと全体時間

直列実行: 10+40+15 = 65 秒 ボトルネック: T (2.5 MB/s 最小) T を 2 倍化すると 10+20+15 = 45 秒 (31% 短縮)

🐍 Python で再現

1
2
3
4
5
6
7
import numpy as np
times = np.array([10, 40, 15])
throughput = 100 / times
total = times.sum()
print(f"スループット: {throughput.round(2)} MB/s")
print(f"ボトルネック: stage {throughput.argmin()}")
print(f"直列合計: {total} 秒")

📤 実行結果

スループット: [10. 2.5 6.67] MB/s ボトルネック: stage 1 直列合計: 65 秒

💬 手計算 (Step 2) ボトルネック T、 65 秒と Python 出力が完全一致。

🐍 Python 実装 — 3 つのスケーラを 1 行ずつ

🎯 目的:sklearn の StandardScaler / MinMaxScaler / RobustScaler を SSDSE-B-2026 の A1101 に適用して、 上の表と同じ数値が出ることを確認する。
📥 入力data/raw/SSDSE-B-2026.csv の 2023 年・A1101 列(47 行)。 各 Scaler に渡すには 2 次元化([[...]])が必須。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
x = df[df['SSDSE-B-2026'] == 2023][['A1101']]   # 2023 年・47 都道府県
print('z-score (mean=0, std=1):',
      StandardScaler().fit_transform(x).flatten()[:3].round(2))
print('min-max [0,1]          :',
      MinMaxScaler().fit_transform(x).flatten()[:3].round(2))
print('robust (median, IQR)   :',
      RobustScaler().fit_transform(x).flatten()[:3].round(2))
📤 出力 z-score (mean=0, std=1): [ 0.88 -0.53 -0.54] min-max [0,1] : [0.34 0.05 0.05] robust (median, IQR) : [ 2.21 -0.23 -0.24] (最初の 3 県:北海道・青森・岩手)
💬 解釈:北海道は人口約 509 万人で全国平均(約 265 万人)を上回るため z は +0.88、 Min-Max では 0.34(範囲のおおよそ 34 % 地点)。 RobustScaler は中央値 154.9 万人 / IQR 160.3 万人を使うため、 鳥取県(z=−0.75)と東京都(z=+4.09)の差が縮みすぎず、 外れ値の影響を抑えながら本来の分布を残します。

🐍 ケーススタディ:SSDSE-B-2026 で実現する 8 ステップの最小 ELT

最後に、 これまでの要素を統合して「raw CSV → Parquet → DWH (SQLite) → 分析クエリ → 監視ログ」までの最小 ELT を一気通貫で組みます。 すべて SSDSE-B-2026 を入力とし、 ローカル環境だけで完結します。

📐 数式:データボリュームの増分処理(差分積み上げ)

全件再処理(full refresh)は単純ですが、 データ量 $N$ に比例した時間 $T \propto N$ がかかります。 増分処理(incremental)は新規分 $\Delta N$ だけ処理:

$$T_{\text{incr}} = T_0 + c \cdot \Delta N \ll T_{\text{full}} = c \cdot N \quad (\Delta N \ll N)$$

🔬 数式を言葉で読み解く

🧮 実値で計算:full vs incremental の比較

564 行を処理するパイプライン。 SSDSE-B が毎年 47 行(1 年分の都道府県)追加されると仮定:

full refresh : 564 行 × 0.000035 sec/行 = 0.0197 sec incremental : 47 行 × 0.000035 sec/行 + 0.01 (overhead) = 0.0116 sec 比較 : incremental は full の約 59% の時間

小規模では差は小さいですが、 1 億行になると「1 時間 vs 数秒」の差になります。 incremental 設計は早めに仕込むのが鉄則。

🐍 Python 実装 8:エンドツーエンド ELT スクリプト

🎯 このコードでやること:SSDSE-B-2026 を取得・Parquet 化・SQLite 投入・品質チェック・サマリクエリまで一気通貫で実行する。

📥 入力データ:data/raw/SSDSE-B-2026.csv(cp932)。

 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 pyarrow  # parquet の読み書きに必要(ブラウザには無い)
import pandas as pd
import sqlite3
import time

t0 = time.time()

# STEP 1: Extract(取得)
# 改名元が英字の列名なので skiprows=[1] で読む(header=1 だと日本語の列名になる)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df.rename(columns={'SSDSE-B-2026': 'year', 'Code': 'code', 'Prefecture': 'pref'})

# STEP 2: Save raw to Parquet
df.to_parquet('ssdse_b_2026.parquet', compression='snappy')

# STEP 3: Transform(wide → long)
long_df = df.melt(id_vars=['year', 'code', 'pref'], var_name='metric', value_name='value')

# STEP 4: DQ チェック
# 気温(B41xx)は氷点下があるので、非負チェックの対象外にする
_nonneg = long_df[~long_df['metric'].str.startswith('B41')]
assert (_nonneg['value'].dropna() >= 0).all(), "negative value found"
assert long_df['year'].between(1975, 2030).all(), "year out of range"

# STEP 5: Load → SQLite
conn = sqlite3.connect('warehouse.db')
long_df.to_sql('ssdse_b_long', conn, if_exists='replace', index=False)

# STEP 6: 分析クエリ
top5 = pd.read_sql(
    "SELECT pref, value FROM ssdse_b_long WHERE year=2023 AND metric='A1101' ORDER BY value DESC LIMIT 5", conn)
conn.close()
print(top5); print(f"elapsed: {time.time()-t0:.2f} sec")

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

pref value 0 東京都 14086000.0 1 神奈川県 9229000.0 2 大阪府 8763000.0 3 愛知県 7477000.0 4 埼玉県 7331000.0 所要時間: 0.1 秒未満(実行環境で変わる)

💬 結果の読み方:8 ステップが約 1.5 秒で完了。 raw → Parquet → DWH → クエリまで一気通貫。 本番では各ステップを Airflow Task に分割し、 リトライ・SLA・通知を付与します。 でも本質的な「やること」はこの 30 行と同じです。

📚 学習ロードマップ:データエンジニアになるための 6 ステップ

  1. SQL を読み書きできる — JOIN, WINDOW 関数, CTE まで(SSDSE を SQLite で触る本ページが入り口)。
  2. Python + pandas でデータ整形 — melt, pivot, groupby, merge を自在に。
  3. 列指向ファイル(Parquet)と DWH — Snappy 圧縮、 partition、 clustering。
  4. Orchestration(Airflow/Dagster/Prefect) — DAG、 retry、 SLA、 backfill。
  5. Data Quality と Observability — Great Expectations / dbt tests / OpenLineage。
  6. クラウド DWH と IaC — BigQuery/Snowflake/Redshift + Terraform/dbt。

本ページの 8 つの Python 実装は、 この 6 ステップのうち (1)〜(3) と (5) を実体験できる構成にしてあります。 まずは手を動かすこと、 次に SLI を測ること、 最後に DAG 化すること——この順序で身につけてください。

🐍 補講:DuckDB で「個人用 DWH」を 5 分で立ち上げる

BigQuery/Snowflake を導入する前の「個人検証 DWH」として最強なのが DuckDB。 SQLite と同じくファイル 1 つ・サーバ不要、 しかし列指向で OLAP 高速。 SSDSE-B-2026 をその場で SQL 集計してみます。

🐍 Python 実装 13:DuckDB で CSV を直接 SQL 集計

🎯 このコードでやること:DuckDB で SSDSE-B-2026.csv を直接 SQL クエリ。 INSERT 不要、 ファイルパスのままテーブルとして扱える。

📥 入力データ:data/raw/SSDSE-B-2026.csv(先に Parquet 化済みなら更に速い)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import duckdb

# 1. CSV をテーブルとして直接クエリ(INSERT 不要)
con = duckdb.connect()
con.execute("""CREATE OR REPLACE VIEW ssdse AS
    SELECT * FROM read_csv_auto('data/raw/SSDSE-B-2026.csv',
                                 ignore_errors=true,
                                 skip=2,
                                 header=false)""")

# 2. 集計クエリ: 2023 年の高齢化率 TOP5(A1303 / A1101)
q = """SELECT column02 AS pref, ROUND(column15 / column03 * 100, 1) AS aging_pct
       FROM ssdse WHERE column00 = 2023 ORDER BY aging_pct DESC LIMIT 5"""
print(con.execute(q).fetchdf())

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

pref aging_pct 0 秋田県 39.1 1 高知県 36.3 2 徳島県 35.4 3 山口県 35.4 4 青森県 35.2

💬 結果の読み方:秋田県の高齢化率が 39.1% で全国最高。 DuckDB なら BigQuery を立てる前に、 こうした SQL 検証がローカルで即可能。 列指向 + ベクトル化エンジンで pandas より高速なケースも多い。

🐍 Python 実装 14:DuckDB で Parquet を直接読む

🎯 このコードでやること:先ほど作成した Parquet を DuckDB から直接 SELECT し、 BigQuery 同様の体験をローカルで再現する。

📥 入力データ:ssdse_b_2026.parquet(実装 6 で作成)。

1
2
3
4
5
6
7
8
9
10
11
12
import duckdb

con = duckdb.connect()

# Parquet をテーブルとして直接 SELECT
q = """SELECT Prefecture, A1101 AS population
       FROM 'ssdse_b_2026.parquet'
       WHERE "SSDSE-B-2026" = 2023
       ORDER BY A1101 DESC
       LIMIT 3"""
print(con.execute(q).fetchdf())
print("読み込み時間 (DuckDB + Parquet) はミリ秒オーダー")

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

Prefecture population 0 東京都 14086000 1 神奈川県 9229000 2 大阪府 8763000 読み込み時間 (DuckDB + Parquet) はミリ秒オーダー

💬 結果の読み方:Parquet + DuckDB は数百 MB 〜 数 GB クラスのデータでも秒以下。 個人検証や小チームには、 BigQuery より DuckDB の方が安く・速いケースも多い。 dbt-duckdb と組み合わせれば「dbt 開発をローカルで完結」も可能。

⚠️ DuckDB の使い所と限界

DuckDB を含めて、 本ページの Python 実装は 計 14 本。 SSDSE-B-2026 という 360KB の小さな CSV から、 取得・変換・配信・監視・リネージ・MDS 全レイヤを実体験できる構成にしてあります。 大規模になっても原則は同じです。 自分の手で動かして、 数字を測る習慣を身につけてください。

⚠️ 落とし穴

データエンジニアリングの実務で頻発する落とし穴。 ETL/ELT・パイプライン設計・データ品質の 3 領域で発生しやすい固有の罠を列挙する。

🗺 概念マップ

データエンジニアリングを中心に、 上流の (ETL / ELT / CDC)、 並列の (データクレンジング / データウェアハウス / Data Lake)、 下流の (BI / 機械学習 / Feature Store) を配置した。 SSDSE-B-2026 のような年次更新 CSV データを取り込み、 標準化・変換し、 BI や ML 配信まで運ぶ流れを 1 枚で俯瞰できる。

データエンジニアリング周辺の概念は (1) 抽出・変換・読込 (ETL / ELT / CDC / change data capture)、 (2) ストレージ (DWH / Data Lake / Lakehouse / DataMart)、 (3) 処理基盤 (Spark / Airflow / dbt / Kafka)、 (4) 品質管理 (データクレンジング / バリデーション / リネージ追跡) の 4 層に整理される。 これらを統合的に設計することで、 後段の BI 分析や機械学習を支える堅牢なデータ基盤が構築できる。

データエンジニアリング ETL ELT データクレンジング 特徴量エンジニアリング データパイプライン データ品質

🔗 隣接手法への橋渡し

データエンジニアリングは収集と分析の間の基盤層。 3 視点 (接続・統合・比較) で隣接基盤概念との関係を整理する。

🔌 接続: 上流・下流での連鎖

🧩 統合: 基盤パイプラインへの組み込み

ソース (DB/API/CSV) → 取り込み (ETL/ELT) → DWH/lake (構造化/非構造化) → 変換 (dbt/Airflow) → モデリング → BI/ML 配信の流れで、 SSDSE 系の年次更新を想定した再実行性が要点。 各層に冪等性 (再実行可能) とスキーマバージョニングを組み込むと、 障害復旧と監査追跡が容易になる。

⚖️ 比較: 隣接基盤概念との位置づけ

概念主眼担当領域典型ツール
データエンジニアリング基盤構築・運用収集 → 配信全般Airflow・dbt・Spark
データレイク生データ蓄積非構造化 + 構造化S3・HDFS
分散データ処理並列計算大規模変換Spark・Hadoop
MLOpsML 運用モデル管理・デプロイMLflow・Kubeflow

4 概念は重なるが守備範囲が異なる。 データエンジニアリングが全体を統括し、 レイクが蓄積、 分散処理が計算、 MLOps がモデル運用を担う。 SSDSE 規模なら lake 不要だが、 dbt や Airflow による再実行可能な ETL は導入価値あり。

🌳 手法選択フロー

データエンジニアリングは収集 → 変換 → 配信の三層を設計・運用する。

  1. 収集 → 蓄積の設計は? Yes → データ収集、 No → データレイク を先に確認
  2. 変換 (ETL/ELT) の設計は? Yes → ETL、 No → データクレンジング を先に確認
  3. 配信・基盤の設計は? Yes → 分散データ処理、 No → BI ツール を先に確認

バッチ中心なら ETL + DWH、 ストリーミング中心なら Kafka + Flink、 ML 主体なら feature store、 と「データ流速と用途」で選ぶ。

③ PowerTransformer で歪んだ分布を正規化

🎯 目的:右に強く歪む SSDSE 人口分布(2023 年・47 都道府県)に対して、 log1p / Yeo-Johnson / Quantile の 3 つの非線形変換を適用し、 歪度 (skewness) がどこまで 0 に近づくかを比較する。 線形回帰の前処理として最適な変換を選ぶ判断材料。
📥 入力df[df['SSDSE-B-2026']==2023][['A1101']](47 行 × 1 列、 単位:人)。 sklearn の PowerTransformer / QuantileTransformer はいずれも 2 次元入力を要求する点に注意。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.preprocessing import PowerTransformer, QuantileTransformer

x = df[df['SSDSE-B-2026'] == 2023][['A1101']]   # 2023 年・47 都道府県 × 1 列

# Yeo-Johnson 変換:負値もOK、Box-Cox の拡張
pt = PowerTransformer(method='yeo-johnson')
x_transformed = pt.fit_transform(x)

# Quantile 変換:分位点ベース、任意分布に強制マッピング
qt = QuantileTransformer(output_distribution='normal', n_quantiles=47)
x_quantile = qt.fit_transform(x)

# 歪度の比較
print(f'元の歪度       : {stats.skew(x["A1101"]):.3f}')
print(f'log1p 後       : {stats.skew(np.log1p(x["A1101"])):.3f}')
print(f'Yeo-Johnson 後 : {stats.skew(x_transformed.flatten()):.3f}')
print(f'Quantile 後    : {stats.skew(x_quantile.flatten()):.3f}')
📤 出力 元の歪度 : 2.219 log1p 後 : 0.793 Yeo-Johnson 後 : 0.115 Quantile 後 : 0.000
💬 解釈:歪度 2.22 → 0.12 まで縮む Yeo-Johnson が SSDSE 人口データには実用的。 ただし Quantile は強引すぎて分位点間隔の情報を失うため、 解釈性を重んじるなら log1p か Yeo-Johnson を推奨。 後で逆変換が必要な場合は pt.inverse_transform() が使える。

④ Polars で高速処理(pandas の代替)

🎯 目的:pandas に代わる Rust 製の Polars で SSDSE-B-2026 を読み込み、 「2023 年度のみ抽出 → 高齢化率列を派生 → log1p 列を派生 → 高齢化率降順 → 上位 5 県」を 1 つのメソッドチェーンで処理する。 Lazy 実行による最適化を体感する。
📥 入力data/raw/SSDSE-B-2026.csv (CP932、 2 行目スキップ)。 利用列:SSDSE-B-2026(年)・A1101(総人口)・A1303(65歳以上人口)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import polars as pl

df_pl = pl.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skip_rows_after_skiprows=[1])

# pandas より 5-10 倍速い:lazy evaluation で最適化
result = (df_pl
    .filter(pl.col('SSDSE-B-2026') == 2023)
    .with_columns([
        (pl.col('A1303') / pl.col('A1101') * 100).alias('aging_rate'),
        pl.col('A1101').log1p().alias('log_pop')
    ])
    .sort('aging_rate', descending=True)
    .head(5))
print(result.select(['Prefecture', 'A1101', 'aging_rate', 'log_pop']))
📤 出力 shape: (5, 4) ┌────────────┬─────────┬────────────┬───────────┐ │ Prefecture ┆ A1101 ┆ aging_rate ┆ log_pop │ │ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ f64 ┆ f64 │ ╞════════════╪═════════╪════════════╪═══════════╡ │ 秋田県 ┆ 914000 ┆ 39.059081 ┆ 13.725587 │ │ 高知県 ┆ 666000 ┆ 36.336336 ┆ 13.409046 │ │ 徳島県 ┆ 695000 ┆ 35.395683 ┆ 13.451669 │ │ 山口県 ┆ 1298000 ┆ 35.362096 ┆ 14.076336 │ │ 青森県 ┆ 1184000 ┆ 35.219595 ┆ 13.984410 │ └────────────┴─────────┴────────────┴───────────┘
💬 解釈:Polars は同じ処理を pandas より 5〜10 倍高速に実行できる(高齢化率の上位 5 県はいずれも 35% 超で、 最高は秋田県の約 39%)。 メソッドチェーンが SQL 的に読みやすく、 SSDSE のような中規模データでも将来の数百万行データセットに移行しやすいスタイル。