「データエンジニアリング (data engineering)」は信頼性の高いデータパイプラインを設計・構築・運用する工学分野。 ETL/ELT・データレイク・データウェアハウス・データレイクハウス・データメッシュなどのアーキテクチャと、 dbt・Airflow・Spark などのツール群を活用する。 本ページでは Modern Data Stack・バッチ vs ストリーミング・スキーマ進化・データ品質テスト・観測可能性を整理する。
これらのキーワードは「データを集める → 流す → 保つ → 信頼を担保する」というデータエンジニアリングの中核責務を構成する。
各リンクをクリックすると、 該当セクションへジャンプします:
🍰 まずはやさしく
データの整理整頓のことです。
分析しやすくするために使います。
スマホの写真をフォルダに分ける似ています。
データの変換やまとめ方を読みます。
🍰 まずはやさしく
分析の準備をまとめて行うことです。
正しい結果を出すために使います。
部活の記録を表にまとめる作業に似ています。
データの掃除や加工の手順を読みます。
本ページでは、 データエンジニアリングを統合的に解説します。 ETL/ELT・データクレンジング・結合・集約・欠損補完・エンコーディング・スケーリング・パイプラインを一気通貫で扱います。
「分析の 80% は前処理」と言われます。 SSDSE-B のような綺麗な統計データでも、 結合・型変換・欠損処理が必要です。 ここでは pandas を中心に実務で使う技を整理します。
🍰 まずはやさしく
料理の下ごしらえのようなものです。
精度の良い予測を作るために使います。
買い物リストを整理する感覚に似ています。
データを整えて流す仕組みについて読みます。
データエンジニアリングは 「料理の下ごしらえ」 です。 シェフ(モデル)の腕がいくら良くても、 食材(データ)が泥だらけだったり、 単位がバラバラだったりすれば、 美味しい料理(精度の良い予測)は作れません。 SSDSE-B-2026 を例にとっても、 「都道府県別人口」と「市区町村別所得」を結合するときに、 主キーの粒度・年次・コード体系(JIS コード)の不整合を整える作業が、 すべての分析の土台になります。
具体的には ETL(Extract → Transform → Load) のサイクルでデータを流通させます。 SSDSE-B-2026 → 型変換 → 結合 → 欠損補完 → 標準化 → モデル投入、 という一連の道筋を Pipeline オブジェクト として固定化すれば、 同じ前処理を train と test に一貫して適用できるため データリーク防止 にも直結します。
データエンジニアの設計上の最大の分岐が バッチ vs ストリーミング。 SSDSE-B-2026 のような「年次更新の公的統計」はバッチが最適。 一方、 EC サイトのクリックログや IoT センサー値はストリーミングが必要。 選択基準を整理します。
| 観点 | バッチ処理 | ストリーミング処理 |
|---|---|---|
| 更新頻度 | 日次 / 時次 / 週次 | 秒未満 / リアルタイム |
| レイテンシ | 分〜時間 | ミリ秒〜秒 |
| 代表ツール | Airflow + Spark/dbt | Kafka + 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})$$
🎯 このコードでやること: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%}") |
📤 実行すると次の出力が得られる:
💬 結果の読み方:日本の総人口は 2018→2023 で約 240 万人減(年率 -0.38%)。 これがリアルタイムストリーミングだったら「秒ごと」に更新できますが、 人口統計に秒粒度は不要 → バッチが正解。 「データの意思決定粒度に合わせて頻度を選ぶ」のが正しい設計です。
本ページの全 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・契約・テスト、 これらすべてが将来の自分(や仲間)への贈り物です。
SSDSE-B-2026 のような 564 行 × 112 列 の小規模データなら ETL(Extract → Transform → Load)で十分。 一方、 e-Stat 全体(数千万行)を扱う場合は ELT(Extract → Load → Transform)が定石。 ロード先(BigQuery / Snowflake / DuckDB)の計算資源で変換するほうが速い。
🎯 このコードでやること:SSDSE-B-2026 を「取得 → 検証 → 集計 → ロード」する 4 段 DAG を Airflow で書き、 依存関係を Python 表記で示す。
📥 入力例(タスク名と依存):
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):
💬 >> 演算子で依存を 1 行で書けるのが Airflow の強み。 失敗したタスクから再実行できる(idempotent な処理にしておくこと)。 SSDSE-B のように月次更新がない場合でも、 schedule=@monthly で源データの更新検知 + 通知だけ走らせるのが定石。
dbt を使うと、 SQL ファイルに {{ ref('staging_ssdse_b') }} と書くだけで 依存 DAG が自動生成される。 さらに schema.yml に「not_null / unique / accepted_values」と書くだけでテストが走る。 SSDSE-B のような明確に正規化されたデータは dbt の練習素材として最適。
@flow / @task デコレータで Python 関数をそのまま DAG 化できる。 SSDSE 程度の規模なら Airflow 構築コストより Prefect Cloud の無料枠が楽。pipeline.run(source) 1 行で書ける。📌 SSDSE-B-2026 のような「年 1 回更新・1 年あたり 47 行(累計 564 行)」の小規模データに Airflow を立てるのは過剰。 まず Python script + cron + Slack 通知で MVP を作り、 「複数 DAG が絡む」段階で初めて Airflow / Prefect を導入するのが現実的。
データエンジニアリングは「収集 → 蓄積 → 加工 → 配信」のパイプラインを安定運用する分野。 ここでは「ETL vs ELT」「Lambda / Kappa アーキテクチャ」「データレイクハウス階層」を概念図で押さえる。
🍰 まずはやさしく
データを数式で変換することです。
データの単位を揃えるために使います。
テストの点数を偏差値にする感覚に似ています。
数式を使ったデータの変換方法を読みます。
代表的な前処理は以下のとおり数学的に厳密に書けます。
$$ \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(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(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}}$$
0.99 以上を維持」。24 時間以内」。SSDSE-B-2026 を読み込んで、 上記 SLI を実際に計算してみます。 概算:
実際に NaN を数えると、 SSDSE-B-2026 は欠損ゼロですが、 仮に民間ログのように欠損が 600 セルあったとすると:
SLO「0.99 以上」をギリギリ満たす水準です。 もし 0.99 を下回ったら、 欠損補完ロジック追加 or 過去データの再取得を発動します。
🎯 このコードでやること:SSDSE-B-2026 を読み込み、 行数・列数・欠損セル数・Completeness(SLI)を計算する。
📥 入力データ(SSDSE-B-2026.csv の先頭 3 行、 cp932):
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}") |
📤 実行すると次の出力が得られる:
💬 結果の読み方:SSDSE-B-2026 は欠損ゼロの高品質データ(completeness = 1.000)。 SLO「0.99 以上」を余裕でクリア。 公的統計だけあって極めてクリーンです。 もし民間ログデータなら 0.85 程度に落ち込むことも多く、 SLI 監視の意義が出ます。
🎯 このコードでやること:SSDSE-B-2026 の「年度 × 都道府県 × 指標」wide 形式を、 BI/分析向きの long 形式(縦持ち)に melt で変換する。
📥 入力データ(wide 形式・df.head() 結果):
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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:行数が 564 → 61,476 に増えました(109 倍)。 long 形式は「BI ツールが集計しやすい」「新しい指標が増えても列追加不要」というメリットがあります。 一方ファイルサイズは大きくなるため、 Parquet 等の列指向圧縮と組み合わせるのが定石です。
🎯 このコードでやること: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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:違反ゼロ。 SSDSE は四捨五入があるため小さな差はあるものの、 ±1000 人の閾値内で完全整合。 民間データ(行動ログ等)ではこうしたチェックで 1〜10% 程度の不整合が見つかることが多く、 上流バグの早期検知に役立ちます。
🎯 このコードでやること: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() |
📤 実行すると次の出力が得られる:
💬 結果の読み方:BI ツールから「2023 年人口 TOP5」がワンクエリで出ました。 SQLite は単一ファイルなので個人検証向き。 本番では BigQuery / Snowflake / Redshift など列指向 DWH を選びます。 ここまでが「収集 → 変換 → 配信」の最小エンドツーエンド。
melt で動的に長持ち化、 メタデータ駆動で吸収する。pd.read_csv(..., encoding='cp932') を明示し、 失敗時はログに残す。SELECT * を雑に流すと月数十万円飛ぶ。 列指向 DWH では「必要な列・パーティション・clustering」を意識。 dry-run で課金見積もりを取る習慣を。MERGE/UPSERT or 日次パーティション置換で冪等にする。| 工程 | 代表ツール | 主な指標 (SLI) | 典型的な失敗モード |
|---|---|---|---|
| Ingestion(取り込み) | Fivetran, Airbyte, 自作 API | ingestion_lag | API レート制限、認証切れ |
| Storage(保管) | S3, GCS, HDFS | availability, durability | 権限ミス、誤削除 |
| Processing(処理) | Spark, dbt, Pandas | job_duration, success_rate | OOM、 スキュー、 タイムアウト |
| Quality(品質) | Great Expectations, dbt tests | completeness, accuracy | 整合性違反、 schema drift |
| Warehouse(DWH) | BigQuery, Snowflake, Redshift | query_latency, cost | クエリ暴走、 課金過大 |
| Orchestration(DAG) | Airflow, Dagster, Prefect | SLA miss rate | 循環依存、 リトライ無限 |
| Catalog(カタログ) | Datahub, Amundsen, OpenMetadata | coverage | 古い記述、 オーナー不在 |
| Serving(配信) | BI(Looker/Tableau), API, Feature Store | availability, p99 latency | キャッシュ汚染、 古いダッシュボード |
データエンジニアリングの 7 割は「ストレージ設計」で性能とコストが決まります。 ここでは SSDSE-B-2026 を題材に、 行指向 vs 列指向、 パーティション設計、 スキーマ進化の実例を、 具体的な数値と Python で示します。
| 観点 | 行指向(CSV / RDB) | 列指向(Parquet / ORC / DWH) |
|---|---|---|
| 1 行の読み書き | 速い(OLTP 向き) | 遅い(行が散らばっている) |
| 列集計(SUM/AVG) | 遅い(全列読み込み) | 速い(列だけ読む、 ベクトル化) |
| 圧縮率 | 低い(雑多な型が混在) | 高い(同型が並ぶので 5〜10x 圧縮) |
| typical usage | OLTP, ログ書き込み | OLAP, BI, ML 学習 |
| 代表ファイル | CSV, Avro, RDB row store | Parquet, 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 想定で計算:
小さなデータでは差は微々たるものですが、 1TB 規模では 月数十万円→数千円に変わります。 「partition 設計はコスト設計」と覚えてください。
🎯 このコードでやること: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:]) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:12 個のパーティション(年)が自動で作られた。 「WHERE year=2023」クエリは 1/12 のファイルだけ読めば済む = 約 12 倍速・12 倍安い。 BigQuery / Athena / DuckDB / Spark すべてで同じ仕組み。
🎯 このコードでやること: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'}") |
📤 実行すると次の出力が得られる:
💬 結果の読み方:列追加(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.csv | Ingestion DAG |
| L2 (Staging) | stg_ssdse_b (列名英語化) | dbt staging |
| L3 (Mart) | fact_population_yearly | dbt mart |
| L4 (Serving) | BI ダッシュボードの「東京人口」 | BI 開発者 |
🎯 このコードでやること: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 の数字を 4 ステップ遡って外部 API まで到達できる。 影響範囲分析(impact analysis)で、 上流変更時の波及先も即座にわかる。 dbt は dbt docs generate で同等のグラフを自動生成します。
リネージ・カタログ・契約は、 一見「事務作業」に見えますが、 障害対応の速度・新規メンバーのオンボーディング・経営層の信頼を直接決めます。 データエンジニアリングはコード半分、 ガバナンス半分の職能だと心得てください。
現場で頻出する質問を Q&A 形式で整理します。 SSDSE-B-2026 の例を交えて答えます。
A. 大雑把には「DE はパイプライン、 DS はモデル」。 DE は SSDSE を毎年取得・整形・配信する。 DS はその整形済みデータから人口予測モデルを作る。 ただし最近は Analytics Engineer という中間職もあり、 境界はグラデーション。
A. SQL を先に。 DWH 中心の現代では、 集計・結合・WINDOW 関数を SQL で書ける方が圧倒的に生産的。 Python は ETL や ML での周辺ロジックに使う。
A. Lake は雑多な形式で生データを安く貯める場所(S3/GCS 上の CSV/JSON/Parquet)。 Warehouse は型付きテーブル + SQL 高速集計のための整理済み倉庫(BigQuery/Snowflake)。 最近の Lakehouse(Delta/Iceberg)は両者を融合。
A. dbt は「SQL に Jinja マクロ + テスト + ドキュメント + lineage」を追加するツール。 DWH 内の Transform 層を Git 管理し、 CI/CD を回せる。 SSDSE 例では stg_ssdse_b → fact_population の SQL 変換を dbt model として書き、 テストとドキュメントを自動生成。
A. 競合しません。 Airflow は DAG オーケストレータ(取得・変換・通知の流れ全体)。 dbt は DWH 内の SQL 変換専用。 通常は「Airflow が dbt run を呼ぶ」構成で組み合わせます。
A. (1) 列指向で集計が速い、 (2) 繰り返しの多い大規模データなら Snappy/Zstd で 5〜10x 圧縮、 (3) スキーマを内包する、 (4) BigQuery/Athena/Spark/DuckDB がネイティブ対応。 ただし SSDSE のような 564 行の小データでは圧縮効果は限定的で、 Snappy だと CSV と同程度(実装 6 参照)。 真価は大規模データで出る。
A. 「意思決定に使われる粒度まで」が原則。 全列全行を 100% 検証するのは過剰。 KPI に直結する列・キー列・PII 列に集中投資。 dbt tests / Great Expectations で宣言的に書く。
A. (1) ログで例外箇所特定、 (2) 入力データの schema/件数を前日比で確認、 (3) 上流 API のステータス、 (4) リソース(OOM/Disk Full)、 (5) idempotent ならリトライ、 そうでなければ手動修正後リラン。 リネージで影響範囲を即把握。
A. (1) 移行対象テーブルの優先順位付け、 (2) Fivetran/Airbyte で初期 + CDC レプリ、 (3) dbt で staging → mart 再構築、 (4) BI を新 DWH に切替、 (5) 平行稼働で検証 → 旧停止。 SSDSE 規模なら 1 日、 業務 DB 1000 テーブルなら数ヶ月。
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 の A1101(総人口)について、 3 つの正規化を 2023 年・47 都道府県の最小・中位付近・最大の代表 3 県(鳥取県・岐阜県・東京都)で電卓レベルで追ってみます。 平均 μ ≈ 2,645,809 人、 標準偏差 σ ≈ 2,797,551 人、 最小 = 537,000 人(鳥取)、 中央値 ≈ 1,549,000 人、 最大 = 14,086,000 人(東京)、 IQR ≈ 1,602,500 人とします。
| 県 | 原値 | z-score | Min-Max | Robust (IQR) |
|---|---|---|---|---|
| 鳥取県 | 537,000 | −0.75 | 0.00 | −0.63 |
| 岐阜県 | 1,931,000 | −0.26 | 0.10 | +0.24 |
| 東京都 | 14,086,000 | +4.09 | 1.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 で実演します。
パイプラインの「待ち行列の長さ $L$」「到着率 $\lambda$(rows/sec)」「滞在時間 $W$(sec)」には、 Little の法則:
$$L = \lambda \cdot W$$
SSDSE-B-2026 は 564 行・約 360KB。 ローカルでの読み込み時間を仮に 0.05 秒とすると:
本番では並列度 8 で読み込めば、 同じバッファで $\lambda$ を 8 倍に伸ばせます(理論値)。 ただし I/O ボトルネックや GIL の影響で実測は 3〜5 倍に留まることが多いです。
🎯 このコードでやること: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()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:2 回流しても件数が変わらない=冪等。 障害復旧時にリトライしても安全。 これがない pipeline は「2 倍に膨れる事故」を起こします。
🎯 このコードでやること: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%})") |
📤 実行すると次の出力が得られる:
💬 結果の読み方:この 564 行の小さな整数中心データでは、 Snappy Parquet はむしろ CSV と同程度(約 100%)で、 圧縮メリットは出ません(zstd/brotli にすると約 70% まで下がります)。 Parquet の本質的な強みは「サイズ」より 列指向で必要な列だけ読める点と、 数百万行・繰り返しの多い大規模データで初めて効く 5〜10x 圧縮です。 DWH/Lake 標準フォーマットとして BigQuery / Athena / Spark がネイティブ対応します。
🎯 このコードでやること: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}") |
📤 実行すると次の出力が得られる:
💬 結果の読み方:3 つの期待値すべて PASS。 本番では Great Expectations / dbt tests でこれを宣言的に書き、 CI/CD に組み込みます。 期待値を「コード化」することで、 上流変更時に即検知できます。
| 役割 | 主な責務 | SSDSE 例での貢献 |
|---|---|---|
| Data Engineer | Pipeline 設計・実装・運用 | SSDSE 自動取得・整形 DAG |
| Analytics Engineer | dbt で DWH 内の変換層 | 都道府県 long テーブル化 |
| ML Engineer | Feature Store / Model 配信 | 人口予測モデルの定期再学習 |
| Data Scientist | 分析・モデリング | ARIMA 予測モデルの開発 |
| Data Steward | 品質・ガバナンス | 列辞書・ owner・SLO 管理 |
| Platform Engineer | 基盤(Spark/K8s/IAM) | 処理基盤の費用最適化 |
SSDSE-B-2026 という小さなデータでも、 「取得 → 変換 → 配信 → 監視」のすべての工程を体験できます。 まずは本ページの 7 つの Python 実装をローカルで動かし、 自分の手で SLI を測り、 冪等性を確かめ、 Parquet 化することから始めてみてください。 大規模になった時、 同じ原則がそのまま効きます。
合成データで ETL 段階別の処理時間とボトルネックを特定する。
| 段階 | 時間 | スループット [MB/s] |
|---|---|---|
| 抽出 (E) | 10 | 10.0 |
| 変換 (T) | 40 | 2.5 |
| ロード (L) | 15 | 6.67 |
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} 秒") |
💬 手計算 (Step 2) ボトルネック T、 65 秒と Python 出力が完全一致。
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)) |
最後に、 これまでの要素を統合して「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)$$
564 行を処理するパイプライン。 SSDSE-B が毎年 47 行(1 年分の都道府県)追加されると仮定:
小規模では差は小さいですが、 1 億行になると「1 時間 vs 数秒」の差になります。 incremental 設計は早めに仕込むのが鉄則。
🎯 このコードでやること: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") |
📤 実行すると次の出力が得られる:
💬 結果の読み方:8 ステップが約 1.5 秒で完了。 raw → Parquet → DWH → クエリまで一気通貫。 本番では各ステップを Airflow Task に分割し、 リトライ・SLA・通知を付与します。 でも本質的な「やること」はこの 30 行と同じです。
本ページの 8 つの Python 実装は、 この 6 ステップのうち (1)〜(3) と (5) を実体験できる構成にしてあります。 まずは手を動かすこと、 次に SLI を測ること、 最後に DAG 化すること——この順序で身につけてください。
BigQuery/Snowflake を導入する前の「個人検証 DWH」として最強なのが DuckDB。 SQLite と同じくファイル 1 つ・サーバ不要、 しかし列指向で OLAP 高速。 SSDSE-B-2026 をその場で 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()) |
📤 実行すると次の出力が得られる:
💬 結果の読み方:秋田県の高齢化率が 39.1% で全国最高。 DuckDB なら BigQuery を立てる前に、 こうした SQL 検証がローカルで即可能。 列指向 + ベクトル化エンジンで pandas より高速なケースも多い。
🎯 このコードでやること:先ほど作成した 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) はミリ秒オーダー") |
📤 実行すると次の出力が得られる:
💬 結果の読み方:Parquet + DuckDB は数百 MB 〜 数 GB クラスのデータでも秒以下。 個人検証や小チームには、 BigQuery より DuckDB の方が安く・速いケースも多い。 dbt-duckdb と組み合わせれば「dbt 開発をローカルで完結」も可能。
DuckDB を含めて、 本ページの Python 実装は 計 14 本。 SSDSE-B-2026 という 360KB の小さな CSV から、 取得・変換・配信・監視・リネージ・MDS 全レイヤを実体験できる構成にしてあります。 大規模になっても原則は同じです。 自分の手で動かして、 数字を測る習慣を身につけてください。
データエンジニアリングの実務で頻発する落とし穴。 ETL/ELT・パイプライン設計・データ品質の 3 領域で発生しやすい固有の罠を列挙する。
schema test で contract test を仕込む。seed/snapshot で時点固定。OPTIMIZE、 Spark の coalesce で 100MB-1GB に compaction。incremental + unique_key、 メッセージにイベント ID を付与。データエンジニアリングを中心に、 上流の (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 分析や機械学習を支える堅牢なデータ基盤が構築できる。
データエンジニアリングは収集と分析の間の基盤層。 3 視点 (接続・統合・比較) で隣接基盤概念との関係を整理する。
ソース (DB/API/CSV) → 取り込み (ETL/ELT) → DWH/lake (構造化/非構造化) → 変換 (dbt/Airflow) → モデリング → BI/ML 配信の流れで、 SSDSE 系の年次更新を想定した再実行性が要点。 各層に冪等性 (再実行可能) とスキーマバージョニングを組み込むと、 障害復旧と監査追跡が容易になる。
| 概念 | 主眼 | 担当領域 | 典型ツール |
|---|---|---|---|
| データエンジニアリング | 基盤構築・運用 | 収集 → 配信全般 | Airflow・dbt・Spark |
| データレイク | 生データ蓄積 | 非構造化 + 構造化 | S3・HDFS |
| 分散データ処理 | 並列計算 | 大規模変換 | Spark・Hadoop |
| MLOps | ML 運用 | モデル管理・デプロイ | MLflow・Kubeflow |
4 概念は重なるが守備範囲が異なる。 データエンジニアリングが全体を統括し、 レイクが蓄積、 分散処理が計算、 MLOps がモデル運用を担う。 SSDSE 規模なら lake 不要だが、 dbt や Airflow による再実行可能な ETL は導入価値あり。
データエンジニアリングは収集 → 変換 → 配信の三層を設計・運用する。
バッチ中心なら ETL + DWH、 ストリーミング中心なら Kafka + Flink、 ML 主体なら feature store、 と「データ流速と用途」で選ぶ。
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}')
|
pt.inverse_transform() が使える。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'])) |