CHAPTER 6 ・ pandas を一歩進める / Lesson 26
条件で区分けする(where・map・cut)
金額帯のランク付け、コードを名前に変換、条件で値を入れ替え。IFやVLOOKUPの置き換えです。
このレッスンが終わったら
「条件で列を作るには np.where、対応表の変換には map、区間分けには cut。1行ずつ回す apply は最後の手段」と言える。
こんな場面、ありませんか?
売上明細の1行ずつに、金額に応じたランク(A・B・C)を付けたい。Excelなら、IF を入れ子にして、列を足す作業です。基礎編の for と if で書くと、こうなります。ここでは、30万円以上を A、10万円以上を B、それ未満を C とします。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
A が19件、B が30件、C が79件と、正しく動きます。ただ、「空のリストを作る → 1行ずつ回す → 追加する → 列にする」という手順を、毎回書くことになります。pandas には、列全体に一度で条件を当てる道具があります。この回では、その使い分けを学びます。
列全体に、条件を当てる
2通りに分けるなら np.where、3通り以上なら np.select です。numpy(np)は、pandas の内側で使われている、数値計算の道具です。pandas と一緒に、そのまま使えます。
まず、「30万円以上なら大口、そうでなければ通常」の2通りです。Excelの =IF(条件, 値1, 値2) と同じ形です。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
次は、A・B・C の3通りです。条件を上から順に調べて、最初に当てはまったものを採用します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
さきほどの for の書き方と、同じ件数になりました。6行が、1つの式になっています。
仕組みを読む
np.select(条件を上から順に調べて、列を作る [df["金額"] >= 300000, df["金額"] >= 100000],条件のリスト。1つ目:30万円以上か。2つ目:10万円以上か ["A", "B"],それぞれが当てはまったときの値。条件と同じ順番に並べる default="C",どの条件にも当てはまらなかったときの値)全部の行に、一度に当てる日本語にすると
条件は上から調べ、最初に当てはまったものが採用されます。45万円は、1つ目にも2つ目にも当てはまりますが、最初の A になります。順番を逆にすると、全部 B になってしまうので、順番に注意します。
やりたいことと、使う道具の対応は、次のとおりです。
| やりたいこと | 使う道具 |
|---|---|
| 2通りに分ける(IF) | np.where(条件, 値1, 値2) |
| 3通り以上に分ける(IF の入れ子) | np.select([条件, ...], [値, ...], default=値) |
| コードを名前に置き換える(VLOOKUP のかんたん版) | 列.map(辞書) |
| 金額などを、区間で分ける | pd.cut(列, bins=[...], labels=[...]) |
| 文字に含まれるか調べる/一部を取り出す | 列.str.contains(...) / 列.str.extract(...) |
| 上のどれでもできないとき | 列.apply(関数)(最後の手段) |
変えて、試す
① コードを名前に置き換える。 map は、辞書の「キー → 値」の対応表で、列の値を置き換えます。辞書にないコードは、NaN(欠損値)になります。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
最後の行は、辞書にない「P99」を試した結果で、nan になりました。対応表が小さいときは map が手軽です。付けたい列がいくつもあるときは、Lesson 23 の merge を使います。
② 金額を、区間で分ける。 pd.cut は、区切りの数字(bins)で区間を作り、名前(labels)を付けます。right=False は、「区切りの数字を、右の区間に入れる」指定です。(10万円ちょうどは B。30万円ちょうどは A)
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
np.select と同じ結果です。区切りの数字を並べるだけなので、区間が多いときは pd.cut のほうが読みやすくなります。
③ 摘要から、請求番号を取り出す。 銀行の入金明細の「摘要」には、請求番号が混ざっています。str.extract で、パターンに合う部分だけを取り出せます(INV-2025-0001 の形を表す書き方は、Lesson 20 の正規表現です)。取り出せなかった行は NaN になるので、請求番号のない入金を見つけられます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
str.contains("INV") は「INV を含むか」を調べます。10件中9件が含んでいて、1件は含んでいません。含まない1件は、「請求番号なし」の入金として、人が確認する対象です。
④ 複数の条件で、「要確認」のフラグを付ける。 経費の一覧に、確認が必要な行の目印と理由を付けます。np.select の条件を、確認したい順に並べます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
6件が見つかりました。ex["摘要"].isna() は、摘要が空欄の行です。1行が複数の条件に当てはまるときは、最初の条件の理由だけが付きます。理由を全部並べる作り方は、Lesson 38 で学びます。
自分で書く
お題① 経費の金額が、3,000円未満なら「小額」、10,000円未満なら「中額」、それ以外は「高額」とします。「高額」は何件でしょうか。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
np.select([ex["金額"] < 3000, ex["金額"] < 10000], ["小額", "中額"], default="高額") で、区分の列を作ります。件数は、(ex["区分"] == "高額").sum() で数えられます。
答えの例を見る
import numpy as np
import pandas as pd
ex = pd.read_csv("expenses.csv")
ex["区分"] = np.select(
[ex["金額"] < 3000, ex["金額"] < 10000],
["小額", "中額"],
default="高額",
)
print((ex["区分"] == "高額").sum())お題② payments.csv の摘要から請求番号を取り出し、請求番号が取り出せなかった入金の、入金額の合計を表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
pay["摘要"].str.extract(r"(INV-\d{4}-\d{4})", expand=False) で請求番号を取り出し、.isna() が True の行を取り出します。その入金額の合計を .sum() で求めます。
答えの例を見る
import pandas as pd
pay = pd.read_csv("payments.csv")
pay["請求番号"] = pay["摘要"].str.extract(r"(INV-\d{4}-\d{4})", expand=False)
print(pay[pay["請求番号"].isna()]["入金額"].sum())お題③ 経費のうち、「領収書がなし」または「摘要が空欄」の行は、何件でしょうか。(「または」は |、条件ごとに ( ) で囲みます)
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
(ex["領収書"] == "なし") | ex["摘要"].isna() で、当てはまる行が True になります。True の数は .sum() で数えられます。
答えの例を見る
import pandas as pd
ex = pd.read_csv("expenses.csv")
flag = (ex["領収書"] == "なし") | ex["摘要"].isna()
print(flag.sum())仕事ならどう使う?
理解チェック金額を A・B・C・D の4通りに分けたいとき、まず考える道具は、どれでしょう?
理解チェック商品コードの列に map(辞書) を使いました。辞書にないコード P99 があるとき、結果はどうなりますか?