Pythonデータ分析 #3 選択とフィルタ — loc、iloc、ブールインデックス
データを読み込んだら、次にやることは決まっています。全体から必要な部分だけを選び出すことです。「この列だけ」「この条件に合う行だけ」「両方を同時に」といった選択が、実際の分析コードの半分を占めます。今回は pandas の選択ツールである列選択、loc、iloc、ブールインデックスを順に扱い、誰もが一度は出会う SettingWithCopyWarning まで整理します。#2 読み込みと探索では CSV を読み込んでデータの姿を眺めました。今回はどこでもすぐ追試できるように、小さな DataFrame をコードで作ってから始めます。
import pandas as pd
df = pd.DataFrame({
"name": ["味噌ラーメン", "親子丼", "焼肉定食", "ざるそば", "パスタ", "ピザ"],
"category": ["和食", "和食", "和食", "和食", "洋食", "洋食"],
"price": [900, 1000, 1500, 1100, 1400, 1800],
"qty": [42, 35, 21, 18, 27, 30],
})
print(df)
# name category price qty
# 0 味噌ラーメン 和食 900 42
# 1 親子丼 和食 1000 35
# 2 焼肉定食 和食 1500 21
# 3 ざるそば 和食 1100 18
# 4 パスタ 洋食 1400 27
# 5 ピザ 洋食 1800 30列選択: 角かっこ1つと2つの違い #
いちばん基本になるのは列選択です。ただし、角かっこを 1 つ使うか 2 つ使うかで、結果の型が変わります。
df["price"] # Series (1次元)
df[["name", "price"]] # DataFrame (2次元の表)df["price"] は Series を返します。1 次元の配列にインデックスが付いた形で、.mean() のような集計をそのまま掛けられます。一方 df[["price"]] は、列が 1 本だけでも DataFrame なので表の形が保たれます。この違いを知らないと、同じ列を選んだのにあるコードは動き、あるコードは動かないという状況に遭遇します。1 本の列の値を扱うなら角かっこ 1 つ、表の形を保つなら角かっこ 2 つです。
locとiloc: ラベルと位置 #
行を選ぶときは loc と iloc を使います。両者の区別は 1 行でまとまります。loc は ラベル(インデックスの値)で、iloc は 位置(整数の順番)で選びます。たとえば df.loc[2] と df.iloc[2] は、どちらも焼肉定食の行を返します。今はインデックスが 0 から順に振られているので同じに見えますが、フィルタリングを経るとラベルと位置がずれます。
sub = df[df["category"] == "洋食"] # ラベル4、5の行だけが残る
sub.loc[4] # ラベル4: パスタ
sub.iloc[0] # 最初の行: パスタ
sub.loc[0] # KeyError! ラベル0の行はありません「何番目の行」が必要なら iloc、「インデックス値が何の行」が必要なら loc です。カンマを使うと行と列を同時に選べます。カンマの前が行、後ろが列です。
df.loc[1:3, ["name", "price"]] # ラベル1〜3の行のname、price列
df.iloc[1:3, 0:2] # 位置1〜2の行の0〜1番目の列ここに落とし穴が 1 つあります。loc のスライスは 末尾のラベルを含み、iloc のスライスは Python のリストと同じように末尾の位置を含みません。そのため df.loc[1:3] は 3 行、df.iloc[1:3] は 2 行になります。
ブールインデックス: 条件式がマスクになります #
今回の核心です。pandas で「条件に合う行だけ」を表現するモデルは単純です。列に比較演算を掛けると True/False からなる Series が返り、これを マスク と呼びます。
df["price"] >= 1200
# 0 False
# 1 False
# 2 True
# 3 False
# 4 True
# 5 True
# Name: price, dtype: boolこのマスクを角かっこに入れた df[df["price"] >= 1200] がそのままフィルタです。True の行、つまり焼肉定食・パスタ・ピザだけが残ります。条件を組み合わせるときは and、or、not ではなく &、|、~ を使い、各条件を必ずかっこで囲む 必要があります。
df[(df["category"] == "和食") & (df["price"] >= 1200)] # かつ
df[(df["price"] <= 900) | (df["qty"] >= 30)] # または
df[~(df["category"] == "洋食")] # 否定かっこが必須なのは、演算子の優先順位のためです。& は >= より先に評価されるので、かっこを外すと条件が変な形で結ばれて TypeError になります。and を使うと「The truth value of a Series is ambiguous」という ValueError になります。どちらもブールインデックスで最もよく出会うエラーなので、条件ごとにかっこを付ける習慣をつけるのが良いです。
条件を磨くツール: isin, between, str.contains #
よく使う条件パターンには専用メソッドがあります。
df[df["category"].isin(["和食", "中華"])] # 値がリストの中にあるか
df[df["price"].between(1000, 1500)] # 範囲の中にあるか (両端を含む)
df[df["name"].str.contains("ラーメン")] # 文字列にパターンが含まれるかisin は、同じ列の比較を | で並べるより短くて読みやすいです。between は両端を含む範囲条件で、str.contains は文字列の列専用で部分一致を検査します。3 つとも結果はマスクなので、&、|、~ の組み合わせにそのまま混ぜて使えます。
query: 可読性が欲しいとき #
条件が長くなると、かっこと df[...] の繰り返しで式が読みにくくなります。そんなとき query メソッドが代わりになります。条件を文字列で書き、and と or をそのまま使えます。
df.query("price >= 1200 and category == '洋食'")
min_price = 1000
df.query("price >= @min_price and qty > 20") # 外部変数は@で参照同じ条件をブールインデックスで書くと df[(df["price"] >= 1200) & (df["category"] == "洋食")] になりますが、query の方が明らかに目に入りやすいです。ただし条件が文字列なので IDE の自動補完や型検査が届かず、列名に空白があるとバッククォートで囲む必要があるという制約があります。基本はブールインデックスで固めて、条件が 3 つを超えるコードで query を可読性のツールとして取り出す、くらいがバランスの良い使い方です。
SettingWithCopyWarning: ビューとコピー #
選択とフィルタを使っていると、必ず一度はこの警告に出会います。
cheap = df[df["price"] < 1200]
cheap["price"] = 0
# SettingWithCopyWarning: A value is trying to be set on a copy of a slice...原因は、df[...] で切り出した結果が元データの ビュー(同じデータを覗く窓)なのか コピー なのかを pandas が保証しない、という点にあります。ビューなら cheap への変更が df まで変えてしまい、コピーなら cheap だけが変わります。どちらになるかは状況によって変わるので、代入の結果が予測できません。警告は「この代入が元データに反映されるか分からない」というシグナルです。解決策は意図によって 2 つに分かれます。
# 元データを直すのが目的なら: 切り出してから代入せず、.locで一度に
df.loc[df["price"] < 1200, "price"] = 0
# 部分集合を独立したデータとして使うなら: 最初からcopy()を明示
cheap = df[df["price"] < 1200].copy()
cheap["price"] = 0 # 警告なし、dfはそのままちなみに pandas 3.0 からは Copy-on-Write がデフォルトの動作になり、上のような連鎖代入は警告の代わりに「元データには一切反映されない」ことが確定します。曖昧さが消える方向ですが、「元データの修正は .loc で一度に、部分集合は copy() で」という習慣はどのバージョンでもそのまま有効です。
まとめ #
今回扱った内容です。
- 列選択は角かっこ 1 つなら Series、2 つなら DataFrame
locはラベル、ilocは位置で、フィルタ後は 2 つがずれます- 条件式はマスクになり、組み合わせは
&、|、~にかっこが必須 isin、between、str.containsでよく使う条件を短く表現- 条件が長くなったら
queryが可読性の代替手段 - SettingWithCopyWarning はビューとコピーの曖昧さへのシグナルで、解決策は
.locでの一括代入とcopy()
次回(#4 変形と欠損値)では、選び出したデータを変える段階に進みます。新しい列の作成、apply とベクトル演算、そして実務データの半分を占める欠損値処理(dropna、fillna)を扱います。