Pythonでプログラミングを始めると、データを扱う際に最も基本的で頻繁に使われるデータ構造として「リスト」が登場します。しかし、数値計算やデータ処理の効率性を重視する場面では、Python標準のリストではなく、「NumPy配列」を使うことが推奨されます。特に科学計算や機械学習といった分野では、NumPy(ナンパイ)は欠かせないライブラリのひとつです。
この記事では、Python初学者の方向けに「リスト」と「NumPy配列」の違いに焦点を当てて解説します。それぞれの使い方、計算方法、そして処理速度の違いについて具体例を交えながら比較し、どちらをどのように使えばよいのかを学んでいきましょう。
この記事で学べること
- PythonのリストとNumPy配列の基本的な使い方
- それぞれの計算方法と記述の違い
- 処理速度の差を測定する方法
- リストとNumPy配列の使い分け方
Python標準のリストは、柔軟性が高くさまざまなデータを保持できる一方で、数値計算の効率性においてはやや劣る点があります。一方のNumPy配列は、効率的に数値計算を行うことを目的に作られたデータ構造です。NumPy配列を活用することで、コードを短く書けるだけでなく、計算速度を大幅に向上させることが可能です。
これから順を追って具体例を見ていきます。まずは、「リスト」と「NumPy配列」で空のデータ構造を作る方法から学んでみましょう。
第2章: 空の配列を作る方法
Pythonでプログラミングを始めるとき、多くの場面で「空のデータ構造」を作る必要があります。この空のデータ構造は、後から値を追加したり、計算結果を格納したりするために便利です。ここでは、PythonのリストとNumPy配列で空のデータ構造を作成する方法を比較してみましょう。
リストで空のデータ構造を作成する
Python標準のリストで空のデータ構造を作る場合、非常にシンプルなコードで実現できます。以下がその例です。
# 空のリストの作成
python_list = []
print(python_list) # 出力: []
print(type(python_list)) # 出力: <class 'list'>
リストを初期化する際には、単に []
を使えばOKです。初期値が設定されていないため、空の状態が維持されます。このリストに要素を追加していくことで、さまざまなデータを格納することができます。
NumPy配列で空のデータ構造を作成する
NumPy配列で空のデータ構造を作る場合、numpy.empty()
関数を使用します。この関数は、指定された形状の配列を作成しますが、要素の値は初期化されず、ランダムな値が含まれます。
以下が基本的な例です。
import numpy as np
# 空のNumPy配列の作成
numpy_array = np.empty(0)
print(numpy_array) # 出力: []
print(type(numpy_array)) # 出力: <class 'numpy.ndarray'>
リストと同じく、空の配列が作成されます。ただし、NumPy配列のデータ型は numpy.ndarray
であることに注意してください。
empty()
関数の引数を活用して形状を指定する
NumPyのempty()
関数は、引数に配列の「形状」を指定することで、多次元配列を作成することができます。以下は、1次元と2次元の例です。
# 1次元の空配列
array_1d = np.empty(5) # 要素数5の配列を作成
print(array_1d)
# 2次元の空配列
array_2d = np.empty((2, 3)) # 2行3列の配列を作成
print(array_2d)
出力例
[1.23e-310 0.00e+000 2.34e-310 0.00e+000 1.23e-310] # ランダムな値
[[0. 0. 0.]
[0. 0. 0.]] # 2次元の配列
ここで生成される配列には、初期値が設定されていないため「ランダムな値」が含まれます。この点は注意が必要です。もし初期値を0にしたい場合は、np.zeros()
関数を使用します。
空配列を使うときの注意点
NumPyの配列にランダムな値が含まれる理由は、empty()
関数がメモリ領域をそのまま使用するためです。このため、初期化のコストを省ける分、高速に配列を作成することができます。以下はempty()
関数とzeros()
関数の違いを示した例です。
# 空配列(ランダムな値)
empty_array = np.empty((2, 3))
print("empty()関数:", empty_array)
# 0で初期化された配列
zeros_array = np.zeros((2, 3))
print("zeros()関数:", zeros_array)
出力例
empty()関数: [[1.23e-310 0.00e+000 2.34e-310]
[0.00e+000 1.23e-310 0.00e+000]]
zeros()関数: [[0. 0. 0.]
[0. 0. 0.]]
簡単な演習問題
ここで、空のNumPy配列を作成する演習に挑戦してみましょう。
# numpyのインポート
import numpy as np
# 空のリスト
python_list = []
# 空のNumPy配列
numpy_array = np.empty(0)
# 結果の出力
print(python_list) # 出力: []
print(type(python_list)) # 出力: <class 'list'>
print(numpy_array) # 出力: []
print(type(numpy_array)) # 出力: <class 'numpy.ndarray'>
# (2, 3)の形状の空配列を作成
numpy_array1 = np.empty((2, 3))
print(numpy_array1) # ランダムな値が含まれる
課題: 上記コードを実行して、出力結果を予想してみましょう。そして、ランダム値がどのように変化するかを確認してみてください。
リストとNumPy配列の空データ構造の違い
特徴 | リスト | NumPy配列 |
---|---|---|
作成方法 | python_list = [] | numpy_array = np.empty(0) |
データ型 | <class 'list'> | <class 'numpy.ndarray'> |
初期化の有無 | 空(データなし) | ランダムな値(未初期化) |
多次元対応 | × | ○(簡単に実現可能) |
次の章では、リストとNumPy配列の「計算」に注目して、その違いを詳しく解説します。
計算の違い
リストとNumPy配列では、数値計算の方法が大きく異なります。Pythonのリストは一般的なデータ構造であり、数値計算をする場合には手動でループを使って処理を行う必要があります。一方、NumPy配列は数値計算を効率的に行えるように設計されているため、演算子を使ってシンプルに処理を記述することができます。
ここでは、具体例を通して両者の計算方法の違いを見ていきましょう。
リストを使った数値計算
Pythonのリストで計算を行う場合、リスト同士を直接足し合わせることはできません。以下の例では、リストの各要素を1つずつ足し合わせて、新しいリストに結果を格納しています。
# 2つのリスト
listA = [1, 2, 3]
listB = [4, 5, 6]
# リストCの初期化
listC = []
# 各要素を足し合わせる
for i in range(len(listA)):
listC.append(listA[i] + listB[i])
print(listC) # 出力: [5, 7, 9]
ポイント
- 各要素を足し合わせるためにはループが必要です。
- リスト同士の
+
演算は「連結」を意味するため、数値の計算には使えません。
リスト同士を直接足し合わせた場合のコード
listA = [1, 2, 3]
listB = [4, 5, 6]
# リストを連結する
listC = listA + listB
print(listC) # 出力: [1, 2, 3, 4, 5, 6]
このように、リスト型では直接的に数値計算を行うことができず、ループなどを使った処理が必要になります。要素数が増えると計算コストが高くなり、コードも複雑になりがちです。
NumPy配列を使った数値計算
一方で、NumPy配列を使うと、リストで必要だったループを記述することなく、簡単に計算を行うことができます。以下の例を見てみましょう。
import numpy as np
# 2つの配列
arrayA = np.array([1, 2, 3])
arrayB = np.array([4, 5, 6])
# 配列同士の加算
arrayC = arrayA + arrayB
print(arrayC) # 出力: [5 7 9]
ポイント
- 配列同士をそのまま足し合わせるだけで、各要素の計算が自動で行われます。
- ループを書かなくてもよいため、コードがシンプルになります。
- さらに、NumPy配列は内部的に最適化されており、計算速度が速いという利点もあります。
NumPy配列の演算の特徴
NumPy配列は、加算だけでなく、減算、乗算、除算といった演算も同様に簡単に実行できます。
import numpy as np
# 2つの配列
arrayA = np.array([1, 2, 3])
arrayB = np.array([4, 5, 6])
# 各種演算
add_result = arrayA + arrayB # 加算
sub_result = arrayA - arrayB # 減算
mul_result = arrayA * arrayB # 乗算
div_result = arrayA / arrayB # 除算
print("加算:", add_result) # 出力: [5 7 9]
print("減算:", sub_result) # 出力: [-3 -3 -3]
print("乗算:", mul_result) # 出力: [ 4 10 18]
print("除算:", div_result) # 出力: [0.25 0.4 0.5 ]
このように、NumPy配列は数学的な演算を自然な形で記述できるようになっているため、数値計算に非常に適しています。
リストとNumPy配列の計算コード比較
以下は、リストとNumPy配列でそれぞれ同じ計算を行うコードの比較です。
リストでの計算
listA = [1, 2, 3]
listB = [4, 5, 6]
listC = []
for i in range(len(listA)):
listC.append(listA[i] + listB[i])
print(listC) # 出力: [5, 7, 9]
NumPy配列での計算
import numpy as np
arrayA = np.array([1, 2, 3])
arrayB = np.array([4, 5, 6])
arrayC = arrayA + arrayB
print(arrayC) # 出力: [5 7 9]
比較結果
特徴 | リスト | NumPy配列 |
---|---|---|
コードの簡潔さ | 手動でループが必要 | 演算子を使って簡単に記述 |
数値計算の最適化 | × | ○ |
計算速度 | 遅い | 高速 |
簡単な演習問題
次の課題に挑戦してみましょう。
課題: リストとNumPy配列を使って、それぞれ次の計算を行ってみてください。
- リスト
[1, 2, 3]
と[4, 5, 6]
の加算計算をリスト型で実現。 - NumPy配列
[1, 2, 3]
と[4, 5, 6]
を使って加算計算を実現。
リストの例
listA = [1, 2, 3]
listB = [4, 5, 6]
# 各要素を足し合わせるコードを記述
NumPyの例
import numpy as np
arrayA = np.array([1, 2, 3])
arrayB = np.array([4, 5, 6])
# 配列同士を足し合わせるコードを記述
次の章では、リストとNumPy配列の「処理速度の違い」について具体的に解説していきます。計算量が増えた場合に、どのような差が出るのかを確認していきましょう。
第4章: 処理速度の比較
リストとNumPy配列では、数値計算の際の処理速度にも大きな違いがあります。これは、NumPyがC言語で実装された低レベルの高速なライブラリを内部的に利用しているためです。この章では、実際に処理時間を測定し、その違いを確認してみましょう。
処理時間を測定する方法
Pythonでは、処理時間を測定するために標準ライブラリの time
モジュールを使用できます。time.time()
関数を使うと、現在の時刻を「秒数」として取得できるため、処理の開始時刻と終了時刻を記録し、その差を取ることで処理時間を計測することができます。
以下は基本的な例です。
import time
# 処理の開始時刻を記録
start_time = time.time()
# 実行したい処理
result = 0
for i in range(1, 1000001): # 1~100万まで足し合わせる
result += i
# 処理の終了時刻を記録
end_time = time.time()
# 処理時間を出力
print("処理時間:", end_time - start_time, "秒")
リストでの計算の処理時間
まずは、リストを使って要素数10,000のリスト同士を足し合わせる計算を行い、その処理時間を測定してみましょう。
import time
# 要素数10,000のリストを作成
listA = list(range(10000))
listB = list(range(10000, 20000))
listC = []
# 処理時間の測定開始
start_time = time.time()
# 各要素を足し合わせる処理
for i in range(len(listA)):
listC.append(listA[i] + listB[i])
# 処理時間の測定終了
end_time = time.time()
# 処理時間を出力
print("リストの処理時間:", end_time - start_time, "秒")
ポイント
- 要素数が増えると、リストでのループ処理は時間がかかるようになります。
- ループ回数が多い場合や複雑な処理を行う場合は、リストでは効率が悪くなることが分かります。
NumPy配列での計算の処理時間
次に、同じ要素数10,000のデータをNumPy配列として扱い、処理時間を測定してみましょう。
import time
import numpy as np
# 要素数10,000のNumPy配列を作成
arrayA = np.array(range(10000))
arrayB = np.array(range(10000, 20000))
# 処理時間の測定開始
start_time = time.time()
# 配列同士の加算
arrayC = arrayA + arrayB
# 処理時間の測定終了
end_time = time.time()
# 処理時間を出力
print("NumPy配列の処理時間:", end_time - start_time, "秒")
ポイント
- NumPy配列の場合、演算が内部的に最適化されており、高速に処理が行われます。
- リストと異なり、ループを記述する必要がなく、コードも簡潔になります。
処理時間の比較
リストとNumPy配列の計算処理時間を比較するコードを記述すると、以下のようになります。
import time
import numpy as np
# リストの計算
listA = list(range(10000))
listB = list(range(10000, 20000))
listC = []
start_time1 = time.time()
for i in range(len(listA)):
listC.append(listA[i] + listB[i])
end_time1 = time.time()
list_time = end_time1 - start_time1
# NumPy配列の計算
arrayA = np.array(range(10000))
arrayB = np.array(range(10000, 20000))
start_time2 = time.time()
arrayC = arrayA + arrayB
end_time2 = time.time()
array_time = end_time2 - start_time2
# 結果の表示
print("リストの処理時間:", list_time, "秒")
print("NumPy配列の処理時間:", array_time, "秒")
print("処理時間の差:", list_time - array_time, "秒")
出力例
リストの処理時間: 0.0025 秒
NumPy配列の処理時間: 0.0003 秒
処理時間の差: 0.0022 秒
結果の分析
- NumPy配列の方が圧倒的に速いことが確認できます。
- 要素数が増えるほど、この差はさらに顕著になります。
要素数が増えた場合のパフォーマンス
次に、要素数を100万に増やして計算してみましょう。
import time
import numpy as np
# リストの計算
listA = list(range(1000000))
listB = list(range(1000000, 2000000))
listC = []
start_time1 = time.time()
for i in range(len(listA)):
listC.append(listA[i] + listB[i])
end_time1 = time.time()
list_time = end_time1 - start_time1
# NumPy配列の計算
arrayA = np.array(range(1000000))
arrayB = np.array(range(1000000, 2000000))
start_time2 = time.time()
arrayC = arrayA + arrayB
end_time2 = time.time()
array_time = end_time2 - start_time2
# 結果の表示
print("リストの処理時間:", list_time, "秒")
print("NumPy配列の処理時間:", array_time, "秒")
print("処理時間の差:", list_time - array_time, "秒")
処理速度の違いの理由
リストよりNumPy配列の方が高速な理由は、次の点にあります。
- 低レベル最適化: NumPyはC言語で実装されており、内部的に非常に効率的に計算を行います。
- ベクトル化: NumPyはループを使わずに、配列全体に対して一括で演算を行う「ベクトル化」という手法を利用しています。これにより、Pythonでループを使う場合と比べて格段に速くなります。
- 固定データ型: NumPy配列はすべての要素が同じデータ型を持つため、処理が効率化されています。一方、リストは異なる型のデータを混在させることができるため、計算時に余分な処理が発生します。
演習問題
以下の課題に挑戦してみましょう。
- 要素数100,000のリストを作成し、2つのリストを足し合わせる処理の時間を測定してください。
- 同じ処理をNumPy配列で行い、処理時間を比較してください。
次の章では、「NumPyを選ぶ理由」として、数値計算以外の利点や、リストとNumPy配列の使い分けについて詳しく解説します。
NumPyを選ぶ理由
ここまでで、リストとNumPy配列の基本的な違いや、計算方法、さらには処理速度の違いを確認してきました。この章では、数値計算以外でのNumPy配列の利点や、リストとNumPy配列の使い分けについて詳しく解説します。
NumPy配列の利点
NumPyが単に「速いだけ」ではない理由として、次のような利点が挙げられます。
(1) 多次元配列のサポート
Pythonのリストは1次元のデータを扱うには十分ですが、2次元以上のデータ(例えば行列やテンソル)を扱う際には、構造が複雑になります。一方、NumPyは多次元配列(ndarray
)をネイティブでサポートしており、簡単に操作することが可能です。
以下は、2次元配列(行列)の例です。
import numpy as np
# 2次元配列の作成
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(matrix)
print("形状:", matrix.shape) # 出力: (2, 3)
出力
[[1 2 3]
[4 5 6]]
形状: (2, 3)
形状(行と列の数)を確認したり、任意の要素を簡単にアクセスできる点がNumPyの大きな特徴です。
(2) 豊富な数学的関数
NumPyには、科学計算やデータ分析で役立つさまざまな数学的関数が組み込まれています。これにより、リストでは記述が複雑になりがちな処理も、簡潔なコードで実現できます。
例えば、配列の平均や標準偏差を求める場合は次のように書けます。
import numpy as np
# 配列を定義
data = np.array([1, 2, 3, 4, 5])
# 平均、標準偏差、合計を計算
mean = np.mean(data) # 平均
std_dev = np.std(data) # 標準偏差
total = np.sum(data) # 合計
print("平均:", mean) # 出力: 3.0
print("標準偏差:", std_dev) # 出力: 1.4142135623730951
print("合計:", total) # 出力: 15
これらの数学的関数を使うことで、データ分析や機械学習の前処理を効率的に行うことが可能です。
(3) メモリ効率が高い
NumPy配列は、すべての要素が同じデータ型を持つため、メモリを効率的に利用できます。一方、Pythonのリストは異なるデータ型の要素を持てる柔軟性がある反面、メモリ消費量が多くなります。
次の例では、同じデータをリストとNumPy配列で扱った場合のメモリ消費量を比較しています。
import numpy as np
import sys
# リストとNumPy配列の作成
python_list = list(range(1000))
numpy_array = np.array(range(1000))
# メモリ使用量の比較
print("リストのメモリ使用量:", sys.getsizeof(python_list), "バイト")
print("NumPy配列のメモリ使用量:", numpy_array.nbytes, "バイト")
出力例
リストのメモリ使用量: 8056 バイト
NumPy配列のメモリ使用量: 4000 バイト
このように、NumPy配列はメモリ効率が良いため、大規模データを扱う際に有利です。
(4) ベクトル化演算によるコードの簡潔化
NumPyでは、ループを使わずに配列全体に対して一括で演算を行う「ベクトル化」が可能です。この特性により、コードを簡潔に記述できるだけでなく、処理速度も向上します。
例えば、すべての要素を2倍にする処理を考えてみましょう。
リストの場合
list_data = [1, 2, 3, 4, 5]
doubled_list = [x * 2 for x in list_data]
print(doubled_list) # 出力: [2, 4, 6, 8, 10]
NumPyの場合
import numpy as np
array_data = np.array([1, 2, 3, 4, 5])
doubled_array = array_data * 2
print(doubled_array) # 出力: [ 2 4 6 8 10]
NumPyを使うと、わずか1行で処理が記述できます。
リストとNumPy配列の使い分け
リストとNumPy配列は、それぞれの特性を理解した上で適切に使い分けることが重要です。
項目 | リスト | NumPy配列 |
---|---|---|
柔軟性 | 異なるデータ型の要素を扱える | 同じデータ型の要素のみ扱える |
多次元配列の扱いやすさ | 不便 | 非常に便利 |
数学的処理 | 手動で処理する必要あり | 組み込み関数で簡単に処理可能 |
処理速度 | 遅い | 高速 |
メモリ効率 | 非効率的 | 効率的 |
NumPyの応用例
NumPyは単純な数値計算だけでなく、以下のような幅広い分野で利用されています。
- データ分析
- Pandasなどのライブラリと連携し、大規模データの前処理や分析に使用。
- 画像処理
- 画像を配列として読み込み、ピクセル操作を行う際に利用。
- 機械学習
- モデルの作成やトレーニングデータの前処理に欠かせない。
- 科学計算
- 数学的なモデルやシミュレーションを効率的に実装可能。
演習問題
以下の課題に挑戦してみましょう。
- NumPy配列
[1, 2, 3, 4, 5]
の要素の平均、合計、標準偏差を求めてください。 - 2次元配列
[[1, 2, 3], [4, 5, 6]]
の形状を確認し、すべての要素を2倍にしてください。
次の章では、これまでの内容をまとめ、リストとNumPy配列の違いを総括していきます。
まとめ
ここまで、PythonのリストとNumPy配列の違いについて、基本的な使い方から計算方法、処理速度、そしてNumPyの利点に至るまで幅広く解説してきました。この章では、これまでの内容を振り返り、リストとNumPy配列を使い分けるポイントを整理します。
リストとNumPy配列の違いのまとめ
以下は、リストとNumPy配列の違いを比較した表です。
項目 | リスト | NumPy配列 |
---|---|---|
柔軟性 | 異なるデータ型を混在させることが可能 | 同じデータ型のみ |
多次元データの扱い | サポートは弱い(リストのネストが必要) | 多次元配列を簡単に扱える |
数学的演算 | 手動でループを記述する必要がある | 組み込み演算で簡単に実現可能 |
処理速度 | 遅い(特に要素数が増えると顕著) | 高速(内部的にC言語で最適化) |
メモリ効率 | 非効率的 | 高効率 |
用途 | 汎用的な用途(データの保持、加工など) | 数値計算やデータ処理 |
リストを使うべき場面
リストは、次のような場面で便利です。
- 異なるデータ型を扱う場合: リストは異なる型のデータを格納できます。例えば、
[1, "文字列", 3.14]
のような構造はリストでしか実現できません。 - データの小規模な操作: 数値計算や高速性を求めない場合には、リストで十分対応可能です。特に、要素数が少ない場合にはリストを使う方が簡単です。
- データ構造の柔軟性が必要な場合: リストは入れ子構造を簡単に作成できます。そのため、階層的なデータ構造を扱う場合には適しています。
NumPy配列を使うべき場面
NumPy配列は、特に次のような場面で効果を発揮します。
- 大量の数値データを扱う場合: NumPyはメモリ効率が良く、大規模な数値データの処理が得意です。
- 数学的な計算が必要な場合: NumPyには平均、標準偏差、行列演算、フーリエ変換など、科学計算に必要な機能が豊富に揃っています。
- 高速な処理が必要な場合: NumPyは内部的に最適化されており、大量データの計算を短時間で行うことができます。
- 多次元データを扱う場合: 行列やテンソルといった多次元配列を簡単に扱えるため、機械学習やデータ分析の分野で特に有用です。
リストとNumPyの使い分け例
具体的な用途ごとに使い分けの例を見てみましょう。
用途 | 推奨データ構造 | 理由 |
---|---|---|
簡単なデータ保持や加工 | リスト | 柔軟性が高く、学習コストが低い |
数値データの大量計算 | NumPy配列 | 高速でメモリ効率が良い |
科学計算や機械学習 | NumPy配列 | 数学的関数や多次元データの操作が簡単 |
異なる型のデータを混在させる | リスト | 異なる型を扱う柔軟性がある |
今後取り組むべき課題
NumPyをさらに活用するために、次のような課題に取り組んでみるとよいでしょう。
- NumPyの数学的機能の習得:
np.dot
を使った行列演算- 線形代数(例: 行列の逆行列、固有値の計算)
- 統計関数(例: 分散、中央値)
- データ分析の準備:
- NumPyでのデータクリーニング(欠損値の処理など)
- データの正規化や標準化
- 他のライブラリとの連携:
- PandasやMatplotlibと組み合わせて、データの分析や可視化を行う。
- 機械学習の準備:
- TensorFlowやPyTorchの利用に備え、NumPyの多次元配列操作に慣れておく。
本記事の要点の振り返り
- Pythonのリストは汎用性が高く、数値以外のデータを扱う場合に便利。
- NumPy配列は数値計算に特化しており、処理速度とメモリ効率が高い。
- 計算やデータ量が少ない場合はリスト、大量データや計算が必要な場合はNumPyを選ぶべき。
- NumPyの豊富な関数を活用することで、科学計算や機械学習の基盤を作れる。
これで「リストとNumPy配列の違い」に関する解説は終了です。この知識を活用して、Pythonでのデータ処理や数値計算を効率化し、さらに高度なスキルを身につけていきましょう。
この記事が皆さんの学習の一助となれば幸いです!
コメント