ndarrayの基本概念

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

1. 学習内容



2. データアナリストの実体験

(1) 問題:PythonでのExcel操作、混乱

ボブは、3行4列のExcel表を持っています。彼はこれを、外側のリストの中に3つのサブリストがネストされたPythonのリストのリストとして保存しています。2列目の合計を求めたいので、各行を反復処理するためのforループを書きました。 100万行の場合、これには5秒かかります。さらに悪いことに、行のどこかに文字列が紛れ込んでしまうと、そのセルに到達するまでループは失敗しません。

(2) ndarray による解決策

チャーリーは、ボブに対し、np.array を使ってテーブルを ndarray に変換するよう提案しています。 NumPyはデータを連続したメモリブロックに平坦化し、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
要素の型 任意の組み合わせ 同じデータ型
格納 ポインタ配列 → 分散した PyObject 連続したメモリブロック
要素ごとのオーバーヘッド 28バイト以上(PyObjectヘッダー) dtypeに依存(1/2/4/8バイト)
キャッシュ効率が良い 低い(ポインタジャンプ) 非常に高い(順次アクセス)
型安全性 なし(実行時に検出される) 作成時に強制される

(1) ▶ サンプル

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.

: 0次元/1次元/2次元/3次元配列の特性 (難易度 ⭐)

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. 寸法、形状、およびサイズ

(1) 数 / 形状 / 大きさ

属性 意味 例 (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 は、指定された軸に沿って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 環境で実行する必要があります。

(1) ▶ サンプル

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
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 はその次の次元、というように続きます。

図形内での位置 直感 2D図形=(3,4) 3D図形=(2,3,4)
軸=0 0番目 最外側 行方向(行を横切る) 「層」軸に沿って
軸=1 第1 第2 列方向(列を横切る) 「行」軸に沿って
軸=2 2番目 最も内側 「列」軸に沿って

覚え方axis=kshape[k] の方向に対応しています。その軸に沿って sum/reduce を行うと、その次元が「消去」されます。

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 型システム

データ型 説明 バイト数 範囲
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 真/偽
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) 形状記憶特性参照表

形状 次元数 サイズ データ型=float64 バイト数 ストライド
(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)

(1) ▶ サンプル

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 環境で実行する必要があります。

(2) ▶ サンプル

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. 包括的な例:3次元配列の徹底解説

(1) ▶ サンプル

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 ストライドとは何ですか?
A ストライドとは、各軸に沿って1ステップ進むためにメモリ上で何バイトスキップするかを示すタプルです。これは、ndarrayがデータをコピーせずにリシェイプや転置を行うための鍵となります。
Q float64とfloat32の違いは何ですか?
A float64は8バイトで、小数点以下の桁数は約15桁です。float32は4バイトで、小数点以下の桁数は約7桁です。float32はメモリ使用量が半分ですが、精度は低くなります。 ディープラーニングでは一般的にfloat32が使用され、科学計算では通常float64が使用されます。
Q なぜndarrayは「均質(homogeneous)」と呼ばれるのですか?
A すべての要素がメモリ内に密に配置されており、各要素が同じバイト数を占め、同じ方法で解釈される(共有されるdtypeによって決定される)ためです。 これにより、CPUはベクトル化された命令を用いて複数の要素を一度に処理できるようになり、これがNumPyの高性能の根本的な理由となっています。

📖 まとめ



📝 練習問題

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

  2. 中級(難易度 ⭐⭐):形状=(5, 3, 2)、データ型=int16 の ndarray が与えられたとき、そのストライドと nbytes を手計算で求め、その計算結果をコードを使って検証してください。

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

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%