📚 背景知識(読んでから問題へ)
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 解説
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行の結果(学生別統計)
means = np.mean(scores, axis=0) # shape: (3,) → [数学平均, 英語平均, 科学平均]
axis=0 を指定することで「40人分の数学点を合計して40で割る」という処理が1行で書けます。
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の書き方で、逆順になります。
below60 = (scores < 60) # 各要素が60未満かどうかの True/False 配列 (40, 3)
below60.sum(axis=0) # True=1, False=0 として列方向に合計
Pythonでは True は 1、False は 0 として扱われるため、.sum() でカウントできます。
np.all(scores >= 75, axis=1)
np.all(..., axis=1) は「横方向(3科目)全てが True か」を判定します。
1人でも75点未満があれば False になります。
📐 数学・統計の補足(文系向け)
標準偏差とは?
「データが平均からどれくらい離れているか」の平均的な距離です。
- 標準偏差が小さい = 全員が似たような点数(スポーツ推薦クラス)
- 標準偏差が大きい = 点数にバラつきがある(一般クラス)
argsortのイメージ:
クラスを点数順に並べたときの「出席番号の順番」を返します。
値自体ではなく「元のインデックス」が返る点が重要です。
🏆 Kaggleでの実践的な使い方
- 特徴量エンジニアリング: 複数の数値列から新しい列を計算するときにaxisを使いまくります
df['total_score'] = np.sum(score_cols, axis=1)
- 外れ値フィルタ:
np.where()や条件マスクでデータを絞り込む
valid_mask = np.all((data >= 0) & (data <= 200), axis=1)
clean_data = data[valid_mask]
- カスタム評価関数: XGBoostなどのカスタム損失関数はnumpyで書きます
def custom_rmse(y_true, y_pred):
return np.sqrt(np.mean((y_true - y_pred) ** 2))
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
axis=0 と axis=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でデータフレームを操作する