Pythonでの数値計算を高速化!NumPyの基本操作と使い方【完全ガイド】


Pythonでの数値計算を高速化!NumPyの基本操作と使い方【完全ガイド】

はじめに

Pythonは、その読みやすい文法と豊富なライブラリから、Web開発、データ分析、機械学習など、非常に幅広い分野で利用されているプログラミング言語です。しかし、純粋なPython(標準のリストなど)で大量の数値を扱う計算、特に科学技術計算やデータ分析の場面では、実行速度が課題となることがあります。

この課題を解決するために開発されたのが、NumPy (Numerical Python) です。NumPyは、Pythonで高速な数値計算を行うための基本的なパッケージであり、データサイエンスの世界では「必須ライブラリ」として位置づけられています。

なぜNumPyは高速なのか?

その秘密は主に3つあります。

  1. 内部実装: NumPyのコアな部分はC言語やFortranといったコンパイル言語で実装されています。これにより、Pythonのインタープリタを介さずに、最適化された高速なコードで計算が実行されます。
  2. 効率的なデータ構造: NumPyは ndarray (N-dimensional array) という独自のデータ構造を提供します。これは、同じデータ型の要素がメモリ上で連続的に配置されるため、CPUのキャッシュ効率が非常に高く、メモリアクセスが高速です。Pythonのリストは、異なるデータ型の要素を格納でき柔軟ですが、その分メモリ効率が悪く、計算も遅くなります。
  3. ベクトル化演算 (Vectorization): NumPyでは、forループを使わずに配列全体の要素に対して一括で演算(ベクトル化演算)を行えます。これにより、Pythonレベルでのループ処理が不要になり、C言語レベルで最適化されたループが実行されるため、劇的な速度向上が実現します。

この記事は、Pythonでの数値計算を劇的に高速化するNumPyの世界への「完全ガイド」です。環境構築から始まり、NumPyの核心であるndarrayオブジェクトの基本、様々な配列の作成方法、データへのアクセス方法、そしてNumPyの真骨頂である高速な演算やブロードキャスティングまで、豊富なコード例とともに徹底的に解説します。

この記事を読み終える頃には、あなたはNumPyの基本的な操作をマスターし、Pythonの標準リストを使った冗長で遅いコードを、簡潔で高速なNumPyコードに書き換える力を手に入れているでしょう。さあ、データサイエンスと数値計算の世界を切り拓く、NumPyの旅を始めましょう!

NumPyの環境構築

NumPyを使い始める前に、まずはご自身のPython環境にインストールする必要があります。インストールはPythonのパッケージ管理システムであるpipを使えば非常に簡単です。

ターミナル(Windowsの場合はコマンドプロンプトやPowerShell)を開き、以下のコマンドを実行してください。

bash
pip install numpy

AnacondaやMinicondaのようなデータサイエンス向けディストリビューションを使用している場合は、NumPyはすでにインストールされていることがほとんどです。もしインストールされていない場合や、アップデートしたい場合は、condaコマンドを使用します。

bash
conda install numpy

インストールが完了したら、PythonのスクリプトやインタラクティブシェルでNumPyをインポートして使用できます。NumPyをインポートする際には、慣例として np という別名(エイリアス)を付けるのが一般的です。

“`python
import numpy as np

NumPyのバージョンを確認

print(np.version)
“`

これ以降、この記事のコード例はすべて import numpy as np が実行されていることを前提とします。

NumPyの核心:ndarrayオブジェクト

NumPyのすべての機能は、ndarray(N-dimensional array、N次元配列)というオブジェクトを中心に構築されています。これは、Pythonのリストに似ていますが、より高機能で、はるかに高速です。

Pythonのリストとの違い

ndarrayとPythonのリストの根本的な違いを理解することが、NumPyをマスターする第一歩です。

特徴 NumPy ndarray Python list
データ型 すべての要素が同じデータ型(同種、Homogeneous) 異なるデータ型の要素を混在可能(異種、Heterogeneous)
メモリ 連続したメモリ領域に要素を格納(高効率) 各要素へのポインタを格納(非効率)
サイズ 作成時にサイズが固定 動的にサイズを変更可能
演算 要素ごとの高速な数値演算(ベクトル化)をサポート 要素ごとの演算はループが必要で低速

例えば、1から100万までの数値の二乗を計算する処理を考えてみましょう。

Pythonのリストを使った場合:
“`python
import time

start_time = time.time()
my_list = list(range(1, 1000001))
result_list = [x**2 for x in my_list]
end_time = time.time()

print(f”Python list execution time: {end_time – start_time:.4f} seconds”)

出力例: Python list execution time: 0.1037 seconds

“`

NumPyのndarrayを使った場合:
“`python
import numpy as np
import time

start_time = time.time()
my_array = np.arange(1, 1000001)
result_array = my_array ** 2 # ベクトル化演算!
end_time = time.time()

print(f”NumPy array execution time: {end_time – start_time:.4f} seconds”)

出力例: NumPy array execution time: 0.0025 seconds

``
この例では、NumPyがPythonリストの**約40倍**も高速に処理を終えています。
for`ループを明示的に書く必要がなく、コードが簡潔になっている点にも注目してください。これがNumPyの力です。

ndarrayの主要な属性

ndarrayオブジェクトは、自身の情報を保持するいくつかの便利な属性(プロパティ)を持っています。これらは配列の特性を理解する上で非常に重要です。

“`python

2次元配列(3行4列)を作成

arr = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])

ndim: 配列の次元数 (Number of dimensions)

print(f”次元数 (ndim): {arr.ndim}”) # 出力: 2

shape: 各次元の大きさ(形状)をタプルで返す

print(f”形状 (shape): {arr.shape}”) # 出力: (3, 4)

size: 配列の全要素数

print(f”全要素数 (size): {arr.size}”) # 出力: 12

dtype: 要素のデータ型 (Data-type)

print(f”データ型 (dtype): {arr.dtype}”) # 出力: int64

itemsize: 各要素のバイトサイズ

print(f”各要素のバイトサイズ (itemsize): {arr.itemsize}”) # 出力: 8 (int64は8バイト)

data: 配列の実際の要素を保持するバッファ

通常は直接使いませんが、低レベルな操作で利用されます

print(f”メモリアドレス (data): {arr.data}”)
“`

ndarrayの作成方法

NumPyでは、様々な方法でndarrayを作成できます。目的に応じて最適な方法を選びましょう。

1. Pythonのリストやタプルから作成

最も基本的な方法は、np.array()関数を使ってPythonのリストやタプルをndarrayに変換することです。

“`python

1次元配列

arr1d = np.array([1, 2, 3, 4, 5])
print(arr1d) # [1 2 3 4 5]

2次元配列(行列)

arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2d)

[[1 2 3]

[4 5 6]]

データ型を明示的に指定

arr_float = np.array([1, 2, 3], dtype=float)
print(arr_float) # [1. 2. 3.]
print(arr_float.dtype) # float64
“`

2. 特定の値を要素とする配列の作成

データ分析やシミュレーションでは、特定の値(0や1など)で初期化された配列が必要になることがよくあります。

“`python

全ての要素が0の配列を作成 (3行5列)

zeros_arr = np.zeros((3, 5))
print(zeros_arr)

[[0. 0. 0. 0. 0.]

[0. 0. 0. 0. 0.]

[0. 0. 0. 0. 0.]]

全ての要素が1の配列を作成 (2x2x2の3次元配列)

ones_arr = np.ones((2, 2, 2), dtype=int)
print(ones_arr)

[[[1 1]

[1 1]]

[[1 1]

[1 1]]]

指定した値で埋められた配列を作成

full_arr = np.full((2, 4), 7.7)
print(full_arr)

[[7.7 7.7 7.7 7.7]

[7.7 7.7 7.7 7.7]]

単位行列を作成 (対角成分が1、その他が0の正方行列)

identity_matrix = np.eye(4)
print(identity_matrix)

[[1. 0. 0. 0.]

[0. 1. 0. 0.]

[0. 0. 1. 0.]

[0. 0. 0. 1.]]

“`

3. 連番配列の作成

連続した数値や等間隔の数値を持つ配列を簡単に作成できます。

“`python

0から9までの整数配列 (Pythonのrangeに似ている)

range_arr = np.arange(10)
print(range_arr) # [0 1 2 3 4 5 6 7 8 9]

5から15未満まで、2ステップずつの配列

step_arr = np.arange(5, 15, 2)
print(step_arr) # [ 5 7 9 11 13]

0から1までの範囲を5つの要素で等間隔に分割

linspace_arr = np.linspace(0, 1, 5)
print(linspace_arr) # [0. 0.25 0.5 0.75 1. ]

10^1から10^3までを対数スケールで5つの要素で分割

logspace_arr = np.logspace(1, 3, 5)
print(logspace_arr) # [ 10. 31.6227766 100. 316.22776602 1000. ]
“`

4. 乱数配列の作成

機械学習の重みの初期化やシミュレーションで頻繁に使用される乱数配列は np.random モジュールで作成します。

“`python

0.0以上1.0未満の一様乱数で3×4の配列を作成

rand_arr = np.random.rand(3, 4)
print(rand_arr)

平均0、標準偏差1の標準正規分布に従う乱数で3×4の配列を作成

randn_arr = np.random.randn(3, 4)
print(randn_arr)

1から100までの整数の乱数を5つ生成

randint_arr = np.random.randint(1, 101, 5)
print(randint_arr)

乱数シードの固定 (再現性の確保)

シードを固定すると、何度実行しても同じ乱数列が生成される

np.random.seed(42)
print(np.random.rand(3)) # [0.37454012 0.95071431 0.73199394]

np.random.seed(42)
print(np.random.rand(3)) # [0.37454012 0.95071431 0.73199394] (同じ結果)
“`

インデックス参照とスライシング (配列の要素へのアクセス)

ndarrayから特定の要素や部分配列を効率的に取り出す方法は、データ操作の基本です。

基本的なインデックス参照

Pythonのリストと同様に、[] を使って要素にアクセスします。多次元配列の場合は、各次元のインデックスをカンマで区切って指定します。

“`python
arr1d = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(f”arr1d[3] = {arr1d[3]}”) # 3番目の要素 -> 3
print(f”arr1d[-1] = {arr1d[-1]}”) # 最後の要素 -> 9

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

[[1 2 3]

[4 5 6]

[7 8 9]]

1行目、2列目の要素 (インデックスは0から始まる)

print(f”arr2d[1, 2] = {arr2d[1, 2]}”) # -> 6

Pythonリスト風の書き方も可能だが、カンマ区切りが推奨される

print(f”arr2d[1][2] = {arr2d[1][2]}”) # -> 6
``
カンマ区切りの
arr2d[1, 2]` の方が、1回のアクセスで済むため効率的です。

スライシング

スライシングは、start:stop:step の形式で部分配列を切り出す機能です。これもPythonのリストと似ていますが、多次元配列に適用できる点が強力です。

“`python
arr1d = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]

インデックス1から4まで (5は含まない)

print(arr1d[1:5]) # [1 2 3 4]

最初からインデックス4まで

print(arr1d[:5]) # [0 1 2 3 4]

インデックス5から最後まで

print(arr1d[5:]) # [5 6 7 8 9]

全要素を2つ飛ばしで

print(arr1d[::2]) # [0 2 4 6 8]

多次元配列のスライシング

arr2d = np.arange(1, 13).reshape(3, 4)

[[ 1 2 3 4]

[ 5 6 7 8]

[ 9 10 11 12]]

最初の2行を取得

print(arr2d[:2, :])

[[1 2 3 4]

[5 6 7 8]]

1列目から2列目までを取得

print(arr2d[:, 1:3])

[[ 2 3]

[ 6 7]

[10 11]]

1行目、1列目から2列目まで

print(arr2d[1, 1:3]) # [6 7]
“`

ファンシーインデックス (Fancy Indexing)

インデックスとして整数の配列やリストを渡すことで、特定の複数の要素を一度に抽出できます。これは非常に強力な機能です。

“`python
arr = np.arange(10) * 10 # [ 0 10 20 30 40 50 60 70 80 90]

インデックスが 2, 5, 8 の要素を抽出

indices = [2, 5, 8]
print(arr[indices]) # [20 50 80]

負のインデックスも使用可能

print(arr[[0, -1, -2]]) # [ 0 90 80]

2次元配列でのファンシーインデックス

arr2d = np.arange(1, 10).reshape(3, 3)

[[1 2 3]

[4 5 6]

[7 8 9]]

0行目と2行目を抽出

print(arr2d[[0, 2]])

[[1 2 3]

[7 8 9]]

(0, 1), (1, 2), (2, 0) の要素を抽出

print(arr2d[[0, 1, 2], [1, 2, 0]]) # [2 6 7]
“`

ブールインデックス (Boolean Indexing)

NumPyで最もよく使われるテクニックの一つが、ブール値(True/False)の配列を使ったインデックス参照です。条件に合う要素だけを効率的に抽出できます。

“`python
data = np.random.randn(5, 4)

[[ 1.22695551 -0.42261596 -0.63806622 -0.16147988]

[-0.26456677 0.49557431 -0.232937 -0.5739839 ]

[ 0.38042436 -0.55743542 1.0829375 0.11433333]

[-0.29797277 -0.58787926 -2.30243575 1.75847493]

[ 0.36636184 0.53489818 -0.96549658 0.92345638]]

dataが0より大きい要素はTrue, それ以外はFalseのブール配列が返る

print(data > 0)

[[ True False False False]

[False True False False]

[ True False True True]

[False False False True]

[ True True False True]]

このブール配列を使って、Trueに対応する要素だけを抽出 (1次元配列になる)

positive_values = data[data > 0]
print(positive_values)

条件に合う要素を特定の値で置き換える

data[data < 0] = 0
print(data)

[[1.22695551 0. 0. 0. ]

[0. 0.49557431 0. 0. ]

[0.38042436 0. 1.0829375 0.11433333]

[0. 0. 0. 1.75847493]

[0.36636184 0.53489818 0. 0.92345638]]

“`

複数の条件を組み合わせる場合は、Pythonの and, or ではなく、ビット演算子の & (AND), | (OR) を使います。各条件式を () で囲むことを忘れないでください。

“`python
arr = np.arange(20)

3の倍数 または 5の倍数を抽出

condition = (arr % 3 == 0) | (arr % 5 == 0)
print(arr[condition])

[ 0 3 5 6 9 10 12 15 18]

10以上 かつ 偶数の値を抽出

condition = (arr >= 10) & (arr % 2 == 0)
print(arr[condition])

[10 12 14 16 18]

“`

NumPyの演算 (高速化のキモ)

NumPyの真価は、高速な「ベクトル化演算」にあります。これは、配列の各要素に対して繰り返し処理を記述することなく、配列全体に直接演算子や関数を適用できる機能です。内部的には高度に最適化されたC言語のループが実行されます。

ユニバーサル関数 (ufunc)

NumPyの多くの演算は、ユニバーサル関数(Universal Function、ufunc)として実装されています。これは、ndarrayの要素ごとに操作を実行する関数で、非常に高速です。

四則演算

配列とスカラ値(単一の数値)、または同じ形状の配列同士で、標準的な算術演算子が使えます。

“`python
arr = np.array([[1, 2], [3, 4]])

配列とスカラの演算

print(“arr + 5:\n”, arr + 5)
print(“arr * 2:\n”, arr * 2)

配列同士の演算 (要素ごと)

arr2 = np.array([[10, 20], [30, 40]])
print(“arr + arr2:\n”, arr + arr2)
print(“arr * arr2:\n”, arr * arr2) # 要素ごとの積 (行列積ではない!)
“`

数学関数

NumPyは、三角関数、指数関数、対数関数など、豊富な数学関数(ufunc)を提供しています。

“`python
x = np.array([0, np.pi/2, np.pi])

print(“sin(x):”, np.sin(x))

[0.0000000e+00 1.0000000e+00 1.2246468e-16] (ほぼ0)

arr = np.array([1, 4, 9])
print(“sqrt(arr):”, np.sqrt(arr)) # [1. 2. 3.]

arr = np.array([1, np.e, np.e**2])
print(“log(arr):”, np.log(arr)) # 自然対数 -> [0. 1. 2.]
“`

統計関数(集約関数)

配列全体の合計、平均、最大値などを計算する関数も豊富に用意されています。これらの関数は、axis引数を指定することで、行ごとや列ごとの集計が可能です。

axis引数の考え方:
* axis=0: 列方向(縦方向)に演算。結果の次元数が1つ減る。
* axis=1: 行方向(横方向)に演算。結果の次元数が1つ減る。

“`python
data = np.arange(1, 10).reshape(3, 3)

[[1 2 3]

[4 5 6]

[7 8 9]]

配列全体の合計

print(“Total sum:”, np.sum(data)) # 45

または data.sum()

列ごとの合計 (axis=0)

print(“Sum of each column:”, np.sum(data, axis=0)) # [12 15 18]

行ごとの合計 (axis=1)

print(“Sum of each row:”, np.sum(data, axis=1)) # [ 6 15 24]

平均値

print(“Mean:”, np.mean(data)) # 5.0

標準偏差

print(“Standard Deviation:”, np.std(data)) # 2.5819…

最大値・最小値

print(“Max value:”, np.max(data)) # 9
print(“Min value of each row:”, np.min(data, axis=1)) # [1 4 7]

最大値・最小値のインデックス

print(“Index of max value:”, np.argmax(data)) # 8 (フラット化した配列でのインデックス)
print(“Index of max value in each column:”, np.argmax(data, axis=0)) # [2 2 2]
“`

配列の形状操作 (Reshaping and Transposing)

データを分析する際には、配列の形状を柔軟に変更する必要があります。

reshape(): 形状の変更

reshape()メソッドは、要素数を変えずに配列の形状を変更します。

“`python
arr = np.arange(12) # [ 0 1 2 3 4 5 6 7 8 9 10 11]

3行4列の配列に変換

reshaped_arr = arr.reshape(3, 4)
print(reshaped_arr)

[[ 0 1 2 3]

[ 4 5 6 7]

[ 8 9 10 11]]

reshapeの引数に-1を指定すると、他の次元のサイズから自動的に計算される

非常に便利な機能

reshaped_auto = arr.reshape(4, -1) # 4行x自動計算列
print(reshaped_auto)

[[ 0 1 2]

[ 3 4 5]

[ 6 7 8]

[ 9 10 11]]

“`

ravel() / flatten(): 1次元化

多次元配列を1次元配列に変換(平坦化)します。

“`python
arr2d = np.arange(6).reshape(2, 3)

[[0 1 2]

[3 4 5]]

ravel() はビュー(参照)を返すことがある(効率的)

raveled_arr = arr2d.ravel()
raveled_arr[0] = 100
print(raveled_arr) # [100 1 2 3 4 5]
print(arr2d) # 元の配列も変更される!

[[100 1 2]

[ 3 4 5]]

flatten() は常にコピーを返す(安全)

flattened_arr = arr2d.flatten()
flattened_arr[1] = 200
print(flattened_arr) # [100 200 2 3 4 5]
print(arr2d) # 元の配列は変更されない

[[100 1 2]

[ 3 4 5]]

“`

T / transpose(): 転置

配列の行と列を入れ替える操作です。

“`python
arr = np.arange(1, 7).reshape(2, 3)

[[1 2 3]

[4 5 6]]

.T属性で転置

print(arr.T)

[[1 4]

[2 5]

[3 6]]

np.transpose()関数でも同じ

print(np.transpose(arr))
“`

ブロードキャスティング (Broadcasting)

ブロードキャスティングは、NumPyの最も強力で、時に混乱を招く機能の一つです。これは、形状が異なる配列同士の演算を、あたかも同じ形状であるかのように自動的に拡張して実行する仕組みです。これにより、不要なデータのコピーを防ぎ、メモリ効率と実行速度を向上させます。

ブロードキャスティングのルール:

2つの配列を演算する際、NumPyは末尾の次元から順に比較します。

  1. 次元数が異なる場合: 次元数が少ない方の配列の形状の先頭に 1 を追加して、次元数を合わせる。
  2. 各次元の比較: 各次元で、以下のいずれかの条件を満たせば互換性があるとみなされる。
    • 2つの配列の次元の大きさが等しい
    • どちらかの次元の大きさが 1 である。
  3. 互換性がない場合: 上記のどの条件も満たさない場合は、ValueErrorが発生する。

互換性がある場合、大きさが 1 の次元は、もう一方の配列の次元の大きさに合わせて「引き伸ばされ(ブロードキャストされ)」てから演算が実行されます。

具体例1: 配列とスカラ

python
a = np.array([1, 2, 3]) # shape (3,)
b = 5 # スカラ
c = a + b # [6 7 8]

これは最も単純なブロードキャストです。スカラ bnp.array([5, 5, 5]) のように拡張されて計算されます。

具体例2: 2次元配列と1次元配列

“`python
A = np.arange(1, 10).reshape(3, 3) # shape (3, 3)

[[1 2 3]

[4 5 6]

[7 8 9]]

B = np.array([10, 20, 30]) # shape (3,)

A + B

A (3, 3) vs B (3,)

1. 次元数を合わせる -> B becomes (1, 3)

2. 各次元を比較

– A.shape[0] (3) vs B.shape[0] (1) -> OK (1が3に拡張)

– A.shape[1] (3) vs B.shape[1] (3) -> OK (等しい)

結果、Bの[10, 20, 30]が各行に足される

C = A + B
print(C)

[[11 22 33]

[14 26 36]

[17 29 39]]

“`

具体例3: 列ベクトルと行ベクトルの加算

“`python
col_vec = np.array([[1], [2], [3]]) # shape (3, 1)
row_vec = np.array([10, 20, 30]) # shape (3,)

col_vec + row_vec

col_vec (3, 1) vs row_vec (3,)

1. 次元数を合わせる -> row_vec becomes (1, 3)

2. 各次元を比較

– col_vec.shape[0] (3) vs row_vec.shape[0] (1) -> OK (1が3に拡張)

– col_vec.shape[1] (1) vs row_vec.shape[1] (3) -> OK (1が3に拡張)

result = col_vec + row_vec
print(result)

[[11 21 31]

[12 22 32]

[13 23 33]]

“`
この例では、(3, 1)の配列が(3, 3)に、(1, 3)の配列も(3, 3)に拡張され、それぞれの要素が足し合わされます。

ブロードキャスティングを使いこなせば、冗長なループやメモリを大量に消費する中間配列の作成を避けることができ、非常にエレガントで効率的なコードを書くことができます。

NumPyの実践的な応用例

例1: 画像処理の基礎

画像データは、ピクセル値を持つ多次元配列として表現できます。NumPyを使えば、画像処理も簡単に行えます。(Pillowライブラリのインストールが必要です: pip install Pillow

“`python
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

画像を読み込み、NumPy配列に変換

この例では、64×64のサンプル画像を生成します

try:
img = Image.open(‘some_image.png’).resize((64, 64))
except FileNotFoundError:
print(“サンプル画像を生成します。”)
img_data = np.random.randint(0, 256, (64, 64, 3), dtype=np.uint8)
img = Image.fromarray(img_data)

img_arr = np.array(img)
print(f”Image shape: {img_arr.shape}, dtype: {img_arr.dtype}”)

グレースケール変換 (加重平均法)

Gray = 0.299 * R + 0.587 * G + 0.114 * B

weights = np.array([0.299, 0.587, 0.114])
gray_arr = np.dot(img_arr[…,:3], weights).astype(np.uint8)

明るさを調整 (ベクトル化演算)

brighter_arr = np.clip(img_arr * 1.5, 0, 255).astype(np.uint8)

左右反転 (スライシング)

flipped_arr = img_arr[:, ::-1, :]

画面に表示

fig, axes = plt.subplots(1, 4, figsize=(12, 4))
axes[0].imshow(img_arr)
axes[0].set_title(‘Original’)
axes[1].imshow(gray_arr, cmap=’gray’)
axes[1].set_title(‘Grayscale’)
axes[2].imshow(brighter_arr)
axes[2].set_title(‘Brighter’)
axes[3].imshow(flipped_arr)
axes[3].set_title(‘Flipped’)
for ax in axes: ax.axis(‘off’)
plt.show()
“`

例2: 線形代数

NumPyは線形代数ライブラリ numpy.linalg を含んでおり、行列積、逆行列、固有値問題などを簡単に解くことができます。

“`python

行列積

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

方法1: np.dot()

dot_product = np.dot(A, B)
print(“Dot product:\n”, dot_product)

方法2: @ 演算子 (Python 3.5以降で推奨)

matmul_product = A @ B
print(“\nMatmul (@) product:\n”, matmul_product)

連立一次方程式を解く

2x + 3y = 8

4x + y = 6

[[2, 3], * [[x], = [[8],

[4, 1]] [y]] [6]]

coeffs = np.array([[2, 3], [4, 1]])
constants = np.array([8, 6])

solution = np.linalg.solve(coeffs, constants)
print(f”\nSolution (x, y): {solution}”) # [1. 2.]

逆行列

inv_coeffs = np.linalg.inv(coeffs)
print(“\nInverse matrix:\n”, inv_coeffs)

固有値と固有ベクトル

eigenvalues, eigenvectors = np.linalg.eig(A)
print(“\nEigenvalues:”, eigenvalues)
print(“Eigenvectors:\n”, eigenvectors)
“`

パフォーマンス向上のためのヒント

1. Pythonのforループを避ける (ベクトル化せよ)

NumPyのパフォーマンスを最大限に引き出すための最も重要なルールは、Pythonのforループを可能な限り使わないことです。代わりに、NumPyが提供するベクトル化演算、ufunc、集約関数、ブロードキャスティングを駆使してください。

2. コピーとビューの違いを理解する

NumPyでは、配列操作が新しい配列(コピー)を返すか、元の配列のデータを共有する「ビュー」を返すかが異なります。これを理解しないと、意図しない副作用やパフォーマンスの低下を招きます。

  • ビュー (View): 元の配列とメモリを共有します。ビューへの変更は元の配列にも反映されます。スライシングは基本的にビューを返します。
  • コピー (Copy): 完全に独立した新しい配列を作成します。メモリを新たに消費しますが、元の配列に影響を与えません。ファンシーインデックスやブールインデックス、copy()メソッドはコピーを返します。

“`python
arr = np.arange(5)

スライシングはビューを返す

view_arr = arr[1:4]
view_arr[0] = 99 # ビューを変更
print(f”Original array: {arr}”) # -> [ 0 99 2 3 4] 元の配列も変更された!

明示的にコピーを作成

copy_arr = arr.copy()
copy_arr[0] = 0 # コピーを変更
print(f”Original array: {arr}”) # -> [ 0 99 2 3 4] 元の配列は変更されない
``
メモリを節約したい場合はビューが有効ですが、意図しない変更を防ぎたい場合は明示的に
.copy()` を使いましょう。

3. 適切なデータ型 (dtype) を選択する

巨大な配列を扱う場合、適切なデータ型を選ぶことでメモリ使用量を大幅に削減できます。例えば、値の範囲が0から255に収まることがわかっているなら、int64(8バイト)の代わりにuint8(1バイト)を使うことで、メモリを1/8に節約できます。

“`python
large_arr_64 = np.zeros(10**7, dtype=np.int64)
print(f”int64 array size: {large_arr_64.nbytes / 1e6:.2f} MB”) # 80.00 MB

large_arr_8 = np.zeros(10**7, dtype=np.uint8)
print(f”uint8 array size: {large_arr_8.nbytes / 1e6:.2f} MB”) # 10.00 MB
“`

まとめ

本記事では、Pythonにおける高速な数値計算を実現するライブラリNumPyについて、その基本から応用までを網羅的に解説しました。

  • NumPyの高速性の源泉: C言語による実装、効率的なndarray、そしてベクトル化演算。
  • ndarrayオブジェクト: NumPyの核心。その属性やPythonリストとの違いを理解しました。
  • 配列の作成と操作: np.array, np.zeros, np.arange などの作成関数から、インデックス参照、スライシング、ファンシーインデックス、ブールインデックスといった強力なデータ抽出方法まで学びました。
  • 高速な演算: forループを排除するufunc、統計関数、そして形状が異なる配列間の演算を可能にするブロードキャスティングの仕組みを詳解しました。
  • 実践的な応用: 画像処理や線形代数といった具体的な例を通して、NumPyの強力さを体感しました。

NumPyは、単独で使われるだけでなく、データ分析ライブラリのPandas、機械学習ライブラリのScikit-learn、ディープラーニングフレームワークのTensorFlowPyTorch、そして可視化ライブラリのMatplotlibなど、データサイエンスエコシステムのあらゆる場面でその基盤として機能しています。

NumPyをマスターすることは、Pythonでデータを扱うすべての人のパフォーマンスを飛躍的に向上させるための、最も確実な一歩です。この記事で得た知識を土台に、ぜひ実際のデータでNumPyの力を試し、より高度なデータ処理の世界へと進んでいってください。Happy Numpying

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール