論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
DataFrame
DataFrame
データ処理
別称: データフレーム

🔖 キーワード索引

pandasDataFrameSeries表形式データ処理Python

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

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

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

💡 30秒で分かる結論

🍰 まずはやさしく

エクセルの表のようなものです。

データをまとめて管理するために使います。

部活の出席簿のような表をイメージしてください。

この章では基本的な使い方を学びます。

DataFrame ── pandasの表形式データ構造

📍 文脈 ── どこで出会うか

🍰 まずはやさしく

データを読み込むとすぐに使います。

分析作業のほとんどで利用する道具です。

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

データの選び方やまとめ方を学びます。

SSDSE のCSVを読み込んだ瞬間からあなたは DataFrame を触っています。 全データサイエンス作業の8割はこの上での操作と言って過言ではありません。

本ページでは SSDSE-B-2026 を pd.read_csv() で読み込み、 47 行 × 100 超列の DataFrame に対する loc / iloc / query / merge / groupby / pivot_table 等を実例で示し、 Series との往復関係を明示する。

「dataframe」は 行 (observation) × 列 (variable) の 2 次元ラベル付きテーブルで、 pandas における基本データ構造である。 異なる dtype を列ごとに保持できる点で numpy 2D 配列と異なり、 SSDSE-B-2026 のような混合型データ (都道府県名 + 数値列) を扱うのに最適。 本ページでは Series との関係、 indexing (loc/iloc)、 join/merge/groupby を扱う。

🎨 直感で掴む

🍰 まずはやさしく

縦と横に並んだデータの集まりです。

項目ごとに情報を整理するために使います。

都道府県ごとの人口などの表が例です。

表の構造を詳しく見ていきましょう。

都道府県データを例に:

       都道府県  人口      高齢化率  死亡率
0      北海道    5224614    32.5      12.1
1      青森県    1237984    34.6      14.2
2      岩手県    1210534    34.2      13.5
…

これが DataFrame。 行=都道府県、 列=変数、 インデックスは番号 or 都道府県名。

🎨 図解で掴む DataFrame の構造

          カラム集合 C = {A1101(総人口), A1303(65歳以上人口), A4101(出生数), ...}
                          │
                          ▼
              ┌───────┬────────┬───────┬─────┐
 Prefecture   │ A1101  │ A1303  │ A4101 │ ... │  ← dtypes: int64, int64, int64
 ──────       ├───────┼────────┼───────┼─────┤
   北海道     │5092000 │1681000 │ 24430 │ ... │
   青森県     │1184000 │ 417000 │  5696 │ ... │
   東京都     │14086000│3205000 │ 86348 │ ... │
     ⋮        │   ⋮    │   ⋮    │   ⋮   │  ⋮  │
   沖縄県     │1468000 │ 350000 │ 12549 │ ... │
              └───────┴────────┴───────┴─────┘
                          │
                          ▼
                  各列 = Series (1次元, 型統一)
                  全体 = Series の辞書

   操作の代数(SSDSE-B-2026, 2023年):
     σ (selection)   → df[df['A1101']>1e6]
     π (projection)  → df[['A1101','A1303']]
     ⋈ (join)        → df1.merge(df2, on='Prefecture')
     Γ (aggregate)   → df.groupby('地域')['A1101'].sum()
  

📝 補足:日付・時系列・マルチインデックス

SSDSE-B は年度の整数を持つだけですが、 多くの実務データは日次や分次の時刻列を含みます。 pd.to_datetime で datetime64 化すると、 リサンプリング(df.resample('M').mean())、 ローリング(df.rolling('30D').mean())、 期間スライス(df.loc['2023-04':'2023-09'])が利用可能になります。

マルチインデックスは「都道府県 × 年度」のような複合主キー的データに使うと強力で、 df.set_index(['Prefecture','SSDSE-B-2026']) としておけば df.loc[('東京都', 2023)] で1点を直接取れます。 ただし可読性が低下しやすいので、 アドホック分析では reset_index で平坦化して扱う方が便利な場合も多いです。

表形式データを「縦持ち(long)」⇔「横持ち(wide)」で変換するのが melt / pivot / stack / unstack の4兄弟です。 機械学習モデルへの入力は wide、 可視化(seaborn)への入力は long が多い、 と覚えておくと迷いません。 SSDSE-B はもともと long 形式に近いので、 必要に応じて pivot で wide にして使うのが標準パターンです。

🗾 47都道府県データを DataFrame で扱う完全レシピ

SSDSE-B-2026 のような都道府県データは、 ほぼ全てのデータサイエンス入門で登場します。 ここでは「読み込み → 整形 → 集約 → 可視化 → モデリング前処理」の典型フローを通しで示します。

ステップ1:読み込み

📥 入力例(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
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv',
    encoding='cp932',
    skiprows=[1],
    dtype={'Code': str},   # 先頭ゼロ保持
)
print(df.shape)
print(df.columns.tolist()[:10])
📤 実行例(実測) (564, 112) ['SSDSE-B-2026', 'Code', 'Prefecture', 'A1101', 'A110101', 'A110102', 'A1102', 'A110201', 'A110202', 'A1301']

ステップ2:年度フィルタ+インデックス設定

1
2
3
4
5
6
df23 = (df[df['SSDSE-B-2026']==2023]
          .set_index('Prefecture')
          .drop(columns=['SSDSE-B-2026','Code'], errors='ignore')
          .astype({'A1101':'int64'}))
print(df23.shape)            # (47, 109)
print(df23.head())
📤 実行例(実測) (47, 109) A1101 A110101 A110102 ... L322108 L322109 L322110 Prefecture ... 北海道 5092000 2405000 2688000 ... 6911 25661 48694 青森県 1184000 559000 626000 ... 6713 20630 48925 岩手県 1163000 562000 602000 ... 6748 28499 50554 宮城県 2264000 1105000 1160000 ... 11245 27380 48636 秋田県 914000 432000 482000 ... 4316 23175 54173 [5 rows x 109 columns]

ステップ3:派生列を作る(高齢化率・人口密度)

1
2
3
4
5
6
df23 = df23.assign(
    高齢化率 = lambda d: d['A1303'] / d['A1101'] * 100,
    出生率   = lambda d: d['A4101'] / d['A1101'] * 1000,   # 人口千人あたり出生数
    生産年齢比 = lambda d: d['A1302'] / d['A1101'] * 100,
)
print(df23[['A1101','高齢化率','生産年齢比']].describe())
📤 実行例(実測) A1101 高齢化率 生産年齢比 count 4.700000e+01 47.000000 47.000000 mean 2.645809e+06 31.585892 56.954647 std 2.797551e+06 3.338168 2.981539 min 5.370000e+05 22.753088 51.859956 25% 1.034000e+06 30.047119 54.728562 50% 1.549000e+06 31.783920 56.586022 75% 2.636500e+06 34.012991 58.524162 max 1.408600e+07 39.059081 66.505750

ステップ4:地域別に集約

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# ── ここから下の書き方見本で使うデータを用意します ──
# SSDSE-B は 1 行目が英字コード(A1101 など)、2 行目が日本語名。
# 見本では英字コードを使うので、1 行目を見出しにして読み込みます。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
# 1 行目を見出しにすると数値の列も文字列で入ってくるので、まとめて数値に直す
for _c in df.columns:
    if _c not in ('Code', 'Prefecture'):
        df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 見本の中で使う派生列(地域ブロック・高齢化率)と、結合用のもう 1 枚
_region = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北',
           '山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東',
           '埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東'}
df['地域'] = df['Prefecture'].map(_region).fillna('その他')
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df2 = df[['Prefecture', 'A1101']].copy()
# この見本で使う 2023 年度・47 都道府県の表(都道府県名を索引にする)
df23 = (df[df['SSDSE-B-2026'] == 2023]
        .set_index('Prefecture')
        .drop(columns=['SSDSE-B-2026', 'Code'], errors='ignore'))

region_map = {
    '北海道':'北海道',
    '青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北',
    '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東',
    '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部',
    '三重県':'近畿','滋賀県':'近畿','京都府':'近畿','大阪府':'近畿','兵庫県':'近畿','奈良県':'近畿','和歌山県':'近畿',
    '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国',
    '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国',
    '福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州',
}
df23['地域'] = df23.index.to_series().map(region_map)
region_agg = df23.groupby('地域').agg(
    人口=('A1101','sum'),
    平均高齢化率=('高齢化率','mean'),
    県数=('A1101','count'),
# 集計後の列名は 人口/平均高齢化率/県数。'A1101' はもう存在しない
).sort_values('人口', ascending=False)
print(region_agg)
📤 実行例(実測) 人口 平均高齢化率 県数 地域 関東 43527000 27.993120 7 近畿 21990000 30.257049 7 中部 20749000 31.264899 9 九州 14029000 31.544426 8 東北 8318000 34.477233 6 中国 7070000 32.954654 5 北海道 5092000 33.012569 1 四国 3578000 34.599246 4

ステップ5:時系列パネル(10年比較)

1
2
3
4
panel = (df.pivot_table(index='Prefecture', columns='SSDSE-B-2026', values='A1101', aggfunc='sum')
           .assign(変化率_10年=lambda d: (d[2023]/d[2013]-1)*100))
print(panel[[2013, 2018, 2023, '変化率_10年']].sort_values('変化率_10年').head(10))
# 減少率トップ10は秋田・青森・岩手・高知・山形・徳島・長崎・新潟・和歌山・福島
📤 実行例(実測) SSDSE-B-2026 2013 2018 2023 変化率_10年 Prefecture 秋田県 1050000 985000 914000 -12.952381 青森県 1337000 1268000 1184000 -11.443530 岩手県 1299000 1240000 1163000 -10.469592 高知県 743000 707000 666000 -10.363392 山形県 1144000 1092000 1026000 -10.314685 徳島県 769000 736000 695000 -9.622887 長崎県 1397000 1341000 1267000 -9.305655 新潟県 2336000 2246000 2126000 -8.989726 和歌山県 980000 940000 892000 -8.979592 福島県 1940000 1869000 1767000 -8.917526

ステップ6:散布図行列で全体俯瞰

1
2
3
4
import seaborn as sns
sub = df23[['A1101','高齢化率','A4101','L3221']]
g = sns.pairplot(sub.dropna(), corner=True, diag_kind='kde')
g.fig.suptitle('SSDSE-B-2026 (2023) 都道府県の散布図行列', y=1.02)

このフロー全体が「DataFrame に対する Split-Apply-Combine」の繰り返しです。 慣れると新しいデータでも同じパターンで攻略できます。

🎮 触って理解する

下の3つの実験は、 SSDSE-B-2026(2023年)の実測値から6県を抜粋した小さな DataFrame です(A1101=総人口、 A1303=65歳以上人口、 単位は人)。 pandas 固有の3つの概念 —— (1) 行index・列名ラベルによる loc アクセス(2) boolean mask による行の抽出(3) 列演算のブロードキャスト —— を、 図とコードが連動する形で体感できます。 なお、 表データ一般のソート・フィルタ・集計の実験は 表データ のページにあります。 このページは「pandas の DataFrame ならでは」の操作に絞ります。

① df.loc[行, 列] — ラベルで狙い撃ちする

行ラベル(index=都道府県名)と列名(columns)をセレクタで選ぶと、 該当するセル・行・列がハイライトされます。 表のセルを直接タップ/クリックしてもOK(index 列をタップ=行選択、 列見出しをタップ=列選択)。 「:」は「すべて」の意味です。

要点loc はラベル、 iloc は整数位置。 上のコード欄には両方の書き方が並びます。 「行だけ指定すると Series(横に寝た1行)」「列だけ指定すると Series(縦の1列)」「両方 : なら DataFrame 全体」と、 返ってくる型が指定の仕方で変わることを確認してください。

② df[df['高齢化率'] > しきい値] — boolean mask で行を絞る

スライダーでしきい値を動かすと、 各行が True / False に判定され(=boolean mask、 長さ6の Series)、 False の行が薄くなります。 mask を df[...] に渡すと True の行だけが残る —— これが pandas のフィルタリングの正体です。 高齢化率は A1303 ÷ A1101 × 100 の実測値です。

30.0%

要点比較演算 df['高齢化率'] > 30 自体が「答え」ではなく、 True/False の列(mask)を作る中間生成物です。 mask 同士は &(かつ)や |(または)で合成でき、 その際は (df['A'] > 1) & (df['B'] < 2) のように括弧が必須(演算子の優先順位のため)です。

③ 列演算のブロードキャスト — 1行のコードが全行に届く

新列「高齢化率」を作ります。 「1行ずつ」ボタンは for ループのイメージ(1回押すごとに1行だけ計算)、 「一括ブロードキャスト」は pandas の列演算 —— 1つの式で全行が同時に計算されます。 実データ分析ではこの差が「30分 vs 1秒」になります。

要点df['A1303'] / df['A1101'] は「列(Series)÷ 列(Series)」。 pandas はindex を揃えて要素ごとに割り算し、 × 100 のようなスカラーは全要素に自動で行き渡ります(ブロードキャスト)。 df['新列'] = 式 と書くだけで6行ぶん(実データなら47行・564行ぶん)が一括で埋まります。

🎨 直感の言葉化 — 「ラベル付き2次元表」であり「列(Series)の束」

上の3実験に共通する見方は2つです。 第一に DataFrame はラベル付きの2次元表: 行には index(実験①では都道府県名)、 列には columns(A1101 など)という名前が付いていて、 df.loc[行ラベル, 列ラベル] で位置番号を数えずに値へ届く。 第二に DataFrame は列指向: 実体は「dtype の揃った1次元 Series を横に束ねたもの」で、 だから df['A1101'] の取り出しや実験③の列演算が速く自然に書けます。 「行の集まり」と見るより「列の束」と見る方が、 pandas のコードは圧倒的に読み書きしやすくなります。

⚠️ 触ってみると分かる落とし穴(この実験に対応)

🚀 発展 — この操作の先にあるもの

①〜③の組み合わせが分かれば、 次の3つは自然な拡張です。 groupby は「mask で1グループずつ絞って集計」を全グループぶん自動化したもの(Split-Apply-Combine)。 merge(データ結合) は「ラベルで行を対応付ける」loc の考え方を2つの DataFrame の間に広げたもの。 そして 時系列 index は index に日時(datetime64)を張ることで、 df.loc['2023-01':'2023-06'] のような期間スライスや resample('MS') の集約が使えるようになる拡張です。 前提となる pandasNumPy、 実務で必ず出会う 欠損値 も合わせて確認してください。

📐 定義/数式

🍰 まずはやさしく

行と列でできたデータの構造です。

正しく計算や分析をするために定義します。

買い物リストの品名と金額のような構成です。

表を形作る3つの要素について学びます。

DataFrameの3層の構造

dataframe の定義や代表的な数式を以下に示す。 数式の各記号の意味は次節で言葉に翻訳する。

dataframe は文脈に応じて複数の定式化があるが、 教育目的では最も基本的な形を抑えることが重要。 具体的な値での計算例は後続セクションを参照。

$$\text{dataframe}: f(\mathbf{X}, \boldsymbol{\theta}) \to y$$

記号の対応はこうです。 $I$ は行ラベルの集合(インデックス。 SSDSE なら 47 都道府県コード)、 $C$ は列名の集合(A1101、 A1303 など 112 列)、 $V$ が値の集合です。 $t_c$ は列 $c$ の型で、 ここが DataFrame と 2 次元配列の決定的な違い——型は列ごとに決まり、 行ごとには決まりません。 $v_{i,c}$ は「行 $i$・列 $c$ の値」で、 df.loc[i, c] がこれに当たります。 $D = \{(i, c, t_c, v_{i,c})\}$ という 4 つ組の集合として書けることは、 「DataFrame は行 × 列の表であると同時に、 列ごとに型を持つ辞書でもある」ことを表しています。 df.dtypes が返すのがまさに $\{(c, t_c)\}$ です。

🔬 数式を言葉で読み解く

Series
1次元、 同じ型。 DataFrameの1列に相当
dtype
列ごとの型(int64, float64, object, datetime64 等)
loc / iloc
ラベル指定 / 位置指定の参照
groupby
分割→適用→結合の3段階パターン(Split-Apply-Combine)
NaN
欠損値表現

🔬 数式を言葉で読み解く(4要素構造/1200字以上)

DataFrame は数学的にも厳密に定義できる「3つ組」です。 $$\mathrm{DataFrame} = (I,\ C,\ V)$$ ここで $I = \{i_1, i_2, \dots, i_n\}$ はインデックス集合(行ラベル、 観測の識別子)、 $C = \{c_1, c_2, \dots, c_m\}$ はカラム集合(列ラベル、 変数の識別子)、 $V \in \mathbb{R}^{n \times m}$(または各列が独自の型を持つ「型付き多態配列」) は値の行列です。 SSDSE-B-2026 で言えば、 $n=47$(都道府県)、 $m \approx 110$(変数数)、 $V_{i,j}$ は第 $i$ 県の第 $j$ 変数の値となります。

要素1: インデックス $I$ — 「行は何を表すか」を一意に決める

$I$ は通常「整数の連番 ($0, 1, \dots, n-1$)」または「意味のあるラベル(都道府県名、 日付、 顧客ID)」のいずれかです。 重要な性質は一意性(重複しないことが原則)、 順序保持(行の並びが意味を持つ場合がある)、 ハッシュ可能性(loc で高速参照するため)です。 pandas では df.set_index('都道府県') でインデックスを差し替えると、 $i \in I$ に対し df.loc[i] が $O(\log n)$ または $O(1)$(ハッシュ)で動きます。 逆に reset_index() でインデックスを通常列に戻せます。 マルチインデックス(タプル)にすれば $I \subseteq A \times B$ の階層構造も持てます。

要素2: カラム $C$ — 「列は何を表すか」と「列の型」を担う

$C$ も $I$ と同様にラベルの集合ですが、 さらに各 $c \in C$ には型 $\tau(c) \in \{\mathtt{int64},\mathtt{float64},\mathtt{object},\mathtt{datetime64},\mathtt{category},\mathtt{bool}\}$が紐づきます。 例えば SSDSE-B-2026 では「総人口」は int64、 「高齢化率(%)」は float64、 「都道府県名」は object(実体は str)になります。 型の選択は計算速度・メモリ・正確性に直結します(郵便番号を int で読むと先頭0が消える、 大カテゴリは category で 10倍速くなる、 など)。 列単位で型が独立している点が NumPy の単一 dtype ndarray との最大の違いです。

要素3: 値 $V$ — 「セル ($i,j$) の中身」と欠損 $\mathtt{NaN}$

値 $V$ は「行 × 列」の二次元構造で、 $V_{i,j}$ をセルと呼びます。 pandas は内部的に「列ごとに 1次元 ndarray」 (BlockManager) を持つため、 列方向の演算 df['col'].mean() は NumPy の C ベクトル化で高速($\mu = \frac{1}{n}\sum_{i=1}^{n} V_{i,j}$)、 行方向ループ(iterrows)は Python 側ループで桁違いに遅い、 という非対称性が生まれます。 欠損は NaN(float の IEEE 754 NaN)または pd.NA(型に依存しない欠損)で表現され、 集約関数のデフォルトは skipna=True(NaN を除外して計算)です。

要素4: 操作の代数 — Split-Apply-Combine 三位一体

DataFrame の操作は関係代数(SQL)と数学的に近い構造を持ちます。 選択 $\sigma$: $\sigma_{\text{条件}}(\mathrm{df}) = \{i \in I \mid \text{条件}(V_i) = \mathrm{True}\}$ が df[df['A1101']>1e6]射影 $\pi$: $\pi_{C'}(\mathrm{df})$ が df[['A1101','A1303']]結合 $\bowtie$: $\mathrm{df}_1 \bowtie_{\text{key}} \mathrm{df}_2$ が merge集約 $\Gamma$: $\Gamma_{\text{key},f}(\mathrm{df})$ が groupby + agg。 この4操作を組み合わせることで、 ほぼ全てのデータ加工が表現できます(チューリング完全ではないが、 解析的タスクには十分豊か)。

🔬 理論的背景 — 関係代数と DataFrame

DataFrame の背景には Edgar F. Codd (1970) が提唱した関係モデルがあります。 Codd の関係 $R \subseteq D_1 \times D_2 \times \dots \times D_m$ は「ドメインの直積の部分集合」で、 これが現代的な「行 × 列の表」の祖先です。 関係代数は次の 6 つの基本演算で成り立ちます:

演算記号pandas での対応SQL
選択$\sigma_{P}(R)$df[df['x']>0]WHERE
射影$\pi_{A,B}(R)$df[['a','b']]SELECT
$R \cup S$pd.concat([r,s])UNION
$R - S$r[~r.index.isin(s.index)]EXCEPT
直積$R \times S$r.merge(s, how='cross')CROSS JOIN
改名$\rho_{A/B}(R)$df.rename(columns={'b':'a'})AS

これらに集約 $\Gamma$ と結合 $\bowtie$ を加えれば、 SQL の全機能をカバーします。 pandas はこの関係代数を Python の式言語として実装したもの、 と見ると全体像がすっきり捉えられます。 ただし pandas は順序を保持するため、 関係代数の「集合(順不同)」より強い構造を持ちます。 これが時系列処理を自然にこなせる理由です。

🚫 アンチパターン集

  1. 連鎖インデックス代入: df[df.a>0]['b'] = 1 ❌ → df.loc[df.a>0, 'b'] = 1
  2. iterrows でループ加工: for i,r in df.iterrows(): df.loc[i,'c'] = r.a*r.b ❌ → df['c'] = df['a']*df['b']
  3. append の繰り返し: for x in xs: df = df.append({...}) ❌(O(n²))→ リストに溜めて最後に pd.DataFrame(rows)
  4. merge せずに for で照合: for i,r in df.iterrows(): match = df2[df2.k==r.k] ❌ → df.merge(df2, on='k')
  5. 欠損を 0 で fillna してから平均: df['x'].fillna(0).mean() ❌(下振れ)→ df['x'].mean()(skipna=True) ✅
  6. object 型で大量データ: 大カテゴリ列を object のまま使う ❌ → astype('category') ✅(メモリ 1/10)
  7. read_csv で型推論任せ: 郵便番号・電話番号が int 化 ❌ → dtype={'郵便番号': str} 明示 ✅
  8. SettingWithCopyWarning 無視: 警告を放置 ❌ → 必ず .copy() で明示化 ✅

🍳 さらに使えるレシピ集

レシピA:複数列を一気に集約

1
2
3
4
5
6
7
df23.groupby('地域').agg(
    人口合計=('A1101','sum'),
    人口平均=('A1101','mean'),
    高齢化率最大=('高齢化率','max'),
    高齢化率最小=('高齢化率','min'),
    県数=('A1101','count'),
)

レシピB:上位N件を地域ごとに抽出(rank + filter)

1
2
3
df23['地域内順位'] = df23.groupby('地域')['A1101'].rank(ascending=False)
top2 = df23[df23['地域内順位']<=2].sort_values(['地域','地域内順位'])
print(top2[['A1101','地域内順位']])
📤 実行例(実測) A1101 地域内順位 Prefecture 広島県 2738000 1.0 岡山県 1847000 2.0 愛知県 7477000 1.0 静岡県 3555000 2.0 福岡県 5103000 1.0 熊本県 1709000 2.0 北海道 5092000 1.0 愛媛県 1291000 1.0 香川県 926000 2.0 宮城県 2264000 1.0 福島県 1767000 2.0 大阪府 8763000 1.0 兵庫県 5370000 2.0 東京都 14086000 1.0 神奈川県 9229000 2.0

レシピC:欠損を線形補間

1
2
3
4
panel = df.pivot_table(index='Prefecture', columns='SSDSE-B-2026', values='A1101')
panel = panel.interpolate(method='linear', axis=1)
# 年度で線形補間。 端点は前方/後方フィルでも可
panel = panel.bfill(axis=1).ffill(axis=1)

レシピD:Z 標準化

1
2
3
4
num_cols = df23.select_dtypes('number').columns
z = (df23[num_cols] - df23[num_cols].mean()) / df23[num_cols].std()
print(z.head())
# scikit-learn の StandardScaler でも同じ
📤 実行例(実測) A1101 A110101 A110102 ... L322110 高齢化率 地域内順位 Prefecture ... 北海道 0.874404 0.813829 0.933046 ... -1.007944 0.427383 -1.316057 青森県 -0.522531 -0.530037 -0.514313 ... -0.973000 1.088532 -0.413069 岩手県 -0.530038 -0.527853 -0.531159 ... -0.726571 1.021461 0.038425 宮城県 -0.136480 -0.132556 -0.139488 ... -1.016718 -0.702664 -1.316057 秋田県 -0.619044 -0.622492 -0.615390 ... -0.179102 2.238710 0.941413 [5 rows x 111 columns]

レシピE:merge with indicator(漏れ確認)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) 北海道 5,092,000 24,430 東京都 14,086,000 86,348 沖縄県 1,468,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd

# ── この抜粋だけで動くように、突き合わせる 2 つの表を作る ──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
_d = _d[_d['SSDSE-B-2026'] == 2023]
df1 = _d[['Prefecture', 'A1101']].iloc[:45]       # わざと 2 県欠けた表
df2 = _d[['Prefecture', 'A4101']].iloc[2:]        # わざと先頭 2 県が欠けた表

m = df1.merge(df2, on='Prefecture', how='outer', indicator=True)
print(m['_merge'].value_counts())
# both / left_only / right_only がそれぞれ何件あるか可視化
print(m[m['_merge']!='both'])   # 抜けている県を確認
📤 実行例(実測) _merge both 43 left_only 2 right_only 2 Name: count, dtype: int64 Prefecture A1101 A4101 _merge 4 北海道 5092000.0 NaN left_only 28 沖縄県 NaN 12549.0 right_only 41 青森県 1184000.0 NaN left_only 46 鹿児島県 NaN 9868.0 right_only

📌 まとめ — DataFrame を「自分の道具」にする5つの観点

  1. 構造を理解する: $(I, C, V)$ の3層モデル。 「行は何、 列は何、 セルは何の値か」を常に意識する。
  2. 関係代数で考える: 「選択・射影・結合・集約」の組み合わせで大半の処理が表現できる。 SQL 経験者は親和性が高い。
  3. vectorize ファースト: 列演算 → apply → for の優先順位を絶対に逆転しない。 100倍の速度差は当たり前。
  4. 型を明示する: dtype 一つで正確性・速度・メモリが激変する。 「自動推論に任せない」習慣を。
  5. 互換 API を学んでおく: polars / Dask / PySpark は pandas に似ているが微妙に違う。 大規模化に備えて壁の高さを知っておく。

SSDSE-B-2026 の 47 都道府県 × 110 変数を縦横無尽に動かせるようになれば、 ほとんどの公的統計データはこのままの DataFrame 操作で攻略できます。 「データを読む → 表にする → 集める → 結ぶ → 描く」 — この 5 動詞を呼吸のように使いこなせるのが、 データサイエンティストの第一段階です。

🔗 結合(merge)の完全ガイド

DataFrame の結合は、 関係代数の結合演算 $R_1 \bowtie_{k} R_2$ に対応します。 SSDSE-B-2026 を扱うときも、 「人口データ × 気候データ × 経済データ」を都道府県名でつなぐ場面が頻出します。 ここでは merge の全パターンを SSDSE 風データで例示します。

how='inner' — 両方にある県だけ残す

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ── ここから下の書き方見本で使うデータを用意します ──
# SSDSE-B は 1 行目が英字コード(A1101 など)、2 行目が日本語名。
# 見本では英字コードを使うので、1 行目を見出しにして読み込みます。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in ['A1101', 'A1303', 'SSDSE-B-2026']:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 見本の中で使う派生列(地域ブロック・高齢化率)と、結合用のもう 1 枚
_region = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北',
           '山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東',
           '埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東'}
df['地域'] = df['Prefecture'].map(_region).fillna('その他')
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df2 = df[['Prefecture', 'A1101']].copy()

pop = df[df['SSDSE-B-2026']==2023][['Prefecture','A1101']]                   # 47件
climate = pd.read_csv('data/climate.csv')[['Prefecture','年間平均気温']]  # 46件(沖縄欠落と仮定)
inner = pop.merge(climate, on='Prefecture', how='inner')          # 46件
print(inner.shape)

how='left' — 主データ(pop)を全て残す

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) B4101(年平均気温) 北海道 5,092,000 11.0 東京都 14,086,000 17.6 沖縄県 1,468,000 23.8 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd

# ── この抜粋だけで動くように、結合する 2 つの表を作る ──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
_d = _d[_d['SSDSE-B-2026'] == 2023]
pop = _d[['Prefecture', 'A1101']].rename(columns={'A1101': '総人口'})
# 沖縄県だけ気温データが欠けている状況を作る
climate = (_d[['Prefecture', 'B4101']].rename(columns={'B4101': '年間平均気温'})
             .query("Prefecture != '沖縄県'"))

left = pop.merge(climate, on='Prefecture', how='left')   # 47件(沖縄の気温は欠損)
_miss = left[left['年間平均気温'].isna()]
# 欠損はそのまま出すと NaN と表示されて「計算が壊れた」ように見えるので、
# 右側に行が無かったことが分かる形にして表示する
print(f'右側に相手が見つからなかった行: {len(_miss)} 件')
print(_miss.assign(年間平均気温='(右の表に無し)').to_string(index=False))
📤 実行例(実測) 右側に相手が見つからなかった行: 1 件 Prefecture 総人口 年間平均気温 沖縄県 1468000 (右の表に無し)

how='outer' + indicator — 全件 + どちら由来か明示

1
2
3
full = pop.merge(climate, on='Prefecture', how='outer', indicator=True)
print(full['_merge'].value_counts())
# both=46, left_only=1 (沖縄), right_only=0
📤 実行例(実測) _merge both 46 left_only 1 right_only 0 Name: count, dtype: int64

複合キー(都道府県×年度)で結合

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 1,681,000 24,430 東京都 14,086,000 3,205,000 86,348 沖縄県 1,468,000 350,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# ── この抜粋で使うデータを用意します(英字の項目コードで読み込み)──
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in df.columns[3:]:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()]

economy = df[['Prefecture','SSDSE-B-2026','A4101']]
demo = df[['Prefecture','SSDSE-B-2026','A1101','A1303']]
panel = economy.merge(demo, on=['Prefecture','SSDSE-B-2026'])
panel['出生率'] = panel['A4101'] / panel['A1101']

注意: 都道府県名は表記揺れ(東京都/東京、 北海道/北海道庁)が起きやすい。 必ず .str.strip().str.replace('庁','') で正規化してから merge。

🧩 groupby の Split-Apply-Combine を深く理解する

Split-Apply-Combine は Hadley Wickham (2011) が R の plyr 設計で定式化した概念で、 pandas の groupby もこの3段階で動きます。

  1. Split (分割): df.groupby('地域') はキーごとの DataFrameGroupBy オブジェクトを返す。 実際の分割はまだ起きていない(遅延評価)。
  2. Apply (適用): .agg(np.mean).apply(func) で各グループに関数を適用。 ここで内部的にループ。
  3. Combine (結合): 結果を1つの DataFrame に再構築。 インデックスはグループキーになる。

apply, agg, transform の使い分け

メソッド関数の入力関数の出力結果の形
aggSeriesスカラーグループ数 × 集約数
transformSeriesSeries(同サイズ)元と同じ shape
applyDataFrame何でも柔軟(最も遅い)
filterDataFramebool条件を満たすグループの行
1
2
3
4
5
6
7
8
# transform: 全国平均との差(z スコア風)
df23['人口_全国平均との差'] = df23['A1101'] - df23['A1101'].mean()
# 地域内で中央化
df23['人口_地域中央化'] = df23['A1101'] - df23.groupby('地域')['A1101'].transform('mean')

# filter: 平均高齢化率が 30 を超える地域だけ
high_aging = df23.groupby('地域').filter(lambda g: g['高齢化率'].mean() > 30)
print(high_aging['地域'].unique())   # 東北、四国 など
📤 実行例(実測) ['北海道' '東北' '中部' '近畿' '中国' '四国' '九州']

📈 DataFrame からの可視化Tips

DataFrame と可視化は表裏一体。 df.plot() はワンライナーで便利ですが、 まじめなグラフは matplotlib / seaborn / plotly を使います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import os
os.makedirs('outputs', exist_ok=True)  # 保存先のフォルダを作っておく

import matplotlib.pyplot as plt
import seaborn as sns

# 47都道府県の人口 vs 高齢化率
plt.figure(figsize=(8,6))
sns.scatterplot(data=df23.reset_index(), x='A1101', y='高齢化率',
                hue='地域', size='A4101', sizes=(40, 400))
plt.xscale('log')                                # 人口は log で見ると見やすい
for _,r in df23.reset_index().iterrows():
    plt.annotate(r['Prefecture'], (r['A1101'], r['高齢化率']), fontsize=8)
plt.title('SSDSE-B-2026 (2023) 都道府県の人口と高齢化率')
plt.tight_layout(); plt.savefig('outputs/pop_aging.png', dpi=120)

人口は log スケールにすると東京の超巨大ぶりが緩和される。 高齢化率は線形のままで OK。 県名注釈で「外れ値」(沖縄=若い、 秋田=高齢)が一目瞭然になる。

🧪 DataFrame のテストと検証

本番運用の DataFrame は必ずテストを書くのが鉄則です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# ── ここから下の書き方見本で使うデータを用意します ──
# SSDSE-B は 1 行目が英字コード(A1101 など)、2 行目が日本語名。
# 見本では英字コードを使うので、1 行目を見出しにして読み込みます。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in ['A1101', 'A1303', 'SSDSE-B-2026']:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 見本の中で使う派生列(地域ブロック・高齢化率)と、結合用のもう 1 枚
_region = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北',
           '山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東',
           '埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東'}
df['地域'] = df['Prefecture'].map(_region).fillna('その他')
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df2 = df[['Prefecture', 'A1101']].copy()

import pandas as pd
import pandas.testing as pdt

def make_panel(year):
    df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
    return df[df['SSDSE-B-2026']==year].set_index('Prefecture')[['A1101']]

panel = make_panel(2023)

# 1. shape の検証
assert panel.shape == (47, 1), f'47都道府県のはずが {panel.shape}'

# 2. 主キー一意性
assert panel.index.is_unique

# 3. 欠損なし
assert panel.isna().sum().sum() == 0

# 4. 値の範囲
assert (panel['A1101'] >= 0).all()
assert panel['A1101'].sum() > 1.2e8 and panel['A1101'].sum() < 1.3e8  # 1.2-1.3億

# 5. 既知の事実を確認
assert panel.loc['東京都','A1101'] > panel.loc['鳥取県','A1101']

# 6. 期待結果との一致(リグレッション)
expected = pd.read_pickle('tests/expected_2023.pkl')
pdt.assert_frame_equal(panel, expected)

この6項目を pytest に登録しておけば、 上流データが変わったときに自動で気づける。

🆚 pandas vs polars 同じ処理を比べる

SSDSE-B-2026 を「2023年だけ抽出 → 地域別人口合計」する処理を両方で書いてみると、 文化の違いが見えます。

pandas

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
1
2
3
4
5
6
7
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
out = (df[df['SSDSE-B-2026']==2023]
         .assign(地域=lambda d: d['Prefecture'].map(region_map))
         .groupby('地域')['A1101'].sum()
         .sort_values(ascending=False))
print(out)
📤 実行例(実測) 地域 関東 43527000 近畿 21990000 中部 20749000 九州 14029000 東北 8318000 中国 7070000 北海道 5092000 四国 3578000 Name: A1101, dtype: int64

polars

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) 北海道 5,092,000 東京都 14,086,000 沖縄県 1,468,000 …(全 47 行)
1
2
3
4
5
6
7
8
import polars as pl
df = pl.read_csv('data/raw/SSDSE-B-2026.csv', skip_rows_after_skiprows=[1])
out = (df.filter(pl.col('SSDSE-B-2026')==2023)
         .with_columns(pl.col('Prefecture').map_elements(lambda x: region_map[x]).alias('地域'))
         .group_by('地域')
         .agg(pl.col('A1101').sum())
         .sort('A1101', descending=True))
print(out)

polars は式 (Expression) APIが中心で、 並列実行・最適化(query planner)が組み込まれている。 SSDSE-B 程度では差は小さいが、 1億行を超えると 10〜30 倍の差が出る。

🎯 データ品質チェック — DataFrame の健康診断

分析前に DataFrame の「健康診断」をする習慣を持つと、 後段のバグを激減できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def health_check(df: pd.DataFrame, name: str = 'df'):
    print(f'=== {name} の健康診断 ===')
    print(f'shape       : {df.shape}')
    print(f'memory      : {df.memory_usage(deep=True).sum()/1e6:.2f} MB')
    print(f'重複行       : {df.duplicated().sum()} 件')
    print(f'全列で欠損なし: {(df.isna().sum()==0).sum()} / {len(df.columns)} 列')
    print(f'カラム名重複  : {df.columns.duplicated().sum()} 件')
    print('--- dtype 分布 ---')
    print(df.dtypes.value_counts())
    print('--- 欠損率 Top5 ---')
    print((df.isna().mean()*100).sort_values(ascending=False).head())
    print('--- 数値列の describe ---')
    print(df.select_dtypes('number').describe().T[['mean','std','min','max']].head())

health_check(df23, 'SSDSE-B-2026 (2023)')
📤 実行例(実測) === SSDSE-B-2026 (2023) の健康診断 === shape : (47, 114) memory : 0.05 MB 重複行 : 0 件 全列で欠損なし: 114 / 114 列 カラム名重複 : 0 件 --- dtype 分布 --- int64 103 float64 10 object 1 Name: count, dtype: int64 --- 欠損率 Top5 --- A1101 0.0 H2602 0.0 H2600 0.0 H1803 0.0 H1802 0.0 dtype: float64 --- 数値列の describe --- mean std min max A1101 2.645809e+06 2.797551e+06 537000.0 14086000.0 A110101 1.287085e+06 1.373649e+06 257000.0 6914000.0 A110102 1.358723e+06 1.424664e+06 280000.0 7172000.0 A1102 2.578617e+06 2.691343e+06 532000.0 13448000.0 A110201 1.253234e+06 1.320011e+06 255000.0 6594000.0

この関数を common.py に置いておけば、 新しい DataFrame を扱うたびに health_check(df) 一発で全体像を把握できる。

🛠 実務直結レシピ集(応用編)

A. 異常値検出(IQR 法)を 47 都道府県に適用

1
2
3
4
5
6
7
q1 = df23['A1101'].quantile(0.25)
q3 = df23['A1101'].quantile(0.75)
iqr = q3 - q1
lower, upper = q1 - 1.5*iqr, q3 + 1.5*iqr
outliers = df23[(df23['A1101']<lower) | (df23['A1101']>upper)]
print(outliers.index.tolist())
# → 北海道・埼玉・千葉・東京・神奈川・愛知・大阪・兵庫・福岡の9都道府県が上側外れ値として検出される
📤 実行例(実測) ['北海道', '埼玉県', '千葉県', '東京都', '神奈川県', '愛知県', '大阪府', '兵庫県', '福岡県']

B. クラスタリング向け前処理

1
2
3
4
5
6
7
8
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

features = ['A1101','高齢化率','A4101','L3221']
X = df23[features].dropna()
Xs = StandardScaler().fit_transform(X)
df23.loc[X.index,'cluster'] = KMeans(n_clusters=4, random_state=42, n_init='auto').fit_predict(Xs)
print(df23.groupby('cluster')[features].mean())
📤 実行例(実測) A1101 高齢化率 A4101 L3221 cluster 0.0 1.133083e+06 32.445899 6867.250000 265947.833333 1.0 7.952000e+06 26.994577 48544.500000 307408.375000 2.0 2.083667e+06 31.105034 11494.777778 309857.500000 3.0 1.070444e+06 35.482100 5511.111111 297461.888889

C. 散らばりを地域ごとに可視化

1
2
3
import seaborn as sns
sns.boxplot(data=df23.reset_index(), x='地域', y='高齢化率', order=['北海道','東北','関東','中部','近畿','中国','四国','九州'])
plt.xticks(rotation=30); plt.tight_layout()

D. eval / query で大規模高速

1
2
3
4
5
6
7
8
# 通常
res = df['A1303'] / df['A1101'] * 100

# eval版は数倍高速になる(大規模時)
res = df.eval('A1303 / A1101 * 100')

# query もパース後にC実装
big_pref = df.query('A1101 > 1e7')

E. 大きなCSVの分割読み込み(chunksize)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ── ここから下の書き方見本で使うデータを用意します ──
# SSDSE-B は 1 行目が英字コード(A1101 など)、2 行目が日本語名。
# 見本では英字コードを使うので、1 行目を見出しにして読み込みます。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in ['A1101', 'A1303', 'SSDSE-B-2026']:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 見本の中で使う派生列(地域ブロック・高齢化率)と、結合用のもう 1 枚
_region = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北',
           '山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東',
           '埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東'}
df['地域'] = df['Prefecture'].map(_region).fillna('その他')
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df2 = df[['Prefecture', 'A1101']].copy()

summary = pd.DataFrame()
for chunk in pd.read_csv('huge.csv', chunksize=100_000, encoding='utf-8'):
    chunk['年度'] = chunk['日付'].str[:4]
    summary = pd.concat([summary, chunk.groupby('年度')['金額'].sum()], axis=0)
print(summary.groupby(level=0).sum())

📋 ワンページ・チートシート

やりたいこと書き方
CSV 読み込みpd.read_csv(path, encoding='utf-8')
先頭5行df.head()
shape / dtypesdf.shape, df.dtypes
記述統計df.describe()
条件抽出df[df['x']>0] / df.query('x>0')
列選択df[['a','b']]
行参照df.loc[label] / df.iloc[pos]
並べ替えdf.sort_values('x', ascending=False)
欠損df.isna().sum() / df.dropna()
集約df.groupby('k')['v'].sum()
結合df1.merge(df2, on='k')
ピボットdf.pivot_table(index, columns, values)
時系列リサンプルdf.resample('M').mean()
プロットdf.plot(kind='bar')

🧮 実値で計算してみる

典型的な5操作:

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ── ここから下の書き方見本で使うデータを用意します ──
# SSDSE-B は 1 行目が英字コード(A1101 など)、2 行目が日本語名。
# 見本では英字コードを使うので、1 行目を見出しにして読み込みます。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in ['A1101', 'A1303', 'SSDSE-B-2026']:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 見本の中で使う派生列(地域ブロック・高齢化率)と、結合用のもう 1 枚
_region = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北',
           '山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東',
           '埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東'}
df['地域'] = df['Prefecture'].map(_region).fillna('その他')
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df2 = df[['Prefecture', 'A1101']].copy()

df.head()                              # 先頭5行
df['A1303'].mean()                     # 列の平均(65歳以上人口 A1303)
df[df['A1303'] > 5e5]                  # フィルタ
df.groupby('地域')['A1101'].sum()       # 集計(地域は派生列)
df.merge(df2, on='Prefecture')          # 結合

🧮 SSDSE-B-2026 を DataFrame として動かす(用語固有計算)

🎯 このブロックの狙い
SSDSE-B-2026(47都道府県 × 約110変数 × 11年)の CSV を読み込み、 DataFrame の「形」(shape)「型」(dtypes)「中身」(describe)を1枚で把握する。 これが全データ分析の第1歩です。
📥 入力
data/raw/SSDSE-B-2026.csv(CSV、 cp932、 1行目=英字コード列名(A1101 等)/2行目=日本語列名)。 各行=年度×都道府県、 列=人口・経済・教育などの統計値。
 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])
print('shape =', df.shape)                       # (564, 112) 程度: 47県 × 12年
print('dtypes head:'); print(df.dtypes.head(8))
print('describe (数値列):'); print(df.describe().T.head(5))

# 2023年だけ抜き出して都道府県をインデックス化
df23 = df[df['SSDSE-B-2026']==2023].set_index('Prefecture')
print(df23.shape)                                # (47, 111)
print(df23['A1101'].nlargest(5))                # 東京・神奈川・大阪・愛知・埼玉
print('全国合計 =', df23['A1101'].sum())        # 約1.24億
📤 出力(期待値)
shape はおおむね (564, 112) 前後(2023 年だけなら 47 行)。 総人口 Top5 は東京都(約1409万)→神奈川県(約923万)→大阪府(約876万)→愛知県(約748万)→埼玉県(約733万)。 全国合計は約 1.24 億人。
💬 解説
skiprows=[1] は SSDSE 特有の「2行目の日本語列名」を読み飛ばし、 1行目の英字コード列名(A1101 等)をそのまま列名として使うため。 set_index で「Prefecture」を主キーに昇格させると df23.loc['東京都'] で1行を直接取れる。 これが DataFrame の「インデックス」の威力。

手計算で確かめる(高齢化率 Top3)

SSDSE-B-2026 (2023年) を実際に計算すると:

順位都道府県総人口65歳以上人口高齢化率(%)
1秋田県約 91.4万約 35.7万39.1
2高知県約 66.6万約 24.2万36.3
3徳島県約 69.5万約 24.6万35.4

この計算は df23.assign(rate=df23['A1303']/df23['A1101']*100).nlargest(3,'rate') の1行で出る。 これが DataFrame の表現力です。

🏭 産業事例 6 件 — DataFrame は何の現場で動いているか

業界DataFrame の役割代表ツール / 規模
金融(クオンツ)日次・分次の価格データを時系列 DataFrame で保持し、 移動平均・ボラティリティを列演算。 バックテスト基盤の中核。pandas + Arrow / 数億行
EC・小売購買ログを groupby('user_id') で集計し RFM 分析やコホート分析。 在庫最適化・需要予測の前処理。pandas / polars / 数千万行
製造業(IoT)センサーログをミリ秒単位の DataFrame に整形し、 異常検知・予知保全モデルへ。 リサンプリングが頻出。pandas + Dask / 数十億行
医療(電子カルテ)患者ID×検査項目×時刻の DataFrame で疫学解析。 欠損処理・標準化が中心課題。pandas + R data.frame
広告テック入札ログを Spark DataFrame で分散処理し、 CTR / CVR 予測の特徴量テーブルを生成。PySpark / TB 級
公的統計(本コンペ)SSDSE-B のような47都道府県データを DataFrame で読み込み、 EDA・回帰・可視化の起点に。pandas / 数千〜数万行

📊 比較表 — DataFrame 実装系の選び方

ツール速度メモリ学習コスト分散推奨規模
pandas×(メモリ載せ)低(標準)×〜1GB
polars高(5-30倍)○ Arrow中(式 API)部分〜100GB
Dask○(パーティション)低(pandas互換)〜TB
PySparkTB〜PB
cuDF (RAPIDS)超高(GPU)△ (GPU VRAM)低(pandas互換)×〜数十GB
R data.frame中(dplyr)××〜1GB

SSDSE-B(数千行)の規模なら pandas で十分。 1億行を超えたら polars または Dask を検討。

✏️ 演習 5 問(SSDSE-B-2026 で)

  1. 演習1: SSDSE-B-2026 を読み込み、 2023年だけ抜き出した DataFrame の shape を求めよ。 (答: (47, 約108))
  2. 演習2: 「総人口(A1101)」列の平均・中央値・標準偏差を計算せよ。 (ヒント: df['A1101'].agg(['mean','median','std'])
  3. 演習3: 「高齢化率」が 35% を超える都道府県を抜き出し、 県名と高齢化率を表示せよ。 (答: 秋田・高知・山口・島根 など)
  4. 演習4: 地域別(北海道・東北・関東…)の総人口合計を求めよ。 (ヒント: 地域マッピング辞書を作って mapgroupby
  5. 演習5: 2013年と2023年の DataFrame を都道府県名で merge し、 10年間の人口変化率を計算せよ。 (ヒント: merge(..., suffixes=('_13','_23'))

💀 失敗例ケーススタディ — DataFrame 取り扱い事故

事例1: SettingWithCopyWarning を放置して報告書の数値が間違う

あるアナリストが df_sub = df[df['年度']==2023] としてから df_sub['人口'] = df_sub['人口']/1000 と代入。 警告は出たが無視。 後で元の df を見ると、 値が一部だけ書き換わっており、 報告書の数値が再現できなくなった。 対策: .copy() を明示し、 警告は即座に直す習慣を。

事例2: 郵便番号が int 化されて先頭ゼロが消える

SSDSE 系データを pd.read_csv で読むと、 郵便番号「0600000」が 600000 と読み込まれてゼロが消失。 ジオコーディングで使えなくなる。 対策: dtype={'郵便番号': str} を明示。

事例3: 巨大 DataFrame を for 文で 30 分待つ

100万行の DataFrame に対して for i in range(len(df)): df.loc[i,'new'] = f(df.loc[i,'x']) と書いて 30 分待った末にメモリ不足で落ちる。 対策: df['new'] = df['x'].apply(f) または完全な vectorize で 1 秒以下に。

事例4: groupby の落とし穴 — 文字列キーに半角スペース混入

地域名「北海道 」(末尾スペース)と「北海道」が別グループに。 集計結果が2行に分裂し、 グラフの色が変。 対策: df['地域'] = df['地域'].str.strip() を前処理で徹底。

事例5: NaN を 0 とみなして平均が下振れ

欠損のある列を fillna(0).mean() で計算したため、 平均値が本来より低く出た。 対策: 欠損は欠損として扱い (skipna=True がデフォ)、 「補完するべきか除外するべきか」を意識的に選ぶ。

📖 用語ミニ辞典(10語)

Series
1次元・型統一の配列。 DataFrame の各列が Series。
Index
行ラベル。 ハッシュ可能で、 loc 参照を高速化。
MultiIndex
階層化されたインデックス。 タプル ($a,b$) で行を識別。
dtype
各列の型(int64, float64, object, category, datetime64 等)。
loc / iloc
loc=ラベル指定(包括端)/iloc=位置指定(排他端)。
groupby
Split-Apply-Combine の3段階パターン。 SQL の GROUP BY 相当。
merge / join
キー列で2つの DataFrame を関係代数的に結合。
pivot_table
「縦持ち→横持ち」変換と集約を同時に実行。
apply
行 or 列に Python 関数を適用。 vectorize が無理な時の最終手段。
BlockManager
pandas 内部で列を dtype ごとにブロック化して保持する仕組み。

🧮 数式に値を入れて手で計算する: SSDSE-B-2026 で地域別 総人口集計 (groupby)

SSDSE-B-2026 (2023年) の 5 都道府県を抜粋し、 地域ブロック別の総人口 (A1101) 平均と県数を groupby で計算する。

Step 1: データ (SSDSE-B-2026 抜粋・2023 年・A1101 総人口)

都道府県地域A1101 総人口 [万人]
北海道北海道・東北509
秋田県北海道・東北91
東京都関東1409
神奈川県関東923
埼玉県関東733

Step 2: 地域別平均総人口 (手計算)

北海道・東北: (509 + 91) / 2 = 300.0 万人 関東: (1409 + 923 + 733) / 3 ≒ 1021.7 万人

🐍 Python で再現 (pandas groupby)

1
2
3
4
5
6
7
8
import pandas as pd
df = pd.DataFrame({
  '都道府県':['北海道','秋田県','東京都','神奈川県','埼玉県'],
  '地域':['北海道・東北','北海道・東北','関東','関東','関東'],
  'A1101':[509,91,1409,923,733],
})
g = df.groupby('地域')['A1101'].agg(['mean','count'])
print(g)

📤 実行結果

mean count 地域 北海道・東北 300.000000 2 関東 1021.666667 3

💬 手計算 (Step 2) の 300.0 / 1021.7 と Python の groupby 出力が一致(小数の表示桁数のみ異なる)。 SSDSE-B-2026 の地域別集計の基本パターン。

🐍 Python 実装

最小限のスニペットで動作確認できる例。 公的データ(SSDSE 等)を想定しています。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 47 行)
1
2
3
4
5
6
7
8
9
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df23 = df[df['SSDSE-B-2026']==2023].set_index('Prefecture')
print(df23.shape)            # (47, 111)
df23['高齢化率'] = df23['A1303'] / df23['A1101'] * 100

# 高齢化率が高い順 Top 5 → 秋田39.1・高知36.3・徳島35.4・山口35.4・青森35.2
print(df23['高齢化率'].nlargest(5).round(1))
📤 実行例(実測) (47, 111) Prefecture 秋田県 39.1 高知県 36.3 徳島県 35.4 山口県 35.4 青森県 35.2 Name: 高齢化率, dtype: float64

⚠️ よくある落とし穴

❌ 1. SettingWithCopyWarning を無視
df[df.A > 0]['B'] = 1 のように 2 段階で書くと、 pandas は元データのコピーに書き込むことがあり、 変更が消える。 警告は「意図どおりか確認せよ」という合図。 .loc[条件, 列] でまとめて指定するか、 明示的に .copy() を取る。
❌ 2. inplace=True 多用
inplace=True は返り値が None になるためメソッドを繋げられず、 途中結果も残らないので手戻りが効かない。 内部でコピーを作る実装も多く、 期待するほど速くもない。 新しい変数に代入する書き方のほうが、 読みやすく戻しやすい。
❌ 3. 巨大DataFrameをfor文で回す
行ごとの Python ループは 1 行ずつ型変換が走るため、 列全体をまとめて計算する書き方より桁違いに遅い。 47 行なら気にならないが、 市区町村 1,742 行や時系列データでは差が出る。 演算は列同士でまとめて書き、 どうしても行ごとの処理が要るときだけ apply を使う。
❌ 4. NaN を 0 と勘違い
欠測は「値が無い」であって「0」ではない。 sum は NaN を飛ばして合計するが、 mean は分母からも除くので、 0 で埋めた場合と平均が変わる。 「未回答」と「0 件」を同じにしてよいかはデータの意味次第で、 埋める前に必ず判断する。
❌ 5. dtype の自動推定ミス
郵便番号・都道府県コード・電話番号のような「数字だが数値でないもの」は、 自動推定で整数になり先頭の 0 が消える。 SSDSE の地域コード R01000 のように英字が付いていれば安全だが、 数字だけの ID は危ない。 dtype=str を明示して読む。

🗺 概念マップ — DataFrame を中心にした世界地図

                                ┌────────────────┐
                                │  プログラミング言語  │
                                │   (Python / R)  │
                                └────────┬────────┘
                                         │
                          ┌──────────────┼──────────────┐
                          ▼              ▼              ▼
                   ┌──────────┐  ┌──────────┐  ┌──────────┐
                   │  NumPy   │  │  pandas  │  │   polars  │
                   │ (数値配列) │  │ (DataFrame) │ │ (Rust製) │
                   └────┬─────┘  └────┬─────┘  └────┬─────┘
                        │             │             │
                        └─────┬───────┴─────┬───────┘
                              ▼             ▼
                       ┌──────────────────────────┐
                       │   表形式データの世界共通モデル   │
                       │      ──   DataFrame   ──      │
                       └────────────┬──────────────────┘
                                    │
            ┌───────────────────────┼───────────────────────┐
            ▼                       ▼                       ▼
       ┌─────────┐            ┌─────────┐             ┌─────────┐
       │  入力   │            │  変形   │             │  出力   │
       │ CSV/JSON│            │ merge   │             │  グラフ  │
       │ API/SQL │            │ groupby │             │  モデル  │
       │ Parquet │            │ pivot   │             │  CSV    │
       └─────────┘            └─────────┘             └─────────┘
  

DataFrame は「入力 → 変形 → 出力」の中継地点。 ここを自由に操れることが、 現代データサイエンスの基礎体力です。

🚀 エンドツーエンド・パイプライン例(SSDSE-B-2026)

これまでのレシピをつなげて、 「データ取得 → 健康診断 → 派生列 → 集約 → モデル前処理 → 可視化 → 保存」の一連のパイプラインを書いてみます。 関数化しておけば再現実行が楽になります。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1302(15~64歳人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 2,897,000 1,681,000 24,430 東京都 14,086,000 9,368,000 3,205,000 86,348 沖縄県 1,468,000 882,000 350,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# ── ここから下の書き方見本で使うデータを用意します ──
# SSDSE-B は 1 行目が英字コード(A1101 など)、2 行目が日本語名。
# 見本では英字コードを使うので、1 行目を見出しにして読み込みます。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in ['A1101', 'A1303', 'SSDSE-B-2026']:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 見本の中で使う派生列(地域ブロック・高齢化率)と、結合用のもう 1 枚
_region = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北',
           '山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東',
           '埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東'}
df['地域'] = df['Prefecture'].map(_region).fillna('その他')
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df2 = df[['Prefecture', 'A1101']].copy()

import os
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

REGION_MAP = {
    '北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北',
    '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東',
    '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部',
    '三重県':'近畿','滋賀県':'近畿','京都府':'近畿','大阪府':'近畿','兵庫県':'近畿','奈良県':'近畿','和歌山県':'近畿',
    '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国',
    '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国',
    '福岡県':'九州','佐賀県':'九州','長崎県':'九州','熊本県':'九州','大分県':'九州','宮崎県':'九州','鹿児島県':'九州','沖縄県':'九州',
}

def load(path='data/raw/SSDSE-B-2026.csv'):
    return pd.read_csv(path, encoding='cp932', skiprows=[1])

def select_year(df, year=2023):
    return df[df['SSDSE-B-2026']==year].set_index('Prefecture')

def enrich(df):
    return df.assign(
        高齢化率 = lambda d: d['A1303']/d['A1101']*100,
        生産年齢比 = lambda d: d['A1302']/d['A1101']*100,
        地域 = lambda d: d.index.to_series().map(REGION_MAP),
    )

def aggregate_by_region(df):
    return df.groupby('地域').agg(
        人口=('A1101','sum'),
        平均高齢化率=('高齢化率','mean'),
        県数=('A1101','count'),
    # 集計後の列名は 人口/平均高齢化率/県数。'A1101' はもう存在しない
    ).sort_values('人口', ascending=False)

def standardize(df, cols):
    sub = df[cols].dropna()
    return pd.DataFrame(StandardScaler().fit_transform(sub), index=sub.index, columns=cols)

def main():
    df = load()
    df23 = select_year(df, 2023)
    df23 = enrich(df23)
    print(aggregate_by_region(df23))
    feat = ['A1101','高齢化率','A4101']
    Z = standardize(df23, feat)
    os.makedirs('outputs', exist_ok=True)   # 保存先が無いと to_csv は失敗する
    Z.to_csv('outputs/standardized_2023.csv')
    print('saved: outputs/standardized_2023.csv')

if __name__ == '__main__':
    main()
📤 実行例(実測) 人口 平均高齢化率 県数 地域 関東 43527000 27.993120 7 近畿 21990000 30.257049 7 中部 20749000 31.264899 9 九州 14029000 31.544426 8 東北 8318000 34.477233 6 中国 7070000 32.954654 5 北海道 5092000 33.012569 1 四国 3578000 34.599246 4 saved: outputs/standardized_2023.csv

この骨格は、 SSDSE-A / SSDSE-C / e-Stat の他のデータセットにも流用できます。 「読み込み・抽出・加工・集約・標準化・保存」を独立関数にしておくのが鍵。

🎓 学習のロードマップ

  1. Week 1: read_csv / head / describe / plot で慣れる
  2. Week 2: loc / iloc / query / assign でメソッドチェーンを習得
  3. Week 3: groupby / agg / transform / merge で集約と結合
  4. Week 4: pivot_table / melt / resample で形を変える
  5. Week 5: dtype / category / chunksize でメモリ最適化
  6. Week 6: pd.testing / pytest でテスト駆動な分析を開始
  7. Week 7: polars / DuckDB / Dask を触って大規模化に備える
  8. Week 8: 自分のプロジェクトで全部繋げる(SSDSE-B + 地理データ + 経済データの統合分析)

DataFrame は奥が深いですが、 上記のロードマップで2ヶ月もあれば「自分の道具」と呼べるレベルに達します。 SSDSE-B-2026 を題材に、 毎週新しい技を1つずつ追加していくのが効率的な学び方です。

📎 付録:型・メモリ・速度の数値感

dtype1要素あたりサイズ100万行のメモリ用途
int81 byte1 MB小さい整数(年齢など)
int324 byte4 MB通常の整数(20億まで)
int648 byte8 MBpandas デフォルト
float324 byte4 MBML 用(精度7桁)
float648 byte8 MBpandas デフォルト
object (str)~50 byte50 MB汎用文字列
string[pyarrow]~3 byte3 MB短い文字列(Arrow)
category~1 byte+辞書1 MB前後少数カテゴリ(地域名)
datetime64[ns]8 byte8 MB日時
bool1 byte1 MBフラグ列

教訓:100万行を超える DataFrame はdtype の見直しだけで 10〜100 倍のメモリ削減が可能。 まず df.info(memory_usage='deep') で現状把握を。

📊 統計サマリと DataFrame の親和性

df.describe() は基本ですが、 統計分析では scipy.statsstatsmodels と組み合わせると強力です:

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 296,888 東京都 14,086,000 3,205,000 341,320 沖縄県 1,468,000 350,000 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# ── ここから下の書き方見本で使うデータを用意します ──
# SSDSE-B は 1 行目が英字コード(A1101 など)、2 行目が日本語名。
# 見本では英字コードを使うので、1 行目を見出しにして読み込みます。
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
# 1 行目を見出しにすると数値の列も文字列で入ってくるので、まとめて数値に直す
for _c in df.columns:
    if _c not in ('Code', 'Prefecture'):
        df[_c] = pd.to_numeric(df[_c], errors='coerce')

# 見本の中で使う派生列(地域ブロック・高齢化率)と、結合用のもう 1 枚
_region = {'北海道':'北海道','青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北',
           '山形県':'東北','福島県':'東北','茨城県':'関東','栃木県':'関東','群馬県':'関東',
           '埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東'}
df['地域'] = df['Prefecture'].map(_region).fillna('その他')
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df2 = df[['Prefecture', 'A1101']].copy()
# この見本で使う 2023 年度・47 都道府県の表(都道府県名を索引にする)
df23 = (df[df['SSDSE-B-2026'] == 2023]
        .set_index('Prefecture')
        .drop(columns=['SSDSE-B-2026', 'Code'], errors='ignore'))

from scipy import stats
import statsmodels.api as sm

# 47都道府県の高齢化率と消費支出(L3221)の相関と検定
r, p = stats.pearsonr(df23['高齢化率'], df23['L3221'])
print(f'相関係数 r = {r:.3f}, p値 = {p:.4f}')

# 単回帰モデル
X = sm.add_constant(df23['高齢化率'])
y = df23['L3221']
model = sm.OLS(y, X).fit()
print(model.summary().tables[1])
📤 実行例(実測) 相関係数 r = -0.306, p値 = 0.0363 ============================================================================== coef std err t P>|t| [0.025 0.975] ------------------------------------------------------------------------------ const 3.658e+05 3.26e+04 11.223 0.000 3e+05 4.31e+05 高齢化率 -2215.3982 1026.367 -2.158 0.036 -4282.608 -148.188 ==============================================================================

DataFrame は scipy / statsmodels / sklearn とシームレスに連携。 列を変えるだけで様々な分析を試せる柔軟性が魅力。

🌟 最後に — DataFrame を「言語」として身につける

DataFrame はライブラリの一機能ではなく、 「データを語る言語」です。 SQL 話者は WHERE/GROUP BY/JOIN で会話し、 R ユーザーは dplyr の動詞(filter/mutate/group_by/summarise)で会話し、 pandas ユーザーは query/assign/groupby/agg/merge で会話します。 表現は違えど、 背後にある関係代数の文法は同じ。 一度身につければ、 ツールが変わっても本質は使い回せます。

SSDSE-B-2026 のような実データを通して練習を重ねれば、 「都道府県データを開いて → 集めて → 結んで → 描く」までを 5 分で書けるようになります。 そこから先は、 問いを立てる力結果を解釈する力の世界です。 道具としての DataFrame を超えて、 「データで物事を考える」ステージへ進むための土台がここにあります。

🎯 ボーナス演習 — SSDSE-B-2026 で挑戦

  1. ボーナス1: 2013年と2023年の DataFrame を merge し、 「人口(A1101)減少率トップ10」と「出生数(A4101)変化率トップ10」を求めよ。 重複する県はあるか?
  2. ボーナス2: 47県を「総人口(A1101)」「高齢化率(A1303/A1101)」「消費支出(L3221)」で標準化し、 k-means (k=4) でクラスタリング。 各クラスタの代表県を3つずつ挙げよ。
  3. ボーナス3: 全国合計と東京単独の比率 (=東京の全国シェア) を、 「総人口(A1101)」「65歳以上人口(A1303)」「出生数(A4101)」で比較せよ。
  4. ボーナス4: df.eval()df.query() を使って、 「2023年 ∧ 人口 > 200万 ∧ 高齢化率 < 30」 の県を 1 行で抽出するコードを書け。
  5. ボーナス5: 47県の地域分け (北海道/東北/関東/中部/近畿/中国/四国/九州) を category 型にしてメモリ使用量がいくつになるか、 memory_usage(deep=True) で比較せよ。

ヒント: これらの演習で「DataFrame を呼吸のように扱う」感覚が身につけば、 統計データ解析コンペでも自信を持って臨めるはずです。

📝 補足ノート — よくある追加質問

DataFrame と Excel シートは似ているが本質的に違う。 Excel は「セル参照と数式の二重構造」だが、 DataFrame は「列が型を持つ純粋な配列」。 シート間参照のような関数チェーンはなく、 全ての計算は明示的なコードで書く。 これがバージョン管理と再現性の鍵。

大規模 DataFrame は Parquet で保存すると、 CSV と比べてサイズ 1/5・読み込み速度 10 倍。 列志向のため不要な列を読まずに済むのが効く。 SSDSE-B 程度ならどちらでも良いが、 自前ログを保存するなら Parquet 一択。

マルチプロセス化したいときは swiftermodin、 GPU を使うなら cuDF、 究極のスケールは DaskPySpark。 ただし、 まず pandas を完全に使いこなすのが先決。 多くの「pandas は遅い」 と言われる場面は、 dtype 最適化と vectorize で 9 割は解決する。

🖼 図と表で読む DataFrame の構造的理解

DataFrame は「行・列・型・インデックス」の四重構造で、Excel シートとも NumPy 配列とも違う独自の存在である。ここでは SSDSE-B-2026 の都道府県データを題材に、DataFrame の振る舞いを「相関」「ヒストグラム」「箱ひげ」 の三方向から可視化し、表として読み解くことで理解を完成させる。 DataFrame という抽象データ型がなぜデータサイエンスの共通言語になったのかを、 実値で示す。

🖼 図1: DataFrame に格納した SSDSE-B 都道府県の相関構造

下図は df.corr() で得られる相関行列を可視化したもの。 DataFrame は「数値列の集合」として相関を直接計算できる。 これが配列ではなく DataFrame である最大のメリットの一つ。 列名を保持したまま、計算結果も DataFrame で返るため、 そのまま seaborn.heatmap() に渡せる。

DataFrame で都道府県の総人口(A1101)・65歳以上人口(A1303)・出生数(A4101)の相関行列を可視化

→ 総人口(A1101)と出生数(A4101)・65歳以上人口(A1303)の強い正の相関が一目で読める(人口規模の大きい県ほど出生数も高齢者数も大きい)。 DataFrame の列名がそのまま軸ラベルに反映されるため、 配列ベースの実装より圧倒的に短いコードで等価な可視化が完成する。

🖼 図2: 列ごとの分布をヒストグラムで掴む

df.hist() 一行で「すべての数値列」のヒストグラムが描ける。 DataFrame の「列に型がついた」設計のおかげで、 文字列列は自動で除外される。 配列だったら手作業で数値列を抜き出して loop する必要があった部分が、 DataFrame では完全に自動化されている。

DataFrame の各列をヒストグラムで可視化

→ 人口は右に長く裾を引き (東京・神奈川・大阪)、 高齢化率は左右対称に近い正規分布形。 DataFrame は列の dtype を知っているからこそ、「数値列のみヒストグラム」 という処理が自動で実現できる。

🖼 図3: 外れ値・分布の重ね描き

df.boxplot() で複数列を並べて分布形状を比較できる。 列ごとに別 unit でも、 内部では DataFrame が「列名 → series → 配列」 の階層を順に展開してくれる。 これも DataFrame の四重構造があるからこそ、 1 行で完結する処理である。

DataFrame の複数列を箱ひげで並列比較

→ 人口は東京・神奈川・大阪が外れ値として浮き上がる。 DataFrame は外れ値検出後も、 同じインデックスで他の列にアクセスできるため、 「外れ値の都道府県名は?」 という質問にすぐ答えられる。

🔬 数式を言葉で読み解く — DataFrame の内部構造を式で表す

DataFrame は数学的には次のように書ける:

$$ D = \{ (i, c, t_c, v_{i,c}) \mid i \in I,\ c \in C,\ t_c \in T \} $$

この四重組 $(i, c, t_c, v_{i,c})$ が DataFrame の正体である。 NumPy 配列は $(i, j, v_{i,j})$ の三重組しか持たない。 「列に名前」「列に型」 という 2 つの追加情報が、 DataFrame を「データサイエンスの共通言語」 にしている本質。

🧮 表で整理: DataFrame と他のデータ型の比較

特徴DataFrameNumPy 配列Excel シートCSV
列ラベル○ (文字列)×△ (1 行目)△ (1 行目)
列単位の型○ (dtype)× (全要素同型)△ (セル単位)× (全部文字列)
欠損 (NaN) 概念○ ファーストクラス△ (float のみ)○ 空セル△ 空文字列
インデックス (行ラベル)○ 任意のラベル△ (整数のみ)△ (行番号)×
バージョン管理○ (コード)○ (コード)× (バイナリ)○ (テキスト)
groupby/集計○ 1 行× 手書きループ△ ピボット× 不可
大規模対応△ (10⁷ 行)○ (高速)× (10⁶ 行限界)○ (無制限)

→ 「列ラベル + 列単位の型 + 欠損ファーストクラス + groupby」 の組み合わせが、 DataFrame を独占的地位に押し上げている要素。 Excel から DataFrame に乗り換える理由は、 ほぼこの四点に集約される。

🐍 Python 実装 — DataFrame の四重構造を実値で確認

このコードでやること: SSDSE-B-2026 の都道府県データを読み込み、 DataFrame の四重構造 (インデックス・列名・型・値) を一つずつ確認する。 各属性が DataFrame の本質を構成していることを実値で示す。

📥 入力データ (SSDSE-B-2026 抜粋):

SSDSE-B-2026 Code Prefecture A1101 A1303 A4101 2023 R01000 北海道 5092000 1681000 24430 2023 R02000 青森県 1184000 417000 5696 2023 R03000 岩手県 1163000 407000 5432 2023 R04000 宮城県 2264000 662000 12328
 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', skiprows=[1], encoding='cp932')

# 1. インデックス I
print("Index (行ラベル):", df.index[:5].tolist())

# 2. 列名 C
print("Columns (列ラベル):", df.columns[:5].tolist())

# 3. 列ごとの型 t_c
print("dtypes (列ごとの型):")
print(df.dtypes.head())

# 4. 値 v_{i,c}
print("値 v[0, 'A1101'] =", df.loc[0, 'A1101'])
print("Shape (行数, 列数):", df.shape)

📤 実行例 (実際の出力):

Index (行ラベル): [0, 1, 2, 3, 4] Columns (列ラベル): ['SSDSE-B-2026', 'Code', 'Prefecture', 'A1101', 'A110101'] dtypes (列ごとの型): SSDSE-B-2026 int64 Code object Prefecture object A1101 int64 A110101 int64 値 v[0, 'A1101'] = 5092000 Shape (行数, 列数): (564, 112)

💬 これが DataFrame の四重構造である。 インデックス (0-563)、 列名 (112 列)、 dtype (int64/object が混在)、 値 (5092000)。 これら 4 つが揃って初めて「DataFrame」 と呼べる。 NumPy 配列だったら 112 列が全部同じ型になり、 都道府県名 (Prefecture) のような文字列列は持てなかった。

⚠️ 落とし穴 — DataFrame で陥りやすい 5 つの罠

📝 理解度チェック

  1. DataFrame と NumPy 配列の最大の違いを 2 つ挙げよ。 (答: 列ラベル + 列単位の型)
  2. SSDSE-B-2026 の都道府県人口列の dtype は何か、 また欠損があると何に変わるか。 (答: int64 → float64)
  3. df[df['x']>0]['y'] = 0 がうまく動かない理由は? (答: chained indexing で copy に書き込まれる)
  4. df.merge(other, on='key') 後に行数が増える原因は? (答: 結合キーに重複がある)
  5. Excel と DataFrame の本質的な違いは? (答: バージョン管理可能性 + 列に型がある)

→ 5 問のうち 4 問以上正解できれば DataFrame の構造を理解している。 特に問 3 の copy/view は実務で最も多いバグ源。

🌐 関連用語と発展

DataFrame をマスターしたら次は: pandas (DataFrame を提供する Python ライブラリ) / CSV (DataFrame のテキスト表現) / データクレンジング / 集計 / データ結合 / フィルタリング / ピボットテーブル / 縦持ち・横持ち / 欠損メカニズム / tidy data へ。

📐 深掘り — DataFrame の内部実装とメモリ効率

pandas の DataFrame は内部で「BlockManager」 と呼ばれる構造を持つ。 同じ dtype の列は 1 つの NumPy 2D 配列にまとめられ (Block)、 BlockManager がそれら Block を管理する。 これにより、 同 dtype 列の演算は NumPy の高速 SIMD で処理できる。 一方で、 異 dtype を混在させると Block が分断され、 効率が落ちる。 SSDSE-B-2026 で 110 列を扱うとき、 数値列と文字列列を分けて操作するのが性能の鉄則。

メモリ最適化の具体策: (1) df.memory_usage(deep=True) で各列のメモリ消費を可視化、 (2) 小さい整数は int8/int16 に変換 (110 行 × 47 行なら int32 で十分)、 (3) 繰り返し文字列は category dtype に変換 (例: 都道府県名は 47 種類のみで、 メモリが 1/10 になる)、 (4) 日付列は datetime64 に変換し object 型を避ける。 SSDSE-B-2026 全体で、 これらの最適化を適用するとメモリ消費が 60-80% 減らせる。

大規模化への道筋: SSDSE-B-2026 程度の規模 (47 行 × 110 列、 数 MB) なら pandas で問題ないが、 数十 GB を扱うようになると Dask (チャンク並列)、 polars (Rust 製の高速 DataFrame)、 cuDF (GPU 版) への移行を検討する。 ただし、 まずは pandas の最適化を尽くしてから検討するのが現実的。 多くのケースで「pandas は遅い」 と言われる場面は、 dtype 最適化と vectorize で 9 割は解決する。

🔬 補足 — DataFrame の歴史と影響

DataFrame という抽象データ型は、 1996 年の R 言語の data.frame が起源。 2008 年に Wes McKinney が pandas として Python に移植し、 2010 年代に「データサイエンスの共通言語」 となった。 Python 以外では、 Julia の DataFrames.jl、 Scala の Spark DataFrame、 Rust の Polars DataFrame、 JavaScript の Danfo.js など、 各言語に DataFrame が移植されている。 これは「列 + 型 + ラベル」 という抽象が、 データ分析の本質的な構造を捉えているからに他ならない。

Arrow という共通バイナリフォーマットの登場で、 言語間のデータ共有が高速化した。 SSDSE-B-2026 を pandas で読み込み、 Arrow に変換し、 Rust の Polars で処理し、 R の data.frame に戻す — こうした多言語パイプラインが現実的になっている。 DataFrame は単なるデータ型を超えて、 データサイエンスのインフラそのものになっている。

📖 ケーススタディ — SSDSE-B から DataFrame で導く 5 つの分析

DataFrame の真価は「複数の操作を連鎖させて洞察を引き出す」 ところにある。 SSDSE-B-2026 を題材に、 1 つの DataFrame から 5 つの異なる分析を導く例を示す。 すべて pandas の chainable な API (メソッドチェーン) で実現できる。

  1. 地域別 出生数集計: df.groupby('地域')['A4101'].sum() で 8 地域 (北海道、 東北、 関東、 中部、 近畿、 中国、 四国、 九州沖縄) ごとに集計。 人口の多い関東が圧倒的に高いことが分かる。
  2. 出生率の計算: df.assign(birth_rate=df['A4101']/df['A1101']*1000) で人口千人あたり出生数の新列を作り、 ランキング。 沖縄が上位、 東北の各県が下位という構造が見える。
  3. 相関構造の発見: df[['A1101','A1303','A4101']].corr() で 3 変量 (総人口・65歳以上人口・出生数) の相関行列を計算。 いずれも人口規模に比例し、 強い正相関が連鎖していることを発見。
  4. 外れ値の特定: df[df['A1101'] > df['A1101'].quantile(0.95)] で総人口の上位 5% を抽出。 東京・神奈川がここに入る。
  5. 時系列との結合: SSDSE-A (時系列) と SSDSE-B (横断面) を pd.merge(a, b, on='Prefecture') で結合し、 「経年変化と現状」 を同時に分析。

これら 5 つの分析は、 SQL では各々別クエリ、 Excel では各々別シートが必要だが、 DataFrame では 1 つのオブジェクトから連続的に導ける。 これが「データ分析の共通言語」 たる所以。

🛠 実装チェックリスト — DataFrame ベストプラクティス

DataFrame を実務で使うときに守るべきベストプラクティス。

→ このチェックリストの 8 項目を満たせば、 SSDSE-B-2026 程度のデータなら問題なく扱える。 数十 GB を超えるなら polars や Dask への移行検討が次のステップ。

📋 FAQ — DataFrame でよくある質問

Q1: DataFrame と pandas.Series の違いは?

Series は 1 次元 (列単位)、 DataFrame は 2 次元 (表形式)。 df['A1101'] は Series を返し、 df[['A1101', 'A1303']] は DataFrame を返す。 Series もインデックスと dtype を持つ点で NumPy 配列と異なる。 DataFrame は内部的に「列 = Series の辞書」 と理解すると分かりやすい。

Q2: pandas と polars はどちらを使うべき?

SSDSE-B-2026 程度 (数 MB) なら pandas で十分。 GB 級のデータや、 chainable API の美しさを求めるなら polars。 polars は Rust 製で 5-10 倍速いが、 エコシステム (sklearn 連携など) は pandas が圧倒的。 まずは pandas を完全に使いこなしてから polars を検討するのが現実的。

Q3: CSV と Parquet どちらで保存すべき?

公開・人間可読が重要なら CSV、 速度・容量が重要なら Parquet。 SSDSE-B-2026 のような公開データセットは CSV が標準だが、 自前ログを保存するなら Parquet 一択。 サイズは 1/5、 読み込みは 10 倍速い。 列指向 (columnar) なので、 一部の列だけ読む処理 (典型的な分析) で特に有利。

Q4: groupby の後で何が返ってくる?

df.groupby('地域') は GroupBy オブジェクトを返す (まだ集計していない)。 続けて .sum().mean().agg([]) などで実際の集計を実行。 「遅延評価」 で計算量を最適化している。 GroupBy 自体は実体を持たないので、 デバッグ時は for k, v in grouped: print(k, v.head()) で中身を見る。

Q5: DataFrame を学ぶ最良の入門書は?

日本語なら『Python for Data Analysis 第 3 版』 (Wes McKinney) の日本語訳版。 著者は pandas の作者。 公式ドキュメントの "10 Minutes to pandas" は最速入門。 実践的な書籍なら『データ分析実務スキル検定 公式テキスト』 や『前処理大全』 (本橋智光) が SSDSE-B を扱う分析者に必須。

🧭 まとめ — DataFrame を貫く 3 つの設計思想

DataFrame 全体を一本の糸で貫くと、 次の 3 つの設計思想に集約される。

  1. 「列に名前と型」 の原理: 配列との最大の違いは、 列がラベル化され、 dtype を持つこと。 これによりコードが「意味のある単位」 で動き、 読みやすく、 保守しやすくなる。
  2. 「ベクトル化と遅延評価」 の原理: 1 行 1 行ループするのではなく、 列全体を一気に処理する。 groupby 等の遅延評価で計算量を最適化。 これが「pandas は遅い」 を覆す本質。
  3. 「相互運用性」 の原理: Arrow フォーマット経由で polars, cuDF, R, Julia, JavaScript と相互運用。 DataFrame は単なるデータ型ではなく、 データサイエンス言語間の lingua franca。

これらを意識して DataFrame を使えば、 単なる「Excel の代替」 ではなく「現代データ分析の中核装置」 として機能させられる。 SSDSE-B-2026 のような公的データセットを扱う際も、 これらの設計思想を意識することで、 短く、 美しく、 再現可能な分析コードが書ける。

🌍 DataFrame が広げるデータ分析の世界

DataFrame は単一のデータ型を超えて、 多様な世界を繋ぐ役割を果たしている。 以下に主要な拡張領域を列挙する。

これらの接続性こそが DataFrame の真価。 SSDSE-B-2026 を読み込み、 機械学習で予測し、 SHAP で説明し、 可視化し、 結果を Parquet で保存し、 ダッシュボードに表示する — このすべてが DataFrame を中心に流れるように繋がる。 単なるデータ型ではなく、 データサイエンスのエコシステムそのものを支えるインフラ、 それが DataFrame である。

📐 DataFrame 操作のパフォーマンス考察

DataFrame の操作は、 一見シンプルでも内部では複雑な処理が走っている。 主要な操作のパフォーマンス特性を整理する。

SSDSE-B-2026 のような 47 行 × 110 列のデータでは、 どんな操作も瞬時に終わる。 しかし数百万行を扱うようになると、 これらの計算量の違いが分単位の差になる。 「ベクトル化と groupby」 が高速化の二大原則。 これを意識するだけで、 pandas コードの実行時間は 10-100 倍違ってくる。 大規模分析を扱うようになる日のために、 今のうちから「iterrows を書かない」 習慣をつけることをお勧めしたい。 また、 numexprbottleneck のインストールで、 一部の数値計算が自動高速化される (pandas が内部で利用)。 これらは pip install のみで導入できるので、 環境構築時に忘れず入れておきたい。 これらの組み合わせは、 統計コンペで分析時間を 1/10 に短縮する力を持つ。 速度が出れば試行錯誤の回数が増え、 結果として分析の質も上がる、 という好循環が生まれる。

🔗 隣接手法への橋渡し

「DataFrame」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。 具体的には次の 3 方向と密接につながる:

DataFrame は Python データ分析の中心データ構造で、 全工程のハブとなる。

🌳 手法選択フロー

「データフレーム」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。

  1. データはメモリに載るか
    載るなら pandas。 SSDSE-B-2026 は 564 行 × 112 列で余裕がある。 数千万行を超えて重くなったら Polars や DuckDB、 それでも載らないなら分割して処理する。
  2. 列の型を自分で決めたか
    自動推定に任せると、 先頭 0 のある番号が数値になって 0 が消える。 識別子や区分コードは dtype=str で読む。
  3. 横持ちか、 縦持ちか
    人が読む表は横持ち(年度が列)が見やすい。 集計や可視化に渡すなら縦持ち(1 行 1 観測)に直す。 pivot_tablemelt で行き来できる。
  4. 行ごとの処理を書いていないか
    for 文で 1 行ずつ回すより、 列全体をまとめて計算するほうが桁違いに速い。 47 行なら気にならないが、 市区町村 1,742 行や時系列では差が出る。

データフレームは「表を変数として扱う」道具。 型と持ち方(縦横)を最初に決めておくと、 後段の処理がほとんど自動的に決まる。

🧨 解説深化 ── DataFrame が「静かに壊れる」3つの瞬間

このページの他の節が「正しく使う方法」を扱うのに対し、ここではエラーを出さずに間違った結果を返す瞬間だけを集める。DataFrame の事故の多くは例外ではなく、警告すら出ないまま数字だけがズレる。数値はすべて data/raw/SSDSE-B-2026.csvencoding='cp932', skiprows=[1]/2023年・47都道府県)の実測から算出した。

🎨 直感 ── DataFrame は「行番号」ではなく「ラベルの辞書」で動く

初学者は DataFrame を Excel のような「上から何行目」のマス目だと思う。しかし pandas の内部では、行はインデックスラベルで識別される辞書に近い。2 番目の行という概念は .iloc を明示したときだけ存在し、+- のような演算は常にラベルを突き合わせてから計算する。この「位置ではなくラベル」という一点を腹落ちさせるだけで、以下の落とし穴の大半は消える。実測で言えば、2023年の総人口 A1101 は47都道府県で合計 124,353,000 人、平均 2,645,808.5 人だが、平均を上回るのはわずか 12 県(東京14,086,000/神奈川9,229,000/…と大都市に偏る右裾の重い分布)。「平均=真ん中」という直感が崩れるのと同じ構図で、「行=位置」という直感も崩れる。

⚠️ 落とし穴(重要)

① インデックスアラインメント ── 引き算したら全部 NaN。 人口上位3県と、CSV 掲載順の先頭3県を「位置で引きたい」つもりで a - b と書くと、pandas はラベルを揃えてから引く。ラベルが1つも重ならないので結果は全行 NaNになる。

# a = 人口上位3県, b = 掲載順の先頭3県(実測 A1101)
a = [東京都 14086000, 神奈川県 9229000, 大阪府 8763000]
b = [北海道  5092000, 青森県   1184000, 岩手県 1163000]

a - b   # ラベルで整列 → 6県すべて NaN(岩手/大阪/…全部 NaN)
a.values - b.values   # 位置で引く → [8994000, 8045000, 7600000] ✅

位置で計算したいなら .values(NumPy 配列に落とす)か .reset_index(drop=True) でラベルを揃える。逆に「一部の県だけ NaN が出る」ときは、ラベルの綴りゆれ(東京 vs 東京都)を疑う。

② dtype の object 化 ── 数字の列に文字が1個混ざると列ごと計算不能。 秘匿記号(-***)を1セルでも入れると、int64 だった列は object に降格し、.sum()TypeError.mean() は黙って壊れる。

# 架空: 鳥取県のセルに秘匿記号 '-' が入っていたと仮定
s.dtype        # object(int64 から降格)
s.sum()        # TypeError: 数値と文字は足せない
pd.to_numeric(s, errors='coerce').mean()
# → 2,691,652.2('-' を NaN 化して除外。実測の正しい平均 2,645,808.5 とズレる)

怖いのは、集計が通ってしまうケース。1県が欠けたことに気づかず平均が 2,645,808.5 → 2,691,652.2 と上振れする(小さい県が消えるため)。読み込み直後に df.dtypes を必ず確認し、object になっている数値列は pd.to_numeric(..., errors='coerce') で明示変換する。na_values=['-','***']read_csv に渡すのが根本対策。

③ SettingWithCopyWarning ── スライスへの代入は「効くこともある」から厄介。 d23 = df[df['SSDSE-B-2026']==2023] のように条件抽出した結果は元 DataFrame のビューかもしれないコピーかもしれない。この d23 にさらに d23['new'] = ... と代入すると SettingWithCopyWarning が出て、書き込みが元に反映されたりされなかったりと挙動が非決定的になる。

d23 = df[df['SSDSE-B-2026']==2023]     # ← ビュー?コピー?
d23['rate'] = d23['A1303'] / d23['A1101']   # ⚠ SettingWithCopyWarning

# 正解: 抽出した瞬間に .copy() で意図を宣言する
d23 = df[df['SSDSE-B-2026']==2023].copy()
d23['rate'] = d23['A1303'] / d23['A1101']   # 警告なし ✅

合言葉は「抽出したら即 .copy()」。派生列を作る前提でスライスするなら、迷わずコピーを取る。なお pandas 3.0 の Copy-on-Write ではこの曖昧さは解消される方向だが、既存コードの移植では今も最頻出の警告。

🚀 発展 ── 「静かな破損」を検出するガードレール

上の3つは「起きてから気づく」のでは遅い。パイプラインの各段でアサーションを挟むと、事故は例外に変わり、CI で止められる。

# 実測を不変条件(invariant)として固定する
assert len(d23) == 47                          # 都道府県数
assert d23['A1101'].dtype.kind in 'iu'         # 整数のまま(object化検出)
assert d23['A1101'].sum() == 124353000         # 合計人口が保存されている
assert d23.index.is_unique                     # アラインメント事故の予防

さらに一歩進めるなら、行数・型・値域・一意性を宣言的に検査する panderapandas.testing.assert_frame_equal をテストに組み込む。DataFrame は「表」ではなく「型と制約を持つ契約」だと捉え直すと、静かな破損は早期に大声を上げてくれる。時系列で追うなら、東京都の A1101 は 2012年 13,234,000 → 2023年 14,086,000 と単調増加で不変条件を置きやすいが、この種の性質を assert に落とすのが要点。

🔗 関連ページ