📚 背景知識(読んでから問題へ)
numpyの「配列(ndarray)」は、Excelのセルが並んだ表をイメージすると分かりやすいです。
- 1次元配列: 1行に数値が並んだもの →
[10, 20, 30, 40, 50] - 2次元配列: 行と列がある表 → 行列(マトリックス)
- ブロードキャスト: 異なるサイズの配列同士で計算する際、自動的に形を合わせる機能。例えば「配列の全要素に2を掛ける」ことが1行で書ける
Kaggleのデータ処理では、数千〜数百万行のデータを一括処理する場面が多く、numpyのベクトル演算(ループなしで全要素に同じ処理)は必須スキルです。
また、スライシング(配列の一部を切り出す)とフィルタリング(条件に合う要素を取り出す)は、データ分析で毎日使う基本操作です。
📝 問題
以下のシナリオを読んで、設問に答えてください。
シナリオ: Titanicコンペの年齢データが1次元numpy配列で与えられています。この配列に対して分析を行い、データの特徴を把握しましょう。
import numpy as np
# Titanicの乗客年齢データ(一部サンプル、NaNは除去済み)
ages = np.array([22, 38, 26, 35, 35, 54, 2, 27, 14, 4,
58, 20, 39, 14, 55, 2, 31, 35, 34, 15,
28, 8, 38, 19, 49, 40, 66, 28, 42, 3,
28, 19, 32, 62, 3, 36, 21, 19, 29, 18])
以下の分析を numpyのみ(pandasは使わない)で実施してください:
- 配列の要素数・最小値・最大値・平均・標準偏差を求める
- 年齢を「子供(18歳未満)」「大人(18〜59歳)」「高齢者(60歳以上)」に分類し、それぞれの人数を出力する
- 平均年齢より高い乗客の年齢を全て取り出し、その平均を計算する(「平均より上の人の平均」)
- 年齢を0〜1の範囲に正規化(Min-Max normalization)した配列を作る
🔍 ヒント(段階的開示)
ヒント1(方向性)
各設問はnumpyの基本関数とブール配列インデックス(条件式で要素を絞り込む方法)で解けます。ループは一切不要です。
ヒント2(アプローチ)
- 設問1:
np.min(),np.max(),np.mean(),np.std(),.sizeまたはlen() - 設問2:
np.sum(ages < 18)のようにブール配列のTrue=1を活用 - 設問3:
ages[ages > mean_age]でフィルタリング - 設問4: Min-Max正規化の公式は
(x - min) / (max - min)
ヒント3(コード骨格)
# 設問1
print(f"要素数: {ages.size}")
print(f"平均: {np.mean(ages):.2f}")
# ... 他も同様に
# 設問2
child_count = np.sum(ages < 18)
adult_count = np.sum((ages >= 18) & (ages < 60)) # & で複数条件
elder_count = np.sum(ages >= 60)
# 設問3
mean_age = np.mean(ages)
above_mean = ages[ages > mean_age] # フィルタリング
print(f"平均より上の人の平均: {np.mean(above_mean):.2f}")
# 設問4
normalized = (ages - ages.min()) / (ages.max() - ages.min())
✅ 模範解答
import numpy as np
ages = np.array([22, 38, 26, 35, 35, 54, 2, 27, 14, 4,
58, 20, 39, 14, 55, 2, 31, 35, 34, 15,
28, 8, 38, 19, 49, 40, 66, 28, 42, 3,
28, 19, 32, 62, 3, 36, 21, 19, 29, 18])
# 設問1: 基本統計
print("=== 設問1: 基本統計 ===")
print(f"要素数: {ages.size}")
print(f"最小値: {ages.min()}")
print(f"最大値: {ages.max()}")
print(f"平均: {np.mean(ages):.2f}")
print(f"標準偏差: {np.std(ages):.2f}")
# 設問2: 年代別人数
print("\n=== 設問2: 年代別分類 ===")
child_count = np.sum(ages < 18)
adult_count = np.sum((ages >= 18) & (ages < 60))
elder_count = np.sum(ages >= 60)
print(f"子供(18歳未満): {child_count}人")
print(f"大人(18〜59歳): {adult_count}人")
print(f"高齢者(60歳以上): {elder_count}人")
print(f"合計確認: {child_count + adult_count + elder_count} == {ages.size}")
# 設問3: 平均より上の人の平均
print("\n=== 設問3: 平均より上の人の平均 ===")
mean_age = np.mean(ages)
above_mean_ages = ages[ages > mean_age]
print(f"全体平均: {mean_age:.2f}歳")
print(f"平均より上の人数: {above_mean_ages.size}人")
print(f"平均より上の人の平均: {np.mean(above_mean_ages):.2f}歳")
# 設問4: Min-Max正規化
print("\n=== 設問4: Min-Max正規化 ===")
normalized = (ages - ages.min()) / (ages.max() - ages.min())
print(f"正規化前(最初の5件): {ages[:5]}")
print(f"正規化後(最初の5件): {np.round(normalized[:5], 3)}")
print(f"正規化後の最小値: {normalized.min():.3f}(0に近いか確認)")
print(f"正規化後の最大値: {normalized.max():.3f}(1に近いか確認)")
期待される出力:
▶ 出力を見る
=== 設問1: 基本統計 ===
要素数: 40
最小値: 2
最大値: 66
平均: 28.70
標準偏差: 15.27
=== 設問2: 年代別分類 ===
子供(18歳未満): 9人
大人(18〜59歳): 28人
高齢者(60歳以上): 3人
合計確認: 40 == 40
=== 設問3: 平均より上の人の平均 ===
全体平均: 28.70歳
平均より上の人数: 19人
平均より上の人の平均: 40.89歳
=== 設問4: Min-Max正規化 ===
正規化前(最初の5件): [22 38 26 35 35]
正規化後(最初の5件): [0.313 0.563 0.375 0.516 0.516]
正規化後の最小値: 0.000(0に近いか確認)
正規化後の最大値: 1.000(1に近いか確認)🪜 Step-by-Step 解説
ages.size # 配列の要素数(len(ages)でも同じ)
ages.min() # 最小値(np.min(ages)とも書ける)
ages.max() # 最大値
np.mean(ages) # 平均値
np.std(ages) # 標準偏差(母標準偏差、ddof=0がデフォルト)
なぜこう書くか: numpyの集計関数はCで実装されており、Pythonのループより10〜100倍高速です。大規模データでは必須。
ages < 18 # → array([False, False, ..., True, ...]) ブール配列
np.sum(ages < 18) # TrueをFalseを1/0として合計 → 人数
複数条件は &(AND)、|(OR)で結合:
(ages >= 18) & (ages < 60) # 括弧が必須!演算子の優先順位に注意
落とし穴: and ではなく & を使う。and はスカラー(単一値)用、& はベクトル(配列)用。
above_mean_ages = ages[ages > mean_age]
ages > mean_age はブール配列(True/False)を生成し、ages[...] でTrueの位置だけを取り出します。結果は元の配列のサブセット。
normalized = (ages - ages.min()) / (ages.max() - ages.min())
ブロードキャストが自動で働く:
ages.min()はスカラー(単一値)ages - ages.min()は全要素から最小値を引く(ループなし)- 除算も同様に全要素に一括適用
📐 数学・統計の補足(文系向け)
Min-Max正規化とは?
「最小が0、最大が1になるようにスケールを変える操作」です。
例えば試験の点数が 40〜90点の範囲なら:
- 40点 →
(40-40)/(90-40)= 0.0 - 65点 →
(65-40)/(90-40)= 0.5 - 90点 →
(90-40)/(90-40)= 1.0
これを「正規化」と呼び、異なるスケールのデータ(年齢:0〜100、収入:0〜10,000,000)を同じスケールに揃えるために使います。
なぜ重要?: 距離を計算する機械学習アルゴリズム(KNN、線形回帰など)は、スケールの大きい変数の影響を過剰に受けてしまいます。正規化でこれを防ぎます。
🏆 Kaggleでの実践的な使い方
Titanicコンペでの実例:
# 実際のKaggleコードでよく見るパターン
import pandas as pd
import numpy as np
df = pd.read_csv('train.csv')
# pandasのSeries → numpyに変換して高速処理
ages_array = df['Age'].dropna().values # .values でndarrayを取得
# 年齢グループを特徴量として追加
df['AgeGroup'] = np.where(df['Age'] < 18, 0,
np.where(df['Age'] < 60, 1, 2)) # 0:子供, 1:大人, 2:高齢
# Min-Max正規化(sklearn版も覚えておく)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df['Age_normalized'] = scaler.fit_transform(df[['Age']].fillna(df['Age'].mean()))
コンペでの活用場面:
- 数値特徴量の正規化 → 線形モデル・ニューラルネットの前処理
- 年齢・価格などの区間分割 → カテゴリ特徴量としてGBDTに投入
- ブール配列での高速フィルタリング → 大規模データのEDA
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
(ages >= 18) and (ages < 60) とする | Pythonの and に慣れているから | numpy配列では & を使う。and はValueError |
np.std() の結果がpandasと違う | デフォルトの分母が異なる | numpyはn(母標準偏差)、pandasはn-1(標本標準偏差)。np.std(x, ddof=1) でpandasに合わせられる |
| 正規化後に最大値が1.0にならない | 浮動小数点誤差 | np.round() か np.isclose() で確認 |
| スライスが元の配列を変更する | コピーと参照の混同 | ages[2:5] はビュー(参照)。変更が元に影響する場合は .copy() を使う |
🚀 次のステップ
- 発展: 2次元配列(行列)の操作 —
reshape,axis=0/1を使った行・列単位の集計 - 次回予告: pandas入門(DataFrame・Series) — numpyの上に構築された高レベルAPIで、表形式データの操作がさらに簡単になります