NumPy: ndarray の基本概念
最終更新:2026-08-26
NumPyの中核となるデータ構造はndarray(N次元配列)です。見た目はPythonリストに似ていますが、その内部はまったく異なります。型は固定され、メモリは連続しており、演算はベクトル化されています。ndarrayの内部構造を理解することが、NumPyを効果的に使うための第一歩です。
1. 学習内容
- ❶ ndarrayの本質:均一・固定型・連続メモリ
- ❷ 次元(ndim)、shape、size
- ❸ 軸の直感的な理解
- ❹ dtypeシステムとメモリ使用量
- ❺ ndarrayとリストのメモリモデルの比較
2. データアナリストの実話
(1) 問題:PythonでExcelを扱う際の混乱
ボブは3行4列のExcelテーブルを持っています。彼はそれをPythonのリストのリスト、つまり外側のリストに3つのサブリストをネストした形で格納しています。2列目を合計したいので、各行を反復処理するforループを書きました。100万行では5秒かかります。さらに悪いことに、行の1つに文字列が紛れ込むと、そのセルに到達するまでループはエラーになりません。
(2) ndarrayによる解決策
チャーリーはボブに、テーブルをnp.arrayでndarrayに変換するよう提案しました。NumPyはデータを連続したメモリブロックに平坦化し、列の合計を1回のCレベルのベクトル化演算で実行します。同じ100万行でもわずか5ミリ秒で済みます。型は作成時に強制されるため、紛れ込んだ文字列は計算の途中ではなく即座に検出されます。
(3) 得られる成果
- 1000倍高速な計算
- 型の不一致は作成時に検出される
- shape・軸・stridesなどのメタデータにより、多次元演算が直感的になる
3. ndarrayの本質
(1) 均一なデータ型
ndarrayのすべての要素は同じ型でなければなりません。これはPythonリストとは根本的に異なります。リストは整数、文字列、さらにはオブジェクトまで混在できますが、ndarrayは1つのdtypeしか許可しません。
import numpy as np
a = np.array([1, 2, 3])
print(a.dtype) # int64
b = np.array([1.0, 2.0, 3.0])
print(b.dtype) # float64
c = np.array([1, 2.0, 3])
print(c.dtype) # float64 (auto upcast)
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(2) 連続したメモリ配置
Pythonリストはポインタの配列を格納します。各ポインタはヒープメモリ上に散らばった個別のPyObjectを指します。一方、ndarrayはすべてのデータを単一の連続したブロックに配置するため、ポインタのオーバーヘッドがなく、CPUキャッシュ効率にも優れています。
| 特徴 | Pythonリスト | ndarray |
|---|---|---|
| 要素の型 | 混在可能 | 同じdtype |
| 格納方式 | ポインタ配列 → 分散したPyObject | 連続したメモリブロック |
| 要素あたりのオーバーヘッド | 28バイト以上(PyObjectヘッダー) | dtype依存(1/2/4/8バイト) |
| キャッシュ効率 | 低い(ポインタのジャンプ) | 優れている(シーケンシャルアクセス) |
| 型安全性 | なし(実行時に検出) | 作成時に強制 |
▶ サンプル
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: 0D/1D/2D/3D配列のプロパティ(難易度 ⭐)
import numpy as np
a0 = np.array(42) # 0D scalar
a1 = np.array([1, 2, 3]) # 1D
a2 = np.array([[1, 2], [3, 4]]) # 2D
a3 = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]]) # 3D
for name, arr in [("0D", a0), ("1D", a1), ("2D", a2), ("3D", a3)]:
print(f"{name}: ndim={arr.ndim}, shape={arr.shape}, size={arr.size}")
# 0D: ndim=0, shape=(), size=1
# 1D: ndim=1, shape=(3,), size=3
# 2D: ndim=2, shape=(2,2), size=4
# 3D: ndim=3, shape=(2,2,2), size=8
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
4. 次元・shape・size
(1) ndim / shape / size
| 属性 | 意味 | 例(shape=(3,4)) |
|---|---|---|
ndim |
次元(軸)の数 | 2 |
shape |
各軸の長さ(タプル) | (3, 4) |
size |
要素の総数 = 各軸の長さの積 | 12 |
import numpy as np
a = np.zeros((3, 4))
print(a.ndim) # 2
print(a.shape) # (3, 4)
print(a.size) # 12
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(2) strides
stridesは、ある軸に沿って1ステップ進むためにメモリ上で何バイトスキップすればよいかを示します。
| 属性 | 意味 | shape=(3,4) dtype=int64 |
|---|---|---|
strides |
各軸のストライド(バイト) | (32, 8) |
itemsize |
要素あたりのバイト数 | 8 |
import numpy as np
a = np.zeros((3, 4), dtype=np.int64)
print(a.strides) # (32, 8)
# axis 0: move 1 row = 4 * 8 = 32 bytes
# axis 1: move 1 col = 1 * 8 = 8 bytes
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
▶ サンプル
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: stridesの可視化(難易度 ⭐⭐)
import numpy as np
a = np.arange(12, dtype=np.int32).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(f"shape: {a.shape}") # (3, 4)
print(f"strides: {a.strides}") # (16, 4)
print(f"itemsize: {a.itemsize}") # 4
# axis 0 stride: 4 elements * 4 bytes = 16
# axis 1 stride: 1 element * 4 bytes = 4
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
5. 軸の直感的な理解
軸はndarrayの演算を理解するための鍵です。axis=0が最も外側の次元、axis=1がその次、というように続きます。
| 軸 | shape内の位置 | 直感的な意味 | 2D shape=(3,4) | 3D shape=(2,3,4) |
|---|---|---|---|---|
| axis=0 | 0番目 | 最も外側 | 行方向(行をまたぐ) | 「レイヤー」軸に沿う |
| axis=1 | 1番目 | 2番目 | 列方向(列をまたぐ) | 「行」軸に沿う |
| axis=2 | 2番目 | 最も内側 | — | 「列」軸に沿う |
覚え方:axis=kはshape[k]の方向に対応します。その軸に沿ってsumやreduceを行うと、その次元が「消滅」します。
import numpy as np
a = np.arange(24).reshape(2, 3, 4)
print(a.shape) # (2, 3, 4)
print(a.sum(axis=0).shape) # (3, 4) — axis 0 eliminated
print(a.sum(axis=1).shape) # (2, 4) — axis 1 eliminated
print(a.sum(axis=2).shape) # (2, 3) — axis 2 eliminated
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
6. dtypeとメモリ使用量
(1) dtypeの型システム
| dtype | 説明 | バイト数 | 範囲 |
|---|---|---|---|
np.int8 |
符号付き8ビット整数 | 1 | -128 ~ 127 |
np.int32 |
符号付き32ビット整数 | 4 | -2^31 ~ 2^31-1 |
np.int64 |
符号付き64ビット整数 | 8 | -2^63 ~ 2^63-1 |
np.float32 |
32ビット浮動小数点数 | 4 | 約±3.4e38、7桁の精度 |
np.float64 |
64ビット浮動小数点数 | 8 | 約±1.8e308、15桁の精度 |
np.bool_ |
ブール値 | 1 | True/False |
np.complex128 |
128ビット複素数 | 16 | float64×2 |
(2) メモリモデル:ndarrayはネストしたリストではない
graph TB
A["ndarray object"] --> B["data pointer<br/>raw memory block"]
A --> C["dtype<br/>element type"]
A --> D["shape<br/>(3, 4)"]
A --> E["strides<br/>(32, 8)"]
B --> F["0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | ... | 2,3"]
style A fill:#4CAF50,color:#fff
style B fill:#2196F3,color:#fff
style F fill:#FF9800,color:#fff
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(3) shapeとメモリの参照表
| shape | ndim | size | dtype=float64のnbytes | strides |
|---|---|---|---|---|
| (10,) | 1 | 10 | 80 | (8,) |
| (3, 4) | 2 | 12 | 96 | (32, 8) |
| (2, 3, 4) | 3 | 24 | 192 | (96, 32, 8) |
| (5, 2, 3, 4) | 4 | 120 | 960 | (192, 96, 32, 8) |
▶ サンプル
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: dtypeごとのメモリ比較(難易度 ⭐)
import numpy as np
for dtype in [np.int8, np.int32, np.int64, np.float32, np.float64]:
a = np.zeros(1000, dtype=dtype)
print(f"{dtype.__name__:10s} itemsize={a.itemsize} nbytes={a.nbytes}")
# int8 itemsize=1 nbytes=1000
# int32 itemsize=4 nbytes=4000
# int64 itemsize=8 nbytes=8000
# float32 itemsize=4 nbytes=4000
# float64 itemsize=8 nbytes=8000
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
▶ サンプル
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: reshapeがデータをコピーしないことの確認(難易度 ⭐⭐)
import numpy as np
a = np.arange(12, dtype=np.int64)
b = a.reshape(3, 4)
print(a.shape, b.shape) # (12,) (3, 4)
print(a.strides, b.strides) # (8,) (32, 8)
print(a.nbytes, b.nbytes) # 96 96
b[0, 0] = 999
print(a[0]) # 999 — same memory!
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
7. 総合例:3D配列の徹底解説
▶ サンプル
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: 3×4×2配列の全プロパティ解析(難易度 ⭐⭐⭐)
import numpy as np
a = np.arange(24, dtype=np.int64).reshape(3, 4, 2)
print("=== Full array ===")
print(a)
# [[[ 0 1]
# [ 2 3]
# [ 4 5]
# [ 6 7]]
# [[ 8 9]
# [10 11]
# [12 13]
# [14 15]]
# [[16 17]
# [18 19]
# [20 21]
# [22 23]]]
print(f"ndim: {a.ndim}") # 3
print(f"shape: {a.shape}") # (3, 4, 2)
print(f"size: {a.size}") # 24
print(f"dtype: {a.dtype}") # int64
print(f"itemsize:{a.itemsize}") # 8
print(f"nbytes: {a.nbytes}") # 192 (24 * 8)
print(f"strides: {a.strides}") # (64, 16, 8)
# axis 0: 4*2*8 = 64 bytes per step
# axis 1: 2*8 = 16 bytes per step
# axis 2: 1*8 = 8 bytes per step
# Verify strides manually
expected_strides = (
a.shape[1] * a.shape[2] * a.itemsize, # 4*2*8 = 64
a.shape[2] * a.itemsize, # 2*8 = 16
a.itemsize # 8
)
print(f"manual strides: {expected_strides}") # (64, 16, 8)
print(f"match: {a.strides == expected_strides}") # True
# axis reduction
print(a.sum(axis=0).shape) # (4, 2)
print(a.sum(axis=1).shape) # (3, 2)
print(a.sum(axis=2).shape) # (3, 4)
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
❓ よくある質問
📖 まとめ
- ndarrayは均一・固定型・連続メモリのN次元配列です
ndimは次元数、shapeは各軸の長さのタプル、sizeは要素の総数です- 軸は外側から内側へ番号が付けられます。axis=0が最も外側、axis=ndim-1が最も内側です
stridesは各軸に沿って進むためのメモリオフセットを表します。reshapeしても基になるデータは変わりませんdtypeは要素の型とitemsizeを決定します。nbytes = size * itemsizeです- ndarrayの連続メモリモデルとリストのポインタジャンプモデルの違いが、パフォーマンス差の根本原因です
📝 練習問題
-
初心者(難易度 ⭐):int8、int32、float32、float64のdtypeで、長さ5000の配列を4つ作成してください。各配列の
nbytesを出力し、dtypeが異なるとメモリ使用量が異なる理由を説明してください。 -
中級(難易度 ⭐⭐):shape=(5, 3, 2)、dtype=int16のndarrayについて、stridesとnbytesを手計算で求め、コードで計算結果を検証してください。
-
上級(難易度 ⭐⭐⭐):shape=(2,3,4)のndarrayがメモリ上でどのように配置されるかを自分の言葉で説明してください。図を描き、stridesにラベルを付け、各要素のメモリオフセットを示してください。