Day 030 — numpy入門(配列操作・数値計算)

2026-05-09 白 / Phase 1 分析 numpy入門(配列操作・数値計算)

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

numpyは「数値計算の専用倉庫」です。Pythonのリストより圧倒的に速く、大量の数値を一気に処理できます。

なぜnumpyが速いのか?

  • リスト: データがバラバラの場所に保存される(引っ越し段ボールが家中に散らばっている状態)
  • numpy配列: データが連続した場所にまとまって保存される(段ボールが一列に整理された倉庫)

よく使う操作のイメージ:

  • np.mean() → 平均値を計算(全員の身長を足して人数で割る)
  • np.std() → 標準偏差(データのバラつき具合)
  • np.where() → 条件でデータを仕分け(「180cm以上の人を選んで」)
  • ブロードキャスト → 配列全体に一度で計算(「全員の点数に+10点」を一行で書ける)
  • ベクトル化演算 → ループなしで配列同士を計算(pandas/sklearn内部でも使われる)

Kaggleでの重要性:

大量の特徴量を一括変換したり、カスタム損失関数を書く際にnumpyが必須です。


📝 問題

あなたはKaggleコンペで学生の試験データを分析しています。以下のデータが与えられています。

import numpy as np

# 40人の学生の3科目スコア(数学、英語、科学)
np.random.seed(42)
scores = np.random.randint(40, 100, size=(40, 3))
subjects = ['数学', '英語', '科学']

以下の分析をnumpyのみ(ループ禁止)で実施してください:

問題1: 各科目の平均点・最高点・最低点・標準偏差を求めてください。

問題2: 各学生の3科目合計点と平均点を求め、合計点の上位10人のインデックスを取得してください。

問題3: 各科目で60点未満の学生は何人いるか求めてください。

問題4: 全科目で75点以上の学生(優秀生)の割合(%)を求めてください。


🔍 ヒント(段階的開示)

ヒント1(方向性)

配列の「軸(axis)」を意識することが鍵です。axis=0 は縦方向(列ごと)、axis=1 は横方向(行ごと)の計算です。

ヒント2(アプローチ)
  • 問題1: np.mean(arr, axis=0) で列(科目)ごとの平均
  • 問題2: np.sum(arr, axis=1) で行(学生)ごとの合計 → np.argsort() でソート
  • 問題3: (arr < 60).sum(axis=0) で条件を満たす要素数をカウント
  • 問題4: np.all(arr >= 75, axis=1) で全列が条件を満たす行を検出
ヒント3(コード骨格)
# 問題1
means = np.mean(scores, axis=___)  # 科目ごとなので axis=?
maxes = np.max(scores, axis=___)
mins  = np.min(scores, axis=___)
stds  = np.std(scores, axis=___)

# 問題2
totals = np.sum(scores, axis=___)  # 学生ごとの合計
top10_idx = np.argsort(totals)[___]  # 降順の上位10

# 問題3
below60 = (scores < 60).sum(axis=___)

# 問題4
excellent = np.all(scores >= 75, axis=___)
ratio = excellent.sum() / len(scores) * 100

模範解答

import numpy as np

# データ生成
np.random.seed(42)
scores = np.random.randint(40, 100, size=(40, 3))
subjects = ['数学', '英語', '科学']

# 問題1: 各科目の統計
means = np.mean(scores, axis=0)
maxes = np.max(scores, axis=0)
mins  = np.min(scores, axis=0)
stds  = np.std(scores, axis=0)

print("=== 問題1: 科目別統計 ===")
for i, subj in enumerate(subjects):
    print(f"{subj}: 平均={means[i]:.1f}, 最高={maxes[i]}, 最低={mins[i]}, 標準偏差={stds[i]:.2f}")

# 問題2: 学生ごとの合計・平均・上位10人
totals   = np.sum(scores, axis=1)
avg_each = np.mean(scores, axis=1)
top10_idx = np.argsort(totals)[::-1][:10]  # 降順ソート後、先頭10件

print("\n=== 問題2: 上位10人のインデックスと合計点 ===")
for rank, idx in enumerate(top10_idx, 1):
    print(f"  {rank}位: 学生{idx} (合計={totals[idx]}, 平均={avg_each[idx]:.1f})")

# 問題3: 科目ごとの60点未満の人数
below60 = (scores < 60).sum(axis=0)
print("\n=== 問題3: 60点未満の人数 ===")
for i, subj in enumerate(subjects):
    print(f"  {subj}: {below60[i]}人")

# 問題4: 全科目75点以上の優秀生の割合
excellent_mask = np.all(scores >= 75, axis=1)
ratio = excellent_mask.sum() / len(scores) * 100
print(f"\n=== 問題4: 全科目75点以上の学生 ===")
print(f"  {excellent_mask.sum()}人 / {len(scores)}人 = {ratio:.1f}%")

🪜 Step-by-Step 解説

1
axis の意味を掴む

scores は shape (40, 3) の2次元配列です。

        数学  英語  科学
学生0  [  82,  73,  91 ]   ← axis=1 の方向(横)
学生1  [  57,  88,  64 ]
 ↑
axis=0 の方向(縦)
  • axis=0 で集計 → 40行を潰して3列の結果(科目別統計)
  • axis=1 で集計 → 3列を潰して40行の結果(学生別統計)
2
科目別統計(問題1)
means = np.mean(scores, axis=0)  # shape: (3,) → [数学平均, 英語平均, 科学平均]

axis=0 を指定することで「40人分の数学点を合計して40で割る」という処理が1行で書けます。

3
上位10人の取得(問題2)
totals = np.sum(scores, axis=1)     # shape: (40,) 各学生の合計
sorted_idx = np.argsort(totals)     # 昇順インデックス
top10_idx  = sorted_idx[::-1][:10] # [::-1]で逆順にして先頭10件

np.argsort() は「値の大きさで並べたときの元のインデックス」を返します。

[::-1] は「後ろから前に」スライスするPythonの書き方で、逆順になります。

4
条件カウント(問題3)
below60 = (scores < 60)        # 各要素が60未満かどうかの True/False 配列 (40, 3)
below60.sum(axis=0)            # True=1, False=0 として列方向に合計

Pythonでは True1False0 として扱われるため、.sum() でカウントできます。

5
複合条件(問題4)
np.all(scores >= 75, axis=1)

np.all(..., axis=1) は「横方向(3科目)全てが True か」を判定します。

1人でも75点未満があれば False になります。


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

標準偏差とは?

「データが平均からどれくらい離れているか」の平均的な距離です。

  • 標準偏差が小さい = 全員が似たような点数(スポーツ推薦クラス)
  • 標準偏差が大きい = 点数にバラつきがある(一般クラス)

argsortのイメージ:

クラスを点数順に並べたときの「出席番号の順番」を返します。

値自体ではなく「元のインデックス」が返る点が重要です。


🏆 Kaggleでの実践的な使い方

  1. 特徴量エンジニアリング: 複数の数値列から新しい列を計算するときにaxisを使いまくります
   df['total_score'] = np.sum(score_cols, axis=1)
  1. 外れ値フィルタ: np.where() や条件マスクでデータを絞り込む
   valid_mask = np.all((data >= 0) & (data <= 200), axis=1)
   clean_data = data[valid_mask]
  1. カスタム評価関数: XGBoostなどのカスタム損失関数はnumpyで書きます
   def custom_rmse(y_true, y_pred):
       return np.sqrt(np.mean((y_true - y_pred) ** 2))

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
axis=0axis=1 を逆に使うaxisの向きが直感と逆に感じるaxis=0は「行方向に潰す」→列ごとの結果。axis=1は「列方向に潰す」→行ごとの結果
np.argsort() が昇順なことを忘れるランキング取得時に上位を取ろうとして下位を取ってしまう[::-1] で反転するか np.argsort(-arr) を使う
ブール配列の .sum() を使うのを忘れるlen([x for x in arr if x]) のようなループを書いてしまう(arr > 0).sum() の方が速くシンプル
np.all()np.any() を混同する日本語で「全て」と「いずれか」の使い分けが曖昧all=全条件満足、any=少なくとも1つ満足

🚀 次のステップ

  • 発展: np.applyalongaxis() でより複雑な行列操作、np.einsum() による高速行列計算
  • 次回予告: pandas入門(DataFrame・Series) — numpyの上に乗っているpandasでデータフレームを操作する

🎯 自己評価

自分の回答

気づき・メモ