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

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

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

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は使わない)で実施してください:

  1. 配列の要素数・最小値・最大値・平均・標準偏差を求める
  2. 年齢を「子供(18歳未満)」「大人(18〜59歳)」「高齢者(60歳以上)」に分類し、それぞれの人数を出力する
  3. 平均年齢より高い乗客の年齢を全て取り出し、その平均を計算する(「平均より上の人の平均」)
  4. 年齢を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 解説

1
基本統計量をnumpyで取得する
ages.size    # 配列の要素数(len(ages)でも同じ)
ages.min()   # 最小値(np.min(ages)とも書ける)
ages.max()   # 最大値
np.mean(ages)  # 平均値
np.std(ages)   # 標準偏差(母標準偏差、ddof=0がデフォルト)

なぜこう書くか: numpyの集計関数はCで実装されており、Pythonのループより10〜100倍高速です。大規模データでは必須。

2
ブール配列インデックスで条件分類する
ages < 18  # → array([False, False, ..., True, ...]) ブール配列
np.sum(ages < 18)  # TrueをFalseを1/0として合計 → 人数

複数条件は &(AND)、|(OR)で結合:

(ages >= 18) & (ages < 60)  # 括弧が必須!演算子の優先順位に注意

落とし穴: and ではなく & を使う。and はスカラー(単一値)用、& はベクトル(配列)用。

3
フィルタリングで部分配列を取得する
above_mean_ages = ages[ages > mean_age]

ages > mean_age はブール配列(True/False)を生成し、ages[...] でTrueの位置だけを取り出します。結果は元の配列のサブセット。

4
Min-Max正規化を1行で実装する
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で、表形式データの操作がさらに簡単になります

🎯 自己評価

自分の回答

気づき・メモ