論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
テーブル
Table
データエンジニアリング
別称: 表

🔖 キーワード索引

テーブルを扱うときに出てくる用語。 後半のチップは、 このページで SSDSE-B-2026 を使って実際に確かめている操作である。

行(レコード)・列(フィールド)主キー (年度, 地域コード)外部キー部分関数従属と第 2 正規形都道府県マスタとファクト縦持ち(long)・横持ち(wide)melt / pivot集計表の新しい主キー位置ではなくキーで取る列指向(Parquet)と行指向(CSV)
#行列構造#RDB#主キー#DataFrame#スキーマ#正規化
テーブル行 (レコード)列 (フィールド)主キー外部キー正規化RDBMSテーブル定義索引 (Index)

💡 30秒で分かる結論

🍰 まずはやさしく

テーブルはデータの表のことです。

情報を整理してまとめるために使います。

スマホの連絡先リストのような形式です。

行と列でできたデータの単位を学びます。

テーブル:DB内の行と列で構成されるデータ単位

📍 文脈ボックス

🍰 まずはやさしく

テーブルはデータ管理の基本です。

大量のデータを正しく扱うために使います。

部活動の名簿を作る感覚に似ています。

データの仕組みと扱い方を学びます。

この用語は データエンジニアリング カテゴリに属します。 関連する別称・略号:表。

テーブル (Table) はリレーショナル DB の基本単位で、 行 (record) と列 (field) で 1 つのエンティティを表現する。 主キー・外部キー・正規化 (第 1〜3 正規形)・索引 (Index) を理解すれば、 SSDSE-B-2026 の都道府県 × 年データを高速かつ整合性のある形で扱える。 DataFrame との対応も本ページで確認する。 このページで使う SSDSE-B-2026 は 564 行 × 112 列の 1 枚の表で、 1 行は「1 都道府県の 1 年度」、 主キーは (年度, 地域コード) の組である。 この 2 点を押さえておくと、 以下の分割・集計・結合・並べ替えの例で、 表の行数と主キーがどう変わるかを追いやすい。

🎨 直感で掴む

🍰 まずはやさしく

テーブルは整理整頓された棚のようなものです。

分析の準備をスムーズにするために使います。

エクセルのシートに書き出すイメージです。

行と列の構造がなぜ大切かを知ります。

SSDSE-B-2026 をエディタで開くと、 1 行目に SSDSE-B-2026,Code,Prefecture,A1101,… という列コード、 2 行目に 年度,地域コード,都道府県,総人口,… という日本語の項目名が並び、 3 行目以降に 2023,R01000,北海道,5092000,… のように 1 つの都道府県の 1 年度につき 1 行のデータが並ぶ。 この「1 行 = 1 都道府県の 1 年度という観測単位、 1 列 = 1 つの変数」が テーブルの本質だ。

同じ情報を Excel で開けばシート、 SQLite に入れれば CREATE TABLE prefectures、 pandas で読めば DataFrame — 表現は違えど概念は同一。 統計学者 Hadley Wickham が 2014 年に提唱した Tidy data 原則 (Journal of Statistical Software) も、 結局のところ「テーブルをどう設計すべきか」を厳密に定義したもの。 つまり あらゆるデータ分析の出発点がこの「行 × 列の構造」なのだ。

逆に、 テーブルになっていないデータ — JSON のネスト、 ログの半構造、 画像のピクセル配列 — はすべて、 「いかにテーブルに落とすか (= tidy 化)」が前処理の主戦場になる。 SSDSE-B では既に綺麗な long-tidy で配布されているので、 本ページではこれを「お手本テーブル」として扱う。

📐 定義・数式

🍰 まずはやさしく

テーブルは行と列の組み合わせです。

データの場所を正確に決めるために使います。

買い物リストの項目と金額のような形です。

テーブルを数式でどう表すか学びます。

【テーブルの形式表現】
$$ T = \{(\text{row}_i, \text{col}_j) \to v_{ij} \mid i=1..n,\, j=1..m\} $$

$n$ 行 $m$ 列のテーブルは、 行 $i$ と列 $j$ の組に値 $v_{ij}$ を割り当てる写像。 各列はデータ型 (整数・文字列・日付) が固定される (スキーマ)。

🔬 数式を言葉で読み解く

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

$n$
行数。 観測数・レコード数。
$m$
列数。 変数の数・属性の数。
Primary Key
主キー。 行を一意に識別する列。
Foreign Key
外部キー。 他テーブルの主キーを参照する。

🔬 数式を言葉で読み解く(深掘り)

先の「記号を読み解く」を、 テーブル の固有事情に即して 500 字以上で詳説する。 ここを読めば、 数式 1 行の裏にある設計判断と歴史的経緯が見えるはずだ。

$n$ (行数) は観測単位の数を表す。 SSDSE-B-2026 では $n=564$(47 都道府県 × 12 年度、 1 年度に絞れば 47)、 家計調査の個票なら約 8,000 世帯、 一般に Web ログなら $n=10^9$ に到達することもある。 $m$ (列数) は変数の数。 SSDSE-B-2026 では 112 列(年度・地域コード・都道府県と 109 指標)で、 1 列 = 1 統計指標。 重要なのは「列はスキーマ (型 + 制約)」を持つこと、 で行とは非対称な関係であり、 列の追加は構造変更、 行の追加は通常運用。 Primary Keyは SSDSE-B-2026 では 地域コード 単独ではなく (年度, 地域コード) の組がそれに相当し(1 年度に絞れば地域コードだけで足りる)、 一意性 + NOT NULL + 不変性 (主キーは後から変えない) の三条件を満たす設計が良い。 Foreign Keyは別テーブルへの参照を担うが、 SSDSE のような単表分析では明示的に出てこない代わりに、 後述する outer-join で同じキー (都道府県) を介して別テーブルを統合するときに概念的に活躍する。 つまりテーブル $T = \{(i,j) \to v_{ij}\}$ という関数表現は、 数学的にきれいだが、 実務的には スキーマ + PK + FK + インデックスの四点セットで実体を持つ。 この四点を意識しないと、 「動くけど壊れている」テーブル設計に陥りやすい。

📌 数式は 暗記対象ではなく検算ツール。 「結果が変だ」と感じたとき、 「この記号は本来こういう意味だから、 ここの値はおかしい」と 逆引きできる状態が、 中級者と上級者の差を生む。

🧪 SSDSE-B-2026 ハンズオン

本サイトの標準データ SSDSE-B-2026(47 都道府県 × 約 100 列、 独立行政法人統計センター提供) を使い、 テーブル の概念を 実コードで体感する。 取得経路は data/raw/SSDSE-B-2026.csv(リポジトリ同梱)。

SSDSE-B-2026.csv (47 都道府県 × 12 年度 = 564 行 × 112 列) を 4 ステップで読み込み、 テーブルの素性を確認していく。 実際にコピペで動くコードを次の Python 実装セクションに置いてあるので、 まずは流れを把握してほしい。

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年度) 年度 地域コード 都道府県 総人口 … 2023 R01000 北海道 5,092,000 … 2022 R01000 北海道 5,140,000 … 2021 R01000 北海道 5,183,000 … …(全 564 行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# SSDSE-B-2026 を読み込み、テーブルとして基本診断する(pandas)
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, dtype={'地域コード': str}, encoding='cp932')
print('shape =', df.shape)             # (564, 112) = 47 都道府県 × 12 年度
print('dtypes:', df.dtypes.value_counts())
print('head:', df.head(3))
# 主キー候補の確認
print('is_unique(都道府県) =', df['都道府県'].is_unique)
print('is_unique(地域コード) =', df['地域コード'].is_unique)
# 欠損サマリ
print(df.isna().sum().sort_values(ascending=False).head(10))
# メモリ使用量(最適化前後)
print('mem MB =', df.memory_usage(deep=True).sum() / 1024**2)
df_opt = df.copy()
df_opt['都道府県'] = df_opt['都道府県'].astype('category')
print('mem MB after category =', df_opt.memory_usage(deep=True).sum() / 1024**2)
📤 実行例(実測) shape = (564, 112) dtypes: int64 104 float64 6 object 2 Name: count, dtype: int64 head: 年度 地域コード 都道府県 ... 教育費(二人以上の世帯) 教養娯楽費(二人以上の世帯) その他の消費支出(二人以上の世帯) 0 2023 R01000 北海道 ... 6911 25661 48694 1 2022 R01000 北海道 ... 9551 27234 46466 2 2021 R01000 北海道 ... 9913 23762 51583 [3 rows x 112 columns] is_unique(都道府県) = False is_unique(地域コード) = False 年度 0 地域コード 0 着工新設住宅戸数 0 外国人延べ宿泊者数 0 延べ宿泊者数 0 一般旅券発行件数 0 就職件数(一般) 0 充足数(一般) 0 月間有効求人数(一般) 0 月間有効求職者数(一般) 0 dtype: int64 mem MB = 0.5473136901855469 me …(以下略)

💬 テーブルの形は 564 行 × 112 列(47 都道府県 × 12 年度)で、型は整数 104・小数 6・文字列 2 列。head の 3 行がすべて北海道(2023・2022・2021 年度)なので、都道府県も地域コードも is_unique が False になり、単独では主キーにならない。都道府県列を category 型にするとメモリは 0.547 MB から 0.508 MB と約 7% 減るだけで、564 行程度の表では型最適化の効果は小さい。

📌 動かないときは: (1) data/raw/SSDSE-B-2026.csv がリポジトリに存在するか、 (2) skiprows=1 でヘッダーが正しく読めているか、 (3) 列名が SSDSE 公式の最新版と一致しているかを df.columns.tolist() で確認。

記号を SSDSE-B-2026 の実際の値で読むと次のとおり。

記号意味SSDSE-B-2026 での値
$n$行数(観測単位の数)564(47 都道府県 × 12 年度)。 2023 年度に絞れば 47
$m$列数(属性の数)112(年度・地域コード・都道府県と 109 指標)
$n \times m$セルの数564 × 112 = 63,168 個。 欠損は 0 個
$v_{ij}$行 $i$ と列 $j$ の組が指す 1 つの値(2023 年度・北海道, A1101)→ 5,092,000
主キー行 $i$ を一意に決める列の組(年度, 地域コード)。 地域コード単独では重複が 517 行
スキーマ各列の型の約束int64 が 104 列、 float64 が 6 列、 文字列が 2 列

🧮 実値で計算してみる

SSDSE-B-2026 のテーブルを読み込み、 形状・型を確認する。

STEP 1 読み込み
pd.read_csv で DataFrame に変換。
STEP 2 形状確認
df.shape で (行数, 列数) を取得。
STEP 3 型確認
df.dtypes で各列のデータ型を確認。
STEP 4 主キー確認
df['都道府県'].is_unique で一意性を確認。

🧮 数式に値を入れて手で計算する: テーブルセル数

合成テーブルで行×列 セル数を計算する。

Step 1: テーブル

テーブル行列セル
顧客10001010,000
注文50001575,000
商品5002010,000

Step 2: 合計

総セル = 10000+75000+10000 = 95,000

🐍 Python で再現

1
2
3
tables = [(1000, 10), (5000, 15), (500, 20)]
total = sum(r*c for r, c in tables)
print(f"総セル: {total:,}")

📤 実行結果

総セル: 95,000

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

🧮 表を 2 つに分けて、 結合し直すと元に戻るか確かめる

🎯 このコードでやること:SSDSE-B-2026 の都道府県名が地域コードだけで決まる(部分関数従属)ことを確かめ、 表を「都道府県マスタ(47 行)」と「年度別ファクト(564 行)」に分けてから、 結合し直すと元の表に戻るかを検査する

📥 入力例 SSDSE-B-2026 全 564 行 × 112 列 SSDSE-B-2026 Code Prefecture A1101 … 2023 R01000 北海道 5092000 … 2022 R01000 北海道 5140000 …
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd

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

# 都道府県名は地域コードだけで決まる(部分関数従属)ことを確かめる
print('Code ごとの県名の種類の最大:', int(df.groupby('Code')['Prefecture'].nunique().max()))

# 2 つの表に分ける: 都道府県マスタ と 年度別ファクト
master = df[['Code', 'Prefecture']].drop_duplicates().reset_index(drop=True)
fact = df.drop(columns=['Prefecture'])
print('マスタ:', master.shape, ' 主キー Code の重複:', int(master['Code'].duplicated().sum()))
print('ファクト:', fact.shape, ' 主キー (年度, Code) の重複:',
      int(fact.duplicated(['SSDSE-B-2026', 'Code']).sum()))

# 県名の文字列を何回持っているか
print('県名のセル数  分ける前:', df['Prefecture'].size, ' 分けた後:', master['Prefecture'].size)

# 結合し直すと元の表に戻るか
back = fact.merge(master, on='Code', how='left', validate='many_to_one')[df.columns]
same = back.sort_values(['SSDSE-B-2026', 'Code']).reset_index(drop=True).equals(
    df.sort_values(['SSDSE-B-2026', 'Code']).reset_index(drop=True))
print('結合し直した表が元と一致:', same)
📤 実行例(実測) Code ごとの県名の種類の最大: 1 マスタ: (47, 2) 主キー Code の重複: 0 ファクト: (564, 111) 主キー (年度, Code) の重複: 0 県名のセル数 分ける前: 564 分けた後: 47 結合し直した表が元と一致: True

💬 どの Code にも県名は 1 種類しかなく、 都道府県名は主キー (年度, Code) の一部である Code だけで決まる。 これが第 2 正規形に反する部分関数従属で、 元の表は同じ県名を 564 回持っている。 Code を主キーとする 47 行 × 2 列のマスタと、 (年度, Code) を主キーとする 564 行 × 111 列のファクトに分けると、 県名は 47 回だけになり、 どちらの主キーにも重複は無い。 validate='many_to_one' で結合し直した表は元と完全に一致したので、 分けても情報は失われていない。 分析では結合済みの 1 枚が便利だが、 県名の表記を直すときなどはマスタの 1 か所を直せば済む点が分けた形の強みである。

🧮 集計すると行の意味と主キーが変わる

🎯 このコードでやること:都道府県番号から 8 地方の区分を作り、 総人口を (地方, 年度) で合計した集計表を作る。 集計表の主キーが何になるかを確かめ、 2023 年度の人口シェアと 2012→2023 年度の増減率を横持ちに組み替えて読む

📥 入力例 SSDSE-B-2026 全 564 行 Code Prefecture SSDSE-B-2026 A1101 R01000 北海道 2023 5,092,000 R13000 東京都 2023 14,086,000 地方の区分: 番号 1 北海道、 2〜7 東北、 8〜14 関東、 15〜23 中部、 24〜30 近畿、 31〜35 中国、 36〜39 四国、 40〜47 九州・沖縄
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
num = df['Code'].str[1:3].astype(int)                  # 都道府県番号 1〜47
bins = [0, 1, 7, 14, 23, 30, 35, 39, 47]
names = ['北海道', '東北', '関東', '中部', '近畿', '中国', '四国', '九州・沖縄']
df['地方'] = pd.cut(num, bins=bins, labels=names)

agg = df.groupby(['地方', 'SSDSE-B-2026'], observed=True)['A1101'].sum().reset_index()
print('集計した表:', agg.shape, ' 主キー (地方, 年度) の重複:',
      int(agg.duplicated(['地方', 'SSDSE-B-2026']).sum()))
wide = agg.pivot(index='地方', columns='SSDSE-B-2026', values='A1101')
share = (wide[2023] / wide[2023].sum() * 100).round(1)
change = ((wide[2023] / wide[2012] - 1) * 100).round(1)
print(pd.DataFrame({'2023 人口シェア%': share, '2012→2023 増減%': change}).to_string())
📤 実行例(実測) 集計した表: (96, 3) 主キー (地方, 年度) の重複: 0 2023 人口シェア% 2012→2023 増減% 地方 北海道 4.1 -6.8 東北 6.7 -9.2 関東 35.0 2.0 中部 16.7 -4.0 近畿 17.7 -3.1 中国 5.7 -5.9 四国 2.9 -9.0 九州・沖縄 11.3 -3.6

💬 集計した表は 8 地方 × 12 年度 = 96 行で、 (地方, 年度) の組の重複は 0。 集計すると行の意味が「県の 1 年度」から「地方の 1 年度」に変わり、 主キーもそれに合わせて変わる。 2023 年度の人口シェアは関東が 35.0% で、 2012→2023 年度に人口が増えたのは関東(+2.0%)だけ、 最も減ったのは東北(−9.2%)と四国(−9.0%)。 集計表を作るときは、 新しい主キーを確かめてから次の結合や比較に使う。

🧮 要約表も 1 枚のテーブル — describe() の行と列

🎯 このコードでやること:2023 年度の 3 列(総人口・合計特殊出生率・年平均気温)に describe() をかけ、 できた要約表の形(何が行で何が列か)と、 転置 .T した後の形を比べる

📥 入力例 SSDSE-B-2026 の 2023 年度 47 行から 3 列 総人口(A1101) 合計特殊出生率(A4103) 年平均気温(B4101) 5,092,000 1.06 11.0 ← 北海道 14,086,000 0.99 17.6 ← 東京都
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])
d = df[df['SSDSE-B-2026'] == 2023][['A1101', 'A4103', 'B4101']]
d.columns = ['総人口', '合計特殊出生率', '年平均気温']

t = d.describe()
print('describe() の形:', t.shape, '→ 行 =', list(t.index)[:3], '… / 列 = 変数')
print('転置 .T の形  :', t.T.shape, '→ 行 = 変数 / 列 = 統計量')
print(t.T[['count', 'mean', 'std', 'min', 'max']].round(2).to_string())
📤 実行例(実測) describe() の形: (8, 3) → 行 = ['count', 'mean', 'std'] … / 列 = 変数 転置 .T の形 : (3, 8) → 行 = 変数 / 列 = 統計量 count mean std min max 総人口 47.0 2645808.51 2797551.41 537000.00 14086000.0 合計特殊出生率 47.0 1.29 0.13 0.99 1.6 年平均気温 47.0 16.80 2.05 11.00 23.8

💬 describe() の結果は 8 行 × 3 列で、 行が統計量(count・mean・std …)、 列が変数になる。 元の表の「行 = 県」は消え、 新しい表の 1 行は「1 つの統計量」を表す。 論文の Table 1 のように変数を縦に並べたいときは .T で転置し、 3 行 × 8 列(行 = 変数)にする。 総人口の平均 2,645,808.5 人・標準偏差 2,797,551.4 人、 出生率の平均 1.29、 年平均気温の範囲 11.0〜23.8℃ が読める。 要約表も 1 枚のテーブルなので、 「この表の 1 行は何か」を最初に確かめる。

🧮 横持ちと縦持ちで、 同じ増減率を 2 通りに計算する

🎯 このコードでやること:総人口の 2012→2023 年度の増減率を、 年度を列にした横持ちの表(47 行 × 12 列)では列どうしの割り算で、 1 行 = 県 × 年度の縦持ちの表(564 行)では県ごとの最初と最後の比較で求め、 結果が一致するかを確かめる

📥 入力例 SSDSE-B-2026 全 564 行から 3 列 Prefecture 年度 A1101 北海道 2023 5,092,000 北海道 2022 5,140,000 …
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd

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

# 横持ち(年度が列): 列どうしの引き算で 2012→2023 の増減
wide = long.pivot(index='Prefecture', columns='年度', values='A1101')
print('横持ちの形:', wide.shape)
chg_w = (wide[2023] / wide[2012] - 1) * 100

# 縦持ち(1 行 = 県 × 年度): 並べ替えてから県ごとに最初と最後を比べる
long = long.sort_values(['Prefecture', '年度'])
g = long.groupby('Prefecture')['A1101']
chg_l = (g.last() / g.first() - 1) * 100
print('縦持ちの形:', long.shape)
print('2 つのやり方の結果が一致:', bool((chg_w.round(6) == chg_l.round(6)).all()))
print('2012→2023 の増減率 上位 3:', chg_w.nlargest(3).round(1).to_dict())
print('2012→2023 の増減率 下位 3:', chg_w.nsmallest(3).round(1).to_dict())
📤 実行例(実測) 横持ちの形: (47, 12) 縦持ちの形: (564, 3) 2 つのやり方の結果が一致: True 2012→2023 の増減率 上位 3: {'東京都': 6.4, '沖縄県': 4.0, '神奈川県': 1.8} 2012→2023 の増減率 下位 3: {'秋田県': -14.0, '青森県': -12.3, '高知県': -11.3}

💬 横持ちでは wide[2023] / wide[2012] − 1 の 1 行で書けるが、 縦持ちでは年度順に並べ替えてから県ごとに最初と最後を取る手順が要る。 2 つの結果は 47 県すべてで一致し、 増えたのは東京都 +6.4%・沖縄県 +4.0%・神奈川県 +1.8% など、 最も減ったのは秋田県 −14.0%・青森県 −12.3%・高知県 −11.3%。 縦持ちで並べ替えを忘れると、 SSDSE-B-2026 は新しい年度が先に並ぶので first() が 2023 年度を返し、 増減の向きが逆になる。 計算の種類に合わせて表の向きを選び、 縦持ちでは並び順に頼らないことが大事である。

🧮 スキーマを数える — 112 列の型は何を表しているか

🎯 このコードでやること:SSDSE-B-2026 の 112 列の型(スキーマ)を数え、 小数を持つ float64 の 6 列と文字列の 2 列がどの項目かを、 2 行目の項目名と並べて確かめる

📥 入力例 data/raw/SSDSE-B-2026.csv 1 行目: SSDSE-B-2026,Code,Prefecture,A1101,…(列コード) 2 行目: 年度,地域コード,都道府県,総人口,…(項目名)
1
2
3
4
5
6
7
8
9
10
11
import pandas as pd

path = 'data/raw/SSDSE-B-2026.csv'
head = pd.read_csv(path, encoding='cp932', header=None, nrows=2)
name = dict(zip(head.iloc[0], head.iloc[1]))          # 列コード → 項目名
df = pd.read_csv(path, encoding='cp932', skiprows=[1])

print(df.dtypes.value_counts().to_string())
for c in df.columns[df.dtypes == 'float64']:
    print(f'  float64: {c:7s} {name[c]:<28s} 例 {df[c].iloc[0]}')
print('  文字列 :', [f'{c}({name[c]})' for c in df.columns[df.dtypes == 'object']])
📤 実行例(実測) int64 104 float64 6 object 2 float64: A4103 合計特殊出生率 例 1.06 float64: B4101 年平均気温 例 11.0 float64: B4102 最高気温(日最高気温の月平均の最高値) 例 30.9 float64: B4103 最低気温(日最低気温の月平均の最低値) 例 -7.4 float64: B4109 降水量(年間) 例 966.0 float64: H5614 ごみのリサイクル率 例 22.8 文字列 : ['Code(地域コード)', 'Prefecture(都道府県)']

💬 112 列のうち int64 が 104 列、 float64 が 6 列、 文字列(object)が 2 列。 float64 は合計特殊出生率(北海道 1.06)・年平均気温(11.0)・最高気温・最低気温(−7.4)・年間降水量(966.0)・ごみのリサイクル率(22.8)で、 どれも小数や負の値を取りうる「率」や「測定値」である。 人数・件数・金額は int64 に収まる。 文字列は地域コードと都道府県の 2 列だけで、 これが行を識別するキーの側にあたる。 列の型は、 その列が「数えたもの」か「測ったもの」か「名前」かを表しており、 テーブルを受け取ったら最初に dtypes を数えることで、 この区別が正しく読み込めたかを確かめられる。

🐍 Python 実装

SSDSE-B-2026 を pandas で読み込み、 (行数, 列数)・各列の dtype・先頭 5 行・「都道府県」が一意 (主キー候補) かを確認する最小実装。 テーブルとして扱うときの定番チェック手順です。

🎯 このコードでやること:SSDSE-B-2026 を読み込み、 テーブルの 4 つの基本属性(形状・型・先頭・主キー候補)を 1 ブロックで点検する。

📥 入力データ:data/raw/SSDSE-B-2026.csv(47 県 × 12 年度 × 約 112 列)

1
2
3
4
5
6
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
print(df.shape)        # (564, 112) = 47 都道府県 × 12 年度
print(df.dtypes)        # 列ごとの型
print(df.head())        # 先頭 5 行
print(df['都道府県'].is_unique)  # True なら主キー候補

📤 実行結果:

(564, 112) 年度 int64 地域コード object 都道府県 object 総人口 int64 総人口(男) int64 ... 教育費(二人以上の世帯) int64 教養娯楽費(二人以上の世帯) int64 その他の消費支出(二人以上の世帯) int64 Length: 112, dtype: object 年度 地域コード 都道府県 ... 教育費(二人以上の世帯) 教養娯楽費(二人以上の世帯) その他の消費支出(二人以上の世帯) 0 2023 R01000 北海道 ... 6911 25661 48694 1 2022 R01000 北海道 ... 9551 27234 46466 2 2021 R01000 北海道 ... 9913 23762 51583 3 2020 R01000 北海道 ... 9394 26539 56316 4 2019 R01000 北海道 ... 8848 29335 57289 [5 rows x 112 columns] False

💬 読み方:形は (564, 112) で、先頭 5 行はすべて北海道の 2023〜2019 年度。同じ県が年度の数だけ並ぶので is_unique は False になり、「都道府県」単独では主キーにならない。「(年度, 都道府県)」の複合キーで 1 行が決まる設計だと読み取るのがテーブル設計の出発点。

🐍 Python 実装(応用編)

先の Python 実装は最小例だった。 ここでは テーブル の本格的な実務シナリオに即した、 もう一段難易度を上げたコードを示す。 そのままコピペで動くよう、 SSDSE 系の実データパスを直書きで残してある。

🎯 このコードでやること:wide ⇄ long のラウンドトリップで「テーブル形式変換が情報を保存するか」を確認し、 category 型でメモリを削減する。

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年度) 年度 地域コード 都道府県 総人口 … 2023 R01000 北海道 5,092,000 … 2022 R01000 北海道 5,140,000 … 2021 R01000 北海道 5,183,000 … …(全 564 行)
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
# SSDSE-B-2026 を long 形式に変換(tidy 化)+ pivot で wide に戻すラウンドトリップ
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
key = ['年度', '地域コード', '都道府県']   # 行を一意に決める複合キー
# wide → long
long_df = df.melt(
    id_vars=key,
    var_name='指標',
    value_name='値'
)
print('long shape =', long_df.shape)
print(long_df.head())

# long → wide (pivot)
wide_df = long_df.pivot_table(
    index=key,
    columns='指標',
    values='値',
    aggfunc='first'   # 複合キーで一意なので「最初の 1 件」=その値そのもの
).reset_index()
wide_df.columns.name = None
print('wide shape =', wide_df.shape)

# ラウンドトリップで行・列・値が元と同じか
back = wide_df[df.columns].sort_values(key).reset_index(drop=True)
orig = df.sort_values(key).reset_index(drop=True)
assert back.shape == orig.shape
assert (back.drop(columns=key).values == orig.drop(columns=key).values).all()
print('round-trip OK')

# 型最適化:カテゴリ + downcast
print('mem MB before opt =', round(df.memory_usage(deep=True).sum()/1024**2, 2))
df['都道府県'] = df['都道府県'].astype('category')
df['地域コード'] = df['地域コード'].astype('category')
for c in df.select_dtypes(include='float64'):
    df[c] = pd.to_numeric(df[c], downcast='float')
print('mem MB after opt =', round(df.memory_usage(deep=True).sum()/1024**2, 2))

📤 実行結果:

long shape = (61476, 5) 年度 地域コード 都道府県 指標 値 0 2023 R01000 北海道 総人口 5092000.0 1 2022 R01000 北海道 総人口 5140000.0 2 2021 R01000 北海道 総人口 5183000.0 3 2020 R01000 北海道 総人口 5224614.0 4 2019 R01000 北海道 総人口 5259000.0 wide shape = (564, 112) round-trip OK mem MB before opt = 0.55 mem MB after opt = 0.47

💬 読み方:(年度, 地域コード, 都道府県) の 3 列をキーに残して melt すると、564 行 × 109 指標 = 61,476 行の long になり、pivot で 564 行 × 112 列に戻って値も元と一致した(assert 通過)。キーに年度を入れずに melt・pivot すると 12 年度分が 1 つのセルに重なり、aggfunc='first' が 2023 年度だけを拾って 47 行に縮んでも列の集合は同じなので「OK」に見えてしまう。category 型への変換でメモリは 0.55 MB から 0.47 MB に減るが、この値は pandas のバージョンで多少変わる。

🧭 用語クロスリンク集

テーブル と関わりの深い用語を、 一画面で巡回できるよう チップ形式で並べた。 「これも知っておきたい」と思った瞬間にクリックして、 元のページに戻ってくる ── ジャストインタイム学習の理想的な使い方。

🔗 DataFrame 🔗 CSV 🔗 JSON 🔗 SQL 🔗 正規表現 🔗 ピボットテーブル 🔗 外部結合 🔗 内部結合 🔗 主キー 🔗 外部キー 🔗 データベース 🔗 ログデータ 🔗 用語索引

📌 これらはすべて html/glossary/ 配下の実在ページにリンクしているので、 リンク切れの心配はない。

🗂 用語の階層・派生・対立関係

テーブル を中心に、 上位概念・並列概念・派生概念・対立概念の 4 区分で整理。 学術論文を読むときの「この用語はこの分野のどこに位置するか」を直感的に把握できる。

関係該当用語関係性の説明
上位概念構造化データ / データベース行・列の二次元格子で値が整列している大きな枠組み
並列概念DataFrame / CSV / スプレッドシート物理表現は異なるが論理的にテーブルと同型
派生概念Tidy data / ピボットテーブル / マテリアライズドビューテーブルを再構成・要約した派生形
対立概念非構造化データ / JSON / グラフデータ行・列スキーマに収まらない形のデータ
前提知識主キー / 外部キー / データ型テーブルの整合性を支える基本概念

⚠️ よくある落とし穴

テーブル設計の失敗は「主キー不在」「型の不統一」「縦持ち / 横持ちの混在」「NULL の意味曖昧」が四大要因。 SSDSE のような公的データを扱う際にも、 県コードを文字列で持つか数値で持つかで JOIN 結果が一致しないトラブルが頻発します。

❌ ワイドとロングの混同
横長 (wide) と縦長 (long) は同じ情報でも分析しやすさが違う。
❌ 型の自動推定ミス
CSV 読み込みで「都道府県コード」が数値扱いされ先頭 0 が消える事故。
❌ 欠損の扱い
NaN を 0 と混同するとミスリーディング。
❌ 結合キー不整合
都道府県名の表記揺れ (北海道/Hokkaido) で結合に失敗。

⚠️ もっと深い落とし穴

上のセクションの 4 つに加えて、 テーブル 固有の実務でハマる典型をさらに 4 つ。 経験 5 年以下のエンジニア・研究者はほぼ全員、 ここのいずれかで 1 度は事故を起こしている。

❌ CSV の encoding 自動推定失敗
Windows 由来は cp932、 Mac/Linux は utf-8。 chardet.detect() で事前推定し、 BOM 付き UTF-8 は utf-8-sig。
❌ 混在型列の object 化
1 行だけ「-」が混じると列全体が object に。 pd.to_numeric(col, errors='coerce') で NaN 化。
❌ インデックスのコピー伝播
df2 = df1 はビュー共有。 安全に複製したいなら df2 = df1.copy()。 SettingWithCopyWarning の出所もここ。
❌ メモリ使用量の見落とし
df.info(memory_usage='deep') で文字列列が 想定の 10 倍食っていることが発覚。 category 型・parquet化で削減。

⚠️ 行は「何番目か」ではなくキーで指す

🎯 このコードでやること:2023 年度の 47 行から、 行の位置(iloc)で東京都を取る書き方と、 地域コード(キー)で取る書き方を比べる。 表を人口順に並べ替えた後に同じ書き方で何が返るかを見る

📥 入力例 SSDSE-B-2026 の 2023 年度 47 行(R01000 北海道 → R47000 沖縄県の順) 位置 12: R13000 東京都 14,086,000
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])
d = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)

# 行の「位置」で東京都を取る(ファイルの並びが R01000→R47000 である前提)
print('並べ替え前 iloc[12]:', d.iloc[12]['Prefecture'], f"{d.iloc[12]['A1101']:,}")

# 人口順に並べ替えてから同じ位置を見ると…
s = d.sort_values('A1101', ascending=False).reset_index(drop=True)
print('人口順に並べ替え後 iloc[12]:', s.iloc[12]['Prefecture'], f"{s.iloc[12]['A1101']:,}")

# キー(地域コード)で取れば並び順に左右されない
for t in [d, s]:
    row = t.set_index('Code').loc['R13000']
    print('Code R13000 で取る:', row['Prefecture'], f"{row['A1101']:,}")
📤 実行例(実測) 並べ替え前 iloc[12]: 東京都 14,086,000 人口順に並べ替え後 iloc[12]: 京都府 2,535,000 Code R13000 で取る: 東京都 14,086,000 Code R13000 で取る: 東京都 14,086,000

💬 並べ替える前は 13 行目(iloc[12])が東京都だが、 人口順に並べ替えた後の iloc[12] は京都府(2,535,000 人)になる。 行の位置はテーブルの並び順という偶然の性質で、 並べ替え・絞り込み・結合のたびに変わる。 地域コード R13000 をキーにして取れば、 どちらの並びでも東京都 14,086,000 人が返る。 テーブルの行はキーで指し、 位置に意味を持たせない。

⚠️ 外部キーの参照先が欠けると、 inner 結合は黙って行を落とす

🎯 このコードでやること:ファクト(564 行)の外部キー Code が、 すべて都道府県マスタにあるか(参照整合性)を確かめる。 マスタから沖縄県の 1 行が抜けていた場合に、 inner 結合と left 結合(indicator=True)で何が起きるかを比べる

📥 入力例 ファクト: SSDSE-B-2026 の (年度, Code, A1101) 564 行 マスタ : (Code, Prefecture) 47 行 → 沖縄県 R47000 を抜いた 46 行
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])
fact = df[['SSDSE-B-2026', 'Code', 'A1101']]
master = df[['Code', 'Prefecture']].drop_duplicates()

# マスタから沖縄県(R47000)の行が抜けていた場合を再現する
broken = master[master['Code'] != 'R47000']
print('マスタの行数:', len(master), '→', len(broken))

inner = fact.merge(broken, on='Code', how='inner')
left = fact.merge(broken, on='Code', how='left', indicator=True)
print('inner 結合の行数:', len(inner), '(黙って', len(fact) - len(inner), '行が消える)')
print('left 結合の行数 :', len(left), '/ 県名が欠けた行:', int(left['Prefecture'].isna().sum()))
print(left['_merge'].value_counts().to_string())
print(f"inner 結合後の総人口合計(2023 年度): {inner.loc[inner['SSDSE-B-2026'] == 2023, 'A1101'].sum():,}"
      f" / 本来: {fact.loc[fact['SSDSE-B-2026'] == 2023, 'A1101'].sum():,}")
📤 実行例(実測) マスタの行数: 47 → 46 inner 結合の行数: 552 (黙って 12 行が消える) left 結合の行数 : 564 / 県名が欠けた行: 12 _merge both 552 left_only 12 right_only 0 inner 結合後の総人口合計(2023 年度): 122,885,000 / 本来: 124,353,000

💬 マスタが 46 行になると、 inner 結合は何の警告も出さずに沖縄県の 12 年度分 12 行を落とし、 552 行になる。 2023 年度の総人口合計も 124,353,000 人から 122,885,000 人へ、 沖縄県の 1,468,000 人ぶん少なく出る。 left 結合に indicator=True を付けると、 564 行が残ったうえで left_only が 12 行と数えられ、 県名が欠けた行として見つけられる。 外部キーでつなぐ前に「ファクトのキーがすべてマスタにあるか」を left 結合と indicator で数えておくと、 行が黙って消える事故を防げる。

🧠 理解度チェック

  1. Q1. SSDSE-B-2026 で地域コード単独の重複行はいくつあり、 主キーは何か。
  2. Q2. SSDSE-B-2026 が第 2 正規形を満たさない理由を、 列の名前を使って 1 文で説明せよ。
  3. Q3. 都道府県マスタと年度別ファクトに分けると、 それぞれ何行何列になるか。 分けた後に県名を持つセルはいくつになるか。
  4. Q4. 総人口を (地方, 年度) で合計した集計表は何行か。 2012→2023 年度に人口が増えた地方はどこか。
  5. Q5. 2023 年度の 47 行を人口順に並べ替えた後の iloc[12] は何を返したか。 東京都を確実に取るにはどう書くか。
  6. Q6. マスタから沖縄県の 1 行が抜けていると、 564 行のファクトとの inner 結合は何行になるか。 その事故を見つけるにはどう結合すればよいか。
  7. Q7. 縦持ちの表で県ごとに first() と last() から 2012→2023 年度の増減率を求めるとき、 並べ替えを忘れるとどうなるか。

解答

  1. 重複行 517(564 − 47)。 主キーは (年度, 地域コード) の組で、 この組の重複は 0。
  2. 主キー (年度, 地域コード) の一部である地域コードだけで都道府県名が決まる(部分関数従属がある)から。
  3. マスタ 47 行 × 2 列、 ファクト 564 行 × 111 列。 県名のセルは 564 から 47 に減る。
  4. 8 地方 × 12 年度 = 96 行。 増えたのは関東(+2.0%)だけ。
  5. 京都府(2,535,000 人)。 set_index('Code').loc['R13000'] のように地域コードをキーにして取る。
  6. 552 行(沖縄県の 12 行が黙って消える)。 left 結合に indicator=True を付けて left_only の行数(12)を数える。
  7. SSDSE-B-2026 は新しい年度が先に並ぶので、 first() が 2023 年度、 last() が 2012 年度を返し、 増減率の向きが逆になる。 年度で並べ替えてから使うか、 横持ちにして列で指定する。

🗺 概念マップ:テーブル の知識ネットワーク

テーブルは「データエンジニアリング」の中で、 データを行と列に並べる最も基本的な形として置かれる。 上位にはそれを含む構造化データとデータベース、 並列には同じ形を別の道具で表す DataFrame・CSV・スプレッドシート、 派生には並べ替えた形の Tidy data(縦持ち)やピボットテーブル(横持ち)、 対立には行と列に収まらない非構造化データやグラフデータがある。 このページで SSDSE-B-2026 について確かめた「主キーは (年度, 地域コード) の組」「指標は横持ち・年度は縦持ち」は、 この地図のどこに今の表があるかを決める情報である。 集計すれば主キーは (地方, 年度) に、 describe() をかければ行は統計量に変わるように、 操作のたびに表は地図の上を移動する。 表を受け取ったら、 まず「1 行は何か」「主キーは何か」の 2 つを確かめる。 この 2 つが言えれば、 その表をどの操作に渡してよいかが決まる。 言えなければ、 結合や集計の前に shape と、 候補のキーでの重複の数を数えて確かめる。

❓ よくある質問(用語固有 10 連発)

汎用 FAQ 5 件に加えて、 テーブル 固有の質問 10 件を Q&A 形式でまとめた。 自分の状況に近いものから読んでほしい。

Q1. テーブル と DataFrame の違いは?
A. テーブルは論理概念 (Codd の関係モデル)、 DataFrameはその pandas/R/Spark での実装。 DataFrame は順序を保持する点で厳密にはテーブルの拡張。
Q2. 列指向 (Parquet) と行指向 (CSV) どっちを使う?
A. 分析中心なら Parquet。 必要な列だけを読めるので、 列の多い大きな表の集計が速く、 圧縮でファイルも小さくなる。 人間が直接読みたいなら CSV。 SSDSE-B はソース配布が CSV だが、 ローカルで Parquet 化するのが定石。
Q3. PK と外部キーは絶対必要?
A. 厳密な OLTP では Yes (整合性の保証)。 分析系の DWH では No (ロード速度優先)。 SSDSE のような既配布データなら、 PK 候補 (都道府県コード) を 意識するだけで良い。
Q4. テーブルが巨大すぎてメモリに乗らない
A. (1) chunksize で分割読み込み、 (2) dtype 指定でメモリ削減、 (3) DuckDBでファイル直接 SQL、 (4) Polars で lazy 評価、 (5) BigQuery 等の DWH に上げる。
Q5. SSDSE の CSV が文字化けする
A. encoding='cp932' または 'utf-8-sig'。 SSDSE-B-2026 は cp932(Shift_JIS 系)。 BOM 付き UTF-8 のファイルなら utf-8-sig。 chardet で自動推定可能。
Q6. 列名に日本語があると重い?
A. 列アクセスは文字列キーでハッシュ参照なので、 言語に関わらず O(1)。 ただし保存時のエンコード/デコードでコストはわずかに増す。
Q7. Long と Wide どっちで配るべき?
A. 配布は Long (tidy) が推奨。 受け取り側が pivot で wide にできる。 wide で配ると列順・列名の変更で破壊的変更になる。
Q8. テーブルのバージョン管理は?
A. Git は CSV を行単位で扱えるが大規模になると苦しい。 DVC (Data Version Control) や LakeFS、 Delta Lake等の専用ツールが台頭。
Q9. 複合主キーは使うべき?
A. (年, 都道府県) のような 自然複合キーは OK だが、 自動採番の id を補助的に追加するハイブリッド設計が現代的。 結合と参照が楽になる。
Q10. テーブルのスキーマ進化はどう管理?
A. Avroや Icebergはスキーマ進化を組込み機能で持つ。 単純な CSV/Parquet なら、 ファイル名にバージョンを含め (SSDSE-B-2026.csv)、 列名追加は許容、 削除は禁止が安全。

🏋️ 演習問題(5 問)

学習の定着には、 自分の手を動かすのが一番。 SSDSE-B-2026 や実ログを題材に、 テーブル を実践する 5 問を用意した。 答えは Python 実装セクションと数値例セクションを参考に組み合わせれば導ける。

  1. 演習 1:SSDSE-B-2026 を読み込み、 列数を取得して 「人口」 「医療」 「教育」 を含む列名を grep せよ。
  2. 演習 2:都道府県 47 行のテーブルを year ごとに stack して 5 年 × 47 行 = 235 行の long テーブルを作れ。
  3. 演習 3:dtypes を category や float32 に最適化し、 メモリ削減率を測定せよ。
  4. 演習 4:SSDSE-B-2026 の 2022 年度と 2023 年度を縦に積み、 地域コードだけと (年度, 地域コード) の組とで、 キーの重複を duplicated() で確認せよ。
  5. 演習 5:テーブルを Parquet に保存し、 CSV と Parquet でファイルサイズ・読込速度を比較せよ。

📌 演習を解いて疑問が残ったら、 「よくある質問」セクションに戻るか、 リポジトリの「論文一覧」から類似研究を探して、 実コード (本サイトには 159 本の再現論文) を読むのが最速の理解への道。

🍳 コード・クックブック(テーブル 編)

テーブル を実務で使う際の頻出パターンを、 動くコードのレシピ集としてまとめた。 必要な料理 (タスク) だけを取り出して使ってほしい。

複合主キーで一意性確認

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) 北海道 2,023 東京都 2,023 沖縄県 2,023 …(全 47 行)
1
2
3
4
5
6
7
8
9
10
# 複合キーで重複が無いか診断
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
key_cols = ['地域コード']
n_dup = df.duplicated(subset=key_cols).sum()
print(f'重複行 = {n_dup}, 一意性 = {n_dup == 0}')
# 地域コード単独では 12 年度ぶん重複するので、(地域コード, 年度) の複合キーで確かめる
key2 = ['地域コード', '年度']
n_dup2 = df.duplicated(subset=key2).sum()
print(f'複合キー重複 = {n_dup2}, 一意性 = {n_dup2 == 0}')
📤 実行例(実測) 重複行 = 517, 一意性 = False 複合キー重複 = 0, 一意性 = True

💬 地域コード単独で数えると重複行は 517 で、これは 564 行から各県の最初の 1 行(47 行)を引いた数。(地域コード, 年度) の複合キーにすると重複は 0 になり、このテーブルの主キーは 2 列の組だと確かめられる。年度をすべて 2023 に書き換えるなどキー列を上書きしてから検査すると、同じ 517 が出てしまい複合キーの意味が消えるので、診断では元の値を使う。

テーブル → SQLite → SQL クエリ

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) A1101(総人口) A1303(65歳以上人口) L322106(保健医療費(二人以上の世帯)) Prefecture(都道府県) 北海道 2,023 5,092,000 1,681,000 15,491 北海道 東京都 2,023 14,086,000 3,205,000 18,166 東京都 沖縄県 2,023 1,468,000 350,000 11,686 沖縄県 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# テーブルを SQLite に書き込み、 SQL で集計
import pandas as pd
import sqlite3

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
df = df[df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
df['年度'] = pd.to_numeric(df['年度'], errors='coerce')
df = df[df['年度'] == df['年度'].max()].copy()
for _c in ['総人口', '65歳以上人口', '保健医療費(二人以上の世帯)']:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

# SSDSE-B に「高齢化率」「医療費」の列は無いので、実在する列から作る
df['高齢化率'] = df['65歳以上人口'] / df['総人口'] * 100
df['医療費'] = df['保健医療費(二人以上の世帯)']

con = sqlite3.connect(':memory:')
df[['都道府県', '高齢化率', '医療費']].to_sql('prefectures', con,
                                             if_exists='replace', index=False)
q = """
SELECT 都道府県, 高齢化率, 医療費
FROM prefectures
WHERE 高齢化率 > 30
ORDER BY 医療費 DESC
LIMIT 10
"""
print(pd.read_sql(q, con))
📤 実行例(実測) 都道府県 高齢化率 医療費 0 山梨県 31.783920 17817 1 香川県 32.505400 17197 2 長野県 32.684631 16680 3 北海道 33.012569 15491 4 奈良県 32.638889 15311 5 山口県 35.362096 15213 6 山形県 35.185185 15170 7 鹿児島県 33.828276 14919 8 岐阜県 31.227343 14907 9 三重県 30.631152 14758

💬 2023 年度 47 行のうち高齢化率 30% 超は 35 県で、それを保健医療費の高い順に 10 件並べると山梨県 17,817 円が先頭、山口県は高齢化率 35.4% と最も高いが 6 位(15,213 円)にとどまる。保健医療費が全国で最も高い埼玉県 21,000 円・愛知県 18,175 円・東京都 18,166 円は高齢化率が 30% 未満なので WHERE で落ちており、高齢化と世帯の医療支出は単純に比例しない。

Parquet 化でメモリ・速度両得

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import os
os.makedirs('data', exist_ok=True)   # 書き出し先を先に作る
import os
os.makedirs('data/processed', exist_ok=True)  # 保存先のフォルダを作っておく

import pyarrow  # parquet の読み書きに必要(ブラウザには無い)
import pandas as pd, time, os
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932')
df.to_parquet('data/processed/SSDSE-B-2026.parquet')
size_csv = os.path.getsize('data/raw/SSDSE-B-2026.csv')
size_par = os.path.getsize('data/processed/SSDSE-B-2026.parquet')
print(f'CSV={size_csv/1024:.1f} KB, Parquet={size_par/1024:.1f} KB, 比={size_csv/size_par:.1f}x')
t0 = time.time(); pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932'); t1 = time.time()
t2 = time.time(); pd.read_parquet('data/processed/SSDSE-B-2026.parquet'); t3 = time.time()
print(f'read CSV={t1-t0:.3f}s, Parquet={t3-t2:.3f}s')
📤 実行例(実測) CSV=351.4 KB, Parquet=383.8 KB, 比=0.9x read CSV=0.006s, Parquet=0.046s

💬 この 351.4 KB の CSV を Parquet にすると 383.8 KB と逆に 1.1 倍ほど大きくなり(比 0.9x)、読み込みも CSV 0.006 秒に対し Parquet 0.046 秒と遅かった。Parquet は列ごとの圧縮と列の選択読み込みで効くので、564 行程度の小さな表ではメタデータの分だけ損をする。時間は実行環境とキャッシュで毎回変わるため、「両得」を確かめるなら数十万行以上の表で繰り返し測る。

📌 すべてのレシピは「実データ前提・引数なし直書き」スタイル。 そのままコピペで動くよう設計したので、 まずは動かしてから読むのを推奨する。

🙋 著者・教材設計について

本教材は、 広島工業大学 統計・データ解析コンペティション参加学生向けに、 松本伸平 (s.matsumoto.gk@cc.it-hiroshima.ac.jp) が監修・執筆した。 「論文を読む過程で出てきた専門用語を、 その場で 5 分で補完して論文に戻る」というジャストインタイム型の学習体験を目的に、 514 用語ページを統一フォーマットで整備している。

本ページ「テーブル」は データエンジニアリング カテゴリに属し、 同カテゴリ内の他用語と相互リンクされている。 用語間の関係性は 概念マップ でも俯瞰できる。

本サイトは SSDSE (教育用標準データセット, 独立行政法人統計センター) を主データとして使い、 「合成データ ではなく実公的データを使う」 を方針に据えている。 ハンズオン教材の質を最大化するための判断である。

テーブル 行 (レコード) 列 (カラム) 主キー JOIN 結合 DataFrame 正規化

🔗 隣接手法への橋渡し

表 (テーブル) は「行 (レコード) × 列 (フィールド)」の 2 次元構造でデータを整理する基本概念。 SSDSE-B-2026 は 47 行 × 110 列の典型的テーブルで、 各行=都道府県 (主キー: 地域コード)、 各列=指標 (人口 A1101、 高齢人口 A1303、 合計特殊出生率 A4103 など)。 「1 つの値 = 1 つのセル」を厳守する第 1 正規形 (1NF) は分析の基本前提。

表は「人間が読みやすい (wide format) ⇄ 機械が処理しやすい (long format / tidy)」の 2 形式があり、 用途で pd.melt / pd.pivot で変換する。 SSDSE-B は wide 形式、 BI ダッシュボードへの投入時には long に変換することが多い。

🌳 手法選択フロー

SSDSE のようなテーブルを扱う際の形式・操作の選択フロー。

  1. ① 表の形式は wide か long か? SSDSE-B-2026 は wide (列が指標)。 集計や可視化は wide のまま、 BI / 時系列分析は long が便利。 pd.melt(id_vars=['地域コード']) で long 化。
  2. ② 主キーは何か? 単一表なら 1 列で一意 (地域コード)、 パネルデータなら複合キー (地域コード × 年)。 df.duplicated(subset=key).any() で確認。
  3. ③ 第 1 正規形を満たすか? 1 セル = 1 値 (「東京, 大阪」のような複数値 NG)。 違反していればまず分割 → 1NF 化してから分析。 SSDSE は最初から 1NF。

表の設計品質が分析品質を決定する。 SSDSE-B-2026 は理想的な 1NF テーブルなので、 そのまま pandas / SQL 解析にかけられる学習用素材として優秀。