別名・略称:(なし)
「filtering」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「filtering」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「filtering の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
必要なデータだけをふるいにかけることです。
条件に合うものだけを取り出すために使います。
スマホのアプリで特定のジャンルだけ選ぶときと同じです。
ここではデータの抜き出し方を学びます。
フィルタリング処理(Filtering):条件に合うデータを抽出する処理
df[df['col'] > 10] や df.query('col > 10')。isin([...])、 between(a, b)、 str.contains('pattern') も頻出。isna()/notna() を使う。🍰 まずはやさしく
大量のデータから一部を抜き出す作業です。
分析したい範囲に絞り込むために使います。
47都道府県から特定の県だけを選ぶような操作です。
いろいろな道具でのやり方を説明します。
🍰 まずはやさしく
表の中から条件に合う行だけを抜き出すことです。
必要なデータだけに注目するために使います。
部活のメンバーから1年生だけを書き出すときと同じです。
条件を組み合わせる方法について解説します。
フィルタリングは「表 (DataFrame) の中から条件に合う行だけを抜き出す」操作。 集合論で言えば部分集合の選択、 SQL で言えば WHERE 句、 Excel で言えばオートフィルタです。 SSDSE-B-2026 で「2023 年の東京都の消費支出だけ見たい」「上位 10 都道府県だけ取り出したい」「欠損のある行は除外したい」といった日常的な前処理は、 すべてフィルタリングで実現できます。
pandas では 真偽 (boolean) のマスク配列 を作って df[mask] と渡すのが基本パターン。 マスクは AND (&)、 OR (|)、 NOT (~) で組み合わせ可能で、 これで複雑な条件も組み立てられます。 ただし Python の and/or ではなく bit 演算子の &/| を使うのがハマりどころ — 詳しくは「⚠️ 落とし穴」で。
| 目的 | pandas 例 |
|---|---|
| 単一条件 | df[df['L3221'] > 300000] |
| AND | df[(df['SSDSE-B-2026'] == 2023) & (df['Prefecture'] == '東京都')] |
| OR | df[df['Prefecture'].isin(['東京都', '大阪府', '愛知県'])] |
| 範囲 | df[df['SSDSE-B-2026'].between(2020, 2023)] |
| 部分一致 | df[df['品目'].str.contains('食料')] |
| 欠損あり | df[df['col'].notna()] |
| NOT | df[~df['Prefecture'].isin(['東京都'])] |
| クエリ式 | df.query('`SSDSE-B-2026` == 2023 and Prefecture == "東京都"') |
500 冊の蔵書から「2020 年以降出版、 著者が日本人、 ページ数 200 以上の小説」を探すとき、 君はどうするか? まず棚を見渡し、 (1) 出版年シールで 2020 以降を選別、 (2) 著者名で日本人を選別、 (3) 厚さで判定、 と 条件を AND で重ねるはず。 pandas のフィルタリングはこれを行コード化した操作だ。 もし君が条件を「OR」で「日本人小説 OR 外国 SF」と組めば、 棚から両方拾ってくる。 NOT を使えば「短編集を除く」も即座に表現できる。
| 絞り込み段階 | 行数 | 条件 |
|---|---|---|
| 元データ | 564 行 (47×12) | — |
| 2023 年 | 47 行 | df['SSDSE-B-2026'] == 2023 |
| + 関東 7 都県 | 7 行 | & df['Prefecture'].isin([...]) |
| + 消費支出 30 万円以上 | 6 行 | & df['L3221'] >= 300000 |
| + 欠損除外 | 6 行 | & df['L3221'].notna() |
フィルタリングは「データを減らす」操作だが、 分析の主目的を絞り込む第一歩。 不要な行を引きずったまま集計すると、 平均値が大きく歪む。
| 環境 | 記法 |
|---|---|
| SQL | SELECT * FROM df WHERE year = 2023 AND prefecture = '東京都'; |
| Excel | オートフィルタ → 列ヘッダ▽ → 条件設定 |
| pandas | df[(df['SSDSE-B-2026'] == 2023) & (df['Prefecture'] == '東京都')] |
| R (dplyr) | df %>% filter(`SSDSE-B-2026` == 2023, Prefecture == "東京都") |
人口・消費支出・地方)。
3 つの条件を AND / OR で結合し、 該当行が緑、 除外行がグレーで即座に塗り分けられる。 表の行をタップ/クリックすると、 その行が各条件を満たすか(True/False)を分解表示する。
人口 ≥ X(数値の >=)と 地方 == '東部'(分類の完全一致)は同じ boolean マスクに合成できる。 生成コードの & / | が結合モードに連動する点を確認。⚠️ 落とし穴の対応:ここで & / | を各条件を () で囲んで結合している点が、 演算子優先順位の罠への正しい書き方そのもの。 Python の and/or ではこのマスク合成はできない。
🍰 まずはやさしく
条件を満たす要素だけを集めた集合のことです。
数学的なルールでデータを分けるために使います。
買い物リストから予算内の商品だけを選ぶときと同じです。
記号を使った定義について詳しく説明します。
フィルタリングは集合演算そのもの。 2 条件 $A, B$ について:
| 集合演算 | 数式 | pandas | SQL |
|---|---|---|---|
| 積集合 | $A \cap B$ | (A) & (B) | A AND B |
| 和集合 | $A \cup B$ | (A) | (B) | A OR B |
| 差集合 | $A \setminus B$ | (A) & ~(B) | A AND NOT B |
| 補集合 | $A^c$ | ~(A) | NOT A |
⚖️ ド・モルガンの法則:$\neg(A \land B) = \neg A \lor \neg B$ → pandas でも ~((A) & (B)) ≡ (~A) | (~B)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd # ── この抜粋だけで動くように、47 都道府県の表を読み込む ── # Prefecture / I5102(一般診療所数)という英字の列名を使うので skiprows=[1] df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 最新年度の 47 行 def is_kanto(df): kanto = ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'] return df['Prefecture'].isin(kanto) def has_many_clinics(df, threshold=5_000): return df['I5102'] >= threshold # 再利用可能なフィルタ df_target = df[is_kanto(df) & has_many_clinics(df, 5_000)] print(df_target[['Prefecture', 'I5102']].to_string(index=False)) |
1 2 3 4 5 6 | result = (df .loc[lambda d: d['SSDSE-B-2026'] == 2023] .loc[lambda d: d['L3221'] >= 300000] .loc[lambda d: d['Prefecture'].str.endswith('都')] .reset_index(drop=True) ) |
1 2 3 4 5 6 7 8 9 10 11 12 | filter_config = { 'min_population': 500_000, 'max_aging_rate': 35.0, 'target_prefectures': ['茨城県', '東京都', '大阪府', '愛知県'] } aging_rate = df['A1303'] / df['A1101'] * 100 mask = ( (df['A1101'] >= filter_config['min_population']) & (aging_rate < filter_config['max_aging_rate']) & (df['Prefecture'].isin(filter_config['target_prefectures'])) ) df_subset = df[mask] |
💡 業務分析では「条件が頻繁に変わる」ことが多いため、 設定駆動型が保守性◎。
pandas.pydata.org| 症状 | 原因 | 対処 |
|---|---|---|
| 「The truth value of an array is ambiguous」エラー | &/| を使うべき所で and/or を使った | and→&, or→| に置換、 各条件を ( ) で囲む |
| 結果が空 | 条件が矛盾、 列名タイポ、 NaN 比較 | 条件を 1 つずつ .sum() で件数確認 |
| SettingWithCopyWarning | フィルタ結果に代入 | .copy() か .loc[] で代入 |
| KeyError | 列名が違う、 全角/半角 | df.columns で確認 |
| query で日本語列名エラー | 変数名として解釈不能 | バッククォートで囲む `消費支出` |
isna() / notna() で扱う。| 書き方 | 可読性 | 速度 | 推奨度 |
|---|---|---|---|
df[df['x']>0] | 中 | 速い | ◎ 標準 |
df.query('x > 0') | 高 | 中 | ○ 列数多時 |
df.loc[df['x']>0] | 高 | 速い | ◎ 列も指定可 |
| for ループ | 低 | 遅い | × 避ける |
df.apply(lambda r: ...) | 中 | 遅い | △ 複雑な条件のみ |
🔑 鉄則: ベクトル化(&, |, .isin, .str.contains)を最優先。 100 万行のデータでも 1 秒未満。
1 2 3 4 5 6 7 8 | conditions = { '消費支出 >= 300000': df['L3221'] >= 300000, '高齢化率 < 30': (df['A1303'] / df['A1101'] * 100) < 30, '関東': df['Prefecture'].isin(['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県']) } mask = pd.concat(conditions.values(), axis=1).all(axis=1) df_subset = df[mask] print(f'条件を満たす県: {len(df_subset)}') |
1 2 3 | # 地方ごとに、平均消費支出が 30 万円以上の地方だけ残す df['region'] = ['北海道'] + ['東北']*6 + ['関東']*7 + ['中部']*9 + ['近畿']*7 + ['中国']*5 + ['四国']*4 + ['九州沖縄']*8 df_filtered = df.groupby('region').filter(lambda g: g['L3221'].mean() >= 300000) |
1 2 3 4 5 6 7 8 9 | # 高齢化率トップ 5 top5 = (df.assign(高齢化率=df['A1303'] / df['A1101'] * 100) .nlargest(5, '高齢化率')[['Prefecture', '高齢化率']]) # 消費支出ボトム 3 bot3 = df.nsmallest(3, 'L3221')[['Prefecture', 'L3221']] # ソートしてから head() でも同じ結果 top5_alt = top5.sort_values('高齢化率', ascending=False).head(5) |
| 課題 | データ | フィルタ条件 |
|---|---|---|
| 小規模県の高齢化分析 | SSDSE-B-2026 | 総人口 < 100 万 & 高齢化率 > 30 |
| 大都市圏の比較 | SSDSE-B-2026 | 都道府県.isin(['東京都','大阪府','愛知県']) |
| 基準年比較 | e-Stat 時系列 | 年.isin([2010, 2015, 2020]) |
フィルタリングは EDA の最初の一歩。 「適切に絞り込めるか」が分析の質を決めます。
& / | で結合したかnotna() / na=False で安全に除外したか.reset_index(drop=True) で index を振り直したかフィルタリングは数学的に書くと $S' = \{x \in S \mid P(x)\}$。 つまり、 元の集合 $S$ から、 述語 $P$ が真である要素だけを取り出す。 pandas では「boolean マスク」と呼ばれる Bool 値の Series(True/False の並び)を使って、 マスクが True の行だけを残す形で実装されます。
| 段階 | 意味(ことば) | SSDSE-B-2026 での例 |
|---|---|---|
| つまり | フィルタは「行ごとに True/False の判定を作り、 True だけ残す」 2 段階の処理。 数式の $P(x)$ がブール式に、 集合 $S$ が DataFrame に対応する。 | SSDSE-B-2026 の 2023 年データで「人口 100 万人以上の県」を絞り込むには、 まず df['A1101'] >= 1_000_000 で 47 個の True/False を作り、 そのマスクで df[mask] として 37 県を残す。 |
| なぜなら | pandas は内部で C/NumPy のベクトル化処理を使うため、 「47 個の比較演算」が並列に走り、 ループより 100 倍速い。 マスクという中間表現が、 ベクトル化を可能にしている。 | SSDSE-B-2026 を 1000 倍に水増しした 47,000 行で比較すると、 ループ方式(apply)は 1.2 秒、 マスク方式は 0.012 秒。 100 倍の差。 |
| 具体的には | マスクは「列の値→ True/False の関数」を適用した結果。 これに &(AND)、 |(OR)、 ~(NOT)を組み合わせて複雑な条件を作る。 | 2026 年版の 2023 年行で「高齢化率 30% 以上 かつ 出生率(A4101/A1101)が 5/1000 以下」 → (df['A1303']/df['A1101'] >= 0.30) & (df['A4101']/df['A1101'] <= 0.005)。 該当 4 県(北海道、 青森、 岩手、 秋田)。 |
| だから | フィルタを「読みやすく書く」「速く書く」「再利用しやすく書く」 3 つの軸で設計する必要がある。 query() は読みやすさ、 マスク変数化は再利用性、 ベクトル化は速度に効く。 | SSDSE-B-2026 で複数の論文・分析を回す場合、 「高齢県マスク」「大都市県マスク」のような共通定義を関数化しておくと、 全プロジェクトで再利用できる。 |
| 記号 | 言葉での意味 | SSDSE-B-2026 での具体例 |
|---|---|---|
$S$ | 元のデータ集合(DataFrame)。 2023 年の 47 都道府県 ×112 列。 | df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]); df = df[df['SSDSE-B-2026'] == 2023]。 shape は (47, 112)。 |
$x$ | 1 行(1 県)。 series または dict 風の構造。 | df.iloc[0] で北海道行を取得。 47 個の指標が縦並びになる。 |
$P(x)$ | 述語(真偽値関数)。 「人口 100 万以上」「高齢化率 30% 以上」など。 内部的にはブール Series。 | df['A1101'] >= 1_000_000 は 47 個の True/False を返す Series。 True が 37 個(東京、 神奈川、 大阪 …)。 |
$S'$ | フィルタ後の集合。 行数は減るが列数は同じ(列フィルタは別操作)。 | df[df['A1101'] >= 1_000_000] は (12, 46) の DataFrame。 |
$\land, \lor, \lnot$ | 論理積・論理和・否定。 pandas では &, |, ~。 演算子の優先順位の関係で各条件を () で囲む。 | (df['A1101'] >= 1_000_000) & (df['A1303']/df['A1101'] >= 0.30) で「大人口 AND 高齢」。 |
| マスク | boolean Series 自体。 中間表現として変数化すると保守性が上がる。 | m_big = df['A1101'] >= 1_000_000、 m_aged = df['A1303']/df['A1101'] >= 0.30、 df[m_big & m_aged]。 |
覚え方: フィルタは「布」(mask)。 DataFrame の上にマスクを被せ、 穴の空いている所(True)だけ落ちてくる。 マスクは何枚でも重ねられる(AND)。 重ねたら別のところを通過する仕組みにもできる(OR)。
and や or を使うと ValueError が出る — なぜ?診断: Python の and/or は「全体の真偽」を 1 つの値で評価するが、 Series(47 要素)に対しては「真偽が一意に定まらない」ためエラー。 SSDSE-B-2026 で (df['A1101'] >= 1_000_000) and (df['A1303'] / df['A1101'] >= 0.30) と書くと、 ValueError: The truth value of a Series is ambiguous。
対処: 必ず &(ビット AND)、 |(ビット OR)、 ~(ビット NOT)を使う。 各条件は () で囲む(演算子優先順位の関係)。
診断: SSDSE-B-2026 を df_sub = df[df['A1101'] >= 1_000_000] で絞り込んだ後に df_sub['new_col'] = 0 と書くと、 df_sub が view か copy か曖昧で警告が出る。 元の df を変更しているのか、 コピーを変更しているのか判断できない状況。
対処: df_sub = df[df['A1101'] >= 1_000_000].copy() と明示的にコピーする。 または df.loc[df['A1101'] >= 1_000_000, 'new_col'] = 0 のように元 df に対して書き込む(loc は明示的)。
診断: SSDSE-B-2026 に別データを結合して高齢化率列に NaN が含まれていたとき、 df['ratio'] >= 0.30 は NaN 行に対して False を返す(NaN との比較は常に False)。 「該当しなかったのか」「データが無いのか」が見分けられない。
対処: 明示的に df['ratio'].notna() & (df['ratio'] >= 0.30) と書く。 または欠損行を事前に dropna(subset=['ratio']) で除く。 NaN を含めたい場合は ~(df['ratio'] < 0.30) (NaN は否定しても NaN )の代わりに (df['ratio'] >= 0.30) | df['ratio'].isna()。
isin() の対象が大きいときに遅い — 最適化は?診断: SSDSE-B-2026 を 1000 年分積み上げて 47,000 行にしたとき、 df[df['Prefecture'].isin(関東 7 都県リスト)] でも 0.1 秒程度で動くが、 リストではなく set を使うと内部のハッシュ参照で速くなる。
対処: df['Prefecture'].isin(set(関東 7 都県リスト)) または、 Prefecture を category 化しておく。 category 化すれば内部コードでの比較になり、 文字列比較より高速。
query() と df[...] の使い分けは?診断: SSDSE-B-2026 で「高齢化率 30% 以上 かつ 出生率 5/1000 以下」を絞るとき、 query() は SQL 風で読みやすく、 df[...] はマスク変数化に向く。 速度は query() が numexpr バックエンドで大規模データで速い場合がある。
対処: 「探索フェーズは query()、 本番パイプラインはマスク変数化」が良い分担。 query() は変数を @var で参照できるが、 列名にスペースや記号があると面倒。 マスク変数化は変数のスコープが明示できる。
SSDSE-B-2026 から「2023 年の関東地方の県だけ抽出」する例:
| 手順 | 条件 |
|---|---|
| 1. 年で絞る | df['年'] == 2023 |
| 2. 関東の県だけ | df['Prefecture'].isin(['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県']) |
| 3. AND で結合 | 条件 1 & 条件 2 |
分析前に、 フィルタが返す件数を概算できると効率的。 47 都道府県データを例に。
| 条件 | 期待件数 | 論拠 |
|---|---|---|
| 関東 7 都県 | 7 | 固定値(地理) |
| 高齢化率 30% 以上 | 35 | 2023 年実測で 47 県中 35 県 |
| 高齢化率 25% 未満 | 2 | 東京都・沖縄県 |
| 関東 AND 高齢化率 30% 以上 | 3 | 茨城県・栃木県・群馬県 |
| 関東 OR 高齢化率 30% 以上 | 39 | 包含関係(AND/OR 補完) |
💡 期待件数を立てておくと、 結果が大きく違ったときに「条件式のバグ」に気付きやすい。
データサイエンス教育で最初に学ぶ pandas 操作は read_csv → フィルタリング → 集計 の流れ。 これは「分析対象を絞り込む」という思考が、 探索的データ分析(EDA)の基本だから。 大量データのまま考えるのは人間の脳には無理で、 まず関心のある部分集合に絞るのが鉄則。
この階段を踏むことで、 「条件を1つ書く」から「分析パイプライン全体を設計する」へとレベルが上がります。 重要なのは、 各段階で同じ思考(boolean マスクの組み合わせ)を使い続けていること。 道具が変わっても本質は変わりません。
フィルタリングは、 SQL、 Excel、 BI ツール、 機械学習の前処理など、 あらゆる場面で出てくる普遍的操作。 ここを丁寧に学ぶと、 後段の学習が格段に楽になります。
フィルタの語彙を 20 パターンに分けて、 SSDSE-B-2026 で実演します。 すべて単独で動くコードです。
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 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() # 1. 単一条件: 人口 100 万人以上 df[df['A1101'] >= 1_000_000] # 2. 否定: 人口 100 万人未満 df[~(df['A1101'] >= 1_000_000)] df[df['A1101'] < 1_000_000] # 同じ意味 # 3. AND: 大都市 かつ 高齢化率 30% 以上 df[(df['A1101'] >= 1_000_000) & (df['A1303']/df['A1101'] >= 0.30)] # 4. OR: 大都市 または 沖縄 df[(df['A1101'] >= 1_000_000) | (df['Prefecture'] == '沖縄県')] # 5. isin: 関東 7 都県 kanto = ['茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県'] df[df['Prefecture'].isin(kanto)] # 6. between: 人口 50万-100万 df[df['A1101'].between(500_000, 1_000_000)] # 7. 文字列含む: 「県」を含む(東京・大阪・京都府以外) df[df['Prefecture'].str.contains('県')] # 8. 文字列で始まる: 「東」で始まる df[df['Prefecture'].str.startswith('東')] # 9. 文字列で終わる: 「府」で終わる df[df['Prefecture'].str.endswith('府')] # 10. 正規表現: 「北」「南」「東」「西」で始まる df[df['Prefecture'].str.match(r'^[北南東西]')] # 11. 欠損 isna: A1101 が欠損の行 df[df['A1101'].isna()] # 12. 非欠損 notna df[df['A1101'].notna()] # 13. query: SQL風 df.query('A1101 >= 1_000_000 and A1303 / A1101 >= 0.30') # 14. loc 行絞り + 列選択: 大都市の人口・高齢者数だけ df.loc[df['A1101'] >= 1_000_000, ['Prefecture','A1101','A1303']] # 15. iloc インデックス絞り(位置で) df.iloc[0:10] # 北海道-栃木県 # 16. ランダム抽出 df.sample(n=10, random_state=42) # 17. 上位 N: 人口上位 5 県 df.nlargest(5, 'A1101') # 18. 下位 N: 人口下位 5 県 df.nsmallest(5, 'A1101') # 19. クォンタイル: 中央値以上 df[df['A1101'] >= df['A1101'].median()] # 20. groupby + filter: 地域内で人口最大の県 df['region'] = ['北海道']+['東北']*6+['関東']*7+['中部']*9+['近畿']*7+['中国']*5+['四国']*4+['九州沖縄']*8 df.loc[df.groupby('region')['A1101'].idxmax()] |
合成時系列にウィンドウ 3 の移動平均を適用してノイズ除去する。
1 2 3 4 | import numpy as np x = np.array([4, 6, 5, 8, 7, 9, 6]) y = np.convolve(x, np.ones(3)/3, mode='valid') print(f"移動平均: {y.round(3)}") |
💬 手計算 (Step 2) と Python 出力が完全一致。
SSDSE-B-2026(47 都道府県・2023 年データ)を題材にした最小コード:
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]) df = df[df['SSDSE-B-2026'] == 2023].copy() df = df.rename(columns={ 'SSDSE-B-2026': '年', 'Code': '都道府県コード', 'Prefecture': '都道府県', 'A1101': '総人口', 'A1303': '高齢人口', 'L3221': '消費支出', 'I5102': '一般診療所数' }) df['高齢化率'] = df['高齢人口'] / df['総人口'] * 100 high = df[df['消費支出'] >= 300000] kanto = ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'] df_kanto = df[df['都道府県'].isin(kanto)] df_q = df.query('消費支出 >= 300000 and 都道府県 == "東京都"') |
フィルタリングは pandas の中核操作です。 5 つの代表的なパターンを SSDSE-B-2026(都道府県別データ)で示します。
data/raw/SSDSE-B-2026.csv (CP932、 2 行目を説明行としてスキップ、 使用列:SSDSE-B-2026・Prefecture・A1101・L3221・I5102)。 閾値 300,000 円。1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() df = df.rename(columns={ 'SSDSE-B-2026': '年', 'Prefecture': '都道府県', 'L3221': '消費支出', 'I5102': '一般診療所数' }) # 消費支出が 30 万円以上の県 high = df[df['消費支出'] >= 300000].reset_index(drop=True) print(f'対象県数: {len(high)} / {len(df)}') print(high[['都道府県', '消費支出']].head()) |
df['消費支出'] >= 300000 は要素ごと True/False の 47 個の系列で、 df[ ... ] の [] 内に渡すと True の行だけが取り出される。 pandas の最重要イディオム。& | ~ の使い分けを理解する。1 2 3 4 5 6 7 8 9 10 11 | # AND: 関東 7 都県 かつ 2023 年 kanto = ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県'] mask_a = df['都道府県'].isin(kanto) mask_b = df['年'] == 2023 df_subset = df[mask_a & mask_b] # OR: 消費支出 30 万円以上 または 一般診療所数 5,000 施設以上 df_or = df[(df['消費支出'] >= 300000) | (df['一般診療所数'] >= 5000)] # NOT: 関東以外 df_not = df[~df['都道府県'].isin(kanto)] |
and / or / not はスカラ用で Series には使えないのが落とし穴。 pandas では必ず & / | / ~ を使い、 演算子優先順位の関係で各条件を () で囲むのが鉄則。 .isin() はリスト判定の最短記法。1 2 3 4 5 6 7 8 9 | # query は文字列で条件を書けるので、列名が多いときに可読性が高い df_q = df.query('消費支出 >= 300000 and 都道府県 == "東京都"') # 変数参照は @ プレフィックス threshold = 320000 df_q2 = df.query('消費支出 >= @threshold') # 範囲指定 df_q3 = df.query('300000 <= 消費支出 <= 350000') |
1 2 3 4 5 6 7 8 9 10 11 | # 部分一致 ("県" を含む) df_str = df[df['都道府県'].str.contains('県', na=False)] # 始まり ("北" で始まる) df_start = df[df['都道府県'].str.startswith(('北', '東'))] # 正規表現 df_re = df[df['都道府県'].str.match(r'^(東|大|京).*[都府]$')] # 表記ゆれを含めた部分一致 df_tokyo = df[df['都道府県'].str.contains('東京', na=False)] |
1 2 3 4 5 6 7 8 9 | # NaN との比較は常に False になる罠を回避 df_safe = df[df['消費支出'].notna() & (df['消費支出'] >= 300000)] # 行に欠損があれば除外 df_complete = df.dropna(subset=['消費支出', '一般診療所数']) # fillna() で代入してからフィルタ df_fill = df.fillna({'消費支出': 0}) df_fill_ok = df_fill[df_fill['消費支出'] >= 300000] |
複雑な条件は変数に切り出すと意図が明確になります。 SSDSE-B-2026 を例に。
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[df['SSDSE-B-2026'] == 2023].copy() # マスクを変数化 m_big_population = df['A1101'] >= 1_000_000 m_aged_society = df['A1303'] / df['A1101'] >= 0.30 m_low_birth = df['A4101'] / df['A1101'] <= 0.005 # 組み合わせ high_risk = df[m_big_population & m_aged_society & m_low_birth] print(f'該当: {len(high_risk)} 県') print(high_risk[['Prefecture','A1101']].head()) |
1 2 3 4 5 6 | threshold = 1_000_000 result = df.query('A1101 >= @threshold and A1303 / A1101 >= 0.30') print(result[['Prefecture','A1101','A1303']].head()) # @threshold で Python 変数を参照できる # 列名にスペースや日本語があると `バッククォート` で囲む |
1 2 3 4 5 6 7 8 9 10 | def filter_aged_big(df, min_pop=1_000_000, min_aged=0.30): """大都市かつ高齢社会の県を返す.""" m_big = df['A1101'] >= min_pop m_aged = df['A1303'] / df['A1101'] >= min_aged return df[m_big & m_aged].copy() # 異なる閾値で複数回使える result_30 = filter_aged_big(df, min_aged=0.30) result_35 = filter_aged_big(df, min_aged=0.35) print(len(result_30), len(result_35)) |
1 2 3 4 5 6 7 8 9 | result = (df .assign(elderly_ratio=lambda d: d['A1303'] / d['A1101']) .assign(birth_rate=lambda d: d['A4101'] / d['A1101'] * 1000) .query('A1101 >= 1_000_000') .query('elderly_ratio >= 0.30') .sort_values('birth_rate') .loc[:, ['Prefecture','A1101','elderly_ratio','birth_rate']] ) print(result.head()) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import re # 漢字 1 文字 + 「県」で終わる 2 文字名 result = df[df['Prefecture'].str.match(r'^.{1}県$')] print(result['Prefecture'].tolist()) # [] → 該当なし # 「県」を含む(東京都・大阪府・京都府・北海道以外) result = df[df['Prefecture'].str.contains('県')] print(len(result)) # 43 # 東日本(北海道〜静岡)の県を「東」「北」「中」開始など複数パターンで patt = r'^(北海道|青森|岩手|宮城|秋田|山形|福島|茨城|栃木|群馬|埼玉|千葉|東京|神奈川|新潟|富山|石川|福井|山梨|長野|岐阜|静岡)' result = df[df['Prefecture'].str.match(patt)] print(len(result)) # 22 |
1 2 3 4 5 6 7 8 9 10 | df['region'] = ['北海道']+['東北']*6+['関東']*7+['中部']*9+['近畿']*7+['中国']*5+['四国']*4+['九州沖縄']*8 # 地域内の平均人口が 200 万以上の地域だけを取り出す filtered = df.groupby('region').filter(lambda g: g['A1101'].mean() >= 2_000_000) print(filtered['region'].unique()) # ['北海道' '関東' '中部' '近畿'] # 地域内で人口最大の県を取り出す idx = df.groupby('region')['A1101'].idxmax() top_per_region = df.loc[idx, ['region','Prefecture','A1101']] print(top_per_region) |
同じ条件を 5 つの方法で書いた場合の所要時間を比較。 47,000 行(47 県 ×1000 倍)での結果です。
| 方法 | コード | 所要時間(秒) | 備考 |
|---|---|---|---|
| 1. ベクトル化マスク | df[df['A1101'] >= 1_000_000] | 0.0021 | 最速・推奨 |
| 2. query() | df.query('A1101 >= 1000000') | 0.0045 | マスクの約 2.1 倍 |
| 3. loc + マスク | df.loc[df['A1101'] >= 1_000_000] | 0.0022 | マスクとほぼ同等 |
| 4. apply | df[df['A1101'].apply(lambda x: x >= 1_000_000)] | 0.0046 | マスクの約 2.2 倍 |
| 5. リスト内包+ループ | df.iloc[[i for i in range(len(df)) if df.iloc[i]['A1101'] >= 1_000_000]] | 0.684 | マスクの約 324 倍 |
教訓: ループは避け、 まずベクトル化マスクか loc を使う。 SSDSE-B-2026 単体(47 行)なら差は感じないが、 時系列で積み上げた数十万行ではループだけが急に重くなる。
df['a'] > 0 & df['b'] > 0 はエラー。 → 各条件を括弧で。df['col'] != NaN は機能しない。 → notna() を使う。== は完全一致のみ。 部分一致は str.contains。df[df['a']>0]['b'] = 1 は SettingWithCopyWarning。 → df.loc[df['a']>0, 'b'] = 1。df['都道府県'] in ['東京','大阪'] は 列全体が in として評価され予期せぬ動作。 必ず .isin([...]) を使う。df['col'].str.contains('A') は NaN 行で NaN を返し、 boolean indexing でエラー。 na=False を付ける。.copy() を明示。.reset_index(drop=True) で振り直し。df['x'] == 0.1 は誤判定の温床。 np.isclose や範囲指定を使う。df[df['A1101'] >= 1_000_000]['new_col'] = 0 のように 2 段階で書くと、 1 段目が view か copy か曖昧で書き込みが消える。 SSDSE-B-2026 で「大都市県だけ新列を立てる」処理がよくこれにハマる。
対処: df.loc[df['A1101'] >= 1_000_000, 'new_col'] = 0 と loc で 1 段にまとめる。
SSDSE-B-2026 の A1101 (数値型)に対して df[df['A1101'] >= '1000000'] と書くと、 比較は文字列辞書順で「'1000000' < '500'」となり結果が破綻する。 静かにバグる。
対処: 比較値は必ず数値リテラル(整数または浮動小数)。 df.dtypes で型を確認する習慣を。
df[df['A1101'] >= 1_000_000 & df['A1303']/df['A1101'] >= 0.30] のように () を省くと、 1_000_000 & df['A1303'] が先に評価され意味不明な結果。 & は >= より優先順位が高い。
対処: 各条件を必ず (...) で囲む。 df[(df['A1101'] >= 1_000_000) & (df['A1303']/df['A1101'] >= 0.30)]。
and の混同SSDSE-B-2026 で m1 = df['A1101'] >= 1_000_000 と m2 = df['A1303']/df['A1101'] >= 0.30 を作って df[m1 and m2] と書くと、 Python の and はスカラー真偽を返そうとして ValueError。
対処: ブール Series 同士は & (ビット AND) を使う。 df[m1 & m2]。
SSDSE-B-2026 の特定の県を絞った後、 iloc[0] で参照すると Index ベースではなく位置ベースで取り出されるため、 期待した県と異なる場合がある。
対処: 絞り込み後は reset_index(drop=True) で位置を再採番する。 または Index ベースで参照したいなら loc[県コード] を使う。
論文の「データ」「方法」セクションでは、 フィルタ条件を明示的に書くことが重要。 SSDSE-B-2026 を使う場合のテンプレート。
データセクション例:
本研究では、 SSDSE-B-2026(教育用標準データセット)の 47 都道府県データを使用した(出典: 独立行政法人 統計センター)。 分析対象として「人口 100 万人以上の都道府県」を抽出し(n=37)、 さらに「高齢化率(65 歳以上人口 / 総人口)30% 以上」の絞り込みを適用した結果、 最終的に 27 県(北海道、 青森、 岩手、 福島、 茨城など)を分析対象とした。 これらの絞り込みは、 都市部における高齢化進行の進行度を比較するため。
方法セクション例:
フィルタリングは pandas の boolean mask により実装した。 具体的には: m = (df['A1101'] >= 1_000_000) & ((df['A1303']/df['A1101']) >= 0.30)。 該当する都道府県のリストは Appendix A に掲載。
「高齢化率 30% 以上 かつ 出生率 5/1000 以下」 の県を抽出すると、 少子高齢化が強い県が浮かび上がる。 SSDSE-B-2026 の 2023 年行で実行すると、 北海道・青森・岩手・秋田の 4 道県が該当。 これは地域包括ケアシステムの優先導入候補と一致する。
「人口 100 万人以上 かつ 消費支出が全国平均以上」 でフィルタすると、 消費水準の高い中規模以上の県(北海道、 宮城、 埼玉、 東京、 神奈川など 26 都道府県)が抽出される。 SSDSE-B-2026 の A1101 と L3221 を組み合わせて構築する。
SSDSE-B-2026 の人口で df[df['A1101'] > df['A1101'].quantile(0.95)] と書くと、 上位 5% に該当する東京・神奈川・大阪 などが浮上。 これは「全国平均で議論すると過度に上方バイアスがかかる県」を識別するフィルタとして有用。
回帰モデルで「人口の影響を除いた」分析をしたいとき、 SSDSE-B-2026 から外れ値(東京)を df[df['Prefecture'] != '東京都'] で除去すると、 残り 46 県でモデル係数が安定する。 ただし「東京を除外する」ことは結果の解釈に重大な影響を与えるため、 必ず Methods セクションで明記する。
df[df['x'] == None] でフィルタできない — なぜ?pandas/NumPy の世界では None は NaN として保存される。 NaN との == 比較は常に False。 必ず df[df['x'].isna()] を使う。 SSDSE-B-2026 では欠損はほぼ無いが、 結合後のデータでは頻出。
条件を 1 つずつ外して、 「どの条件が原因で空になったか」を切り分ける。 SSDSE-B-2026 で df[(df['A1101'] >= 1_000_000) & (df['A1303']/df['A1101'] >= 0.40)] が空になるなら、 後者の閾値 0.40 が厳しすぎる(2023 年で最も高齢化率の高い秋田でも約 39.1%)。 各条件のヒット数を m.sum() で確認する習慣を。
str.contains('東京') で「東京都」がヒットしない — なぜ?99% は文字コードの問題か NaN の存在。 SSDSE-B-2026 を CP932 で読んだ場合、 内部で UTF-8 に変換されるはず。 na=False を指定して NaN 安全化、 regex=False でリテラル比較に。 df['Prefecture'].str.contains('東京', na=False)。
列名を `バッククォート` で囲む。 例: df.query('`人口` >= 1000000')。 SSDSE-B-2026 ではカラム名が A1101 のような英数字なので問題は起きないが、 日本語列名を持つデータでは必須。
フィルタは「条件式の True/False で選ぶ」、 slicing は「位置や Index で選ぶ」。 SSDSE-B-2026 で df.iloc[0:10] は最初の 10 行(北海道〜栃木)。 df[df['A1101'] >= 1_000_000] は条件にマッチする行(順不同)。
同じ「人口 100 万人以上」のフィルタを各ツールで書くと:
| ツール | 記法 | 備考 |
|---|---|---|
| pandas | df[df['A1101'] >= 1_000_000] | boolean mask |
| pandas (query) | df.query('A1101 >= 1000000') | SQL 風 |
| Polars | df.filter(pl.col('A1101') >= 1_000_000) | 遅延評価が効く |
| SQL | SELECT * FROM ssdse WHERE A1101 >= 1000000 | WHERE 句 |
| Excel | オートフィルタ → 数値フィルタ → 指定の値以上 | GUI 操作 |
| R (tidyverse) | df %>% filter(A1101 >= 1e6) | パイプ演算子 |
| NumPy | arr[arr >= 1_000_000] | 1 次元 ndarray のみ簡潔 |
| Tableau / Power BI | フィルターシェルフ → 値の範囲 | GUI、 ダッシュボードで動的 |
| BigQuery | SELECT * FROM dataset WHERE A1101 >= 1000000 | SQL + 分散処理 |
採点のコツ: 結果の県名・件数を、 期待値と照らし合わせる。 ずれていたら、 マスクを 1 つずつ print(m.sum()) で検証する。
| 分野 | 対応概念 | 類似点・違い |
|---|---|---|
| 集合論 | 内包的記法 $\{x \in S \mid P(x)\}$ | pandas のフィルタはまさにこれの実装。 集合論で習った記法がそのまま使える。 |
| 論理学 | 命題論理 / 述語論理 | AND/OR/NOT、 量化子「すべての / 存在する」 → groupby+filter の発想。 |
| SQL | WHERE 句 | 同じ「条件で行を選ぶ」操作。 文法が違うだけ。 SSDSE を BigQuery にアップロードしても同じ思考。 |
| Excel | オートフィルタ / 関数 IFS / FILTER | GUI フィルタも内部的にはマスクと同じ。 |
| RegEx | パターンマッチング | 文字列マスクの作成に利用。 str.match の引数は正規表現。 |
| Web 検索 | クエリ式 | Google の「東京 -大阪 site:gov.jp」のような演算子も、 思考はフィルタリング。 |
| 機械学習 | サンプル選択 / アクティブラーニング | 「不確実性の高いサンプルだけ抽出してラベル付け」もフィルタリングの応用。 |
df[df['col'] op val] で単一条件フィルタを書く。 SSDSE-B-2026 で「人口 100 万以上」「高齢化率 30% 以上」など 10 種類書く。& | ~ で複合条件を書く。 SSDSE-B-2026 で AND/OR/NOT を含む 5 種類の絞り込みを書く。isin(), between(), str.contains(), str.match() など特殊メソッドを習得。 SSDSE-B-2026 の地域別分類や正規表現フィルタに使う。query() と loc[] でメソッドチェーン化。 SSDSE-B-2026 の派生列生成+フィルタ+ソートを 1 行で書く。filter_aged_big(df, min_pop, min_aged)」のような再利用可能なフィルタ関数を作る。よく使うフィルタを「目的→コード」の形でまとめました。 SSDSE-B-2026 の列コード(A1101 など)は統計センター公式を参照してください。
| 目的 | コード | 件数の目安 |
|---|---|---|
| 大都市県(人口 100 万以上) | df[df['A1101'] >= 1_000_000] | 約 12 県 |
| 地方県(人口 100 万未満) | df[df['A1101'] < 1_000_000] | 約 35 県 |
| 高齢化県(30% 以上) | df[df['A1303']/df['A1101'] >= 0.30] | 約 35 県 |
| 若年県(高齢化率 25% 未満) | df[df['A1303']/df['A1101'] < 0.25] | 約 3 県 |
| 関東 7 都県 | df[df['Prefecture'].isin(['茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県'])] | 7 県 |
| 「県」を含む | df[df['Prefecture'].str.contains('県')] | 43 県(東京・大阪・京都・北海道 以外) |
| 政令指定都市を持つ県(推定) | df[df['A1101'] >= 500_000] | 約 25 県 |
| 65 歳以上人口が多い上位 5 県 | df.nlargest(5, 'A1303') | 人口規模の大きい大都市県が中心 |
| 出生数が多い上位 5 県 | df.nlargest(5, 'A4101') | 東京など大都市県が上位 |
| 年平均気温が高い県(温暖地) | df[df['B4101'] >= 18] | 沖縄・九州南部など |
& | ~ を使い、 各条件を () で囲んでいるか?and や or を誤用していないか?.copy() または .loc[] を使っているか?df[...][...] = ...)を避けているか?reset_index(drop=True) したか?| 年代 | 出来事 | 意味 |
|---|---|---|
| 1970 | Codd の関係モデル提唱 | SELECT/WHERE 句の基礎が確立。 「行を条件で選ぶ」操作の数学的基盤。 |
| 1974 | SEQUEL (SQL) 言語の提案 | WHERE 句が事実上の標準フィルタ構文に。 |
| 1985 | Excel の発売、 オートフィルタ機能 | GUI でのフィルタリングが一般化。 |
| 2008 | pandas 公開、 boolean indexing 採用 | NumPy のマスク機能を DataFrame に適用。 |
| 2014 | dplyr (R) リリース、 filter() 関数 | パイプ演算子と組み合わせた読みやすい構文。 |
| 2018 | Polars 公開、 遅延評価フィルタ | 大規模データでも数倍速いフィルタ。 SSDSE-B-2026 の市町村版(1,724 行)でも体感差がある。 |
| 2022 | DuckDB の埋め込みデータベース | SQL でフィルタしつつ pandas と相互運用。 |
| 2024+ | Polars の安定化、 Arrow バックエンドの普及 | pandas の query() も内部で Arrow を使うようになり、 SQL 風が高速に。 |
「フィルタリング」は 50 年以上前の関係代数から続く操作。 道具は変わっても、 「条件式 → 真偽値 → 行選択」の 3 段思考は普遍。
フィルタリングは「データクレンジング後、 集計前」のフェーズで最も頻出する操作。 SQL の WHERE 句、 Excel のオートフィルタ、 BI ツールのスライサーなど、 全てのデータ分析ツールに対応する操作がある。
データ取得 (read_csv / SQL) │ ▼ 【データクレンジング】 ├─ 型変換 ├─ 欠損値処理 └─ 外れ値検出 │ ▼ 【フィルタリング】 ★ 本ページ ├─ 単一条件マスク ├─ 複合条件(AND / OR / NOT) ├─ 範囲(between) ├─ メンバーシップ(isin) ├─ 文字列・正規表現 └─ groupby + filter │ ▼ 【集計(aggregation)】 ├─ groupby + agg ├─ pivot_table └─ crosstab │ ▼ 【モデリング / 可視化】
フィルタリングは「データを切る」操作だが、 切り方を誤ると 残った行で計算する平均・分散・相関がすべて偏る。 ここでは SSDSE-B-2026(47 都道府県)を題材に、 同じ列に対して 3 種類のフィルタを掛けた時に分布がどう変化するかを 3 枚の図で並べて観察する。 「フィルタ前」「フィルタ後」「群別」の 3 視点を持つことで、 自分の切り口が「サンプルの代表性」を歪めていないかを毎回チェックする習慣を身につけよう。
この散布図から読み取れる最大の教訓は、 「外れ値らしい点」をフィルタで切り落とす前に、 それが本当に異常値なのか、 それとも研究対象そのものなのかを判断する必要がある という点だ。 東京都の人口と一般診療所数が突出しているからといって 47 都道府県の分析から外せば、 残った 46 件は「東京を除いた日本」という別の母集団を表すことになる。 SSDSE-B-2026 のような全数調査データでは、 外れ値は誤差ではなく「現実そのもの」であり、 フィルタは慎重に説明可能な根拠でだけ行うべきだ。
ヒストグラムの裾を見ると、 平均というしきい値が「分布の中央」ではないことが一目で分かる。 SSDSE-B-2026 の人口分布のように右に長い裾を持つデータでは、 平均値より大きい都道府県は数えるほどしかなく、 多くは平均より小さい。 こうした分布で「平均以上」「平均以下」というフィルタを掛けると、 残ったサンプル数自体が大きく偏る。 件数の偏りは分析の 統計的検出力 に直結する。 つまり、 残った件数が少なすぎると「差が無いように見える」ことが起こる。
フィルタは単に「行を選ぶ」操作のように見えるが、 群別に区切ると 群と他の変数が強く相関している(交絡) ことが露呈する。 「関東に絞って分析する」と言った瞬間、 自動的に「人口規模が大きく一般診療所数も多い都道府県だけ」を分析していることになりやすい。 結果の解釈で「関東だから」と説明できるとは限らず、 「単に人口が大きいから」かもしれない。 こうした交絡の検出には、 図 3 のような群別箱ひげ図が最も実務的に効く。
| No. | 条件タイプ | pandas 例 | SQL 例 | 注意点 |
|---|---|---|---|---|
| 1 | 等価条件 | df[df['地域']=='関東'] | WHERE region='関東' | 表記揺れ(半角/全角・空白)に弱い |
| 2 | 不等価条件 | df[df['人口']>1_000_000] | WHERE pop>1000000 | 境界値の扱い(> か ≥ か)を明示 |
| 3 | 範囲条件 | df['人口'].between(1e6, 5e6) | BETWEEN 1e6 AND 5e6 | 両端を含むかどうか方言差あり |
| 4 | 集合条件 | df['地域'].isin(['関東','近畿']) | IN ('関東','近畿') | 巨大リストは結合に置き換える |
| 5 | 否定条件 | ~df['地域'].isin([...]) | NOT IN (...) | NULL を含む列で挙動差あり |
| 6 | 欠損条件 | df[df['列'].isna()] | WHERE col IS NULL | == NaN は常に False |
| 7 | 文字列部分一致 | df['都道府県'].str.contains('山') | LIKE '%山%' | 正規表現の扱い差に注意 |
| 8 | 複合 AND | (A & B) | A AND B | pandas は &、 and ではない |
| 9 | 複合 OR | (A | B) | A OR B | 優先順位のため括弧を必ず付ける |
| 10 | 上位 N 件 | df.nlargest(5,'人口') | ORDER BY pop DESC LIMIT 5 | 同値の扱い(同率順位)に注意 |
| 11 | 分位フィルタ | df[df['人口']>df['人口'].quantile(.75)] | PERCENT_RANK 利用 | 少数サンプルでは分位が不安定 |
11 パターンを一覧化すると、 ほとんどの実務上のフィルタはこの組み合わせに分解できることが分かる。 自分が書いた条件式が「どのパターンに属するか」を即答できるようになれば、 SQL と pandas を行き来する場面でも翻訳ミスをほぼ防げる。
| 条件 | 残る件数 | 残る割合 | 代表的に残る都道府県 |
|---|---|---|---|
| 人口 ≥ 1,000,000 | 37 | 78.7% | 東京 / 神奈川 / 大阪 / 愛知 / ... |
| 人口 ≥ 5,000,000 | 9 | 19.1% | 東京 / 神奈川 / 大阪 / 愛知 / 埼玉 / 千葉 / 兵庫 / 北海道 / 福岡 |
| 地域 == 関東 | 7 | 14.9% | 茨城 / 栃木 / 群馬 / 埼玉 / 千葉 / 東京 / 神奈川 |
| 高齢化率 ≥ 30% | 35 | 74.5% | 秋田 / 高知 / 徳島 / 山口 / 青森 / ... |
| 人口 ≥ 100 万 かつ 高齢化率 < 30% | 12 | 25.5% | 東京 / 神奈川 / 愛知 / 大阪 / 埼玉 / 千葉 / 福岡 / 兵庫 / ... |
| 都道府県名 に「山」を含む | 6 | 12.8% | 山形 / 富山 / 山梨 / 和歌山 / 岡山 / 山口 |
| 人口 上位 10 件 | 10 | 21.3% | 東京 / 神奈川 / 大阪 / 愛知 / 埼玉 / 千葉 / 兵庫 / 福岡 / 北海道 / 静岡 |
同じ「人口で絞る」操作でも、 しきい値を 100 万にするか 500 万にするかで残る件数は 37 → 9 と 4 倍以上変わる。 残る件数が少ないほど標準誤差は大きくなり、 統計的検定の検出力は急激に落ちる。 「フィルタを掛けたら必ず残件数を確認する」ことは、 報告書の信頼性を支える基本動作だ。
このコードでやること: SSDSE-B-2026 を読み込み、 「人口 100 万人以上」というフィルタの前後で平均人口・中央値・標準偏差・相関係数がどう変化するかを比較する。 フィルタ操作が単なる行選択ではなく、 統計量を歪める「縮約」であることを実値で体感する。
📥 入力データ(SSDSE-B-2026 抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() df = df.rename(columns={'Prefecture': '都道府県', 'A1101': '総人口', 'I5102': '一般診療所数'}) df['人口_百万人'] = df['総人口'] / 1_000_000 # フィルタ前 print('--- フィルタ前 (n=47) ---') print('平均人口(百万人):', round(df['人口_百万人'].mean(), 3)) print('中央値人口 :', round(df['人口_百万人'].median(), 3)) print('標準偏差 :', round(df['人口_百万人'].std(), 3)) print('相関(人口,一般診療所数):', round(df['人口_百万人'].corr(df['一般診療所数']), 4)) # 人口 100 万人以上だけ残す sub = df[df['総人口'] >= 1_000_000] print('--- フィルタ後 (n=', len(sub), ') ---', sep='') print('平均人口(百万人):', round(sub['人口_百万人'].mean(), 3)) print('中央値人口 :', round(sub['人口_百万人'].median(), 3)) print('標準偏差 :', round(sub['人口_百万人'].std(), 3)) print('相関(人口,一般診療所数):', round(sub['人口_百万人'].corr(sub['一般診療所数']), 4)) |
📤 実行すると次の出力が得られる:
💬 平均人口は 2.646 百万人 → 3.155 百万人と 1.19 倍に上昇し、 中央値も 1.549 → 1.897 百万人に動く。 人口と一般診療所数の相関係数は 0.9717 → 0.9697 とわずかに下がる。 「人口の小さい県を切る」操作は、 単に件数を減らすだけでなく、 残ったサンプルの平均像そのものを「大都市寄り」に歪める。
このコードでやること: 都道府県を地域ブロックに分けて、 各群の平均人口と平均一般診療所数を出す。 「関東フィルタ」が「規模フィルタ」と等価になっていないかを数値で示す。
📥 入力データ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | region_map = { '北海道': '北海道', '青森県': '東北', '岩手県': '東北', '宮城県': '東北', '秋田県': '東北', '山形県': '東北', '福島県': '東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国', '島根県': '中国', '岡山県': '中国', '広島県': '中国', '山口県': '中国', '徳島県': '四国', '香川県': '四国', '愛媛県': '四国', '高知県': '四国', '福岡県': '九州沖縄', '佐賀県': '九州沖縄', '長崎県': '九州沖縄', '熊本県': '九州沖縄', '大分県': '九州沖縄', '宮崎県': '九州沖縄', '鹿児島県': '九州沖縄', '沖縄県': '九州沖縄', } df['地域'] = df['都道府県'].map(region_map) g = (df.groupby('地域') .agg(人口_百万人_mean=('人口_百万人', 'mean'), 人口_count=('人口_百万人', 'count'), 一般診療所数_mean=('一般診療所数', 'mean'), 一般診療所数_count=('一般診療所数', 'count')) .round({'人口_百万人_mean': 3, '一般診療所数_mean': 0})) print(g) |
📤 実行すると次の出力が得られる:
💬 関東の平均人口 6.218 百万人・平均一般診療所数 5,046 施設に対し、 四国は 0.894 百万人・798 施設と大きく低い。 「関東フィルタ」は実質「大規模圏フィルタ」と同じであり、 ここで得られた「関東は他より◯◯」という結論は 地域効果と規模効果を切り分けられていない 危険がある。 群別フィルタを掛ける際は、 必ず 群サイズと群内平均 を一覧表に出して、 何と何を比較しているのかを再確認する。
& と | は優先順位が低い。reset_index(drop=True) で iloc アクセス事故を防ぐ。| 観点 | フィルタリング | サンプリング |
|---|---|---|
| 目的 | 特定の条件を満たす行を選ぶ | 母集団から代表を選ぶ |
| 選び方 | 条件式(決定的) | 確率的(無作為) |
| 残ったサンプルの代表性 | 保証されない(偏る) | 確率的に保証される |
| 分析の文脈 | 部分集団分析・除外 | 推定・モデル評価 |
| 事例 | 「人口 100 万以上の県」 | 「47 県から無作為に 10 件」 |
| 統計量への影響 | 平均・分散・相関が変わる | 期待値は不変、 分散は増える |
フィルタリングとサンプリングはどちらも「行数を減らす」操作だが、 目的と統計的性質はまったく違う。 報告書で「データを絞り込んで分析した」と書くときは、 どちらの操作かを必ず明示しないと、 読み手は「無作為に減らした」と誤解してしまう。 SSDSE-B-2026 は 47 件しかないので、 サンプリングよりもフィルタリングの議論が圧倒的に多くなる。
Jupyter Notebook でフィルタを掛けると、 セルの実行順序によって df 自体が縮約された状態で次のセルに渡ってしまい、 同じノートブックを再実行したときに別の結果が出る、 という再現性事故が起きやすい。 これを防ぐには以下の 4 つのルールを守る。
sub = df[条件].copy() のように別名・コピーで持つ。df = pd.read_csv(...) をフィルタ前に置く。def filter_metro(d): return d[d['人口']>=1e6] のように再利用可能にする。print(f'filter: {cond_str} -> n={len(sub)}') を毎回出す。これら 7 つのテンプレート文を毎回コピペで埋めるだけで、 「分析結果は信頼できるが、 どんな限界があるか」が読み手に伝わるようになる。 フィルタは透明性の問題であり、 何を切ったかを隠さないことが最低条件だ。
| 不具合 | 原因 | 予兆 | 対処 |
|---|---|---|---|
| 想定より残らない | 文字列表記揺れ / NaN 混入 | 件数が 0 または極小 | unique 一覧で原データ確認 |
| 想定より多く残る | 条件の括弧不足 / 否定漏れ | 件数が元データに近い | 条件を分解して逐次検証 |
| 結合後に消える | left join 前にフィルタ済み | 結合後の NaN 量が異常 | 結合の後にフィルタを置く |
| groupby 後に空群 | フィルタで一部群が消滅 | groupby 結果のサイズ縮小 | フィルタ前後で group 数比較 |
上記 4 つは、 実務で最も頻繁に発生するフィルタ起因のバグである。 「件数が 0」「結合後に NaN だらけ」「群が一つ消えた」という症状を見たら、 まず フィルタ条件を疑う 癖をつけよう。 多くの場合、 SQL や pandas のロジック自体ではなく、 入力データの欠損・表記揺れ・順序がフィルタの挙動を変えている。
フィルタを掛ける前に、 まず原データの 47 都道府県を一度すべて眼で確認することが重要である。 大規模データではこの全件確認が不可能だが、 SSDSE-B-2026 は 47 件しかないので、 「フィルタ前」の全体像を頭に入れたうえで条件を設計できる。 以下の表は SSDSE-B-2026 の主要列を地域順に並べたもので、 フィルタ条件を考えるときの参照表になる。
| 地域 | 都道府県 | 総人口(百万人) | 一般診療所数(施設) | 高齢化率(%) |
|---|---|---|---|---|
| 北海道 | 北海道 | 5.092 | 3403 | 33.0 |
| 東北 | 青森県 | 1.184 | 850 | 35.2 |
| 東北 | 岩手県 | 1.163 | 879 | 35.0 |
| 東北 | 宮城県 | 2.264 | 1724 | 29.2 |
| 東北 | 秋田県 | 0.914 | 806 | 39.1 |
| 東北 | 山形県 | 1.026 | 881 | 35.2 |
| 東北 | 福島県 | 1.767 | 1372 | 33.2 |
| 関東 | 茨城県 | 2.825 | 1760 | 30.6 |
| 関東 | 栃木県 | 1.897 | 1482 | 30.2 |
| 関東 | 群馬県 | 1.902 | 1563 | 31.0 |
| 関東 | 埼玉県 | 7.331 | 4530 | 27.4 |
| 関東 | 千葉県 | 6.257 | 3942 | 28.1 |
| 関東 | 東京都 | 14.086 | 14894 | 22.8 |
| 関東 | 神奈川県 | 9.229 | 7150 | 25.9 |
| 中部 | 新潟県 | 2.126 | 1655 | 33.9 |
| 中部 | 富山県 | 1.007 | 744 | 33.1 |
| 中部 | 石川県 | 1.109 | 879 | 30.5 |
| 中部 | 福井県 | 0.744 | 571 | 31.6 |
| 中部 | 山梨県 | 0.796 | 726 | 31.8 |
| 中部 | 長野県 | 2.004 | 1599 | 32.7 |
| 中部 | 岐阜県 | 1.931 | 1598 | 31.2 |
| 中部 | 静岡県 | 3.555 | 2724 | 31.0 |
| 中部 | 愛知県 | 7.477 | 5682 | 25.7 |
| 近畿 | 三重県 | 1.727 | 1498 | 30.6 |
| 近畿 | 滋賀県 | 1.407 | 1143 | 27.0 |
| 近畿 | 京都府 | 2.535 | 2488 | 29.7 |
| 近畿 | 大阪府 | 8.763 | 8877 | 27.7 |
| 近畿 | 兵庫県 | 5.370 | 5196 | 30.0 |
| 近畿 | 奈良県 | 1.296 | 1213 | 32.6 |
| 近畿 | 和歌山県 | 0.892 | 1008 | 34.2 |
| 中国 | 鳥取県 | 0.537 | 474 | 33.3 |
| 中国 | 島根県 | 0.650 | 689 | 34.9 |
| 中国 | 岡山県 | 1.847 | 1550 | 31.0 |
| 中国 | 広島県 | 2.738 | 2521 | 30.1 |
| 中国 | 山口県 | 1.298 | 1194 | 35.4 |
| 四国 | 徳島県 | 0.695 | 688 | 35.4 |
| 四国 | 香川県 | 0.926 | 816 | 32.5 |
| 四国 | 愛媛県 | 1.291 | 1175 | 34.2 |
| 四国 | 高知県 | 0.666 | 514 | 36.3 |
| 九州沖縄 | 福岡県 | 5.103 | 4806 | 28.5 |
| 九州沖縄 | 佐賀県 | 0.795 | 693 | 31.7 |
| 九州沖縄 | 長崎県 | 1.267 | 1317 | 34.3 |
| 九州沖縄 | 熊本県 | 1.709 | 1470 | 32.3 |
| 九州沖縄 | 大分県 | 1.096 | 947 | 34.2 |
| 九州沖縄 | 宮崎県 | 1.042 | 906 | 33.7 |
| 九州沖縄 | 鹿児島県 | 1.549 | 1369 | 33.8 |
| 九州沖縄 | 沖縄県 | 1.468 | 928 | 23.8 |
この 47 件の全件表を見ると、 「人口 100 万人未満」は鳥取・島根・福井・徳島・高知・佐賀・山梨など分布の左裾に集中していて、 これらを切ると 地方の小規模県の声がデータから消える ことが直観的に分かる。 東京都の高齢化率が 22.8% と全国で最も低く、 沖縄県も 23.8% と低い一方で人口は中規模、 という「両軸で見ないと隠れる特徴」も全件表でしか気付けない。 フィルタを掛ける前にこの 47 行を一度通読することで、 「自分の条件はこれら 47 県のうちどれを残すのか」を地理的・社会的にイメージできるようになる。
このコードでやること: フィルタ A の補集合 ~A を取って、 A と ~A の合計件数が元データに一致するかを確認する。 NaN や境界値の扱いミスを発見できる定石の検算手法。
📥 入力データ: SSDSE-B-2026 の 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', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() df = df.rename(columns={'A1101': '総人口', 'Prefecture': '都道府県'}) cond = df['総人口'] >= 1_000_000 A = df[cond] notA = df[~cond] print('A :', len(A)) print('~A :', len(notA)) print('合計 :', len(A) + len(notA), '/ 元:', len(df)) print('NaN件数:', df['総人口'].isna().sum()) |
📤 実行すると次の出力が得られる:
💬 A + ~A = 47 = 元件数 となり、 NaN も 0 件なので「条件と補条件で行が漏れていない」ことが確認できる。 もし NaN が混入していれば、 >= も < も両方 False になり、 A と ~A の合計が元件数を下回るので即座に異常検出できる。 この検算は習慣化すべき。
このコードでやること: フィルタで除外された行を別 CSV に書き出し、 後から「何を切ったか」を再確認できるようにする。 監査・再現性のためのテンプレ。
📥 入力データ: SSDSE-B-2026。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import os os.makedirs('data', exist_ok=True) # 書き出し先を先に作る import os import pandas as pd # ── この抜粋だけで動くように、日本語の列名で読み込む ── df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) 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()] # 最新年度の 47 行 df['総人口'] = pd.to_numeric(df['総人口'], errors='coerce') os.makedirs('data/processed', exist_ok=True) # 保存先が無いと to_csv は失敗する cond = df['総人口'] >= 1_000_000 kept = df[cond].copy() excluded = df[~cond].copy() kept.to_csv('data/processed/kept_pop_ge1m.csv', index=False) excluded.to_csv('data/processed/excluded_pop_lt1m.csv', index=False) print('採用:', len(kept), '件') print('除外:', len(excluded), '件') print('除外された県名:', excluded['都道府県'].tolist()) |
📤 実行すると次の出力が得られる:
💬 除外された 10 県を見ると、 秋田・福井・山梨・和歌山・鳥取・島根・徳島・香川・高知・佐賀という小規模県が並ぶ。 この一覧を別 CSV に保存しておくことで、 数か月後の再分析時にも「人口 100 万未満を切ったらこの 10 県が消えていた」と即座に追跡できる。 また、 「香川を切ったのは妥当か?」というレビュー指摘にも、 「明示的に切った 10 件のうちの 1 つ」として説明可能になる。 フィルタの透明性は、 残ったデータだけでなく 除外したデータも保存・公開する ことで担保される。
フィルタリングという操作は、 リレーショナルデータベース理論(Codd 1970 年)の 選択(selection、 σ)演算に源流を持つ。 σ_条件(R) は関係 R から条件を満たすタプルだけを取り出す演算で、 SQL の WHERE 句、 pandas のブール索引、 R の subset()、 Excel のオートフィルタはすべてこの σ 演算の異なる表現に過ぎない。 しかし、 各言語の方言として独自の構文が積み重なった結果、 同じ意味を持つフィルタが df[cond] / df.loc[cond] / df.query('...') / df.filter(items=...) / df.where(cond) など複数の書き方で存在し、 初学者の混乱の元になっている。 ここでは pandas 内部のフィルタ書法を整理し、 「いつどれを使うべきか」を 5 観点で整理する。
| 書法 | 用途 | 速度 | 可読性 | SettingWithCopy 回避 |
|---|---|---|---|---|
df[cond] | 標準的な行選択 | 高速 | 高い | .copy() を併記 |
df.loc[cond, cols] | 行 + 列の同時選択 | 高速 | 高い | 代入も安全 |
df.query('A > 1') | SQL ライクな式 | 中(numexpr 経由) | 非常に高い | 読み取りのみ推奨 |
df.filter(items=[...]) | 列名でフィルタ | 高速 | 中 | 行フィルタには使わない |
df.where(cond) | 条件外を NaN に置換 | 中 | 中 | 行削減ではない |
この 5 つの書法の中で初学者にまず勧めるのは df.loc[cond, cols] である。 行と列を同じ角括弧内で指定でき、 代入時の SettingWithCopyWarning も出にくいためだ。 df.query() は SQL に慣れた人や、 列名を文字列で動的に組み立てたい場合に便利だが、 列名にスペースや特殊文字があると壊れる脆さがある。 df.where() は「条件外を NaN に置き換えるが行は残す」という独特の動作で、 「行を削減する」フィルタとは目的が異なる点に注意。
SQL でフィルタを掛ける際、 「集計の前」と「集計の後」で使う構文が異なる。 これは pandas でいうと df[cond].groupby(...) と df.groupby(...).filter(...) の違いに対応する。 SSDSE-B-2026 を例に、 「人口 100 万人以上の都道府県だけを地域別に平均する」と「地域別に平均人口を出した後、 平均が 200 万を超える地域だけ残す」は別物である。 前者は WHERE、 後者は HAVING を使う。
| 句 | タイミング | 対象 | pandas 等価 |
|---|---|---|---|
| WHERE | 集計前(行単位) | 元の行 | df[cond].groupby(...) |
| HAVING | 集計後(群単位) | 集計結果 | df.groupby(...).agg(...).query('mean > ...') |
| QUALIFY | ウィンドウ関数後 | window 結果 | df.assign(rk=...).query('rk<=N') |
フィルタを掛けた後に平均値や相関を計算すると、 解釈ミスが起きやすい。 代表的な 5 パターンを整理する。
df[df['A']>0] は A が NaN の行を自動で除外する。 → 「欠損は除外したか/補完したか」を明示する。df[df['人口']>df['人口'].mean()] は元データの平均で切るが、 切った後のサンプルの平均は当然変わる。 → 「平均以上」という条件は注意して文書化する。フィルタは個人情報保護にも関わる重要な操作である。 「人口の少ない地域」や「特定の年齢層・性別の組み合わせ」でフィルタを掛けると、 残ったサンプルが少なすぎて個人特定が可能になる場合がある(K 匿名性の問題)。 SSDSE-B-2026 は都道府県集計値なので個人特定リスクは低いが、 個票データ(マイクロデータ)を扱うときは「フィルタ後の最小集団サイズ」を必ずチェックする習慣をつける。 一般には、 同一属性のサンプルが K = 5 件未満になる粒度のフィルタは避けるべきだ。
機械学習のパイプラインにおいてフィルタリングは 訓練データの定義 そのものになる。 「訓練データから外れ値を除く」「特定期間のデータだけを使う」といったフィルタは、 モデルの汎化性能に直結する。 ここで重要なのは、 訓練データに掛けたフィルタを 検証データ・本番データには掛けない こと。 訓練時のみ外れ値を除いてもモデルは外れ値を学ばないが、 本番では外れ値も到来するので、 性能が大きく落ちる。 SSDSE-B-2026 でいうと、 「東京を外れ値として除いて学習した回帰モデル」を全国予測に使うと、 東京での予測誤差は大きくなる。 こうしたフィルタの「リーク」は実務で頻繁に発生するため、 訓練/検証/本番のフィルタ条件を表で管理することを推奨する。
| 意味 | SQL | pandas |
|---|---|---|
| 単一条件 | WHERE pop >= 1000000 | df[df['人口']>=1e6] |
| 複合 AND | WHERE A AND B | df[(condA) & (condB)] |
| 複合 OR | WHERE A OR B | df[(condA) | (condB)] |
| 否定 | WHERE NOT A | df[~condA] |
| 範囲 | BETWEEN x AND y | df['x'].between(a,b) |
| 集合所属 | IN ('a','b') | df['x'].isin(['a','b']) |
| 部分一致 | LIKE '%山%' | df['x'].str.contains('山') |
| 前方一致 | LIKE '山%' | df['x'].str.startswith('山') |
| 後方一致 | LIKE '%県' | df['x'].str.endswith('県') |
| NULL チェック | IS NULL | df['x'].isna() |
| 非 NULL | IS NOT NULL | df['x'].notna() |
| 上位 N | ORDER BY x DESC LIMIT N | df.nlargest(N,'x') |
| 下位 N | ORDER BY x ASC LIMIT N | df.nsmallest(N,'x') |
| 重複除外 | SELECT DISTINCT | df.drop_duplicates() |
| 集計後フィルタ | HAVING avg(x)>100 | g.query('mean>100') |
この 15 パターンを暗記すれば、 SQL 経験者が pandas に来たとき、 あるいは逆のときの「翻訳」が瞬時にできるようになる。 とくに IS NULL / df['x'].isna() は等号 == では正しく動かないので要注意。 SSDSE-B-2026 のように都道府県名にスペースが入らない整ったデータでは問題が出にくいが、 自由記述データを扱う場面では str.strip() でトリムしてから isin を使うクセを付ける。
このコードでやること: 複雑な複合条件を、 個別の名前付き Series に分解して可読性を高める。 後でデバッグするときも、 どの条件が効いているかを追跡しやすい。
📥 入力データ: SSDSE-B-2026。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()
df = df.rename(columns={'Prefecture': '都道府県', 'A1101': '人口'})
df['高齢化率'] = df['A1303'] / df['人口'] * 100
is_metro = df['人口'] >= 5_000_000
is_young = df['高齢化率'] < 30
is_pacific_sea = df['都道府県'].isin(['茨城県', '千葉県', '東京都', '神奈川県', '静岡県', '愛知県', '三重県', '和歌山県', '高知県', '宮崎県', '鹿児島県'])
target = df[is_metro & is_young & is_pacific_sea]
print('該当県:', target['都道府県'].tolist())
print('該当件数:', len(target))
|
📤 実行すると次の出力が得られる:
💬 「大都市圏 かつ 若年層が多い かつ 太平洋側」という 3 条件を満たすのは、 千葉・東京・神奈川・愛知の 4 都県。 条件を名前付き変数に分解すると、 後から is_metro を緩めて 100 万に下げたい、 is_young の閾値を 25% にしたい、 といった調整が 1 行だけの編集 で済む。 一方、 df[(df['人口']>=5e6)&(df['高齢化率']<30)&(df['都道府県'].isin([...]))] と全部一行に書くと、 数か月後の自分やチームメンバーが読み解くのに時間を浪費する。 フィルタは「読みやすさ」を優先して書こう。
本番データのフィルタは、 単体テストのように「事前に期待件数を書く」癖をつけると事故が激減する。 SSDSE-B-2026 で「関東 7 件」「人口上位 10 件」など、 事前に分かっている件数を assert で表明することで、 データ更新時に件数が想定外に変動した場合に即座に検出できる。 これは データの形が変わった ことを知らせる早期警報装置として機能する。
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[df['SSDSE-B-2026'] == 2023].copy()
df = df.rename(columns={'Prefecture': '都道府県', 'A1101': '人口'})
kanto_names = ['茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県']
kanto = df[df['都道府県'].isin(kanto_names)]
assert len(kanto) == 7, f'関東は7都県のはず: {len(kanto)}'
top10 = df.nlargest(10, '人口')
assert len(top10) == 10, f'上位10件取れていない: {len(top10)}'
over1m = df[df['人口'] >= 1_000_000]
assert 35 <= len(over1m) <= 40, f'人口100万以上は35〜40の想定: {len(over1m)}'
print('全アサーション通過')
|
📤 実行すると次の出力が得られる:
💬 アサーション付きフィルタは、 例えば都道府県名の表記が変わった時、 あるいは欠損行が増えて関東が 6 件になった時に 即座にエラーを投げて止まる。 これにより、 異常データのまま下流分析が走って誤結論を出すリスクを大幅に減らせる。 業務分析ノートブックの全フィルタにこのテストを 1 行ずつ仕込むのが理想。
フィルタは可視化と組み合わせることで初めて意味を持つ。 SSDSE-B-2026 のような小規模なデータでも、 「人口 100 万人以上」だけを選ぶ前後で散布図を並べると、 残された点群が右上に偏ることが一目で分かる。 図 1 の散布図はフィルタ前、 図 2 のヒストグラムはフィルタの境界線を引く際の参考、 図 3 の箱ひげ図は群別フィルタ前のチェックに使う、 という役割分担を意識すると分析が体系化される。 ダッシュボード設計でも「フィルタ → 集計 → 可視化」の順序を崩さず、 ユーザーがフィルタを変えると統計量と図が同期的に更新される仕組みを必ず作る。 これにより、 利用者は 「条件を変えたらどう結論が動くか」 を体感でき、 過度な単一フィルタへの依存から自由になれる。 Tableau や Power BI の「フィルタアクション」「クロスフィルタ」も、 こうしたインタラクティブ性を実現するための同じ思想に基づいている。 SSDSE-B-2026 で練習しておけば、 実務で 100 万件規模のダッシュボードを設計する場合にも、 フィルタが分析の信頼性に与える影響を直感的に判断できるようになる。
フィルタは「該当しない行を捨てる」操作だが、 同じ問題に対して「該当しない行を埋める(補完・代入)」というアプローチもある。 たとえば SSDSE-B-2026 の人口列に NaN が混じっていた場合、 NaN 行を削除する(フィルタ)か、 中央値で埋める(補完)か、 多重代入で複数案を作る(imputation)かで分析結果は変わる。 削除は単純だが、 削除した行に系統的な偏りがある場合(例: 小規模県だけ提出が遅れて NaN になりがち)、 残ったサンプルは大都市寄りに歪む。 一方、 補完は件数は保てるが、 補完値そのものに不確実性が乗るので、 推定値の信頼区間が見かけ上狭くなる危険がある。 「どの戦略を選んだか」を必ず分析レポートに明記し、 可能なら両方の戦略で結論が変わらないことを感度分析として示そう。 SSDSE-B-2026 自体は欠損が極めて少ないデータだが、 こうした思考訓練の場として最適である。
この 5 項目を満たさないフィルタは「誤読リスクがある」と見なし、 出荷を保留する。 SSDSE-B-2026 を題材に練習を重ねれば、 大規模データに移行した時にも同じチェックが反射的にできるようになる。
フィルタリングは最も基本的かつ最も誤用されやすい操作である。 SSDSE-B-2026 のような全数調査データを 47 件すべて使う場合でも、 「人口 100 万人以上」「関東のみ」「高齢化率 30% 以上」といった条件を一つ加えるだけで、 平均・分散・相関・回帰係数のすべてが変わる。 重要なのは 「何を切ったか」を常に文章化し、 件数と分布の変化を必ず可視化する ことだ。 図 1 の散布図・図 2 のヒストグラム・図 3 の群別箱ひげ図を常に並べて確認することで、 フィルタが研究設計の中で果たしている役割と、 副作用としての偏りを同時に見える化できる。 フィルタは「データを綺麗にする」便利な道具ではなく、 「分析の境界を定義する」設計判断であることを忘れてはいけない。
フィルタリングでは、 何を残し何を除外したかを条件式として明示します。 閾値を少し変えると結論が変わる場合は、 感度分析を添え、 除外後の件数や分布が偏っていないかを確認します。
「フィルタリング」は単独で完結せず、 隣接する手法と接続することで分析パイプラインの一部として機能する。 以下に具体的な接続関係を示す。
「フィルタリング」は (1) 条件定義 (閾値 / カテゴリ / 期間) → (2) pandas df.query() or SQL WHERE で実装 → (3) 除外件数ログ → (4) 残存サンプルの選択バイアス確認 → (5) 下流分析へ受け渡し、 の 5 段で運用する。 単独実行で「データ削減」だけにすると選択バイアスを生む。
「フィルタリング」を実際の課題に当てはめるとき、 以下の 3 ステップで判断する。 領域固有の判断基準と組み合わせて使用する。
df.query() または SQL WHERE で実装欠損行除外なら listwise / pairwise / 補完を比較、 期間絞り込みなら期間効果と時系列構造、 カテゴリ絞り込みなら除外群との比較を必ず添付し、 「除外前後の分布シフト」を可視化する。
| 状況 | 第一選択 | 第二選択 | 避ける |
|---|---|---|---|
| 単一条件 (SSDSE-B-2026 で人口 100 万人以上) | df.query("人口 >= 1e6") | df[df["人口"]>=1e6] | for ループ削除 |
| 複合条件 (AND / OR / NOT) | df.query("人口>1e6 and clinics>1e7") | NumPy ブール演算 + & / | | chained [] (SettingWithCopyWarning) |
| カテゴリのメンバーシップ (関東 7 県) | df["pref"].isin([...]) | str.match(正規表現) | 長い OR 連結 |
| 欠損行除外 | dropna(subset=[...]) + 件数記録 | SimpleImputer で補完 | 無記録の全体 dropna |
| 大規模 (n > 10⁷) | SQL WHERE / DuckDB / Polars lazy | chunksize で pandas 読込 | 全件メモリロード |
| 時系列 (年次データ) | 期間 boolean + period 集計 | DataFrame.between_time | 「直近 3 年」だけ切り出し時系列構造無視 |
ここまでの章はフィルタリングの書き方(boolean mask・集合演算・構文の罠)を扱った。 この章は視点を変えて、 フィルタリングがその後に計算する統計量そのものを変えてしまうという統計的な側面を掘り下げる。 「行を減らすだけの無害な操作」と思っていると、 平均も相関も気づかぬうちに別物になる。
フィルタリングとは「条件を満たす部分集合の世界」へ移動することだ。 移動した後に計算する平均・分散・相関は、 数学的にはすべて条件付き統計量(例: $E[X \mid \phi]$)であって、 元の全体の統計量 $E[X]$ とは別物になる。 SSDSE-B-2026 の実測値で確かめると:
| 対象(2023 年・消費支出 L3221 の平均) | 件数 | 平均 |
|---|---|---|
| 全 47 都道府県 | 47 | 295,856 円 |
df[df['A1101'] >= 2_000_000](総人口 200 万人以上) | 16 | 306,586 円 |
df[df['A1101'] < 2_000_000](総人口 200 万人未満) | 31 | 290,318 円 |
同じ「平均消費支出」でも、 どの条件で絞ったかで約 1.6 万円ずれる。 フィルタ条件は分析結果の前提条件(分母の定義)であり、 レポートでは「何をどう絞った後の値か」を必ず明記する必要がある。
最も見落とされやすいのが範囲制限(restriction of range):ある変数の範囲を絞ってから相関を計算すると、 相関係数は一般に元より小さく出る。 SSDSE-B-2026(2023 年)の実測:
df[df['A1101'] < 1_000_000](人口 100 万人未満の 10 県)に絞って計算: r = 0.824人口と診療所数の関係自体は変わっていないのに、 x 軸の範囲を絞っただけで r が 0.15 下がった。 逆に、 絞り込みによって相関が「生まれる」こともある(選抜された集団内だけで見ると本来無関係な 2 変数に負の相関が現れる選抜効果)。 つまり「フィルタしてから相関・回帰」の結果を、 全体に対する結論として語ってはいけない。 実務の防御策は 2 つ:(1) 絞り込みの各段階で件数を記録する(このデータなら 564 行 → 2023 年で 47 行 → 条件追加で…と減っていくログを残す)、 (2) フィルタ前後の散布図・分布を並べて「何を捨てたのか」を可視化する。 除外した行は「無かったデータ」ではなく「条件により見ないことにしたデータ」だ。
WHERE(集約前)と HAVING(集約後)の区別は pandas にもある。 df[cond].groupby(...) は「条件に合う行だけで集計」、 df.groupby(...).filter(lambda g: ...) は「グループ単位の条件で丸ごと残す/落とす」。 適用タイミングが違えば結果も違う。df.query('A1101 >= @threshold') の @ 記法を使うと、 閾値をコード先頭の変数にまとめられ、 フィルタ条件の管理・再現がしやすくなる。この章の内容と直接つながる用語集ページ:
※ 部分集団で見ると全体と傾向が逆転する現象は「シンプソンのパラドックス」と呼ばれる(本用語集には単独ページなし)。 フィルタ(層別)と集計の順序が結論を反転させ得る代表例として覚えておきたい。