Day 039 — numpy クリッピング・条件分類・ソート

2026-05-19 白 / Phase 1 コーディング numpy入門

📚 背景知識(読んでから問題へ)

処理パイプラインのイメージ

今日の問題は「生データ → クリーニング → 分類 → 集計 → ソート」という典型的な前処理パイプラインです。

生データ
scores
クリッピング
np.clip()
条件分類
np.where()
統計集計
mean / sum
ソート
np.sort[::-1]

np.clip — 値を範囲内に収める

スーパーの会員ポイントが「マイナスになれない」「上限10000点」のように、値の範囲を制限するイメージです。

np.clip(arr, 0, 100)   # 0未満 → 0, 100超 → 100

np.where — 条件によって値を切り替える

「赤信号なら止まれ、青なら進め」のような条件分岐を、配列全体に一括で適用します。

np.where(条件, Trueの値, Falseの値)

ブール配列の sum() — 条件を満たす個数を数える

Trueは1、Falseは0として合計されます。「何人が合格したか」を数えるのに使います。

(arr >= 60).sum()  # 60以上の要素数を返す

🗂️ データスキーマ

変数名説明値の範囲備考
scores ndarray[int] 生の試験得点データ(10名分) -5 〜 101(入力ミス含む) 前処理前の生データ
scores_clipped ndarray[int] クリッピング後の得点 0 〜 100 np.clip 適用後
grade ndarray[str] 評価(A / B) "A" or "B" 80以上 → A
mean_score float64 平均点 0.0 〜 100.0 クリッピング後で計算
pass_count int 合格者数(60点以上) 0 〜 10 ブール配列の sum()
pass_rate float64 合格率(%) 0.0 〜 100.0 pass_count / 10 × 100
scores_sorted ndarray[int] 降順ソート済み得点 0 〜 100 高得点→低得点の順

📝 問題

📌
シナリオ: 試験の得点データがあります。101点と-5点は入力ミスです(本来は0〜100点)。以下の4問を順番に実装してください。
import numpy as np

scores = np.array([45, 78, 92, 33, 67, 85, 55, 101, -5, 70])

問1 — クリッピング

scores の値を 0以上100以下 に制限し、scores_clipped に格納してください。

💡
期待される出力: [ 45 78 92 33 67 85 55 100 0 70]

問2 — 条件分類(np.where)

scores_clipped を使い、np.where で評価を付けて grade に格納してください。

得点評価
80点以上"A"
80点未満"B"

問3 — 統計計算

scores_clipped から以下を計算してください:

  • mean_score : 平均点(np.mean を使う)
  • pass_count : 60点以上の人数(ブールインデックスと .sum() を使う)
  • pass_rate : 合格率(%)= pass_count / 全人数 × 100

問4 — 降順ソート

scores_clipped降順(大→小) に並べた配列 scores_sorted を作ってください。

💡
期待される出力: [100 92 85 78 70 67 55 45 33 0]
⚠️
np.sort は昇順(小→大)で返します。降順にするにはひと工夫が必要です。

🔍 ヒント(段階的開示)

ヒント1 — 方向性
  • 問1: numpyには「値を範囲内に収める」専用関数があります
  • 問2: 条件によって文字列を切り替えるには np.where が便利です
  • 問3: ブール配列の sum() はTrueの個数を返します
  • 問4: 昇順配列を逆にすれば降順になります
ヒント2 — アプローチ
  • 問1: np.clip(arr, a_min, a_max)
  • 問2: np.where(scores_clipped >= 80, "A", "B")
  • 問3: (scores_clipped >= 60).sum() でTrueの数が数えられます
  • 問4: np.sort(arr)[::-1] で降順になります
ヒント3 — コード骨格(ほぼ答え)
# 問1
scores_clipped = np.clip(scores, ___, ___)

# 問2
grade = np.where(scores_clipped >= ___, "A", "B")

# 問3
mean_score = np.mean(scores_clipped)
pass_count = (scores_clipped >= ___).sum()
pass_rate  = pass_count / len(scores_clipped) * 100

# 問4
scores_sorted = np.sort(scores_clipped)[::___]

模範解答

import numpy as np

scores = np.array([45, 78, 92, 33, 67, 85, 55, 101, -5, 70])

# 問1: クリッピング
scores_clipped = np.clip(scores, 0, 100)
print("クリッピング後:", scores_clipped)
# → [ 45  78  92  33  67  85  55 100   0  70]

# 問2: 評価分類
grade = np.where(scores_clipped >= 80, "A", "B")
print("評価:", grade)
# → ['B' 'B' 'A' 'B' 'B' 'A' 'B' 'A' 'B' 'B']

# 問3: 統計計算
mean_score = np.mean(scores_clipped)
pass_count = (scores_clipped >= 60).sum()
pass_rate  = pass_count / len(scores_clipped) * 100

print(f"平均点: {mean_score:.1f}")
print(f"合格者数: {pass_count}")
print(f"合格率: {pass_rate:.1f}%")
# → 平均点: 62.5
# → 合格者数: 6
# → 合格率: 60.0%

# 問4: 降順ソート
scores_sorted = np.sort(scores_clipped)[::-1]
print("降順:", scores_sorted)
# → [100  92  85  78  70  67  55  45  33   0]
62.5
平均点
6
合格者数
60%
合格率
3
A評価(80点以上)

🪜 Step-by-Step 解説

Step 1: np.clip でデータ品質を守る

scores_clipped = np.clip(scores, 0, 100)

101100-50 に丸められます。
なぜ clip を使うか? if文 で1要素ずつ処理するより10〜100倍速く、コードも1行で済むからです。

Step 2: np.where で条件分岐を配列全体に適用

grade = np.where(scores_clipped >= 80, "A", "B")

Pythonの通常の if は1つの値にしか使えませんが、np.where は配列全体に同時に適用できます。

45 → B
78 → B
92 → A
33 → B
67 → B
85 → A
55 → B
100 → A
0 → B
70 → B

Step 3: ブールインデックスで合格者を数える

pass_count = (scores_clipped >= 60).sum()

scores_clipped >= 60[F, T, T, F, T, T, F, T, F, T] のようなブール配列を返します。sum() はTrueを1、Falseを0として合計するので、Trueの個数(=合格者数 6)が得られます。

Step 4: スライスで降順ソート

scores_sorted = np.sort(scores_clipped)[::-1]

np.sort は昇順(小→大)で返します。[::-1] は「逆から全部」を意味するスライスで、降順(大→小)にできます。

📊 得点分布(クリッピング後)

クリッピング後の10名の得点を棒グラフで確認しましょう。緑色が合格(60点以上)、グレーが不合格です。

100 70 40 0 60 45 1 78 2 92 3 33 4 67 5 85 6 55 7 100 8 ✂ clip 0 9 70 10 A評価(80点以上) 合格(60-79点) 不合格(60点未満)
✂️
✂ clip マークは元データがクリッピングされた箇所です。8番目(101→100)と9番目(-5→0)がクリッピングの対象でした。

🔢 数学・統計の補足(文系向け)

合格率の計算式:

合格率 = 合格者数 ÷ 全体の人数 × 100

これは「全体の中で条件を満たす割合」を求める基本的な割り算です。numpyでは sum()len() を組み合わせるだけで計算できます。

平均点 62.5 の求め方: クリッピング後の合計 45+78+92+33+67+85+55+100+0+70 = 625 を人数 10 で割ると 62.5。

🏆 Kaggleでの実践的な使い方

操作Kaggle場面具体例
np.clip 外れ値の制限 House Prices の面積データ(0〜4000 sqft に制限)
np.where 特徴量の二値化 Titanic:年齢60歳以上 → 高齢者フラグ(1/0)
ブールインデックス 条件フィルタリング 欠損なし行だけ抽出して統計計算
np.sort[::-1] ランキング生成 予測スコア上位N件を取り出してアンサンブル

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
np.sort が降順だと思う "sort"=整列のイメージ np.sort は常に昇順。降順は [::-1] を付ける
(arr > 60).sum() の意味が不明 ブール配列の概念が未理解 Trueは1、Falseは0として合計するので条件一致数になる
np.clip を知らずに if文 でループ numpy以前のPython習慣 numpyには配列全体を一括処理する専用関数がある
pass_rate の分母に pass_count を使う 割合の分母を間違える 分母は全体len(arr))、分子が部分(合格者数)

🚀 次のステップ

  • 発展: 3段階評価(A/B/C)を実装するには np.select が便利。np.searchsorted で区間分類も可能
  • 次回予告: pandas入門(DataFrame・Series)— numpy配列をベースにした表形式データ処理

📋 自己評価(解いた後に記入)

自分の回答・気づき・メモ: