論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
NumPy
Numerical Python
Python ライブラリ / 数値計算基盤

🔖 キーワード索引

このページで扱う主要キーワード(クリックで該当セクションへ):

ndarray ベクトル化 ブロードキャスト dtype 軸 axis ufunc メモリ連続性 線形代数 乱数 RandomState SSDSE 47都道府県 BLAS/LAPACK pandas との橋渡し

💡 30秒で分かる結論

🍰 まずはやさしく

計算を速くする道具箱のようなものです。

大量の数値を効率よく計算するために使います。

スマホアプリの裏側にある計算に似ています。

まずは結論と使い分けについて読みましょう。

💡 NumPy 拡張 Q&A

Q. NumPy と Pandas、 どちらを使うべき?
A. データ構造が「均一な数値の多次元配列」なら NumPy、 「ラベル付きの異種型のテーブル」なら Pandas。 SSDSE-B-2026 のような CSV は Pandas で読み込み、 数値計算部分だけ NumPy にして高速化、 が王道。
Q. NumPy と PyTorch、 どちらが速い?
A. CPU では NumPy(OpenBLAS)と PyTorch(ATen)はほぼ同等。 GPU を使うなら PyTorch・JAX・CuPy が圧勝。 SSDSE 規模なら NumPy で十分。
Q. メモリ不足のときどうする?
A. ① dtype を float64 → float32 / int32 に下げる(メモリ半分)、 ② np.memmap でディスク経由、 ③ Dask Array でチャンク分割。 SSDSE 規模では問題にならないが、 全国小地域メッシュデータで起こり得る。
Q. np.random は使っていい?
A. 本教材では 使わない。 「実データ必須」のルールで、 合成データは禁止。 SSDSE-B-2026 のような実公的データのみを使用してください。
Q. NumPy バージョンを固定すべき?
A. プロダクション環境では requirements.txt でバージョン固定(numpy==1.26.4)。 教育目的なら最新で OK。 NumPy 2.0 で一部 API が変更されたので、 既存コードは numpy>=1.24,<2 が安全。

📍 あなたが今見ているもの

🍰 まずはやさしく

このページはNumPyの案内図です。

統計データを分析する時に役立てます。

学校で習うデータの処理に活用できます。

全体の流れと関連する用語を確認しましょう。

NumPy」 (Numerical Python) は、 SSDSE-B-2026 などの公的統計データを使った教材・分析で頻出するキーワードです。 本ページでは、 まず直感、 次に数式、 そして 47 都道府県の実値で確かめる、 という流れで体系的に整理します。 加えて、 ケーススタディ・FAQ・歴史的経緯・参考文献までを 1 ページに集約し、 用語の「地図」として使えるようにしました。

関連用語(前提・並列・発展)と関連グループ教材も末尾にまとめてあるので、 用語の地図として活用してください。

🎨 直感で掴む

🍰 まずはやさしく

数値をまとめて扱うための便利な仕組みです。

計算の手間と時間を大幅に減らすために使います。

部活のメンバー全員の点数を一気に計算するイメージです。

NumPyならではの考え方を直感的に掴みましょう。

NumPy(Numerical Python)は Python におけるベクトル・行列・テンソルの標準データ構造である ndarray と、 それに対する高速な数値演算を提供するライブラリです。 pandas は内部で NumPy を使い、 scikit-learn の入力も基本は NumPy 配列、 PyTorch/TensorFlow のテンソル設計も NumPy を参考にしています。

たとえば 47 都道府県の総人口を 1 本の ndarray に格納すれば、 「全国平均」「上位 10 県」「対数変換」「人口に占めるシェア」などの計算が すべて 1 行 で書けます。 これを純 Python の for ループでやると、 同じ処理に何倍もの行数と時間がかかります。

NumPy の威力は 「ベクトル化」 という思想にあります。 ループは内部の C 実装に任せ、 ユーザーは「配列同士の演算」として書く。 これが NumPy らしいコードです。

list と ndarray の違い

観点Python listNumPy ndarray
要素の型異種混在 OK原則同一 dtype
メモリ配置飛び石(参照配列)連続したバッファ
演算子の意味[1,2]+[3,4] は連結np.array([1,2])+np.array([3,4]) は要素和
速度遅い(Python オブジェクト操作)速い(C/BLAS 呼び出し)
多次元list of list形状 (行, 列, ...) を直接サポート

🎮 触って理解する

NumPy の核心である ブロードキャスト(形状の自動拡張)と ベクトル化(ループを配列演算に置き換える)は、 文章で読むより「触って」理解するのが早い概念です。 以下の 3 つのミニ実験で、 形状・スライス・演算回数がどう変わるかをリアルタイムに確かめてください(マウス・タッチ両対応)。

① ブロードキャスト可視化 — 形状の異なる配列の足し算

破線+フェードインのセルは、 ブロードキャストにより複製されたセル(長さ 1 の軸が引き伸ばされた部分)。 実メモリ上ではコピーされず、 stride=0 のビューとして扱われます。 規則は 3 段階:① shape を右揃えで比較 → ② 長さ 1 の軸は相手の長さに拡張 → ③ どちらも 1 でなく不一致ならエラー。 (3,) と (3, 4) がエラーになるのに (3, 1) と (1, 4) が (3, 4) になる理由を、 実際に切り替えて確かめてください。

② スライシング — a[i:j, k:l] で部分配列を切り出す

a = np.arange(48).reshape(6, 8) の 6×8 配列。 スライダーを動かすか、 グリッドを直接ドラッグ(タッチでなぞる)と選択範囲が変わります。

基本スライスの結果はビュー(元配列とメモリ共有)です。 切り出した部分に代入すると元の a も書き換わります。 独立させたいときは .copy() を明示。 また j ≤ i のような範囲は要素 0 個の「空スライス」になり、 エラーにはなりません(これも実際に動かして確認できます)。

③ ベクトル化の威力 — for ループ vs 一括演算

長さ n の配列に 10 を足す処理(x + 10)を考えます。 スライダーで n を変えると、 Python インタプリタレベルの実行回数がどう変わるかを比較します。

for ループでは 1 反復ごとに「バイトコード解釈・型チェック・int オブジェクト生成」が発生しますが、 ベクトル化では Python レベルの呼び出しは 1 回だけで、 加算本体は C 実装のループ(SIMD で複数要素同時処理)に委譲されます。 n が大きいほど差が開く、 が本質です。

💡 解説の深化 — 直感・落とし穴・発展

直感(配列を丸ごと演算する):NumPy の思考法は「i 番目の要素をどう処理するか」ではなく「配列全体をひとつの数のように扱う」こと。 ①のブロードキャストは「形が合わない配列も、 長さ 1 の軸を伸ばせば丸ごと演算できる」という拡張規則で、 標準化 (X - X.mean(0)) / X.std(0) が 1 行で書けるのはこのおかげです。 ③で見たように、 丸ごと演算は書きやすさだけでなく速度の源泉でもあります(ベクトル・行列演算も参照)。

よくある落とし穴(形状不一致・ビューとコピー):①で (3,) と (3, 4) を選ぶとエラーになるのは、 右揃え比較で (3,) が「長さ 3 の最後の軸」と解釈され、 4 と衝突するため。 「47 県の列平均を引くつもりが (47,) のままでエラー/意図しない (47, 47) 行列」は頻出ミスで、 x[:, None] で (47, 1) に明示するのが定石です。 ②のスライスはビューを返すため、 b = a[1:3]; b[0] = 0 は元の a も書き換えます。 一方 fancy indexing a[[0, 2]] はコピー。 この非対称性がバグの温床なので、 保護したいデータには .copy() を明示してください(pandasdf.values 経由でも同じ罠があります)。

発展(ブロードキャスト規則・einsum・メモリレイアウト):ブロードキャスト規則の完全形は「右揃え → 長さ 1 を拡張 → 不一致はエラー」の 3 段階で、 3 次元以上にもそのまま適用されます。 全ペア距離 np.linalg.norm(X[:, None, :] - Y[None, :, :], axis=2) はその応用例。 さらに np.einsum('ij,jk->ik', A, B) のようなアインシュタイン縮約記法を使うと、 行列積・対角抽出・加重平均などを添字ひとつで統一的に書けます(主成分分析の共分散計算にも応用可)。 速度面では C オーダー(行優先)のメモリレイアウトと stride の理解が重要で、 転置 .T が O(1) で済むのは stride の入れ替えだけだから。 これらの土台の上に scikit-learn や PyTorch のテンソル演算が構築されています。

📐 数式・定義

🍰 まずはやさしく

計算のルールを決めた数式のようなものです。

正確に計算を行うための定義を明確にします。

買い物で合計金額を出す時のルールに似ています。

具体的な計算方法と数式について読みましょう。

NumPy が裏で行っている要素ごとの演算(ufunc)は、 数式で書けば極めて単純です。 2 つの配列 $\mathbf{x}, \mathbf{y}\in\mathbb{R}^n$ に対して:

【要素和・要素積(element-wise)】
$$(\mathbf{x}+\mathbf{y})_i = x_i + y_i,\quad (\mathbf{x}\odot\mathbf{y})_i = x_i\cdot y_i$$
【ブロードキャスト:形状 $(m,n)$ と $(n,)$ の和】
$$\bigl(\mathbf{X}+\mathbf{v}\bigr)_{ij} = X_{ij} + v_j$$
【行列積( @ または np.dot)】
$$\bigl(\mathbf{X}\mathbf{W}\bigr)_{ij} = \sum_{k=1}^{p} X_{ik} W_{kj}$$

これらはすべて C 実装と BLAS(線形代数の高速ライブラリ、 OpenBLAS/MKL/Accelerate)に委譲され、 内部で SIMD やマルチコア並列が走ります。

📐 NumPy 主要数式集

要素和:$$(\mathbf{x} + \mathbf{y})_i = x_i + y_i$$

要素積(Hadamard 積):$$(\mathbf{x} \odot \mathbf{y})_i = x_i \cdot y_i$$

行列積:$$(\mathbf{X}\mathbf{W})_{ij} = \sum_{k=1}^p X_{ik} W_{kj}$$

内積:$$\mathbf{x} \cdot \mathbf{y} = \sum_i x_i y_i$$

外積:$$(\mathbf{x} \otimes \mathbf{y})_{ij} = x_i y_j$$

L2 ノルム:$$\|\mathbf{x}\|_2 = \sqrt{\sum_i x_i^2}$$

L1 ノルム:$$\|\mathbf{x}\|_1 = \sum_i |x_i|$$

標準化:$$Z_{ij} = \frac{X_{ij} - \bar X_j}{s_j}$$

共分散行列:$$\mathbf{C} = \frac{1}{n-1} \mathbf{X}^T \mathbf{X}$$(X はセンタリング済)

最小二乗解:$$\hat{\boldsymbol\beta} = (\mathbf{X}^T \mathbf{X})^{-1} \mathbf{X}^T \mathbf{y}$$

🔬 数式を言葉で読み解く

NumPy の主要概念を「言葉」で読み解きます。

概念意味
ndarray同一 dtype・固定形状の連続メモリ配列a = np.array([1,2,3])
dtype要素のデータ型(int64, float64, bool, complex128a.dtype で確認
shape各軸の長さを示すタプル2 次元 (3,4) なら 3 行 4 列
axis軸の指定。 0=行方向、 1=列方向a.sum(axis=0) は列ごとの和
ブロードキャスト形状の足りない軸を自動補完して演算列ベクトル−行ベクトルで行列差
ufunc要素ごとの汎関数(np.sinnp.exp配列全体に一括適用
view と copyスライスはビュー(共有メモリ)、 fancy index は copy意図せぬ書き換えに注意
strides軸方向に 1 要素進むときのバイト数転置はストライドの入替で O(1)

🔬 深掘り:NumPy の内部実装

メモリレイアウト:C オーダーと F オーダー

NumPy 配列は内部的に「C オーダー(行優先、 row-major)」または「F オーダー(列優先、 column-major)」のどちらかでメモリに格納されます。 デフォルトは C オーダー。 SSDSE-B-2026 の (47, 5) データを C オーダーで格納すると、 メモリ上には「北海道の 5 指標 → 青森の 5 指標 → ... → 沖縄の 5 指標」と並びます。 これにより「ある県の全指標を取り出す」操作(X[0])が連続メモリアクセスになり、 CPU キャッシュが効きます。 一方「ある指標の全県を取り出す」(X[:, 0])は飛び飛びのアクセスになり遅くなります。 R や Fortran は逆の F オーダーがデフォルトで、 「列ごとの操作」を高速化する設計です。

view と copy の見分け方

NumPy 操作の結果がオリジナル配列を 共有するか 複製するかは、 バグの温床になりがちです。 ルール:① 基本スライス a[1:5] はビュー、 ② fancy indexing a[[1,3,5]] やブール indexing a[a > 0] はコピー、 ③ .T.reshape.view() は基本ビュー、 ④ .copy() は必ずコピー。 ビューを書き換えると 元配列も変化するので、 SSDSE-B-2026 を読み込んだ後 region = df.values[:10]; region[0] = 0 とすると、 元の df の北海道行も 0 になります。 安全策は region = df.values[:10].copy()

ブロードキャスト規則の完全形

NumPy のブロードキャスト規則は 3 段階です:(1) 軸数が少ない配列の左に長さ 1 の軸を追加、 (2) 長さ 1 の軸はもう一方の長さに拡張、 (3) どちらの軸も長さ 1 でないが異なる場合はエラー。 SSDSE-B-2026 で X (47, 5)mean (5,) の演算は、 (5,) → (1, 5) → (47, 5) と拡張され、 行ごとに mean が引かれます。 一方 X (47, 5)col_mean (47, 1) なら、 (47, 1) → (47, 5) と拡張され、 列ごとに col_mean が引かれます。 (47,) のままだと「軸 0 と (5,) の軸 1 が衝突」してエラーになるため、 col_mean[:, None] で形状を明示する必要があります。

vectorize と ufunc の違い

np.vectorize は Python の for ループのシンタックスシュガーで、 実は 遅いです。 真の高速化には np.frompyfunc や Numba・Cython で書いた ufunc が必要。 SSDSE-B-2026 の 47 都道府県に対し「カスタム指標計算」を行う場合、 ベクトル化された NumPy 演算 ((X - X.mean()) ** 2 など) で書くか、 numba.njit で JIT コンパイルされた関数を使うのが正攻法。 np.vectorize は「ループの可読性を上げる」目的にのみ使い、 速度は期待しないこと。

BLAS のスレッド数制御

NumPy の np.dot@ 演算子は内部で BLAS(OpenBLAS / Intel MKL / Apple Accelerate)を呼び出します。 デフォルトでは全コアを使ってマルチスレッド並列実行されるため、 ベンチマーク時にはスレッド数を明示的に制御することが重要です。 環境変数 OMP_NUM_THREADS=1 python script.py でシングルスレッド実行に強制できます。 これを忘れると「複数の MPI ジョブで CPU 競合」「シングルスレッドのつもりが 8 倍速」など、 実験結果の再現性を損ねる落とし穴があります。

構造化配列と recarray

NumPy には「複数の dtype を 1 つの配列で扱う」構造化配列機能があります。 dtype=[('Prefecture', 'U10'), ('Pop', 'i8'), ('Births', 'f8')] のように定義すると、 SSDSE-B-2026 の都道府県名・人口・出生数を 1 つの配列に格納できます。 ただし pandas DataFrame のほうがインデックス・ラベル付き操作が便利なので、 実務では pandas が使われます。 構造化配列は「C 構造体と互換性のあるバイナリファイル」を読むときに重宝します。

線形代数モジュール np.linalg

np.linalg 内の主要関数:inv(逆行列)、 solve(連立 1 次方程式)、 eig(固有値)、 svd(特異値分解)、 qr(QR 分解)、 cholesky(コレスキー分解)、 det(行列式)、 norm(ノルム)。 SSDSE-B-2026 の主成分分析(PCA)なら、 共分散行列を np.cov(X.T) で計算し、 np.linalg.eig で固有値・固有ベクトルを取得することで、 47 県を 2 次元に縮約できます。 ただし数値的安定性のためには SVD ベース(sklearn.decomposition.PCA)のほうが推奨されます。

乱数生成器 np.random.Generator

NumPy 1.17 以降、 旧 API(np.random.seed / np.random.rand)は非推奨となり、 新 API(np.random.default_rng(seed))が推奨されています。 新 API は PCG64 という最新の擬似乱数生成アルゴリズムを使い、 統計的性質が改善されました。 ただし本教材では「実データ必須」のため擬似乱数は使わず、 SSDSE-B-2026 の実データを使ってください。

🧪 SSDSE-B-2026 完全実装例

NumPy の主要機能を使い、 SSDSE-B-2026 の前処理から統計量計算までを一気通貫で実装。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) E4501(高等学校生徒数) 北海道 5,092,000 1,681,000 24,430 109,290 東京都 14,086,000 3,205,000 86,348 299,865 沖縄県 1,468,000 350,000 12,549 42,535 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import pandas as pd
import numpy as np

# 1) データ読み込み
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])

# 2) 2023 年度・47 都道府県に絞り、NumPy 配列として取り出し
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)
X = df[['A1101', 'A1303', 'A4101', 'E4501']].values  # 総人口・65歳以上人口・出生数・高校生徒数
print(f'shape={X.shape}, dtype={X.dtype}, ndim={X.ndim}')
print(f'memory={X.nbytes:,} bytes (strides={X.strides})')

# 3) 基本統計量(ベクトル化計算)
mean_vec = X.mean(axis=0)     # 列ごとの平均(ブロードキャストの起点)
std_vec = X.std(axis=0, ddof=1)  # 不偏標準偏差
print(f'平均: {mean_vec}')
print(f'標準偏差: {std_vec}')

# 4) Z-score 正規化(ブロードキャスト)
Z = (X - mean_vec) / std_vec
print(f'正規化後の平均: {Z.mean(axis=0)}')  # ほぼ 0
print(f'正規化後の標準偏差: {Z.std(axis=0, ddof=1)}')  # ほぼ 1

# 5) 相関行列(行列積で一発)
n = len(X)
corr = (Z.T @ Z) / (n - 1)
print(f'相関行列:\n{corr}')

# 6) 共分散行列の固有値分解 → PCA
cov_mat = np.cov(X.T)
eigenvalues, eigenvectors = np.linalg.eig(cov_mat)
# 寄与率
total_var = eigenvalues.sum()
contributions = eigenvalues / total_var
print(f'寄与率: {contributions}')

# 7) 第 1・第 2 主成分への射影
top2 = eigenvectors[:, :2]
PC = X @ top2  # (47, 2) 行列
print(f'PC1 範囲: [{PC[:,0].min():.0f}, {PC[:,0].max():.0f}]')

# 8) 順序統計(NumPy で argsort)
pop_order = np.argsort(X[:, 0])[::-1]
print(f'人口上位 3 県:')
for i in pop_order[:3]:
    print(f'  {df.iloc[i]["Prefecture"]}: {X[i, 0]:,.0f} 人')

# 9) ブール mask による条件抽出
high_birth = X[X[:, 2] > 30000]  # 出生数 > 3 万人
print(f'出生数 > 3 万人の県数: {len(high_birth)}')

# 10) np.linalg.lstsq による最小二乗法
A = np.column_stack([X[:, 0], np.ones(n)])  # [人口, 1] 行列
coef, residuals, rank, sv = np.linalg.lstsq(A, X[:, 2], rcond=None)
print(f'単回帰係数: y = {coef[0]:.5f} * x + {coef[1]:.0f}')
📤 実行例(実測) shape=(47, 4), dtype=int64, ndim=2 memory=1,504 bytes (strides=(8, 376)) 平均: [2645808.5106383 770829.78723404 15473.80851064 62095.76595745] 標準偏差: [2797551.41102309 693839.26591071 17155.47547608 60140.80579202] 正規化後の平均: [-6.14165929e-17 1.41730599e-17 -5.55111512e-17 6.61409461e-17] 正規化後の標準偏差: [1. 1. 1. 1.] 相関行列: [[1. 0.99097877 0.99542498 0.99737901] [0.99097877 1. 0.98030098 0.99019416] [0.99542498 0.98030098 1. 0.99635365] [0.99737901 0.99019416 0.99635365 1. ]] 寄与率: [9.99016445e-01 9.81108988e-04 2.32562204e-06 1.20626651e-07] PC1 範囲: [564390, 14447170] 人口上位 3 県: 東京都: 14,086,000 人 神奈川県: 9,229,000 人 大阪府: 8,763,000 人 出生数 > 3 万人の県数: 8 単回 …(以下略)

このコードのポイントX.mean(axis=0)Z.T @ Znp.linalg.eignp.argsortnp.linalg.lstsq など、 NumPy の主要 API がほぼ全部使われている。 行列演算の高速性と簡潔性が、 NumPy をデータサイエンスの標準ツールにした理由を体感できる。

📊 NumPy パフォーマンス比較表

SSDSE-B-2026 を模した (47, 5) 配列に対し、 同じ計算を 5 通りの方法で実装したときの実行時間比較(M2 MacBook Air 想定)。

実装 実行時間 (μs) 相対速度 解説
Python for ループ + list1201x基準。 Python の解釈オーバーヘッドが支配
np.vectorize951.3x実は遅い。 シンタックスシュガーのみ
numpy ufunc2.548xC 実装で大幅高速化
numba @njit0.8150xJIT コンパイルで C 並み
CuPy (GPU)0.3400x大規模配列ほど効果大

結論:NumPy ufunc だけで Python ループより 50 倍速い。 GPU やコンパイラは 大規模配列(10MB 以上)で初めて効果が出るため、 SSDSE 規模なら NumPy で十分。

🎯 NumPy 演習問題

  1. 形状操作X.shape = (47, 5) から (47, 1, 5) にする操作を書け。 (答:X[:, None, :] または X.reshape(47, 1, 5))
  2. ブロードキャストX (47, 5)v (47,) を行ごとに掛けるには? (答:X * v[:, None])
  3. view vs copya = np.arange(10); b = a[2:5]; b[0] = 99 後の a は? (答:[0,1,99,3,4,...]。 b はビューなので a も変化)
  4. 線形代数:SSDSE-B-2026 の X (47, 5) の主成分の寄与率を計算するコードを書け。 (答:np.linalg.eigvalsh(np.cov(X.T)) / np.trace(np.cov(X.T)))
  5. パフォーマンス:(1000, 1000) の正方行列の積を計算するベストな方法は? (答:A @ B。 内部で BLAS が呼ばれて並列実行)

📈 NumPy 詳細チュートリアル:SSDSE-B-2026 で 47 都道府県データを扱う

本セクションでは、 NumPy の主要機能を SSDSE-B-2026 を題材に段階的に学べるチュートリアルを示します。 「NumPy の何が便利なのか」を実データで体験できる構成。

レッスン 1:配列の作成と基本属性

SSDSE-B-2026 の総人口列(47 要素)を NumPy 配列にすると、 shape=(47,)dtype=int64ndim=1size=47nbytes=376 という属性が確認できます。 メモリは 47 × 8 = 376 バイトで、 Python リスト(~1700 バイト)より 5 倍効率的。 strides=(8,) は「軸 0 方向に 1 要素進むと 8 バイト先」を意味します。

レッスン 2:要素ごとの演算(ufunc)

配列同士の + - * / はすべて 要素ごとに実行されます。 SSDSE-B-2026 の「総人口 A1101」と「65 歳以上人口 A1303」を引き算すると、 47 要素の差配列(65 歳未満人口)が一発で得られます。 これは内部で C 実装の SIMD ループが走るので、 Python の for ループより 50 倍以上高速。

レッスン 3:集約関数(axis 引数)

2 次元配列 X (47, 5) に対し、 X.sum(axis=0) は列ごとの和(5 要素)、 X.sum(axis=1) は行ごとの和(47 要素)、 X.sum() はすべての和(スカラー)。 SSDSE-B-2026 の「47 県 × 5 指標」データなら、 axis=0 で「全国合計」、 axis=1 で「県ごとの指標合計」が得られます。

レッスン 4:ブール indexing

X[X[:, 0] > 5_000_000] で「人口 500 万人超の県だけ」を抽出。 ブール配列を index に使う仕組みで、 SQL の WHERE 句に相当。 SSDSE-B-2026 で「人口上位 5 県」「出生率 8‰ 以上」のようなフィルタが 1 行で書ける。

レッスン 5:fancy indexing

X[[0, 13, 27, 46]] で「北海道・東京都・愛知県・沖縄県」だけを取り出し。 整数リストを index に使う仕組み。 fancy indexing は 必ずコピーを返すので、 オリジナルデータを保護できます。

レッスン 6:ブロードキャスト

SSDSE-B-2026 の X (47, 5) に対し、 列ごとの平均 mean = X.mean(axis=0)(形状 (5,))を X - mean で引くと、 「全 47 行から列方向の平均を引いた」中心化データが得られます。 これは「形状 (47, 5) と (5,) の演算」で、 (5,) → (1, 5) → (47, 5) と自動拡張されます。 これがブロードキャストの威力。

レッスン 7:線形代数(np.linalg)

np.cov(X.T) で共分散行列、 np.linalg.eig(C) で固有値分解、 np.linalg.lstsq(A, b) で最小二乗法、 np.linalg.inv(A) で逆行列。 これら線形代数の主要操作が numpy 1 つで完結。 SSDSE-B-2026 の PCA・線形回帰・距離計算がすべて表現できます。

レッスン 8:形状操作(reshape / transpose)

SSDSE-B-2026 の 47 要素 1 次元配列を arr.reshape(47, 1) で列ベクトルに、 .T で転置。 これらは ビュー(メモリ共有)なので O(1) で実行。 多次元配列を扱う上で必須の操作。

🎓 NumPy 高度トピック

マスク配列(masked array)

numpy.ma モジュールは「欠損値を持つ配列」を扱うための拡張。 SSDSE-B-2026 で小規模自治体の指標が欠損する場合、 np.ma.masked_invalid(X) で NaN を自動マスク。 arr.mean() が NaN を無視して計算してくれる。 ただし pandas の DataFrame の方が高機能で実用的。

構造化 dtype

NumPy には「複数の dtype を持つ複合配列」機能があります:dtype=[('name', 'U10'), ('pop', 'i8'), ('births', 'f8')]。 SSDSE-B-2026 を構造化配列で読み込むと arr['name'] で都道府県名、 arr['pop'] で人口にアクセス可能。 ただし pandas DataFrame のほうが API が便利で、 実務では pandas を使います。

メモリマップファイル(np.memmap)

巨大データ(GB クラス)を RAM に乗せずに扱う技術。 np.memmap('large.bin', dtype='f8', mode='r', shape=(10_000_000,)) で 80MB のバイナリファイルを「あたかも配列のように」アクセス。 SSDSE 規模では不要だが、 全国市区町村レベル(1700 行 × 100+ 指標)になると便利。

np.einsum(アインシュタイン記法)

np.einsum('ij,jk->ik', A, B) で行列積、 np.einsum('ii->i', A) で対角抽出。 物理学のテンソル計算を Python で表現できる強力な機能。 SSDSE-B-2026 の共分散計算 (X.T @ X) / nnp.einsum('ij,ik->jk', X, X) / n と同じ。 複雑なテンソル演算が 1 行で書ける。

NumPy と Pandas のシームレス連携

pandas DataFrame は内部的に NumPy 配列を持ちます。 df.values で NumPy 配列に変換、 pd.DataFrame(arr) で逆変換。 SSDSE-B-2026 を pandas で読み込み、 数値部分だけを df.values で取り出して NumPy で高速計算、 結果を pandas に戻す、 というハイブリッド使用が王道。

NumPy 1.x → 2.x の主な変更

2024 年リリースの NumPy 2.0 では、 ① 文字列 dtype の効率化、 ② Python 3.9+ サポート、 ③ 一部 API の廃止(np.boolnp.bool_)、 ④ ABI 互換性向上。 既存コードを 2.x に移行する際は、 .bool.int のような型エイリアスを明示的な .bool_.int64 に置き換える必要があります。

🔧 NumPy アンチパターン集

アンチパターン 正しいやり方 理由
for i in range(len(arr)): arr[i] = ...arr[:] = ... やベクトル化Python ループは 50x 遅い
np.array([1,2,3]) + np.array([4,5,6]) in loop事前に配列を作って一括演算配列作成のオーバーヘッド
arr.append(x)事前にサイズ確保 or list で集めて変換NumPy 配列は append が遅い
np.vectorize(f)(arr)既存の ufunc を組み合わせる、 numba で JITnp.vectorize は遅い
arr == Nonearr is None または np.isnan(arr)NumPy は np.nan を None と区別
view を返す操作で書き換え必要なら .copy() を明示予期しない元配列の変化
np.matrix クラスを使うnp.ndarray + @ 演算子np.matrix は非推奨

🔬 NumPy 12 ステップ完全ワークフロー

SSDSE-B-2026 の前処理から PCA まで、 NumPy の主要機能を 12 ステップで体験。

Step 1:データを NumPy 配列として読み込み

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) E4501(高等学校生徒数) 北海道 5,092,000 1,681,000 24,430 109,290 東京都 14,086,000 3,205,000 86,348 299,865 沖縄県 1,468,000 350,000 12,549 42,535 …(全 47 行)
1
2
3
4
5
6
import numpy as np
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)  # 2023 年度・47 県
X = df[['A1101', 'A1303', 'A4101', 'E4501']].values  # 総人口・65歳以上人口・出生数・高校生徒数
print(X.shape, X.dtype)  # (47, 4) int64
📤 実行例(実測) (47, 4) int64

Step 2:軸ごとの統計量

1
2
print(X.mean(axis=0))  # 列ごとの平均
print(X.std(axis=0, ddof=1))  # 不偏標準偏差
📤 実行例(実測) [2645808.5106383 770829.78723404 15473.80851064 62095.76595745] [2797551.41102309 693839.26591071 17155.47547608 60140.80579202]

Step 3:標準化(ブロードキャスト)

1
2
3
Z = (X - X.mean(axis=0)) / X.std(axis=0, ddof=1)
print(Z.mean(axis=0))  # ほぼ 0
print(Z.std(axis=0))   # ほぼ 1
📤 実行例(実測) [-6.14165929e-17 1.41730599e-17 -5.55111512e-17 6.61409461e-17] [0.98930451 0.98930451 0.98930451 0.98930451]

Step 4:相関行列

1
2
corr = np.corrcoef(X.T)
print(corr)  # 4x4 対称行列
📤 実行例(実測) [[1. 0.99097877 0.99542498 0.99737901] [0.99097877 1. 0.98030098 0.99019416] [0.99542498 0.98030098 1. 0.99635365] [0.99737901 0.99019416 0.99635365 1. ]]

Step 5:行列積

1
2
M = Z.T @ Z / (len(Z) - 1)
print(M)  # 共分散行列(標準化後は相関行列と一致)
📤 実行例(実測) [[1. 0.99097877 0.99542498 0.99737901] [0.99097877 1. 0.98030098 0.99019416] [0.99542498 0.98030098 1. 0.99635365] [0.99737901 0.99019416 0.99635365 1. ]]

Step 6:固有値分解

1
2
eigvals, eigvecs = np.linalg.eigh(M)
print('固有値(降順):', eigvals[::-1])
📤 実行例(実測) 固有値(降順): [3.97533770e+00 2.05063582e-02 2.62966176e-03 1.52628011e-03]

Step 7:主成分への射影

1
2
PC = Z @ eigvecs[:, ::-1][:, :2]
print('PC1 範囲:', PC[:, 0].min(), PC[:, 0].max())
📤 実行例(実測) PC1 範囲: -7.8420545649646565 1.5601269536081004

Step 8:ブール indexing

1
2
large = X[X[:, 0] > 5_000_000]
print(f'人口 500万人超: {len(large)} 県')
📤 実行例(実測) 人口 500万人超: 9 県

Step 9:argsort で並び替え

1
2
3
order = np.argsort(X[:, 0])[::-1]  # 人口降順
top3 = df.iloc[order[:3]]['Prefecture'].tolist()
print('人口トップ 3:', top3)
📤 実行例(実測) 人口トップ 3: ['東京都', '神奈川県', '大阪府']

Step 10:最小二乗法

1
2
3
A = np.column_stack([X[:, 0], np.ones(len(X))])
coef, _, _, _ = np.linalg.lstsq(A, X[:, 2], rcond=None)
print(f'回帰式: y = {coef[0]:.5f}x + {coef[1]:.0f}')
📤 実行例(実測) 回帰式: y = 0.00610x + -677

Step 11:np.where による条件分岐

1
2
labels = np.where(X[:, 0] > 5e6, '大規模', '小規模')
print('大規模県数:', (labels == '大規模').sum())
📤 実行例(実測) 大規模県数: 9

Step 12:保存・読み込み

1
2
3
np.savez('outputs/ssdse_arrays.npz', X=X, Z=Z, eigvals=eigvals)
data = np.load('outputs/ssdse_arrays.npz')
print(data.files)  # ['X', 'Z', 'eigvals']
📤 実行例(実測) ['X', 'Z', 'eigvals']

🧮 実値で計算してみる(SSDSE-B-2026)

SSDSE-B-2026 の 2023 年 47 都道府県の 総人口(列 A1101)を NumPy で扱ってみます。 47 個の整数が並んだ 1 次元配列を作り、 基本統計量を直接計算します。

実値計算:47都道府県の総人口(2023)

SSDSE-B-2026 から 2023 年の A1101 列を抽出すると、 おおむね次の数字が得られます(単位:人)。

北海道 5,092,000 / 青森 1,184,000 / 岩手 1,163,000 / 宮城 2,264,000 / 秋田 914,000 / 山形 1,026,000 / 福島 1,767,000 / ... / 東京 14,086,000 / ... / 沖縄 1,468,000

NumPy で扱うと:

  • 合計(全国総人口):約 1 億 2435 万人
  • 平均(単純算術平均):約 264.6 万人
  • 標準偏差:約 279.8 万人(東京がはずれ値的に大きいので大きい)
  • 最大:東京 約 1408.6 万人 / 最小:鳥取 約 53.7 万人
  • 東京 ÷ 全国平均 ≒ 5.32 倍(東京 1 都が平均的な県の 5 倍超)

このような統計量の計算が arr.sum() / arr.mean() / arr.std() / arr.max() / arr.min() の 1 行ずつで完了するのが NumPy の最大の魅力です。

🧮 数式に値を入れて手で計算する: ブロードキャストとベクトル化

合成 1D 配列の演算を numpy で実行する。

Step 1: 配列演算

x = [1, 2, 3, 4, 5] x + 10 = [11, 12, 13, 14, 15] x * 2 = [2, 4, 6, 8, 10] x² = [1, 4, 9, 16, 25]

Step 2: 統計

sum = 15 mean = 3.0 std = √2 ≈ 1.414

🐍 Python で再現

1
2
3
4
5
6
import numpy as np
x = np.array([1, 2, 3, 4, 5])
print(f"x + 10: {x + 10}")
print(f"x * 2: {x * 2}")
print(f"x²: {x**2}")
print(f"sum: {x.sum()}, mean: {x.mean()}, std: {x.std(ddof=0):.3f}")

📤 実行結果

x + 10: [11 12 13 14 15] x * 2: [ 2 4 6 8 10] x²: [ 1 4 9 16 25] sum: 15, mean: 3.0, std: 1.414

💬 手計算 (Step 1,2) と Python 出力が完全一致。

🐍 Python 実装

例 1:SSDSE-B-2026 から ndarray を作って統計

🎯 目的:SSDSE-B-2026 の 47 都道府県・2023 年度の「総人口」列を NumPy ndarray に変換し、 合計・平均・標準偏差・最大最小・上位 5 県をすべて 1 行ずつで算出する。 pandas Series ではなく ndarray にする利点(dtype 単一・高速・線形代数 API)を体感する。
📥 入力data/raw/SSDSE-B-2026.csv (cp932、 2 行目の日本語ヘッダは skiprows=[1] で除外)。 2023 年度に絞り、 総人口列 A1101 を整数で to_numpy() したベクトル pop(shape=(47,)、 dtype=int64)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import pandas as pd
import numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)  # 2023 年度・47 県

pop = df['A1101'].astype(int).to_numpy()  # 47 都道府県の総人口 ndarray
print(pop.shape, pop.dtype)
print('合計:', pop.sum())
print('平均:', round(pop.mean(), 2))
print('標準偏差:', round(pop.std(ddof=1), 2))
print('最大-最小:', pop.max() - pop.min())
print('上位 5 県:', np.sort(pop)[::-1][:5])
📤 出力 (47,) int64 合計: 124353000 平均: 2645808.51 標準偏差: 2797551.41 最大-最小: 13549000 上位 5 県: [14086000 9229000 8763000 7477000 7331000] (東京・神奈川・大阪・愛知・埼玉の順)
💬 解釈:ndarray にすると、 各統計量を .sum() / .mean() / .std() / .max() という 1 メソッドで取れる。 「最大−最小 ≈ 1355 万人」は東京都(約 1409 万)と鳥取県(約 54 万)の差を意味する。 上位 5 県だけで全国総人口の約 38 % を占め、 日本の人口集中度の高さがベクトル演算ひとつで露わになる。

例 2:ブロードキャストで「全国比シェア」を一気に

🎯 目的:47 次元の人口ベクトル pop をスカラ(全国合計)で一度に割り、 各都道府県のシェア(%)を 1 行で計算する。 NumPy のブロードキャストが「ループ書かない」を可能にする実例。
📥 入力:例 1 で得た pop(shape=(47,))。 全国合計 pop.sum() はスカラ → ブロードキャストで自動的に 47 要素全てに割られる。
1
2
3
share = pop / pop.sum() * 100      # 47 個の % を一度に算出
print('東京のシェア:', round(share.max(), 2), '%')
print('小数点 2 桁で:', np.round(share, 2)[:5])
📤 出力 東京のシェア: 11.33 % 小数点 2 桁で: [4.09 0.95 0.94 1.82 0.74] (北海道・青森・岩手・宮城・秋田)
💬 解釈:東京 1 都だけで全国の 11 % を占めるという結果は、 日本の人口一極集中を端的に示す。 純 Python なら for ループで 47 回の割り算が必要だが、 NumPy では 1 行・C 実装でループしてくれるので可読性も速度も段違い。

例 3:軸を指定した集計(複数列をまとめて行列扱い)

🎯 目的:年代別人口(4 列)を (47, 4) の行列にし、 axis 指定で「列ごとの平均(全国平均年齢別人口)」「行ごとの和(県別合計)」を分けて計算する。
📥 入力:SSDSE-B-2026 2023 年度の総人口(A1101)・15 歳未満(A1301)・15〜64 歳(A1302)・65 歳以上(A1303)の 4 列を行列 X(shape=(47, 4))に変換。
1
2
3
4
5
cols = ['A1101', 'A1301', 'A1302', 'A1303']  # 総人口・15歳未満・15〜64歳・65歳以上
X = df[cols].astype(int).to_numpy()
print(X.shape)                     # (47, 4)
print('列ごとの平均:', X.mean(axis=0).round(0))   # 4 つの平均
print('年齢 3 区分の合計:', X[:, 1:].sum(axis=1)[:5])
📤 出力 (47, 4) 列ごとの平均: [2645809. 301511. 1573489. 770830.] 年齢 3 区分の合計: [5092000 1184000 1163000 2264000 914000] (北海道〜秋田の年齢別合計)
💬 解釈:年齢 3 区分の和が「総人口(北海道 5,092,000 人など)とほぼ一致」するなら、 年齢区分が網羅的であることを検算できる。 axis=0 は「列方向に潰す」、 axis=1 は「行方向に潰す」と覚えると迷わない。 SSDSE のような (都道府県, 指標) 形式の表は NumPy 行列で扱うのが自然。

例 4:行列積による加重平均(人口加重)

🎯 目的:各都道府県の人口を重みとして、 「年齢別人口割合の全国平均」を行列積 1 発で計算する。 単純平均ではなく、 人口が多い県の値が重視される加重平均。
📥 入力:重みベクトル W(shape=(47,)、 和=1)と、 各県年齢別割合行列 A(shape=(47, 3)、 単位 %)。 SSDSE-B-2026 の 2023 年度を出典とする。
1
2
3
4
5
W = pop / pop.sum()                 # 47 次元の重み
agecols = ['A1301', 'A1302', 'A1303']  # 15歳未満・15〜64歳・65歳以上
A = df[agecols].astype(int).to_numpy() / pop[:,None] * 100  # 各県の年齢別割合 (%)
nationwide = W @ A                  # 行列積(@)でひと息に
print('全国平均の年齢別割合:', np.round(nationwide, 2))
📤 出力 全国平均の年齢別割合: [11.4 59.47 29.13] (15 歳未満 / 15〜64 歳 / 65 歳以上 %)
💬 解釈:65 歳以上が全国平均で 29.1 % という結果は、 単純な 47 県平均(高齢化率の高い秋田・島根に引きずられる)と異なり、 「実際に住んでいる日本人 1 人を等しく扱った場合の比率」となる。 行列積 @ は加重平均・ニューラルネット・線形回帰すべての土台。

例 5:線形代数 — 最小二乗で 1 次回帰

🎯 目的:sklearn を使わず、 NumPy の np.linalg.lstsq だけで「65 歳以上人口で総人口を予測する」単回帰を実行。 線形回帰の本質が「正規方程式の解 = 最小二乗解」であることを体感する。
📥 入力:目的変数 y=総人口、 説明変数 x=65 歳以上人口(共に SSDSE-B-2026 の 2023 年度・47 都道府県)。 切片付き計画行列 X は (47, 2)。
1
2
3
4
5
6
7
8
y = df['A1101'].astype(int).to_numpy()   # 総人口
x = df['A1303'].astype(int).to_numpy()   # 65歳以上人口
X = np.column_stack([np.ones_like(x), x])  # (47,2) 行列
coef, *_ = np.linalg.lstsq(X, y, rcond=None)
print(f'切片 a: {coef[0]:.0f}, 傾き b: {coef[1]:.3f}')
y_hat = X @ coef
rmse = np.sqrt(((y - y_hat)**2).mean())
print('RMSE:', round(rmse, 1))
📤 出力 切片 a: -434130, 傾き b: 3.996 RMSE: 370914.8 (おおよそ:高齢者人口の 4.0 倍ぶんが総人口)
💬 解釈:傾き b ≈ 4.0 は「65 歳以上が 1 万人多い県は、 総人口も約 4.0 万人多い」という関係を意味する。 sklearn の LinearRegression().fit() と同じ係数が、 NumPy だけで 4 行で求まる。 アルゴリズムを理解する一歩。

📂 ケーススタディ・追加実装例

ケース 1:47 都道府県データの相関行列を NumPy で作る

🎯 目的:pandas の .corr() を呼ばず、 NumPy の行列演算だけで「標準化 → ZTZ / (n-1)」の手順を踏み、 5 変数の相関行列を構築する。 相関係数の定義式をコードで確認する。
📥 入力:SSDSE-B-2026 2023 年度の 5 指標(総人口 A1101・年齢 3 区分 A1301〜A1303・出生数 A4101)。 (47, 5) の行列 X → 標準化して Z(平均 0・分散 1)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd, numpy as np
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)

cols = ['A1101', 'A1301', 'A1302', 'A1303', 'A4101']
names = ['総人口', '15歳未満', '15〜64歳', '65歳以上', '出生数']
X = df[cols].astype(float).to_numpy()
# 標準化
Z = (X - X.mean(axis=0)) / X.std(axis=0, ddof=1)
# 相関行列 = (1/(n-1)) Z^T Z
R = (Z.T @ Z) / (len(Z) - 1)
print(pd.DataFrame(R, index=names, columns=names).round(2))
📤 出力 総人口 15歳未満 15〜64歳 65歳以上 出生数 総人口 1.00 1.00 1.00 0.99 1.00 15歳未満 1.00 1.00 0.99 0.99 1.00 15〜64歳 1.00 0.99 1.00 0.98 1.00 65歳以上 0.99 0.99 0.98 1.00 0.98 出生数 1.00 1.00 1.00 0.98 1.00
💬 解釈:全変数が非常に強い正の相関(0.98〜1.00)を示すのは、 これらが「都道府県規模(≒人口)」を共通因子としているため。 多重共線性が極めて高く、 そのまま回帰すれば係数が不安定になる。 「人口で割った密度・割合」へ変換する前処理が必要。

ケース 2:np.where で都道府県を 3 クラスに分類

🎯 目的:SSDSE-B-2026 の都道府県を「500 万人超:大規模」「150 万人超:中規模」「それ以下:小規模」の 3 グループにベクトル化条件分岐で分類する。 純 Python の if/elif/else ループ不要。
📥 入力:47 都道府県の総人口 ndarray pop。 閾値 5,000,000 と 1,500,000。 np.where はネストして 3 値以上の分岐に拡張できる。
1
2
3
4
pop = df['A1101'].astype(int).to_numpy()
cls = np.where(pop > 5_000_000, '大規模',
       np.where(pop > 1_500_000, '中規模', '小規模'))
print(pd.Series(cls).value_counts())
📤 出力 小規模 23 中規模 15 大規模 9 Name: count, dtype: int64 (大規模 = 北海道・埼玉・千葉・東京・神奈川・愛知・大阪・兵庫・福岡)
💬 解釈:大規模 9 都道府県が全国人口の約 55 % を占める。 ネストした np.where は可読性が落ちるため、 4 値以上なら pd.cutnp.select が推奨。 ただし速度は np.where が最速。

ケース 3:fancy indexing で上位 5 県の人口を抽出

🎯 目的np.argsort で人口降順インデックスを取得し、 そのインデックス配列で pop と県名 Series を同時にスライス。 「並べ替えて先頭を取る」が 1 行で実現できる NumPy の真骨頂。
📥 入力:47 県の pop ベクトル。 np.argsort(pop)[::-1][:5] で「人口が多い順の先頭 5 インデックス」を取得。
1
2
3
idx = np.argsort(pop)[::-1][:5]
print('上位 5 県の人口:', pop[idx])
print('都道府県:', df['Prefecture'].iloc[idx].tolist())
📤 出力 上位 5 県の人口: [14086000 9229000 8763000 7477000 7331000] 都道府県: ['東京都', '神奈川県', '大阪府', '愛知県', '埼玉県']
💬 解釈:fancy indexing は「整数配列で別の配列を一気に並べ替える」NumPy の中核機能。 pop[idx]df['Prefecture'].iloc[idx] で「人口と県名」を完全に同期して取り出せるため、 ランキング表生成に重宝する。

ケース 4:np.einsum で人口ピラミッドの加重平均

🎯 目的:Einstein 縮約記法 np.einsum('i,ij->j', ...) で「重みベクトル × 年齢構成行列」を 1 行に圧縮し、 全国の年齢構成比(人口加重)を算出。 添字 i を縮約、 j を残す。
📥 入力:重み w(shape=(47,))、 年齢構成比 age(shape=(47, 3)、 SSDSE-B-2026 由来)。 結果は (3,) ベクトル。
1
2
3
4
w = pop / pop.sum()
age = df[['A1301', 'A1302', 'A1303']].astype(int).to_numpy() / pop[:,None]
result = np.einsum('i,ij->j', w, age)
print('全国の年齢構成(重み付き):', np.round(result*100, 2), '%')
📤 出力 全国の年齢構成(重み付き): [11.4 59.47 29.13] %
💬 解釈w @ age と同じ結果だが、 einsum なら 3 階以上のテンソル(時間 × 県 × 年齢)にも自然に拡張できる。 DL モデルのアテンション計算でも頻出する記法。

ケース 5:SIMD ベクトル化と純 Python ループの速度差

🎯 目的:1000 万要素の合計を「NumPy .sum()」と「純 Python for ループ」で比較し、 NumPy が桁違いに高速な理由(C 実装+SIMD ベクトル化)を体感する。
📥 入力np.arange(10_000_000, dtype=np.float64) (0〜9,999,999 の 1 次元配列、 80 MB)。
1
2
3
4
5
6
7
8
9
import time
a = np.arange(10_000_000, dtype=np.float64)

t0 = time.time(); s = a.sum(); print('NumPy:', time.time()-t0, '秒', s)
t0 = time.time()
s2 = 0.0
for v in a.tolist():
    s2 += v
print('Python loop:', time.time()-t0, '秒', s2)
📤 出力 NumPy: 0.012 秒 4.999995e+13 Python loop: 0.842 秒 4.999995e+13 → NumPy は約 70 倍高速
💬 解釈:両者の合計値は同一(精度は浮動小数点誤差レベル)。 しかし実行時間は約 70 倍違う。 SSDSE のような数百 KB のデータでは差を実感しにくいが、 1 億行のセンサー時系列やゲノム配列では NumPy 一択になる。

ケース 6:np.linalg.eig で共分散行列を固有値分解(PCA の中身)

1
2
3
4
5
cov = np.cov(Z.T)
eigvals, eigvecs = np.linalg.eigh(cov)
order = np.argsort(eigvals)[::-1]
print('固有値:', eigvals[order])
print('累積寄与率:', np.cumsum(eigvals[order]) / eigvals.sum())
📤 実行例(実測) 固有値: [4.96786198e+00 2.35830271e-02 7.54451451e-03 1.01045061e-03 2.41363458e-08] 累積寄与率: [0.9935724 0.998289 0.99979791 1. 1. ]

🪜 ステップバイステップ チュートリアル

チュートリアル:SSDSE-B-2026 を NumPy で 5 ステップ分析

ステップ 1:環境準備

pip install numpy pandas
python -c "import numpy as np; print(np.__version__)"

ステップ 2:データを読み込み ndarray に変換

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1302(15~64歳人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 514,000 2,897,000 1,681,000 24,430 東京都 14,086,000 1,513,000 9,368,000 3,205,000 86,348 沖縄県 1,468,000 236,000 882,000 350,000 12,549 …(全 47 行)
1
2
3
4
5
6
import pandas as pd, numpy as np
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)  # 2023 年度・47 県

cols = ['A1101', 'A1301', 'A1302', 'A1303', 'A4101']  # 総人口・年齢3区分・出生数
X = df[cols].astype(float).to_numpy()  # (47, 5) ndarray

ステップ 3:標準化と主成分分析の中身

1
2
3
4
5
Z = (X - X.mean(axis=0)) / X.std(axis=0, ddof=1)
cov = (Z.T @ Z) / (len(Z) - 1)
eigvals, eigvecs = np.linalg.eigh(cov)
order = np.argsort(eigvals)[::-1]
print('累積寄与率:', np.cumsum(eigvals[order])/eigvals.sum())
📤 実行例(実測) 累積寄与率: [0.9935724 0.998289 0.99979791 1. 1. ]

ステップ 4:第 1 主成分への射影

1
2
3
4
pc1 = Z @ eigvecs[:, order[0]]
ranking = np.argsort(pc1)[::-1]
for i in ranking[:10]:
    print(df['Prefecture'].iloc[i], round(pc1[i], 2))
📤 実行例(実測) 東京都 8.92 神奈川県 5.2 大阪府 5.0 愛知県 4.03 埼玉県 3.73 千葉県 2.87 兵庫県 2.28 福岡県 2.16 北海道 1.84 静岡県 0.71

ステップ 5:結果の解釈

第 1 主成分は「人口規模」を表す総合指標(第 1 主成分だけで寄与率約 99 %)。 上位は東京・神奈川・大阪・愛知、 下位は鳥取・高知・島根。 NumPy だけで PCA の中身まで実装できます。

🚀 現場での応用シナリオ(8 例)

応用 1:画像処理の現場

JPEG/PNG 画像は基本的に (高さ, 幅, チャンネル) の 3 次元 ndarray として表現されます。 ピクセル値の正規化、 グレースケール変換、 畳み込み、 リサイズなど、 ほぼすべての画像操作が NumPy 配列上の数値計算として書けます。 OpenCV や Pillow もバックエンドで NumPy を使い、 ユーザに ndarray を返します。

1
2
3
4
5
import numpy as np
# 画像 → グレースケール(行列の重み付き和)
def to_gray(img):
    weights = np.array([0.299, 0.587, 0.114])
    return img @ weights      # (H, W, 3) @ (3,) = (H, W)

応用 2:センサーデータの統計

1 秒間に 1000 サンプル取れる加速度センサー、 100 Hz の心拍計、 これらは長い 1 次元 ndarray。 移動平均、 FFT、 ピーク検出を NumPy + SciPy で書くと、 1 行ごとに研究級の処理が並びます。

応用 3:機械学習の特徴量行列

scikit-learn の API は基本的に (n_samples, n_features) の 2 次元 ndarray を受けます。 SSDSE で言えば 47 行 × N 列の行列。 標準化・PCA・k-means・回帰すべて、 ここから始まります。

応用 4:金融時系列

OHLCV(始値・高値・安値・終値・出来高)データ、 ボラティリティ、 リターン、 シャープ比、 GARCH、 すべて ndarray と pandas で扱う。 ベクトル化により、 数百万行のバックテストが 1 秒以下に。

応用 5:地理空間データ

緯度経度のメッシュデータ(標高、 気温、 衛星画像)は 2 次元 ndarray。 NumPy で「東京を中心とする 100km × 100km の標高ヒートマップ」を切り出す、 のような操作が標準。

応用 6:自然言語処理の埋め込み

BERT・GPT が出力する単語埋め込みは(語数, 768/1024)の 2 次元 ndarray。 コサイン類似度、 t-SNE、 UMAP もすべて NumPy 演算で完結。

応用 7:科学シミュレーション

波動方程式・拡散方程式・分子動力学のような偏微分方程式の数値解法は、 状態を ndarray で表し、 ufunc とスライスで時間発展を計算する。 結果は HDF5 へ。

応用 8:A/B テストの解析

ユーザを 2 群に分け、 各群のコンバージョン率を ndarray で表し、 ブートストラップで信頼区間を出す。 ベクトル化により 10 万回のリサンプルが数秒。

🏋️ 演習問題(8 題)

  1. SSDSE-B-2026 の 47 都道府県の総人口を ndarray に変換し、 平均・標準偏差・最大・最小を計算せよ。
  2. 総人口の上位 10 県をインデックス操作で抽出し、 都道府県名を出力せよ。
  3. 総人口の対数を取り、 ヒストグラムが正規分布に近づくことを確認せよ。
  4. 総人口と出生数の相関行列を np.corrcoef で計算せよ。
  5. 47 県を 3 つのグループ(大規模/中規模/小規模)に np.where で分類し、 各群の平均人口を出せ。
  6. 総人口の z-score を計算し、 ±2σ を超える県(外れ値)を抽出せよ。
  7. 5 列(人口・男性人口・女性人口・15歳未満・65歳以上)の行列を作り、 共分散行列の固有値分解で第 1 主成分を求めよ。
  8. scikit-learn を使わずに、 NumPy だけで最小二乗回帰を実装し、 「高齢人口 → 総人口」 の係数を求めよ。

🐍 NumPy 実データ追補 (SSDSE-B-2026)

本セクションでは SSDSE-B-2026 を題材に、 NumPy の中核機能 4 種を実コードと実出力で確認する。 各ブロックは「やること → 入力 → 実行例 → 結果の読み方」を必ず備える。

① 都道府県人口を ndarray にしてベクトル統計を求める

このコードでやること: SSDSE-B-2026 の総人口 (A1101) 列を np.array 化し、 平均・標準偏差・最大・最小を一括計算する。

📥 入力データ: data/raw/SSDSE-B-2026.csv (564 行 × 112 列、 都道府県 47 件 × 12 年分)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
import numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df_2023 = df[df['SSDSE-B-2026'] == 2023]
pop = np.array(df_2023['A1101'])

print('shape  :', pop.shape)
print('平均人口:', round(pop.mean(), 1))
print('標準偏差:', round(pop.std(ddof=1), 1))
print('最大   :', pop.max(), '(', df_2023.iloc[pop.argmax()]['Prefecture'], ')')
print('最小   :', pop.min(), '(', df_2023.iloc[pop.argmin()]['Prefecture'], ')')

📤 実行例:

shape : (47,) 平均人口: 2645808.5 標準偏差: 2797551.4 最大 : 14086000 ( 東京都 ) 最小 : 537000 ( 鳥取県 )

💬 結果の読み方: 47 都道府県の人口平均は約 265 万人だが、 標準偏差が約 280 万人と大きく、 分布が右に長く伸びていることを示す。 最大 (東京) と最小 (鳥取) の比は 26 倍を超え、 平均値だけでは語れない散らばりを ndarray の .std() 一発で取り出せる。

② ブロードキャストで人口比 (vs 全国平均) を一括計算

このコードでやること: 各都道府県の人口を全国平均で割り、 「全国平均を 1 とした人口比」 をブロードキャストで取得する。 ループは一切書かない。

📥 入力データ: 上記 pop (shape=(47,)) と平均値 pop.mean() (スカラー)

1
2
3
4
5
6
7
8
ratio = pop / pop.mean()
# 上位 3 都道府県を抜く
idx_top3 = np.argsort(ratio)[::-1][:3]
for i in idx_top3:
    name = df_2023.iloc[i]['Prefecture']
    print(f'{name}: 全国平均の {ratio[i]:.2f} 倍')

print('全国比 > 2.0 の都道府県数:', (ratio > 2.0).sum())

📤 実行例:

東京都: 全国平均の 5.32 倍 神奈川県: 全国平均の 3.49 倍 大阪府: 全国平均の 3.31 倍 全国比 > 2.0 の都道府県数: 7

💬 結果の読み方: ブロードキャスト (shape=(47,) ÷ scalar) により、 一切のループなしで 47 件の比率とブール配列 (ratio>2.0) の合計を取り出せる。 全国平均の 2 倍を超えるのは 7 都道府県のみで、 人口の偏在を NumPy 標準機能だけで定量化できる。

③ 多次元 ndarray で「年×都道府県」 の人口行列を扱う

このコードでやること: SSDSE-B-2026 を pivot し、 行 = 年度、 列 = 都道府県 の 2 次元 ndarray を作って、 軸 (axis) 指定での集計を確認する。

📥 入力データ: df[['SSDSE-B-2026', 'Prefecture', 'A1101']] (564 行 = 47 都道府県 × 12 年)

1
2
3
4
5
6
7
pivot = df.pivot(index='SSDSE-B-2026', columns='Prefecture', values='A1101')
mat = pivot.values   # shape = (12, 47)

print('行列形状      :', mat.shape)
print('年度別の総人口:', mat.sum(axis=1)[:3])  # axis=1 = 列方向
print('県別 12 年平均:', mat.mean(axis=0)[:3])  # axis=0 = 行方向
print('年度間の最大差:', (mat.max(axis=0) - mat.min(axis=0)).max())

📤 実行例:

行列形状 : (12, 47) 年度別の総人口: [127589000 127414000 127238000] 県別 12 年平均: [1791426.6 2591870. 821772.8] 年度間の最大差: 852000

💬 結果の読み方: 2 次元 ndarray では axis=0 が「行方向 (=年度をまたぐ集計)」、 axis=1 が「列方向 (=都道府県をまたぐ集計)」 となる。 axis を明示するだけで「年度別総人口 (先頭 3 つは 2012〜2014 年度)」「県別 12 年平均 (pivot の列は県名の辞書順で、 先頭 3 つは三重県・京都府・佐賀県)」「年度間の最大変動 (東京都の 852,000 人)」 が一行で取れる。 この感覚が NumPy 流の高速集計の基本。

④ 線形代数 (np.linalg) で高齢人口と総人口の回帰直線を求める

このコードでやること: 都道府県別の高齢人口 (A1303) と総人口 (A1101) から、 最小二乗回帰 np.linalg.lstsq で傾き・切片を計算し、 NumPy だけで scikit-learn 相当の処理を完結させる。

📥 入力データ: df_2023 (47 行、 A1303 = 65歳以上人口、 A1101 = 総人口)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
x = np.array(df_2023['A1303'], dtype=float)
y = np.array(df_2023['A1101'], dtype=float)

# 設計行列 [x, 1] を組み、 lstsq で最小二乗解を取得
X = np.vstack([x, np.ones_like(x)]).T
slope, intercept = np.linalg.lstsq(X, y, rcond=None)[0]
y_pred = slope * x + intercept

# 決定係数 R^2 を NumPy で算出
ss_res = np.sum((y - y_pred) ** 2)
ss_tot = np.sum((y - y.mean()) ** 2)
r2 = 1 - ss_res / ss_tot

print(f'傾き   : {slope:.4f}')
print(f'切片   : {intercept:.0f}')
print(f'R^2    : {r2:.4f}')

📤 実行例:

傾き : 3.9956 切片 : -434130 R^2 : 0.9820

💬 結果の読み方: 傾き約 4.00 は「65 歳以上人口 1 人あたり総人口が限界的に約 4.00 人増える(傾きの逆数にすると約 25%)」ことを意味し、 決定係数 R²=0.98 と極めて高い説明力を示す。 これらは scikit-learn を使わず NumPy linalg.lstsq だけで取れる。 配列演算と線形代数の組合せで、 統計回帰の主要量がライブラリ追加なく完結する点が NumPy の威力。

⚠️ よくある落とし穴

❌ dtype の暗黙変換に注意
整数配列に float を代入すると配列全体が float に変わる、 逆に astype(int) は小数を切り捨てる。 想定外の型変換は精度バグの温床。
❌ view と copy の取り違え
a[1:3] はビュー、 a[[1,3]] はコピー。 ビューを書き換えると元配列も変わる。 安全側に倒すなら .copy() を明示。
❌ ブロードキャストの暗黙拡張
形状 (47,)(47,1) は別物。 後者は転置×ブロードキャストで (47,47) の行列を作る。 形状が爆発するとメモリを使い切る。
❌ 乱数の再現性
np.random はグローバル状態を共有するため、 並列ジョブで結果が変わる。 np.random.default_rng(seed) でジェネレータを明示する(ただし教材ページでは合成乱数を使わず、 実データを推奨)。
❌ NaN の伝播
arr.mean() は NaN を含むと結果も NaN。 欠損対応は np.nanmean、 もしくは pandas 側で dropna してから ndarray 化する。

❓ よくある質問(FAQ)

Q: pandas と NumPy は何が違うのですか?
A: NumPy は ndarray が中心で「型が同じ・名前なし」、 pandas は DataFrame で「列ごとに型が違ってよい・行/列に名前がある」。 数値だけの行列演算は NumPy、 表形式データの操作は pandas、 と使い分けます。
Q: NumPy はなぜ高速なのですか?
A: 要素型を固定して連続メモリに配置するため、 CPU のキャッシュ・SIMD・BLAS が効きます。 ループは C 実装内で完結し、 Python のオブジェクト管理オーバヘッドが入りません。
Q: np.array([1,2,3])np.asarray([1,2,3]) の違いは?
A: 入力が既に ndarray なら asarray はコピーせずそのまま返す、 array はデフォルトでコピーします。 関数の引数受けでは asarray が無駄なコピーを避けて好まれます。
Q: スライスは view、 fancy indexing は copy という違いは何故?
A: スライスは「歩幅 (stride) を変えるだけ」で元データを共有できますが、 a[[0,2,5]] のような任意位置の取り出しは連続メモリにならないため、 新しいバッファにコピーする必要があるからです。
Q: メモリ消費が気になります
A: dtype=np.float32 を明示すれば 8 → 4 バイトに半減します。 a.nbytesa.itemsizea.dtype で確認可能。 巨大配列は np.memmap でディスクマップも検討。

📜 歴史と背景

歴史と位置づけ:NumPy のルーツは 1995 年の Numeric(Jim Hugunin)、 続いて 2001 年の Numarray、 そして 2005 年に Travis Oliphant がこれらを統合して NumPy 1.0 をリリース。 以後 20 年にわたり、 Python が「データサイエンスのデファクト言語」になる礎となりました。 2020 年には Nature 誌に「Array programming with NumPy」(Harris ら, 2020)が掲載され、 学術界に与えた影響の大きさが正式に承認されています。

主要バージョンの節目

NumPy はインターフェース面でも歴史的役割を担っており、 PyTorch・JAX・CuPy・Dask・xarray などのライブラリが 「NumPy 互換 API」を採用することで、 ユーザは同じ書き方で異なるバックエンド(GPU・分散)を切り替えられます。

🗺 学習ロードマップ

🗺 学習ロードマップ

  1. レベル 1(入門) — list と ndarray の違い、 dtype、 shape、 axis を理解。 1 次元・2 次元配列の基本操作。
  2. レベル 2(基礎) — ブロードキャスト、 ufunc、 fancy indexing、 統計関数(mean, std, sum, axis)。
  3. レベル 3(応用) — np.linalg(行列演算)、 np.fft(フーリエ変換)、 np.einsum(任意の縮約)。
  4. レベル 4(実践) — pandas / scikit-learn / matplotlib との連携、 view vs copy、 stride。
  5. レベル 5(深化) — メモリ最適化、 chunked I/O、 numba/cython で C 並みの速度、 CuPy で GPU 化。
  6. レベル 6(拡張) — Dask、 JAX、 xarray、 PyTorch Tensor — NumPy 互換 API の他バックエンドへ。

📊 比較表(兄弟手法・選択肢)

NumPy ファミリーの比較

ライブラリ位置強み弱み
NumPy基盤標準・安定・高速GPU 不可、 ラベル無し
pandas表データラベル付き、 時系列大規模で遅い
SciPy科学計算最適化・統計・信号NumPy 依存
CuPyGPUNumPy 互換 + CUDANVIDIA GPU 必須
Dask Array分散大規模・遅延評価API 微妙に異なる
JAX自動微分関数型 + XLA学習コスト
xarray多次元ラベル気象・衛星データ初学者向けでない

📖 用語ミニ辞典

用語意味
ndarray多次元配列。 NumPy の中心データ構造
dtype要素型。 int64, float64, complex128 等
shape各軸の長さ。 (47,5) なら 47 行 5 列
axis集計の軸。 0=列方向、 1=行方向
ufunc要素ごとの普遍関数。 sin, cos, exp 等
ブロードキャスト形状の異なる配列同士の自動拡張ルール
viewメモリを共有するスライス結果
copyメモリを別に確保した複製
stride次の要素までのバイト数
vectorizationループを配列演算で書き換えること
SIMD1 命令で複数データを処理する CPU 機能
BLASBasic Linear Algebra Subprograms。 行列演算の標準

🍳 コードレシピ(コピペ用 15 連発)

レシピコード
配列作成 — 0 / 1 / 連番 / 等差
np.zeros(5); np.ones((3,4)); np.arange(0,10,2); np.linspace(0,1,11)
dtype を指定して作る
np.array([1,2,3], dtype=np.float32)
shape と reshape
a = np.arange(12); b = a.reshape(3,4); print(b.shape)
axis 集計
X.sum(axis=0)  # 列方向
X.mean(axis=1) # 行方向
ブール索引
a[a > 100]   # 条件を満たす要素
a[(a>10) & (a<100)]
fancy index
a[[0,3,5]]   # 任意位置
a[a.argsort()[::-1][:5]]  # 上位5位
ブロードキャスト
X = np.arange(12).reshape(3,4)
mean_col = X.mean(axis=0)
X_centered = X - mean_col   # (3,4) - (4,) = (3,4)
ufunc
np.sin(a); np.log(a); np.exp(a); np.sqrt(a)
行列積 (@) と内積 (dot)
A @ B; np.dot(a, b)
逆行列・線形方程式
np.linalg.inv(A); np.linalg.solve(A, b)
最小二乗
coef, *_ = np.linalg.lstsq(X, y, rcond=None)
固有値分解
vals, vecs = np.linalg.eigh(cov)
SVD
U, S, Vt = np.linalg.svd(X)
FFT
np.fft.fft(signal); np.fft.rfft(real_signal)
save/load
np.save('a.npy', a); a = np.load('a.npy')

NumPy を中心に「上下左右」に伸びる依存関係をツリーで表すと:

                       (機械学習・深層学習層)
                   scikit-learn  PyTorch  TensorFlow  JAX
                          \        |          |       /
                           \       |          |      /
                            ─── ndarray インターフェース ───
                             /     |       |       \
                       pandas    xarray    SciPy   matplotlib
                          \       |         |       /
                           \      |         |      /
                            ───── NumPy core (ndarray, ufunc) ─────
                                   |        |        |
                                  BLAS    LAPACK    SIMD
                          (C / Fortran 数値計算ライブラリ)

このように NumPy は ハードウェア寄りの BLASユーザ寄りの pandas/sklearn の中間でデータを橋渡しする共通言語になっています。

numpy arxiv.org Google Scholar Kaggle GitHub Papers with Co scikit-learn 公

🔗 隣接手法への橋渡し

「NumPy」は単独で完結せず、 前後の手法と組み合わさって価値が発揮される。 入力データの準備 (上流)・同目的の代替手法との比較 (並列)・結果の活用 (下流) という 3 軸で隣接領域を整理する。

この上流・並列・下流の対応を地図化することで、 「NumPy」を中核に据えた分析パイプライン (データ準備 → 手法選択 → 結果の検証と展開) の全体像が見えてくる。

🌳 手法選択フロー

「NumPy」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。

  1. Step 1: 目的は記述か予測か?
    • 記述 (現状把握・要約) → 集計・可視化・要約統計量で全体像を掴む
    • 予測 (未知データへの推定) → モデル構築・検証フェーズへ移行
  2. Step 2: データの種類・規模は?
    • 数値データ・小〜中規模 → 「NumPy」やその拡張手法を直接適用
    • カテゴリデータ → カテゴリ専用の手法 (pandas (DataFrame)) と組み合わせ
    • 大規模・高次元 → 計算効率を考慮した派生手法 (DaskCuPy (GPU)) を選択
  3. Step 3: 結果の解釈・共有は?
    • 専門家向け → 数値指標・統計検定で精緻に評価
    • 非専門家向け → 可視化・自然言語での要約を重視

このフローに沿って判断することで、 「NumPy」を中核とした適切な手法選択ができる。

🧭 解説深化 追補 — 直感・落とし穴・発展を実測で締め直す

本ページには既に豊富な解説・ウィジェット・実装例がありますが、 ここでは 「一度ハマると原因が分かりにくい」タイプの落とし穴を、 SSDSE-B-2026(2023 年・47 都道府県、 cp932 / skiprows=[1] で読み込み)の実測値だけを使って締め直します。 直感で押さえるべき一文、 実データで再現できる罠、 その先の発展、 の順に並べます。

🎨 直感(ひとことで)

NumPy の ndarray は「同じ型の数値を、 隙間なく一列に並べたメモリの塊」です。 だからこそ ① ループを C 実装(ufunc / ベクトル・行列演算)に丸投げでき、 ② 形が合わない配列もブロードキャストで丸ごと演算でき、 ③ その塊を切り取っただけのビューは元とメモリを共有します。 速さの源泉(連続メモリ・C 実装)と、 落とし穴の源泉(型が固定・メモリを共有)は表裏一体だ、 というのが最重要の直感です。 この土台の上に pandasDataFramescikit-learn の特徴量行列・PyTorch のテンソルが乗っています。

⚠️ 落とし穴(SSDSE-B-2026 実測で再現)

❌ ① 整数オーバーフロー — int32 で「二乗・積」を計算すると静かに壊れる
総人口の単純な和は問題になりにくい:全国合計 124,353,000 は int32 の上限 2,147,483,647 の範囲内なので、 pop.astype('int32').sum() でも正しく 124,353,000 が出ます。 ところが二乗や積に踏み込んだ瞬間に破綻します。 東京都の総人口 14,086,000 を int32 のまま二乗すると、 真値 14,086,000² = 198,415,396,000,000int64 なら正しく出る)が上限を超え、 int32 では 791,826,688 というまったく無関係な値に化けます(RuntimeWarning: overflow は出るが例外にはならない)。 47 県の二乗和は本来 int64 で 689,023,745,000,000。 相関・共分散・主成分分析は内部で二乗和を通るため、 dtypeint32 だと結果が静かに嘘になります。 対策:X = df[cols].astype('float64').to_numpy() のように読み込み時点で float64 化する(型変換参照)。
❌ ② 浮動小数点の桁 — float32 は約 1677 万を超える整数を「飛び飛び」でしか表せない
float32 の仮数は 24 ビットで、 整数を厳密に表せるのは 2²⁴ = 16,777,216 まで。 47 都道府県の個々の人口は最大でも東京 14,086,000 < 16,777,216 なので float32 でも正確ですが、 総和や二乗和はこの境界を軽々超え、 「隣り合う表現可能値の間隔」が 1 を上回って丸め誤差が入ります。 メモリ半減目的で安易に astype(np.float32) すると、 大きな集計で末尾がずれます。 統計量は float64(NumPy 既定)で計算し、 保存時だけ float32 に落とすのが安全。
❌ ③ インプレース演算 × ビュー — += が元データを書き換える
基本スライスはビュー(元とメモリ共有)なので、 region = X[:5]; region += 1000 のようなインプレース演算+=*=arr[mask]=…)は、 コピーではなく元の X の北海道〜福島行を直接書き換えますregion = region + 1000(新配列を代入)なら元は無傷ですが、 += は同じバッファを更新するため挙動が真逆。 「前処理で一部だけ補正したつもりが、 生データが汚染されて再現不能」は頻出事故です。 保護したいデータは X[:5].copy() を明示。 DataFrame.values 経由でも同じ罠が続きます。
❌ ④ axis 取り違え — (47,4) 行列で axis=0 と axis=1 を逆にすると意味が反転
総人口・15 歳未満・15〜64 歳・65 歳以上の 4 列を並べた X(形状 (47, 4))で、 X.mean(axis=0)列ごと(全国平均)= [2,645,809, 301,511, 1,573,489, 770,830](実測、 四捨五入)で 4 要素。 一方 X.mean(axis=1)行ごと=「1 県の 4 指標の平均」という意味を成さない 47 要素になります。 「axis は潰す軸」と覚えるのが定石:axis=0 は行を潰して列ごと、 axis=1 は列を潰して行ごと。 標準化 (X - X.mean(0)) / X.std(0) は axis=0 が正しく、 ここを間違えると全県が同じ値に潰れます。
❌ ⑤ NaN の伝播 — 1 個の欠損が集計全体を NaN に染める
arr.mean()arr.sum() は NaN が 1 つでも混じると結果も NaN。 SSDSE-B-2026 本体は欠損が少ないですが、 「人口で割った密度・割合」を作る過程で 0 除算 → inf/nan が生じたり、 外部データと結合した瞬間に欠損が入ります。 対策は np.nanmean/np.nansum で NaN を無視するか、 欠損値処理で pandasdropna してから ndarray 化。 「結果が全部 NaN」になったら、 まず np.isnan(X).any() を打つのが最短の切り分け。
❌ ⑥ ブロードキャストの暴発 — (47,) と (47,1) を取り違えると (47,47) が生まれる
47 県の人口ベクトル同士でも、 pop - pop は形状 (47,) の差(正しい)ですが、 pop[:, None] - pop は (47,1) と (47,) がブロードキャストして(47, 47) の総当たり差行列を生みます。 「列平均を引くつもりが (47,) のまま演算してエラー、 あるいは意図せず 2 次元に膨張」は典型ミス。 引きたい軸を x[:, None](列ベクトル化)で明示するのが安全策です。 大きな行列ではこの暴発がメモリを一気に食います(関連ページ broadcasting.html は未整備のため本文で補足)。

🚀 発展(この先に効く 3 点)

(1) リダクションと keepdimsX.mean(axis=0) は形状 (4,) に潰れますが、 X.mean(axis=0, keepdims=True) なら (1, 4) を保ち、 直後の X - X.mean(0, keepdims=True) がブロードキャストで素直に噛み合います。 軸を潰す(平均分散標準偏差・和)操作と keepdims はセットで覚えると、 形状ミスが激減します。

(2) ファンシー/ブール索引はコピー、 メモリレイアウトは strideX[[0, 13, 46]]X[X[:,0] > 5_000_000]コピーを返す(書き換えても元に影響しない)のに対し、 X[0:3] はビュー、 という非対称が落とし穴 ③ の根っこです。 また NumPy 既定の C オーダー(行優先)では「1 県の全指標」(X[0])が連続アクセスで速く、 「1 指標の全県」(X[:, 0])は飛び石で遅い。 転置 .T が O(1) なのは stride を入れ替えるだけだからです。

(3) NumPy は上位ライブラリの共通言語:ここで整えた「dtype を float64 に固定・欠損を先に処理・axis を意識・ビュー汚染を避ける」という作法は、 そのまま pandas.valuesscikit-learnfit(X, y)線形回帰主成分分析相関/共分散の計算で効きます。 CSV からの読み込み(データエンジニアリング)→ NumPy で型と欠損を整地 → 上位ライブラリへ、 という流れが実務の定番です。

🔗 この追補と関連の深いページ

ベクトル・行列演算 pandas DataFrame 型変換 欠損値 scikit-learn 主成分分析 線形回帰 標準偏差 分散 平均 相関係数 共分散 外れ値 CSV データエンジニアリング

※ 本追補の数値は SSDSE-B-2026(2023 年・47 都道府県)の実測に基づく:全国総人口 124,353,000/東京都 14,086,000(東京都²=198,415,396,000,000、 int32 では 791,826,688 に化ける)/47 県二乗和 689,023,745,000,000/列平均 [2,645,809, 301,511, 1,573,489, 770,830]/float32 の整数厳密表現限界 2²⁴=16,777,216。 合成データは使用していません。

NumPy の中核概念を 4 つの要素に分けて読み解きます。 NumPy の数式表現 \( (\mathbf{X}\mathbf{W})_{ij} = \sum_k X_{ik} W_{kj} \) や \( (\mathbf{X} + \mathbf{v})_{ij} = X_{ij} + v_j \) は、 単なる行列演算の定義ではなく、 NumPy 内部の メモリレイアウト・dtype 規約・ブロードキャスト規則・BLAS 呼び出しという 4 つの仕組みを一行で表現したものです。 SSDSE-B-2026 で 47 都道府県の人口配列を扱うとき、 これら 4 要素が連携することで C 言語並みの速度が実現されます。

要素 ①「dtype(データ型)」:NumPy 配列の dtype 属性は要素が int64float64boolcomplex128 など、 どのバイト幅とビット表現を持つかを定めます。 これにより、 C 言語の固定幅整数や浮動小数点と同じメモリ表現が確保され、 Python の list のように要素ごとにオブジェクトヘッダを持つ無駄がなくなります。 SSDSE-B-2026 の総人口列を int64 で読み込めば、 47 要素 × 8 バイト = 376 バイトの連続領域に収まります。 一方 Python リストで保持すれば、 各 int オブジェクト 28 バイト + ポインタ 8 バイトで合計 1,700 バイト超になり、 メモリ効率が 5 倍以上悪化します。

要素 ②「shape と strides」shape は各軸の長さを示すタプル、 strides は「軸方向に 1 要素進むときのバイト数」を示すタプルです。 (3, 4) 形状の float64 配列なら shape=(3, 4)、 strides=(32, 8)(行方向 32 バイト = 4 要素 × 8 バイト、 列方向 8 バイト)。 この 2 つで配列全体のメモリレイアウトが完全に決まります。 転置 .T はデータをコピーせず、 strides の順序を入れ替えるだけで O(1) で実現されます。 これが NumPy が高速な理由の核心です。

要素 ③「ブロードキャスト」:形状の異なる配列同士の演算で、 足りない軸を自動的に補完する規則です。 (47, 5) 形状の SSDSE-B-2026 データ(47 県 × 5 指標)から (5,) 形状の平均ベクトルを引くと、 平均ベクトルが (1, 5) → (47, 5) に自動拡張されて減算されます。 これにより明示的なループを書かずに X - X.mean(axis=0) という 1 行で標準化前処理が完了します。 内部的には strides=0 のビューが生成され、 メモリは複製されません。

要素 ④「ufunc と BLAS 呼び出し」np.sinnp.exp のような ufunc は、 配列の全要素に C 実装の関数を一括適用します。 さらに行列積 @np.dot は OpenBLAS / Intel MKL / Apple Accelerate という超高速線形代数ライブラリにデータを委譲します。 これらは SIMD 命令(AVX-512 など)とマルチコア並列を使い、 純粋な Python コードより 100〜1000 倍速く動作します。 SSDSE-B-2026 で 47 × 5 のデータに対し共分散行列を計算するとき、 NumPy なら 0.0001 秒、 Python のリスト処理だと 0.05 秒程度かかります。