NumPy: ndarray の基本概念

最終更新:2026-08-26

NumPyの中核となるデータ構造はndarray(N次元配列)です。見た目はPythonリストに似ていますが、その内部はまったく異なります。型は固定され、メモリは連続しており、演算はベクトル化されています。ndarrayの内部構造を理解することが、NumPyを効果的に使うための第一歩です。

1. 学習内容



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) 得られる成果



3. ndarrayの本質

(1) 均一なデータ型

ndarrayのすべての要素は同じ型でなければなりません。これはPythonリストとは根本的に異なります。リストは整数、文字列、さらにはオブジェクトまで混在できますが、ndarrayは1つのdtypeしか許可しません。

PYTHON
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)
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。

(2) 連続したメモリ配置

Pythonリストはポインタの配列を格納します。各ポインタはヒープメモリ上に散らばった個別のPyObjectを指します。一方、ndarrayはすべてのデータを単一の連続したブロックに配置するため、ポインタのオーバーヘッドがなく、CPUキャッシュ効率にも優れています。

特徴 Pythonリスト ndarray
要素の型 混在可能 同じdtype
格納方式 ポインタ配列 → 分散したPyObject 連続したメモリブロック
要素あたりのオーバーヘッド 28バイト以上(PyObjectヘッダー) dtype依存(1/2/4/8バイト)
キャッシュ効率 低い(ポインタのジャンプ) 優れている(シーケンシャルアクセス)
型安全性 なし(実行時に検出) 作成時に強制

▶ サンプル

TEXT 📖 参照専用
> **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配列のプロパティ(難易度 ⭐)

PYTHON
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
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。



4. 次元・shape・size

(1) ndim / shape / size

属性 意味 例(shape=(3,4))
ndim 次元(軸)の数 2
shape 各軸の長さ(タプル) (3, 4)
size 要素の総数 = 各軸の長さの積 12
PYTHON
import numpy as np

a = np.zeros((3, 4))
print(a.ndim)    # 2
print(a.shape)   # (3, 4)
print(a.size)    # 12
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。

(2) strides

stridesは、ある軸に沿って1ステップ進むためにメモリ上で何バイトスキップすればよいかを示します。

属性 意味 shape=(3,4) dtype=int64
strides 各軸のストライド(バイト) (32, 8)
itemsize 要素あたりのバイト数 8
PYTHON
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
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。

▶ サンプル

TEXT 📖 参照専用
> **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の可視化(難易度 ⭐⭐)

PYTHON
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
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。



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=kshape[k]の方向に対応します。その軸に沿ってsumreduceを行うと、その次元が「消滅」します。

PYTHON
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
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。



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はネストしたリストではない

100%
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
TEXT 📖 参照専用
> **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)

▶ サンプル

TEXT 📖 参照専用
> **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ごとのメモリ比較(難易度 ⭐)

PYTHON
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
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。

▶ サンプル

TEXT 📖 参照専用
> **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がデータをコピーしないことの確認(難易度 ⭐⭐)

PYTHON
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!
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。



7. 総合例:3D配列の徹底解説

▶ サンプル

TEXT 📖 参照専用
> **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配列の全プロパティ解析(難易度 ⭐⭐⭐)

PYTHON
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)
TEXT 📖 参照専用
> **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.
⚠️ 注意: 以下のコードは、ローカルの Python 環境で実行する必要があります。



❓ よくある質問

Q ndarrayは異なる型を格納できますか?
A いいえ。ndarrayは均一であり、すべての要素が同じdtypeを共有する必要があります。型を混在させようとすると、NumPyは自動的にアップキャストします(例:int → float)。互換性のない型はエラーになります。
Q 行はどちらですか — axis=0とaxis=1?
A 2次元配列では、axis=0が行方向(行をまたぐ)、axis=1が列方向(列をまたぐ)です。axis=0に沿って合計すると「行が1行にまとまり」、axis=1に沿って合計すると「列が1列にまとまります」。
Q stridesとは何ですか?
A stridesは、各軸に沿って1ステップ進むためにメモリ上で何バイトスキップするかを示すタプルです。これは、ndarrayがデータをコピーせずにreshapeや転置を行える仕組みの鍵です。
Q float64とfloat32の違いは?
A float64は8バイトで約15桁の精度、float32は4バイトで約7桁の精度です。float32はメモリ使用量が半分ですが、精度は低くなります。ディープラーニングでは一般的にfloat32が使われ、科学計算では通常float64が使われます。
Q ndarrayが「均一」と呼ばれる理由は?
A すべての要素がメモリ上に密に配置され、それぞれが同じバイト数を占め、同じ方法で解釈される(共有dtypeによって決定される)ためです。これによりCPUはベクトル化命令で複数の要素を一度に処理でき、これがNumPyの高性能の根本的な理由です。

📖 まとめ



📝 練習問題

  1. 初心者(難易度 ⭐):int8、int32、float32、float64のdtypeで、長さ5000の配列を4つ作成してください。各配列のnbytesを出力し、dtypeが異なるとメモリ使用量が異なる理由を説明してください。

  2. 中級(難易度 ⭐⭐):shape=(5, 3, 2)、dtype=int16のndarrayについて、stridesとnbytesを手計算で求め、コードで計算結果を検証してください。

  3. 上級(難易度 ⭐⭐⭐):shape=(2,3,4)のndarrayがメモリ上でどのように配置されるかを自分の言葉で説明してください。図を描き、stridesにラベルを付け、各要素のメモリオフセットを示してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%